Open MitchellHicks opened 1 year ago
That's a great proposal! I unfortunately do not have cycles in the near future to look into this. But anyone is welcome to contribute and make pull requests.
Chiming in to say this integration would be awesome. Unfortunately don't have the skill set to make it happen. Will be following this one for future reference though.
Here is a very quick-and-dirty phonetrack integration:
diff --git a/lib/log_manager.py b/lib/log_manager.py
index fd8e79a..078ad78 100644
--- a/lib/log_manager.py
+++ b/lib/log_manager.py
@@ -79,12 +79,68 @@ class LogManager(object):
writer = csv.writer(f)
writer.writerow([data[k] for k in self._keys])
+ def _send_to_phonetrack(self, name, data):
+ #if not self.initialized:
+ import logging
+ if not hasattr(self, 'initialized'):
+
+ logger = logging.getLogger()
+ logger.setLevel(logging.DEBUG)
+ formatter = logging.Formatter("[%(asctime)s] %(levelname)s: %(message)s")
+
+ file_handler = logging.FileHandler('phonetrack.log')
+ file_handler.setLevel(logging.DEBUG)
+ file_handler.setFormatter(formatter)
+
+ logger.addHandler(file_handler)
+ self.initialized = True
+
+ if data['location|timeStamp'] == 'NULL':
+ logging.info("Ignoring call for %s with empty location data" % data['name'])
+ return
+
+ logging.info("New data for %s" % data['name'])
+
+ phonetrack_key = {
+ 'Item Name': 'phonetrack-session-key',
+
+ }
+ #logging.debug(data)
+
+ if data['name'] not in phonetrack_key:
+ logging.info("No phone key found for %s", data['name'])
+ return
+
+ url = 'https://XXXX/index.php/apps/phonetrack/logGet/%s/%s' % (phonetrack_key[data['name']], data["name"])
+ #logging.debug("Url: %s" % url)
+
+ params = {
+ 'timestamp': data['location|timeStamp'],
+ 'lat': data['location|latitude'],
+ 'lon': data['location|longitude'],
+ 'alt': data['location|altitude']
+ }
+
+ import requests
+ requests.get(url, params=params)
+
+
def refresh_log(self):
items_dict = self._get_items_dict()
for name in items_dict:
if (name not in self._latest_log or
self._latest_log[name] != items_dict[name]):
self._save_log(name, items_dict[name])
+ self._send_to_phonetrack(name, items_dict[name])
self._latest_log[name] = items_dict[name]
self._log_cnt[name] += 1
Note: I will NOT provide any support for this. Use at your own risk.
I wanted to circle back after trying this out, but now so much time has passed, and it's still sitting on my to-do list. Figured I'd belatedly circle back and say thanks for the response. It's more than enough to play around with / build upon.
I modded the code to work.. And have been out of town.. I will try to send you what I made to get it working its been running since a week after I asked. I frankly completely forgot about it.
On December 7, 2023 1:41:05 PM PST, Chris @.***> wrote:
I wanted to circle back after trying this out, but now so much time has passed, and it's still sitting on my to-do list. Figured I'd belatedly circle back and say thanks for the response. It's more than enough to play around with / build upon.
-- Reply to this email directly or view it on GitHub: https://github.com/fjxmlzn/FindMyHistory/issues/11#issuecomment-1846153647 You are receiving this because you authored the thread.
Message ID: @.***>
Awesome, no rush, but would definitely appreciate it.
Hey Mitchel, I would love to have the modded code, I just thought I would reply to remind you in case you forgot. Thank you.
I modded the code to work.. And have been out of town.. I will try to send you what I made to get it working its been running since a week after I asked. I frankly completely forgot about it. … On December 7, 2023 1:41:05 PM PST, Chris @.> wrote: I wanted to circle back after trying this out, but now so much time has passed, and it's still sitting on my to-do list. Figured I'd belatedly circle back and say thanks for the response. It's more than enough to play around with / build upon. -- Reply to this email directly or view it on GitHub: #11 (comment) You are receiving this because you authored the thread. Message ID: @.>
You had Perfect timing... I am sitting in the office... going back out of town... Sorry this is not in DIFF format... Again this is to send airTag information to nextcloud Phonetrack app https://gitlab.com/eneiluj/phonetrack-oc/-/wikis/home Use the Session Token you get from PhoneTrack I didn't make the URL for the server or the Session Token Variables... sorry.. I had to add a check for change or it spams the server with 1,000 of the same datapoint.
It's not Perfect but it's been running for 6mths.
Copyright of changes are under whatever license the existing code is.
import argparse import time import os import curses import requests import urllib.request from subprocess import check_call as shell_cmd from datetime import datetime from tabulate import tabulate from urllib.parse import urlencode from urllib.request import urlopen from lib.constants import JSON_LAYER_SEPARATOR from lib.constants import FINDMY_FILES from lib.constants import NAME_SEPARATOR from lib.constants import JSON_LAYER_SEPARATOR from lib.constants import NULL_STR from lib.constants import TIME_FORMAT from lib.constants import DATE_FORMAT from lib.log_manager import LogManager
def parse_args(): parser = argparse.ArgumentParser( description='Record Apple findmy history for Apple devices.') parser.add_argument( '--refresh', type=int, action='store', default=100, help='Refresh interval (ms).') parser.add_argument( '--name_keys', type=str, action='append', default=['name', 'serialNumber'], help='Keys used to construct the filename for each device.') parser.add_argument( '--store_keys', type=str, action='append', default=['name', 'batteryLevel', 'batteryStatus', 'batteryLevel', f'location{JSON_LAYER_SEPARATOR}timeStamp', f'location{JSON_LAYER_SEPARATOR}latitude', f'location{JSON_LAYER_SEPARATOR}longitude', f'location{JSON_LAYER_SEPARATOR}verticalAccuracy', f'location{JSON_LAYER_SEPARATOR}horizontalAccuracy', f'location{JSON_LAYER_SEPARATOR}altitude', f'location{JSON_LAYER_SEPARATOR}positionType', f'location{JSON_LAYER_SEPARATOR}floorLevel', f'location{JSON_LAYER_SEPARATOR}isInaccurate', f'location{JSON_LAYER_SEPARATOR}isOld', f'location{JSON_LAYER_SEPARATOR}locationFinished', 'id', 'deviceDiscoveryId', 'baUUID', 'serialNumber', 'identifier', 'prsId', 'deviceModel', 'modelDisplayName', 'deviceDisplayName'], help='Keys to log.') parser.add_argument( '--timestamp_key', type=str, action='store', default=f'location{JSON_LAYER_SEPARATOR}timeStamp', help='The key of timestamp in findmy JSON') parser.add_argument( '--log_folder', type=str, action='store', default='log', help='The path of log folder.') parser.add_argument( '--no_date_folder', action='store_true', help='By default, the logs of each day will be saved in a separated ' 'folder. Use this option to turn it off.') parser.add_argument( '--server_url', type=str, action='store', default='https://[Replace with nextcloud Server]/apps/phonetrack/logGet/[Replace with phonetrack Session Token]/', help='The URL of the server to which the data is to be sent') args = parser.parse_args()
return args
def send_to_server(server_url, log): """ Send the log data to a server via HTTP GET request using query string """ if log['serialNumber'] is None: log['serialNumber']=log['name']
server_url = f"https://[Replace with nextcloud
Server]/apps/phonetrack/logGet/[Replace with phonetrack Session Token]/{log['serialNumber']}" # replace with your server URL query_string = { "lat": log[f'location{JSON_LAYER_SEPARATOR}latitude'], "lon": log[f'location{JSON_LAYER_SEPARATOR}longitude'], "alt": log[f'location{JSON_LAYER_SEPARATOR}altitude'], "acc": log[f'location{JSON_LAYER_SEPARATOR}horizontalAccuracy'], "bat": log["batteryLevel"], "sat": log[f'location{JSON_LAYER_SEPARATOR}verticalAccuracy'], "speed": log[f'location{JSON_LAYER_SEPARATOR}positionType'], "bearing": "bearing", "timestamp": log[f'location{JSON_LAYER_SEPARATOR}timeStamp'],
}
query_params = urlencode(query_string)
req = urllib.request.Request(
f"{server_url}?{query_params}",
data=None,
headers={
'User-Agent': '{log[name]}'
}
)
response=""
try:
response = urllib.request.urlopen(req)
#print(f"{server_url}?{query_params}")
#print(f"{server_url}?{query_params}")
#print(f"Data sent to server with response code: {response.code}")
return response
except:
#print(f"{server_url}?{query_params}")
#print("Failed to send data to server")
return response
def main(stdscr): stdscr.clear() args = parse_args() log_manager = LogManager( findmy_files=[os.path.expanduser(f) for f in FINDMY_FILES], store_keys=args.store_keys, timestamp_key=args.timestamp_key, log_folder=args.log_folder, name_keys=args.name_keys, name_separator=NAME_SEPARATOR, json_layer_separator=JSON_LAYER_SEPARATOR, null_str=NULL_STR, date_format=DATE_FORMAT, no_date_folder=args.no_date_folder) server_url = args.server_url prev_timestamps = {}
while True:
log_manager.refresh_log()
latest_log, log_cnt = log_manager.get_latest_log()
table = []
for name, log in latest_log.items():
latest_time = log[args.timestamp_key]
if isinstance(latest_time, int) or isinstance(latest_time, float):
latest_time = datetime.fromtimestamp(
float(latest_time) / 1000.)
latest_time = latest_time.strftime(TIME_FORMAT)
# Check if the timestamp has changed since the last update
if prev_timestamps.get(name) != latest_time:
result = send_to_server(server_url, log)
prev_timestamps[name] = latest_time
table.append([name, latest_time, log_cnt[name]])
table = tabulate(
table,
headers=['Name', 'Last update', 'Log count'],
tablefmt="github")
stdscr.erase()
try:
stdscr.addstr(
0, 0, f'Current time: {datetime.now().strftime(TIME_FORMAT)}')
stdscr.addstr(1, 0, table)
except:
pass
stdscr.refresh()
time.sleep(float(args.refresh) / 1000)
if name == "main": try: shell_cmd("open -gja /System/Applications/FindMy.app", shell=True) except:
pass
curses.wrapper(main)
Mitch
On Sat, Dec 23, 2023 at 3:10 PM DShakar @.***> wrote:
Hey Mitchel, I would love to have the modded code, I just thought I would reply to remind you in case you forgot. Thank you.
I modded the code to work.. And have been out of town.. I will try to send you what I made to get it working its been running since a week after I asked. I frankly completely forgot about it. … <#m-7143903286148740668> On December 7, 2023 1:41:05 PM PST, Chris @.> wrote: I wanted to circle back after trying this out, but now so much time has passed, and it's still sitting on my to-do list. Figured I'd belatedly circle back and say thanks for the response. It's more than enough to play around with / build upon. -- Reply to this email directly or view it on GitHub: #11 (comment) https://github.com/fjxmlzn/FindMyHistory/issues/11#issuecomment-1846153647 You are receiving this because you authored the thread. Message ID: @.>
— Reply to this email directly, view it on GitHub https://github.com/fjxmlzn/FindMyHistory/issues/11#issuecomment-1868386475, or unsubscribe https://github.com/notifications/unsubscribe-auth/APBEZBWW2SYXWTRGINAIVL3YK5QERAVCNFSM6AAAAAAWPFHB2GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNRYGM4DMNBXGU . You are receiving this because you authored the thread.Message ID: @.***>
Just remembered... There is no auto correcting of AIR tag names. You can create an air tag name that will be invalid i don't have an example since I renamed all 20 airtags we use rather than fix the code to autocorrect...
Be sure to add that to the notes!! or normalize(that's the word I was looking for) all the names before submitting to PhoneTrack!
Thanks for the work, I hope others can use this...
also sometimes it gets strange CORDS and well it is what it is... lol
Mitch
On Sat, Dec 23, 2023 at 4:42 PM Mitch Hicks @.***> wrote:
You had Perfect timing... I am sitting in the office... going back out of town... Sorry this is not in DIFF format... Again this is to send airTag information to nextcloud Phonetrack app https://gitlab.com/eneiluj/phonetrack-oc/-/wikis/home Use the Session Token you get from PhoneTrack I didn't make the URL for the server or the Session Token Variables... sorry.. I had to add a check for change or it spams the server with 1,000 of the same datapoint.
It's not Perfect but it's been running for 6mths.
Copyright of changes are under whatever license the existing code is.
import argparse import time import os import curses import requests import urllib.request from subprocess import check_call as shell_cmd from datetime import datetime from tabulate import tabulate from urllib.parse import urlencode from urllib.request import urlopen from lib.constants import JSON_LAYER_SEPARATOR from lib.constants import FINDMY_FILES from lib.constants import NAME_SEPARATOR from lib.constants import JSON_LAYER_SEPARATOR from lib.constants import NULL_STR from lib.constants import TIME_FORMAT from lib.constants import DATE_FORMAT from lib.log_manager import LogManager
def parse_args(): parser = argparse.ArgumentParser( description='Record Apple findmy history for Apple devices.') parser.add_argument( '--refresh', type=int, action='store', default=100, help='Refresh interval (ms).') parser.add_argument( '--name_keys', type=str, action='append', default=['name', 'serialNumber'], help='Keys used to construct the filename for each device.') parser.add_argument( '--store_keys', type=str, action='append', default=['name', 'batteryLevel', 'batteryStatus', 'batteryLevel', f'location{JSON_LAYER_SEPARATOR}timeStamp', f'location{JSON_LAYER_SEPARATOR}latitude', f'location{JSON_LAYER_SEPARATOR}longitude', f'location{JSON_LAYER_SEPARATOR}verticalAccuracy', f'location{JSON_LAYER_SEPARATOR}horizontalAccuracy', f'location{JSON_LAYER_SEPARATOR}altitude', f'location{JSON_LAYER_SEPARATOR}positionType', f'location{JSON_LAYER_SEPARATOR}floorLevel', f'location{JSON_LAYER_SEPARATOR}isInaccurate', f'location{JSON_LAYER_SEPARATOR}isOld', f'location{JSON_LAYER_SEPARATOR}locationFinished', 'id', 'deviceDiscoveryId', 'baUUID', 'serialNumber', 'identifier', 'prsId', 'deviceModel', 'modelDisplayName', 'deviceDisplayName'], help='Keys to log.') parser.add_argument( '--timestamp_key', type=str, action='store', default=f'location{JSON_LAYER_SEPARATOR}timeStamp', help='The key of timestamp in findmy JSON') parser.add_argument( '--log_folder', type=str, action='store', default='log', help='The path of log folder.') parser.add_argument( '--no_date_folder', action='store_true', help='By default, the logs of each day will be saved in a separated ' 'folder. Use this option to turn it off.') parser.add_argument( '--server_url', type=str, action='store', default='https://[Replace with nextcloud Server]/apps/phonetrack/logGet/[Replace with phonetrack Session Token]/', help='The URL of the server to which the data is to be sent') args = parser.parse_args()
return args
def send_to_server(server_url, log): """ Send the log data to a server via HTTP GET request using query string """ if log['serialNumber'] is None: log['serialNumber']=log['name']
server_url = f"https://[Replace with nextcloud Server]/apps/phonetrack/logGet/[Replace with phonetrack Session Token]/{log['serialNumber']}" # replace with your server URL query_string = { "lat": log[f'location{JSON_LAYER_SEPARATOR}latitude'], "lon": log[f'location{JSON_LAYER_SEPARATOR}longitude'], "alt": log[f'location{JSON_LAYER_SEPARATOR}altitude'], "acc": log[f'location{JSON_LAYER_SEPARATOR}horizontalAccuracy'], "bat": log["batteryLevel"], "sat": log[f'location{JSON_LAYER_SEPARATOR}verticalAccuracy'], "speed": log[f'location{JSON_LAYER_SEPARATOR}positionType'], "bearing": "bearing", "timestamp": log[f'location{JSON_LAYER_SEPARATOR}timeStamp'], # add more key-value pairs as necessary } query_params = urlencode(query_string) req = urllib.request.Request( f"{server_url}?{query_params}", data=None, headers={ 'User-Agent': '{log[name]}' } ) response="" try: response = urllib.request.urlopen(req) #print(f"{server_url}?{query_params}") #print(f"{server_url}?{query_params}") #print(f"Data sent to server with response code: {response.code}") return response except: #print(f"{server_url}?{query_params}") #print("Failed to send data to server") return response
def main(stdscr): stdscr.clear() args = parse_args() log_manager = LogManager( findmy_files=[os.path.expanduser(f) for f in FINDMY_FILES], store_keys=args.store_keys, timestamp_key=args.timestamp_key, log_folder=args.log_folder, name_keys=args.name_keys, name_separator=NAME_SEPARATOR, json_layer_separator=JSON_LAYER_SEPARATOR, null_str=NULL_STR, date_format=DATE_FORMAT, no_date_folder=args.no_date_folder) server_url = args.server_url prev_timestamps = {}
while True: log_manager.refresh_log() latest_log, log_cnt = log_manager.get_latest_log() table = [] for name, log in latest_log.items(): latest_time = log[args.timestamp_key] if isinstance(latest_time, int) or isinstance(latest_time, float): latest_time = datetime.fromtimestamp( float(latest_time) / 1000.) latest_time = latest_time.strftime(TIME_FORMAT) # Check if the timestamp has changed since the last update if prev_timestamps.get(name) != latest_time: result = send_to_server(server_url, log) prev_timestamps[name] = latest_time table.append([name, latest_time, log_cnt[name]]) table = tabulate( table, headers=['Name', 'Last update', 'Log count'], tablefmt="github") stdscr.erase() try: stdscr.addstr( 0, 0, f'Current time: {datetime.now().strftime(TIME_FORMAT)}') stdscr.addstr(1, 0, table) except: pass stdscr.refresh() time.sleep(float(args.refresh) / 1000)
if name == "main": try: shell_cmd("open -gja /System/Applications/FindMy.app", shell=True) except:
Maybe Apple changed the name or the dir of the app?
pass curses.wrapper(main)
Mitch
On Sat, Dec 23, 2023 at 3:10 PM DShakar @.***> wrote:
Hey Mitchel, I would love to have the modded code, I just thought I would reply to remind you in case you forgot. Thank you.
I modded the code to work.. And have been out of town.. I will try to send you what I made to get it working its been running since a week after I asked. I frankly completely forgot about it. … <#m_-2237118060453700791m-7143903286148740668_> On December 7, 2023 1:41:05 PM PST, Chris @.> wrote: I wanted to circle back after trying this out, but now so much time has passed, and it's still sitting on my to-do list. Figured I'd belatedly circle back and say thanks for the response. It's more than enough to play around with / build upon. -- Reply to this email directly or view it on GitHub: #11 (comment) https://github.com/fjxmlzn/FindMyHistory/issues/11#issuecomment-1846153647 You are receiving this because you authored the thread. Message ID: @.>
— Reply to this email directly, view it on GitHub https://github.com/fjxmlzn/FindMyHistory/issues/11#issuecomment-1868386475, or unsubscribe https://github.com/notifications/unsubscribe-auth/APBEZBWW2SYXWTRGINAIVL3YK5QERAVCNFSM6AAAAAAWPFHB2GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNRYGM4DMNBXGU . You are receiving this because you authored the thread.Message ID: @.***>
I think this would provide an easy visual interface for people to follow the AirTags.
This is the Nextcloud App https://apps.nextcloud.com/apps/phonetrack
This is the Page for Clients to send updates to the APP. https://gitlab.com/eneiluj/phonetrack-oc/-/wikis/userdoc#logging-methods
It has a built in Post/Get option for adding History Points I am sure there are other options.
"HTTP request You can build your own logging system and make GET or POST HTTP requests to PhoneTrack. Here is an example of logging URL with POST: https://your.server.org/NC_PATH_IF_NECESSARY/index.php/apps/phonetrack/logPost/TOKEN/DEVNAME and with GET: https://your.server.org/NC_PATH_IF_NECESSARY/index.php/apps/phonetrack/logGet/TOKEN/DEVNAME The POST or GET parameters are:
lat (decimal latitude) lon (decimal longitude) alt (altitude in meters) timestamp (epoch timestamp in seconds) acc (accuracy in meters) bat (battery level in percent) sat (number of satellites) useragent (device user agent) speed (speed in meter per second) bearing (bearing in decimal degrees) "
Great Work on the Apple side..