Skip to content

Instantly share code, notes, and snippets.

@jimtng
Last active January 7, 2024 11:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jimtng/e737bb67b77a796a6ecaeafe7cb498ee to your computer and use it in GitHub Desktop.
Save jimtng/e737bb67b77a796a6ecaeafe7cb498ee to your computer and use it in GitHub Desktop.
Android Phone Finder in openHAB
# 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