Advertisement
Guest User

gcunfriend

a guest
Jun 25th, 2019
703
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.66 KB | None | 0 0
  1. #!/usr/bin/python3
  2. # -*- coding: utf-8 -*-
  3.  
  4. """
  5. Original credits:
  6.  
  7. File: gcexport.py
  8. Original author: Kyle Krafka (https://github.com/kjkjava/)
  9. Date: April 28, 2015
  10. Fork author: Michael P (https://github.com/moderation/)
  11. Date: February 15, 2018
  12.  
  13. Description:    Use this script to export your fitness data from Garmin Connect.
  14.                 See README.md for more information.
  15.  
  16. Activity & event types:
  17.     https://connect.garmin.com/modern/main/js/properties/event_types/event_types.properties
  18.     https://connect.garmin.com/modern/main/js/properties/activity_types/activity_types.properties
  19. """
  20.  
  21. def show_exception_and_exit(exc_type, exc_value, tb):
  22.     import traceback
  23.     traceback.print_exception(exc_type, exc_value, tb)
  24.     input("Press ENTER to exit.")
  25.     sys.exit(-1)
  26.  
  27. import sys
  28. sys.excepthook = show_exception_and_exit
  29.  
  30. # ##############################################
  31.  
  32. from datetime import datetime, timedelta
  33. from getpass import getpass
  34. from os import mkdir, remove, stat
  35. from os.path import isdir, isfile
  36. from subprocess import call
  37. from sys import argv
  38. from xml.dom.minidom import parseString
  39.  
  40. import argparse
  41. import http.cookiejar
  42. import json
  43. import re
  44. import urllib.error
  45. import urllib.parse
  46. import urllib.request
  47. import zipfile
  48.  
  49. SCRIPT_VERSION = '0.0.1'
  50. #CURRENT_DATE = datetime.now().strftime('%Y-%m-%d')
  51. #ACTIVITIES_DIRECTORY = './' + CURRENT_DATE + '_garmin_connect_export'
  52.  
  53. PARSER = argparse.ArgumentParser()
  54.  
  55. # TODO: Implement verbose and/or quiet options.
  56. # PARSER.add_argument('-v', '--verbose', help="increase output verbosity", action="store_true")
  57. PARSER.add_argument('--version', help="print version and exit", action="store_true")
  58. PARSER.add_argument('--username', help="your Garmin Connect username (otherwise, you will be \
  59.     prompted)", nargs='?')
  60. PARSER.add_argument('--password', help="your Garmin Connect password (otherwise, you will be \
  61.     prompted)", nargs='?')
  62.    
  63. ARGS = PARSER.parse_args()
  64.  
  65. if ARGS.version:
  66.     print(argv[0] + ", version " + SCRIPT_VERSION)
  67.     exit(0)
  68.  
  69. COOKIE_JAR = http.cookiejar.CookieJar()
  70. OPENER = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(COOKIE_JAR))
  71. # print(COOKIE_JAR)
  72.  
  73.  
  74. # url is a string, post is the raw post data, headers is a dictionary of headers.
  75. def http_req(url, post=None, headers=None):
  76.     """Helper function that makes the HTTP requests."""
  77.     request = urllib.request.Request(url)
  78.     # Tell Garmin we're some supported browser.
  79.     request.add_header('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, \
  80.         like Gecko) Chrome/54.0.2816.0 Safari/537.36')
  81.     if headers:
  82.         for header_key, header_value in headers.items():
  83.             request.add_header(header_key, header_value)
  84.     if post:
  85.         #post = urllib.parse.urlencode(post)
  86.         post = post.encode('utf-8')  # Convert dictionary to POST parameter string.
  87.     # print("request.headers: " + str(request.headers) + " COOKIE_JAR: " + str(COOKIE_JAR))
  88.     # print("post: " + str(post) + "request: " + str(request))
  89.     response = OPENER.open((request), data=post)
  90.  
  91.     if response.getcode() == 204:
  92.         # For activities without GPS coordinates, there is no GPX download (204 = no content).
  93.         # Write an empty file to prevent redownloading it.
  94.         #print('Writing empty file since there was no GPX activity data...')
  95.         return ''
  96.     elif response.getcode() != 200:
  97.         raise Exception('Bad return code (' + str(response.getcode()) + ') for: ' + url)
  98.     # print(response.getcode())
  99.  
  100.     return response.read()
  101.  
  102. print('Welcome to the Garmin Connect Unfriender!')
  103.  
  104. print('')
  105. print('ALL YOUR GARMIN CONNECT CONNECTIONS (FRIENDS) WILL BE DELETED')
  106. RESPONSE = input('Type "YES" and press ENTER if you are absolutely sure: ')
  107. if RESPONSE != 'YES':
  108.     sys.exit(0)
  109.  
  110. print('')
  111. USERNAME=''
  112. PASSWORD=''
  113. while not USERNAME:
  114.     USERNAME = ARGS.username if ARGS.username else input('Username: ')
  115.     if not USERNAME:
  116.         print("Please enter a username.")
  117.         print("")
  118. while not PASSWORD:
  119.     PASSWORD = ARGS.password if ARGS.password else getpass()
  120.     if not PASSWORD:
  121.         print("Please enter a password.")
  122.         print("")
  123.  
  124.  
  125. # Maximum number of activities you can request at once.  Set and enforced by Garmin.
  126. LIMIT_MAXIMUM = 1000
  127.  
  128. WEBHOST = "https://connect.garmin.com"
  129. REDIRECT = "https://connect.garmin.com/modern/"
  130. BASE_URL = "https://connect.garmin.com/en-US/signin"
  131. SSO = "https://sso.garmin.com/sso"
  132. CSS = "https://static.garmincdn.com/com.garmin.connect/ui/css/gauth-custom-v1.2-min.css"
  133.  
  134. DATA = {
  135.     'service': REDIRECT,
  136.     'webhost': WEBHOST,
  137.     'source': BASE_URL,
  138.     'redirectAfterAccountLoginUrl': REDIRECT,
  139.     'redirectAfterAccountCreationUrl': REDIRECT,
  140.     'gauthHost': SSO,
  141.     'locale': 'en_US',
  142.     'id': 'gauth-widget',
  143.     'cssUrl': CSS,
  144.     'clientId': 'GarminConnect',
  145.     'rememberMeShown': 'true',
  146.     'rememberMeChecked': 'false',
  147.     'createAccountShown': 'true',
  148.     'openCreateAccount': 'false',
  149.     'usernameShown': 'false',
  150.     'displayNameShown': 'false',
  151.     'consumeServiceTicket': 'false',
  152.     'initialFocus': 'true',
  153.     'embedWidget': 'false',
  154.     'generateExtraServiceTicket': 'true',
  155.     'generateTwoExtraServiceTickets': 'false',
  156.     'generateNoServiceTicket': 'false',
  157.     'globalOptInShown': 'true',
  158.     'globalOptInChecked': 'false',
  159.     'mobile': 'false',
  160.     'connectLegalTerms': 'true',
  161.     'locationPromptShown': 'true',
  162.     'showPassword': 'true'    
  163. }
  164.  
  165. #print(urllib.parse.urlencode(DATA))
  166.  
  167. # URLs for various services.
  168. URL_GC_LOGIN = 'https://sso.garmin.com/sso/signin?' + urllib.parse.urlencode(DATA)
  169. URL_GC_POST_AUTH = 'https://connect.garmin.com/modern/activities?'
  170. URL_GC_SEARCH = 'https://connect.garmin.com/proxy/activity-search-service-1.2/json/activities?'
  171. URL_GC_LIST = \
  172.     'https://connect.garmin.com/modern/proxy/activitylist-service/activities/search/activities?'
  173. URL_GC_ACTIVITY = 'https://connect.garmin.com/modern/proxy/activity-service/activity/'
  174. URL_GC_ACTIVITY_DETAIL = \
  175.     'https://connect.garmin.com/modern/proxy/activity-service-1.3/json/activityDetails/'
  176. URL_GC_GPX_ACTIVITY = \
  177.     'https://connect.garmin.com/modern/proxy/download-service/export/gpx/activity/'
  178. URL_GC_TCX_ACTIVITY = \
  179.     'https://connect.garmin.com/modern/proxy/download-service/export/tcx/activity/'
  180. URL_GC_ORIGINAL_ACTIVITY = 'http://connect.garmin.com/proxy/download-service/files/activity/'
  181.  
  182. URL_GC_ACTIVITY_PAGE = 'https://connect.garmin.com/modern/activity/'
  183.  
  184. URL_CONNECTIONS_LIST = "https://connect.garmin.com/modern/proxy/userstats-service/leaderboard/wellness/connection?&start=1&limit=999&metricId=29"
  185. URL_CONNECTION_STATUS = "https://connect.garmin.com/modern/proxy/userprofile-service/connection/status/" # + display name
  186. URL_CONNECTION_DELETE = "https://connect.garmin.com/modern/proxy/userprofile-service/connection/connectionRequest/" # + connectionRequestId
  187.  
  188.  
  189. print("Logging in...")
  190.  
  191. # Initially, we need to get a valid session cookie, so we pull the login page.
  192. #print('Request login page')
  193. http_req(URL_GC_LOGIN)
  194. #print('Finish login page')
  195.  
  196. # Now we'll actually login.
  197. # Fields that are passed in a typical Garmin login.
  198. POST_DATA = {
  199.     'username': USERNAME,
  200.     'password': PASSWORD,
  201.     'embed': 'false',
  202.     'rememberme': 'on'
  203.     }
  204.    
  205. headers = {
  206.     'referer': URL_GC_LOGIN
  207. }    
  208.  
  209. #print('Post login data')
  210. LOGIN_RESPONSE = http_req(URL_GC_LOGIN + '#', urllib.parse.urlencode(POST_DATA), headers).decode()
  211. #print('Finish login post')
  212.  
  213. # extract the ticket from the login response
  214. PATTERN = re.compile(r".*\?ticket=([-\w]+)\";.*", re.MULTILINE|re.DOTALL)
  215. MATCH = PATTERN.match(LOGIN_RESPONSE)
  216. if not MATCH:
  217.     raise Exception('Did not get a ticket in the login response. Cannot log in. Did \
  218. you enter the correct username and password?')
  219. LOGIN_TICKET = MATCH.group(1)
  220. #print('login ticket=' + LOGIN_TICKET)
  221.  
  222. #print("Request authentication URL: " + URL_GC_POST_AUTH + 'ticket=' + LOGIN_TICKET)
  223. #print("Request authentication")
  224. LOGIN_AUTH_REP = http_req(URL_GC_POST_AUTH + 'ticket=' + LOGIN_TICKET).decode()
  225. #print(LOGIN_AUTH_REP)
  226. PATTERN = re.compile(r".*{\\\"displayName\\\":\\\"([^\\]*)\\\".*", re.MULTILINE|re.DOTALL)
  227. MATCH = PATTERN.match(LOGIN_AUTH_REP)
  228. displayName = MATCH.group(1)
  229. #print("Display Name = " + displayName)
  230. #print('Finished authentication')
  231.  
  232. ########################################################################
  233.  
  234.  
  235.  
  236. print('')
  237. print('Searching for connections (this might take a while)...')
  238. print('')
  239. #print('Loading activity list')
  240. CONNECTION_LIST = http_req(URL_CONNECTIONS_LIST)
  241.  
  242. #print('Processing activity list')
  243. #print('')
  244.  
  245. JSON_LIST = json.loads(CONNECTION_LIST)["allMetrics"]["metricsMap"]["WELLNESS_TOTAL_STEPS"]
  246.  
  247. if len(JSON_LIST) == 0:
  248.     print("No connections found.")
  249. else:
  250.     print("Found " + str(len(JSON_LIST)-1) + " connections.")
  251.  
  252. for b in JSON_LIST:
  253.     a = b["userInfo"]
  254.  
  255.     if (a['displayname'].lower() == displayName.lower()):
  256.         continue
  257.  
  258.     print('Connection: ' + a['displayname'] + (' | ' + a['fullname'] if a['fullname'] else ''))
  259.  
  260.     print("  Getting connection request ID...")
  261.     urlStatus = URL_CONNECTION_STATUS + a['displayname']
  262.     statusResponse = http_req(urlStatus)
  263.     connectionRequestId = json.loads(statusResponse)[0]["connectionRequestId"]
  264.     print("  Connection request ID = " + str(connectionRequestId))
  265.  
  266.     if (connectionRequestId != None):
  267.         print('  Deleting connection...')
  268.         deleteUrl = URL_CONNECTION_DELETE + str(connectionRequestId)
  269.         deleteHeaders = {
  270.             'referer': 'https://connect.garmin.com/modern/connections/connections',
  271.             'authority': 'connect.garmin.com',
  272.             'origin': 'https://connect.garmin.com',
  273.     #       'Content-Type':'application/json',
  274.             'X-HTTP-Method-Override': 'DELETE',
  275.             'X-Requested-With': 'XMLHttpRequest',
  276.             'nk': 'NT'
  277.         }
  278.         http_req(deleteUrl, {}, deleteHeaders)
  279.  
  280. print('')
  281. print('Done!')
  282.  
  283. input('Press ENTER to quit');
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement