-
-
Save jimtng/e737bb67b77a796a6ecaeafe7cb498ee to your computer and use it in GitHub Desktop.
Android Phone Finder in openHAB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
=begin # rubocop:disable Style/BlockComments | |
A openHAB script to find your Android phone using Google Find My Phone service | |
Author: @jimtng | |
Last updated: 2024-01-07 21:05 | |
Fill out the PHONES constant below with the list of phones to find. | |
Once this script is loaded, send the cookies string as the command to FindMyPhone_Cookies item | |
e.g. From Karaf console: | |
openhab> openhab:send FindMyPhone_Cookies "NID=1234; 1P_JAR=abcd; ..." | |
To obtain the cookies, open www.google.com and login to your google account, | |
open the browser's developer tools and find the "Network" tab. | |
refresh the browser page (www.google.com) | |
Click on the first request (it would be to www.google.com) and scroll down to the "Request Headers" section. | |
Copy the "Cookie" header as one long string, and send it as the command to FindMyPhone_Cookies item. | |
The cookies will be saved in the FindMyPhone_Cookies item's metadata, | |
which is stored in your openhab's jsondb inside the userdata folder. | |
It is a sensitive information, so make sure you don't share it. | |
To clear the data from the FindMyPhone_Cookies item, send an empty string as the command to the item. | |
Do this before removing this script, or else the cookies will remain in your jsondb. | |
This script will create the necessary items and rules at run-time. The following items will be created: | |
- FindMyPhone_Cookies: a string item to store the cookies in its metadata | |
- FindMyPhone_Group: a group item to hold all the phone finder items | |
- FindMyPhone_Phone_<name>: a switch item for each phone to find. | |
The phone items will have `ga` and `alexa` metadata set to "Switch" so | |
they can be discovered by Google Home and Alexa. | |
These items will be removed when this script is unloaded. | |
Once discovered, you can say "Hey Google, turn on Jimmy's Phone Finder" to ring the phone. | |
For more information, see: | |
https://community.openhab.org/t/android-phone-finder-from-openhab-google-assistant-and-alexa/134723 | |
=end | |
######################################################################################################### | |
# Begin configuration | |
######################################################################################################### | |
# | |
# List of phones to find | |
# | |
# For simplicity, one google account is used for all the phones. | |
# Make sure the same google account is added to all the phones. | |
# | |
# The first element is the item's label which will be used for voice command, e.g. | |
# label: "Jimmy's Phone Finder", alexa command: "Alexa, turn on Jimmy's Phone Finder" | |
# | |
# The first word from the label will be used to create the item name, | |
# e.g. for "Jimmy's Phone Finder", an item called "FindMyPhone_Phone_Jimmy" will be created. | |
# | |
# A group called FindMyPhone_Group will be created with all the phone finder items | |
# | |
# The second element is the name of the phone as it appears in Google Find My Phone web site. | |
# This can be changed through Google Play Store web site -> Library & devices. | |
# | |
PHONES = [ | |
["Jimmy's Phone Finder", "Jimmy's Phone"], | |
["Rebecca's Phone Finder", "Rebecca's Phone"] | |
].freeze | |
# set this to nil to disable alert sound | |
ALERT_SOUND = "alert04.mp3" | |
# Customize this method to send an alert when the cookies need to be updated | |
def send_alert_about_cookies | |
msg = "Failed to login to Google Find My Phone. " \ | |
"Update your cookies by sending the cookie string as the command to FindMyPhone_Cookies item!" | |
logger.warn msg | |
notify msg, icon: "error" | |
Audio.play_sound ALERT_SOUND if ALERT_SOUND | |
end | |
SELENIUM_HOST = "127.0.0.1" | |
SELENIUM_PORT = 4444 | |
######################################################################################################### | |
# End configuration | |
######################################################################################################### | |
require "json" | |
require "cgi/cookie" | |
gemfile do | |
source "https://rubygems.org" | |
gem "selenium-webdriver" | |
end | |
items.build do | |
string_item FindMyPhone_Cookies, "Find My Phone Cookies" | |
group_item FindMyPhone_Group, type: :string do | |
PHONES.each do |name, phone| | |
item_name = name[/^[a-zA-Z]+/] | |
switch_item "FindMyPhone_Phone_#{item_name}", name, ga: "Switch", alexa: "Switch", | |
metadata: { phone:, expire: "3s" } | |
end | |
end | |
end | |
provider!(FindMyPhone_Cookies => :persistent) | |
DEBUG = false | |
class AndroidPhoneFinder | |
GOOGLE_HOME_URL = "https://www.google.com" | |
GOOGLE_FIND_URL = "https://www.google.com/android/find" | |
def initialize(server = SELENIUM_HOST, port = SELENIUM_PORT) | |
options = Selenium::WebDriver::Options.chrome | |
options.add_argument("--headless") | |
options.add_argument("--no-sandbox") | |
options.add_argument("--disable-gpu") | |
@driver = Selenium::WebDriver.for(:remote, url: "http://#{server}:#{port}/wd/hub", options:) | |
@driver.manage.timeouts.implicit_wait = 2 | |
end | |
def close = @driver.quit | |
def refresh = @driver.navigate.refresh | |
def logged_in? = @driver.title =~ /Google|Find My Device/ && !@driver.page_source.include?("loginForm") | |
def cookies = @driver.manage.all_cookies | |
def cookies=(cookies) | |
cookies&.each do |cookie| | |
cookie = cookie.compact.except(:expires) | |
@driver.manage.add_cookie(**cookie) | |
end | |
end | |
def home(cookies = nil) | |
logger.trace "Navigating to HOME" | |
@driver.navigate.to(GOOGLE_HOME_URL) | |
self.cookies = cookies if cookies | |
end | |
def ping = @driver.current_url | |
def ring(phone) | |
@driver.navigate.to(GOOGLE_FIND_URL) | |
File.write(OpenHAB::Core.config_folder / "misc" / "google-find-my-phone.html", @driver.page_source) if DEBUG | |
# Find the phone from the list of phone as icons on the top left corner | |
unless (device = @driver.find_element(xpath: %(//img[@data-iml and @aria-label="#{phone}"]))) | |
logger.warn "Find My Phone: Phone not found: #{phone}. " \ | |
"Make sure the phone's name is correct and it is registered with the same google account." | |
return false | |
end | |
device.click | |
ring_button = @driver.find_element(xpath: %(.//button[contains(@title, "Ring this device")])) | |
ring_button.click | |
sleep 5 | |
end | |
end | |
def save_cookies(cookies) | |
FindMyPhone_Cookies.metadata["cookies"] = cookies.to_json | |
end | |
def load_cookies | |
cookies = FindMyPhone_Cookies.metadata["cookies"] | |
unless cookies | |
logger.warn "FindMyPhone cookies haven't been set. Send a cookie string as the command to FindMyPhone_Cookies item" | |
return | |
end | |
JSON.parse(cookies.value).map { |cookie| cookie.transform_keys(&:to_sym) } | |
end | |
def refresh_cookies(cookies) | |
return unless cookies | |
phone_finder = AndroidPhoneFinder.new | |
begin | |
phone_finder.home(cookies) | |
phone_finder.refresh | |
save_cookies(phone_finder.cookies) | |
logger.info "FindMyPhone cookies refreshed" | |
ensure | |
phone_finder.close | |
end | |
end | |
rule "Find My Phone: Set new cookies" do | |
received_command FindMyPhone_Cookies | |
run do |event| | |
cookies = CGI::Cookie.parse(event.command.to_s).values.map do |cookie| | |
{ name: cookie.name, value: cookie.value.map { |v| CGI.escape(v) }.join("&"), | |
path: cookie.path, domain: cookie.domain, expires: cookie.expires, | |
secure: cookie.secure, http_only: cookie.httponly }.compact | |
end | |
refresh_cookies(cookies) | |
end | |
end | |
rule "Find My Phone: Refresh cookies" do | |
every :day | |
run { refresh_cookies(load_cookies) } | |
end | |
rule "Find My Phone: Ring the phone" do | |
received_command FindMyPhone_Group.members, command: ON | |
run do |event| | |
phone = event.item.metadata["phone"].value | |
logger.info "#{event.item}: #{phone}..." | |
phone_finder = AndroidPhoneFinder.new | |
begin | |
phone_finder.home(load_cookies) | |
if phone_finder.logged_in? | |
phone_finder.ring(phone) | |
save_cookies(phone_finder.cookies) | |
else | |
send_alert_about_cookies | |
end | |
rescue | |
Audio.play_sound ALERT_SOUND if ALERT_SOUND | |
raise | |
ensure | |
phone_finder.close | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment