Created
April 23, 2018 21:19
-
-
Save fxchen/ebfc39bbaaaadbe29701f1e3f0d5427e to your computer and use it in GitHub Desktop.
A legible timezone-friendly parser and foursquare / existio integrator for quantifying myself
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
# integrations.py | |
################################################################################################### | |
# usage: integrations.py [-h] [-s [n]] [-f] [-c] [-e] [-w] | |
# A legible timezone-friendly parser and foursquare / existio integrator for quantifying myself | |
# optional arguments: | |
# -h, --help show this help message and exit | |
# -s [n], --show [n] Shows formatted <n> days (default: 30 days) | |
# -f, --foursquare Grab checkins from Foursquare API | |
# -c, --calendar Shows events from Outlook Calendar | |
# -e, --exist Parse times from sleep.json and wake.json | |
# -w, --woke Show when you woke up today | |
################################################################################################### | |
# Derived from prior foursquare.py https://gist.github.com/fxchen/f86239b52ddc3488a222e8c634bd8115 | |
# Change log: | |
# 2017/10/05 updated for python3 | |
# 2017/11/18 add existio integrations | |
# 2018/04/23 publish to github | |
################################################################################################### | |
import requests, json, datetime, operator, argparse, sys, os, subprocess, six, ssl | |
""" Globals """ | |
DEFAULT_DAYS = 30 | |
NA_STR_CONSTANT = "N/A" | |
OUTPUT_FILE = "/Users/fchen/Dropbox/notes/notational/integrations/integrations.txt" | |
EXIST_OAUTH_TOKEN = "<>" | |
FOURSQUARE_OAUTH_TOKEN = "<>" | |
parser = argparse.ArgumentParser(description="A legible timezone friendly parser and integrator for my days") | |
parser.add_argument("-s", "--show", type=int, metavar="n", nargs="?", const=DEFAULT_DAYS, | |
help="Shows formatted <n> days (default: " + str(DEFAULT_DAYS) +" days)") | |
parser.add_argument("-f", "--foursquare", action="store_true", | |
help="Grab checkins from Foursquare API") | |
parser.add_argument("-c", "--calendar", action="store_true", | |
help="Shows events from Outlook Calendar") | |
parser.add_argument("-e", "--exist", action="store_true", | |
help="Parse times from sleep.json and wake.json") | |
parser.add_argument("-w", "--woke", action="store_true", | |
help="Show when you woke up today") | |
args = parser.parse_args() | |
if not (args.foursquare or args.show or args.calendar or args.exist or args.woke): | |
parser.error("No action requested {--help, --show, --calendar, --exist, --foursquare}") | |
# Adding an insecure warning tag for Foursquare | |
requests.packages.urllib3.disable_warnings() | |
# The following sections have more constants and data | |
""" Calendaring """ | |
OUTLOOK_CALENDAR_COMMAND = "/Users/fchen/Dropbox/notes/notational/integrations/calendar.sh" | |
data_formatted_dates = {} | |
""" Exist """ | |
DEFAULT_LIMIT = 100 | |
EXIST_RESULTS_STR_CONSTANT = "results" | |
EXIST_SLEEP_URL_TEMPLATE = "https://exist.io/api/1/users/$self/attributes/sleep_start/?limit={}&page={}" | |
EXIST_SLEEP_JSON = "/Users/fchen/Dropbox/notes/notational/integrations/sleep.json" # Sleep start in minutes from midday | |
EXIST_WAKE_URL_TEMPLATE = "https://exist.io/api/1/users/$self/attributes/sleep_end/?limit={}&page={}" | |
EXIST_WAKE_JSON = "/Users/fchen/Dropbox/notes/notational/integrations/wake.json" # Wake start in minutes from midnight | |
EXIST_HEADERS = {"Authorization": "Token " + EXIST_OAUTH_TOKEN} | |
EXIST_VALUE_STR_CONSTANT = "value" | |
# 720 is the number of minutes to get to midday from midnight | |
NUMBER_OF_MINUTES_MIDDAY = 720 | |
# 1440 is the number of minutes in a day | |
NUMBER_OF_MINUTES_WHOLE_DAY = 1440 | |
data_sleep = {} | |
data_wake = {} | |
def get_minutes_to_human_readable(minutes): | |
# Return formatted time stamps from integer of minutes | |
modhours, minute = divmod(minutes, 60) | |
pmam, hour = divmod(modhours, 12) | |
hour = (hour, 12)[hour == 0] | |
return "%02d:%02d" % (hour,minute) + (" AM", " PM")[pmam == 1] | |
def get_wake_time_human_readable(minutes_str): | |
return get_minutes_to_human_readable(minutes_str) | |
def get_wake_time_from_str(date_str): | |
global data_wake | |
# Data = number of minutes from midnight | |
if date_str in data_wake and not (data_wake[date_str] is None): | |
return get_wake_time_human_readable(data_wake[date_str]) + " Wake up" | |
return "" | |
def get_sleep_time_from_str(str): | |
global data_sleep | |
# Data = number of minutes after midday | |
if str in data_sleep and not (data_sleep[str] is None): | |
return get_minutes_to_human_readable(data_sleep[str] + NUMBER_OF_MINUTES_MIDDAY) + " Sleep" | |
return "" | |
def exist(): | |
global EXIST_SLEEP_JSON, EXIST_WAKE_JSON | |
print("Fetching sleep times >> " + EXIST_SLEEP_JSON) | |
raw_data = {} | |
page = 1 # Use page 1 to start | |
with open(EXIST_SLEEP_JSON, "w") as f: | |
while True: | |
response = requests.get(EXIST_SLEEP_URL_TEMPLATE.format(DEFAULT_LIMIT, page), headers=EXIST_HEADERS) | |
results = response.json().get(EXIST_RESULTS_STR_CONSTANT, None) | |
if results is None or len(results) == 0: | |
break | |
for item in results: | |
raw_data[item["date"]] = item[EXIST_VALUE_STR_CONSTANT] | |
page += 1 | |
f.write(json.dumps(raw_data)) | |
f.close() | |
print("Fetching wake times >> " + EXIST_WAKE_JSON) | |
raw_data = {} | |
page = 1 # Use page 1 to start | |
with open(EXIST_WAKE_JSON, "w") as f: | |
while True: | |
response = requests.get(EXIST_WAKE_URL_TEMPLATE.format(DEFAULT_LIMIT, page), headers=EXIST_HEADERS) | |
results = response.json().get(EXIST_RESULTS_STR_CONSTANT, None) | |
if results is None or len(results) == 0: | |
break | |
for item in results: | |
raw_data[item["date"]] = item[EXIST_VALUE_STR_CONSTANT] | |
page += 1 | |
f.write(json.dumps(raw_data)) | |
f.close() | |
def woke(): | |
woke_str_builder = "{0} Woke up. Slept {1:.1f} hours last night" | |
# Grab the most recent wake time | |
response = requests.get(EXIST_WAKE_URL_TEMPLATE.format(1, 1), headers=EXIST_HEADERS) | |
results = response.json().get(EXIST_RESULTS_STR_CONSTANT, None) | |
if results is None or len(results) == 0 or results[0][EXIST_VALUE_STR_CONSTANT] is None: | |
print (NA_STR_CONSTANT) | |
return | |
wake_time = results[0][EXIST_VALUE_STR_CONSTANT] | |
# Grab last night's sleep time | |
response = requests.get(EXIST_SLEEP_URL_TEMPLATE.format(1, 1), headers=EXIST_HEADERS) | |
results = response.json().get(EXIST_RESULTS_STR_CONSTANT, None) | |
if results is None or len(results) == 0 or results[0][EXIST_VALUE_STR_CONSTANT] is None: | |
print( woke_str_builder.format(get_wake_time_human_readable(wake_time), 0), end="") | |
return | |
# Adjust then find sleep duration | |
sleep_time = results[0][EXIST_VALUE_STR_CONSTANT] + NUMBER_OF_MINUTES_MIDDAY | |
sleep_duration = (wake_time + NUMBER_OF_MINUTES_WHOLE_DAY - sleep_time) / 60 | |
print( woke_str_builder.format(get_wake_time_human_readable(wake_time), sleep_duration), end="") | |
""" Foursquare """ | |
FOURSQUARE_URL_TEMPLATE = "https://api.foursquare.com/v2/users/self/checkins?limit=250&oauth_token={}&v=20131026&offset={}" | |
FOURSQURE_JSON = "foursquare.json" | |
FOURSQUARE_TIMEZONE_STR_CONSTANT = "timeZoneOffset" | |
def foursquare(): | |
raw_data = {} | |
# Saves your foursquare to foursquare.json | |
print("Fetching check ins >> " + FOURSQURE_JSON) | |
offset = 0 # We set this offset for request size | |
with open(FOURSQURE_JSON, "w") as f: | |
while True: | |
response = requests.get(FOURSQUARE_URL_TEMPLATE.format(FOURSQUARE_OAUTH_TOKEN, offset), verify=False) | |
results = response.json()["response"]["checkins"].get("items", None) | |
if len(results) == 0: | |
break | |
for item in results: | |
raw_data[item["createdAt"] + item[FOURSQUARE_TIMEZONE_STR_CONSTANT] * 60] = item | |
offset += 250 | |
f.write(json.dumps(raw_data, sort_keys=True, indent=4, separators=(",", ": "))) | |
f.close() | |
def show(numdays): | |
global data_wake, data_sleep | |
raw_data = {} | |
DAY_FORMAT = "%Y %b %d %a" | |
CAL_FORMAT = "%Y%m%d" | |
TIME_FORMAT = "%I:%M %p" | |
EXIST_DAY_FORMAT = "%Y-%m-%d" | |
FOURSQUARE_VENUE_STR_CONSTANT = "venue" | |
FOURSQUARE_NAME_STR_CONSTANT = "name" | |
# Make Dates | |
base = datetime.datetime.today() | |
date_list = [base - datetime.timedelta(days=x) for x in range(0, numdays)] | |
## Parse Foursquare Data | |
with open(FOURSQURE_JSON, "r") as f: | |
raw_data = json.load(f) | |
for timestamp, dict in six.iteritems(raw_data): | |
utctimestamp = datetime.datetime.utcfromtimestamp(int(timestamp)) + datetime.timedelta(minutes=int(dict[FOURSQUARE_TIMEZONE_STR_CONSTANT])) | |
tz_timestamp = int(timestamp) + int(dict[FOURSQUARE_TIMEZONE_STR_CONSTANT]) | |
# E.g. "2017 May 27 Sat" | |
day_str = datetime.datetime.fromtimestamp(tz_timestamp).strftime(DAY_FORMAT) | |
# E.g. "12:58 PM" | |
time = datetime.datetime.fromtimestamp(tz_timestamp).strftime(TIME_FORMAT) | |
# Construct the object we're inserting. Some check ins no longer have venue names | |
obj = {} | |
if FOURSQUARE_VENUE_STR_CONSTANT in dict and FOURSQUARE_NAME_STR_CONSTANT in dict[FOURSQUARE_VENUE_STR_CONSTANT]: | |
obj[FOURSQUARE_NAME_STR_CONSTANT] = dict[FOURSQUARE_VENUE_STR_CONSTANT][FOURSQUARE_NAME_STR_CONSTANT] | |
else: | |
obj[FOURSQUARE_NAME_STR_CONSTANT] = NA_STR_CONSTANT | |
obj["time"] = time | |
obj["corrected_time"] = utctimestamp.strftime(TIME_FORMAT) | |
obj["timestamp"] = tz_timestamp | |
try: | |
data_formatted_dates[day_str].append(obj) | |
except KeyError: | |
data_formatted_dates[day_str] = [] | |
data_formatted_dates[day_str].append(obj) | |
f.close() | |
## Parse Sleep / Wake Data | |
with open(EXIST_SLEEP_JSON, "r") as f: | |
data_sleep = json.load(f) | |
f.close() | |
with open(EXIST_WAKE_JSON, "r") as f: | |
data_wake = json.load(f) | |
f.close() | |
# Show in reverse order | |
for timestamp in date_list[::-1]: | |
day_str = timestamp.strftime(DAY_FORMAT) | |
cal_str = timestamp.strftime(CAL_FORMAT) | |
wake_date_str = timestamp.strftime(EXIST_DAY_FORMAT) | |
sleep_date_str = (timestamp + datetime.timedelta(days=1)).strftime(EXIST_DAY_FORMAT) | |
cal_str_command = OUTLOOK_CALENDAR_COMMAND + " " + cal_str | |
print(day_str + ": ") | |
print (get_wake_time_from_str(wake_date_str)) | |
if day_str in data_formatted_dates: | |
# For Python2: process = subprocess.Popen(OUTLOOK_CALENDAR_COMMAND.split(), stdout=subprocess.PIPE) | |
# For Python2: output, error = process.communicate() | |
for obj in sorted(data_formatted_dates[day_str], key=lambda k: k["timestamp"]): | |
print(obj["corrected_time"] + " " + obj[FOURSQUARE_NAME_STR_CONSTANT].strip()) | |
print (get_sleep_time_from_str(sleep_date_str)) | |
if args.calendar: | |
# For Python2: process = subprocess.Popen(cal_str_command.split(), stdout=subprocess.PIPE) | |
# Fpr Python2: output, error = process.communicate() | |
output = subprocess.check_output([OUTLOOK_CALENDAR_COMMAND, cal_str]) | |
print("--- calendar ---") | |
print(output.decode("UTF-8")) | |
print ("-----------------------------------------------") | |
print ("") | |
if args.foursquare: | |
foursquare() | |
if args.exist: | |
exist() | |
if args.woke: | |
woke() | |
if args.show: | |
print("Showing " + str(args.show) + " days in the current time zone\n") | |
while True: | |
print ("Press space or 'f' to write to " + OUTPUT_FILE) | |
choice = sys.stdin.read(1)[0] | |
if choice == "f" : | |
# Redirect std out to the text file | |
print ("Please wait…") | |
sys.stdout = open(OUTPUT_FILE, "w") | |
show(args.show) | |
os.system("Open "+OUTPUT_FILE) | |
break | |
else: | |
show(args.show) | |
break | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment