From 46b1d12a0892befe0a9b6dc2718d29c7d3c29803 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 15 Nov 2022 05:44:31 -0500 Subject: [PATCH 1/7] Twitch API using 0Auth token For FoamyGuy, thank you for the help. --- examples/requests_api_twitch.py | 167 ++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 examples/requests_api_twitch.py diff --git a/examples/requests_api_twitch.py b/examples/requests_api_twitch.py new file mode 100644 index 0000000..eb4300a --- /dev/null +++ b/examples/requests_api_twitch.py @@ -0,0 +1,167 @@ +# SPDX-FileCopyrightText: 2022 DJDevon3 for Adafruit Industries +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.0 +"""DJDevon3 Adafruit Feather ESP32-S2 Twitch_API_Example""" +import gc +import time +import ssl +import json +import wifi +import socketpool +import adafruit_requests + +# Twitch Developer Account & 0Auth App Required: +# Ensure Twitch_ClientID & Twitch_Client_Secret are in secrets.py or .env + +# "Twitch_ClientID": "Your Developer APP ID Here", +# "Twitch_Client_Secret": "APP ID secret here", + +# For finding your Twitch User ID +# https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ +Twitch_UserID = "0000000" # Set User ID you want endpoints from + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + +try: + from secrets import secrets +except ImportError: + print("Secrets File Import Error") + raise + +# Converts seconds in minutes/hours/days +def time_calc(time): + if time < 60: + sleep_int = time + time_output = f"{sleep_int:.0f} seconds" + return time_output + elif 60 <= time < 3600: + sleep_int = time / 60 + time_output = f"{sleep_int:.0f} minutes" + return time_output + elif 3600 <= time < 86400: + sleep_int = time / 60 / 60 + time_output = f"{sleep_int:.0f} hours" + return time_output + elif 86400 <= time < 432000: + sleep_int = time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + return time_output + else: # if > 5 days convert float to int & display whole days + sleep_int = time / 60 / 60 / 24 + time_output = f"{sleep_int:.0f} days" + return time_output + + +# First we use Client ID & Client Secret to create a token with POST +# No user interaction is required for this type of scope (implicit grant flow) +twitch_0auth_header = {'Content-Type': 'application/x-www-form-urlencoded'} +TWITCH_0AUTH_TOKEN = ( + "https://id.twitch.tv/oauth2/token" +) + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(secrets["ssid"], secrets["password"]) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("Connected!\n") + +while True: + try: + # ----------------------------- POST FOR BEARER TOKEN ----------------------------------------------- + print("\nAttempting to GENERATE Twitch Bearer Token!") # --------------------------------------- + # Print Request to Serial + debug_bearer_request = False # STREAMER WARNING: your client secret will be viewable + if debug_bearer_request: + print("Full API GET URL: ", TWITCH_0AUTH_TOKEN) + print("===============================") + twitch_0auth_data = ("&client_id=" + + secrets["Twitch_ClientID"] + + "&client_secret=" + + secrets["Twitch_Client_Secret"] + + "&grant_type=client_credentials" + ) + + # POST REQUEST + twitch_0auth_response = requests.post(url=TWITCH_0AUTH_TOKEN, data=twitch_0auth_data, headers=twitch_0auth_header) + try: + twitch_0auth_json = twitch_0auth_response.json() + twitch_access_token = twitch_0auth_json['access_token'] + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + # Print Response to Serial + debug_bearer_response = False # STREAMER WARNING: your client secret will be viewable + if debug_bearer_response: + print("JSON Dump: ", twitch_0auth_json) + print("Header: ", twitch_0auth_header) + print("Access Token: ", twitch_access_token) + + twitch_token_expiration = twitch_0auth_json['expires_in'] + print("Token Expires in: ", time_calc(twitch_token_expiration)) + twitch_token_type = twitch_0auth_json['token_type'] + print("Token Type: ", twitch_token_type) + print("Monotonic: ", time.monotonic()) + + # ----------------------------- GET DATA ----------------------------------------------- + # Bearer token is refreshed every time script runs :) + # Twitch sets token expiration to about 64 days + # Helix is the name of the current Twitch API + # Now that we have POST bearer token we can do a GET for data + # -------------------------------------------------------------------------------------- + twitch_header = { + 'Authorization': 'Bearer '+twitch_access_token+'', + 'Client-Id': ''+ secrets["Twitch_ClientID"] +'' + } + TWITCH_FOLLOWERS_SOURCE = ( + "https://api.twitch.tv/helix/users" + + "/follows?" + + "to_id=" + + Twitch_UserID + + "&first=1" + ) + print("\nAttempting to GET TWITCH Stats!") # ------------------------------------------------ + print("===============================") + twitch_followers_response = requests.get(url=TWITCH_FOLLOWERS_SOURCE, headers=twitch_header) + try: + twitch_followers_json = twitch_followers_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + + # Print Response to Serial + debug_bearer_response = False # STREAMER WARNING: your bearer token will be viewable + if debug_bearer_response: + print("Full API GET URL: ", TWITCH_FOLLOWERS_SOURCE) + print("Header: ", twitch_header) + print("JSON Full Response: ", twitch_followers_json) + + twitch_username = twitch_followers_json['data'][0]['to_name'] + print("Username: ", twitch_username) + twitch_followers = twitch_followers_json['total'] + print("Followers: ", twitch_followers) + print("Monotonic: ", time.monotonic()) # Board Up-Time seconds + + print("\nFinished!") + print("Next Update in: ", time_calc(sleep_time)) + print("===============================") + gc.collect() + + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From 2b84dfab22099a7a27c96a1b5d14aace8613714e Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 15 Nov 2022 06:10:27 -0500 Subject: [PATCH 2/7] Making Black & Pylint happy black you vex me --- examples/requests_api_twitch.py | 112 +++++++++++++++++--------------- 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/examples/requests_api_twitch.py b/examples/requests_api_twitch.py index eb4300a..910bea6 100644 --- a/examples/requests_api_twitch.py +++ b/examples/requests_api_twitch.py @@ -5,7 +5,6 @@ import gc import time import ssl -import json import wifi import socketpool import adafruit_requests @@ -14,7 +13,7 @@ # Ensure Twitch_ClientID & Twitch_Client_Secret are in secrets.py or .env # "Twitch_ClientID": "Your Developer APP ID Here", -# "Twitch_Client_Secret": "APP ID secret here", +# "Twitch_Client_Secret": "APP ID secret here", # For finding your Twitch User ID # https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ @@ -34,35 +33,29 @@ raise # Converts seconds in minutes/hours/days -def time_calc(time): - if time < 60: - sleep_int = time +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time time_output = f"{sleep_int:.0f} seconds" - return time_output - elif 60 <= time < 3600: - sleep_int = time / 60 + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 time_output = f"{sleep_int:.0f} minutes" - return time_output - elif 3600 <= time < 86400: - sleep_int = time / 60 / 60 + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 time_output = f"{sleep_int:.0f} hours" - return time_output - elif 86400 <= time < 432000: - sleep_int = time / 60 / 60 / 24 + elif 86400 <= input_time < 432000: + sleep_int = input_time / 60 / 60 / 24 time_output = f"{sleep_int:.1f} days" - return time_output else: # if > 5 days convert float to int & display whole days - sleep_int = time / 60 / 60 / 24 + sleep_int = input_time / 60 / 60 / 24 time_output = f"{sleep_int:.0f} days" - return time_output + return time_output # First we use Client ID & Client Secret to create a token with POST # No user interaction is required for this type of scope (implicit grant flow) -twitch_0auth_header = {'Content-Type': 'application/x-www-form-urlencoded'} -TWITCH_0AUTH_TOKEN = ( - "https://id.twitch.tv/oauth2/token" -) +twitch_0auth_header = {"Content-Type": "application/x-www-form-urlencoded"} +TWITCH_0AUTH_TOKEN = "https://id.twitch.tv/oauth2/token" # Connect to Wi-Fi print("\n===============================") @@ -80,51 +73,60 @@ def time_calc(time): while True: try: - # ----------------------------- POST FOR BEARER TOKEN ----------------------------------------------- - print("\nAttempting to GENERATE Twitch Bearer Token!") # --------------------------------------- + # ----------------------------- POST FOR BEARER TOKEN ----------------------- + print( + "\nAttempting to GENERATE Twitch Bearer Token!" + ) # --------------------------------------- # Print Request to Serial - debug_bearer_request = False # STREAMER WARNING: your client secret will be viewable - if debug_bearer_request: + debug_bearer_request = ( + False # STREAMER WARNING: your client secret will be viewable + ) + if debug_bearer_request: print("Full API GET URL: ", TWITCH_0AUTH_TOKEN) print("===============================") - twitch_0auth_data = ("&client_id=" - + secrets["Twitch_ClientID"] - + "&client_secret=" - + secrets["Twitch_Client_Secret"] - + "&grant_type=client_credentials" + twitch_0auth_data = ( + "&client_id=" + + secrets["Twitch_ClientID"] + + "&client_secret=" + + secrets["Twitch_Client_Secret"] + + "&grant_type=client_credentials" ) - + # POST REQUEST - twitch_0auth_response = requests.post(url=TWITCH_0AUTH_TOKEN, data=twitch_0auth_data, headers=twitch_0auth_header) + twitch_0auth_response = requests.post( + url=TWITCH_0AUTH_TOKEN, data=twitch_0auth_data, headers=twitch_0auth_header + ) try: twitch_0auth_json = twitch_0auth_response.json() - twitch_access_token = twitch_0auth_json['access_token'] + twitch_access_token = twitch_0auth_json["access_token"] except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") - + # Print Response to Serial - debug_bearer_response = False # STREAMER WARNING: your client secret will be viewable - if debug_bearer_response: + debug_bearer_response = ( + False # STREAMER WARNING: your client secret will be viewable + ) + if debug_bearer_response: print("JSON Dump: ", twitch_0auth_json) print("Header: ", twitch_0auth_header) print("Access Token: ", twitch_access_token) - - twitch_token_expiration = twitch_0auth_json['expires_in'] + + twitch_token_expiration = twitch_0auth_json["expires_in"] print("Token Expires in: ", time_calc(twitch_token_expiration)) - twitch_token_type = twitch_0auth_json['token_type'] + twitch_token_type = twitch_0auth_json["token_type"] print("Token Type: ", twitch_token_type) print("Monotonic: ", time.monotonic()) - - # ----------------------------- GET DATA ----------------------------------------------- + + # ----------------------------- GET DATA ------------------------------------- # Bearer token is refreshed every time script runs :) # Twitch sets token expiration to about 64 days # Helix is the name of the current Twitch API # Now that we have POST bearer token we can do a GET for data - # -------------------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- twitch_header = { - 'Authorization': 'Bearer '+twitch_access_token+'', - 'Client-Id': ''+ secrets["Twitch_ClientID"] +'' + "Authorization": "Bearer " + twitch_access_token + "", + "Client-Id": "" + secrets["Twitch_ClientID"] + "", } TWITCH_FOLLOWERS_SOURCE = ( "https://api.twitch.tv/helix/users" @@ -133,25 +135,31 @@ def time_calc(time): + Twitch_UserID + "&first=1" ) - print("\nAttempting to GET TWITCH Stats!") # ------------------------------------------------ + print( + "\nAttempting to GET TWITCH Stats!" + ) # ------------------------------------------------ print("===============================") - twitch_followers_response = requests.get(url=TWITCH_FOLLOWERS_SOURCE, headers=twitch_header) + twitch_followers_response = requests.get( + url=TWITCH_FOLLOWERS_SOURCE, headers=twitch_header + ) try: twitch_followers_json = twitch_followers_response.json() except ConnectionError as e: print("Connection Error:", e) print("Retrying in 10 seconds") - + # Print Response to Serial - debug_bearer_response = False # STREAMER WARNING: your bearer token will be viewable - if debug_bearer_response: + debug_bearer_response = ( + False # STREAMER WARNING: your bearer token will be viewable + ) + if debug_bearer_response: print("Full API GET URL: ", TWITCH_FOLLOWERS_SOURCE) print("Header: ", twitch_header) print("JSON Full Response: ", twitch_followers_json) - - twitch_username = twitch_followers_json['data'][0]['to_name'] + + twitch_username = twitch_followers_json["data"][0]["to_name"] print("Username: ", twitch_username) - twitch_followers = twitch_followers_json['total'] + twitch_followers = twitch_followers_json["total"] print("Followers: ", twitch_followers) print("Monotonic: ", time.monotonic()) # Board Up-Time seconds From 6f88d7a546a335664645a1aa22615775bb2f7e22 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Tue, 15 Nov 2022 06:24:36 -0500 Subject: [PATCH 3/7] added url to twitch dev app Because searching for a link to the place where you're supposed to create the 0auth app is sometimes like trying to find a needle in a haystack. --- examples/requests_api_twitch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/requests_api_twitch.py b/examples/requests_api_twitch.py index 910bea6..40b8343 100644 --- a/examples/requests_api_twitch.py +++ b/examples/requests_api_twitch.py @@ -10,6 +10,7 @@ import adafruit_requests # Twitch Developer Account & 0Auth App Required: +# Visit https://dev.twitch.tv/console to create an app # Ensure Twitch_ClientID & Twitch_Client_Secret are in secrets.py or .env # "Twitch_ClientID": "Your Developer APP ID Here", From 64d59f60e4b80bc3c01896d49978cc28bb9258c9 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 17 Nov 2022 03:34:33 -0500 Subject: [PATCH 4/7] Add Mastodon API No token required to get your own followers, publicly accessible. --- examples/requests_api_mastodon.py | 116 ++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 examples/requests_api_mastodon.py diff --git a/examples/requests_api_mastodon.py b/examples/requests_api_mastodon.py new file mode 100644 index 0000000..600fed5 --- /dev/null +++ b/examples/requests_api_mastodon.py @@ -0,0 +1,116 @@ +# SPDX-FileCopyrightText: 2022 DJDevon3 +# SPDX-License-Identifier: MIT +# Coded for Circuit Python 8.0 +"""DJDevon3 Adafruit Feather ESP32-S2 Mastodon_API_Example""" +import gc +import time +import ssl +import wifi +import socketpool +import adafruit_requests + +# Mastodon V1 API - Public access (no dev creds or app required) +# Visit https://docs.joinmastodon.org/client/public/ for API docs +# For finding your Mastodon User ID +# Login to your mastodon server in a browser, visit your profile, UserID is in the URL. +# Example: https://mastodon.YOURSERVER/web/accounts/YOURUSERIDISHERE + +Mastodon_UserID = "000000000000000000" # Set User ID you want endpoints from +# Test in browser first, this will pull up a JSON webpage +# https://mastodon.YOURSERVER/api/v1/accounts/YOURUSERIDHERE/statuses?limit=1 + +# Initialize WiFi Pool (There can be only 1 pool & top of script) +pool = socketpool.SocketPool(wifi.radio) + +# Time between API refreshes +# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour +sleep_time = 900 + +try: + from secrets import secrets +except ImportError: + print("Secrets File Import Error") + raise + +# Converts seconds in minutes/hours/days +def time_calc(input_time): + if input_time < 60: + sleep_int = input_time + time_output = f"{sleep_int:.0f} seconds" + elif 60 <= input_time < 3600: + sleep_int = input_time / 60 + time_output = f"{sleep_int:.0f} minutes" + elif 3600 <= input_time < 86400: + sleep_int = input_time / 60 / 60 + time_output = f"{sleep_int:.0f} hours" + elif 86400 <= input_time < 432000: + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.1f} days" + else: # if > 5 days convert float to int & display whole days + sleep_int = input_time / 60 / 60 / 24 + time_output = f"{sleep_int:.0f} days" + return time_output + + +# Publicly available data no header required +MAST_SOURCE = ( + "https://mastodon.cloud/api/v1/accounts/" + + Mastodon_UserID + + "/statuses?limit=1" +) + +# Connect to Wi-Fi +print("\n===============================") +print("Connecting to WiFi...") +requests = adafruit_requests.Session(pool, ssl.create_default_context()) +while not wifi.radio.ipv4_address: + try: + wifi.radio.connect(secrets["ssid"], secrets["password"]) + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + time.sleep(10) + gc.collect() +print("Connected!\n") + +while True: + try: + print("\nAttempting to GET MASTODON Stats!") # ----------------------------- + # Print Request to Serial + debug_mastodon_full_response = ( + False # STREAMER WARNING: your client secret will be viewable + ) + print("===============================") + mastodon_response = requests.get(url=MAST_SOURCE) + try: + mastodon_json = mastodon_response.json() + except ConnectionError as e: + print("Connection Error:", e) + print("Retrying in 10 seconds") + mastodon_json = mastodon_json[0] + if debug_mastodon_full_response: + print("Full API GET URL: ", MAST_SOURCE) + print(mastodon_json) + mastodon_userid = mastodon_json['account']['id'] + print("User ID: ", mastodon_userid) + + mastodon_username = mastodon_json['account']['display_name'] + print("Name: ", mastodon_username) + mastodon_join_date = mastodon_json['account']['created_at'] + print("Member Since: ", mastodon_join_date) + mastodon_toot_count = mastodon_json['account']['statuses_count'] + print("Toots: ", mastodon_toot_count) + mastodon_follower_count = mastodon_json['account']['followers_count'] + print("Followers: ", mastodon_follower_count) + print("Monotonic: ", time.monotonic()) + + print("\nFinished!") + print("Next Update in: ", time_calc(sleep_time)) + print("===============================") + gc.collect() + + except (ValueError, RuntimeError) as e: + print("Failed to get data, retrying\n", e) + time.sleep(60) + continue + time.sleep(sleep_time) From bc7a4680e61f3b402f064c98b0a311e0fa58a647 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Thu, 17 Nov 2022 03:39:08 -0500 Subject: [PATCH 5/7] ran black on Mastodon file addition --- examples/requests_api_mastodon.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/examples/requests_api_mastodon.py b/examples/requests_api_mastodon.py index 600fed5..99c651d 100644 --- a/examples/requests_api_mastodon.py +++ b/examples/requests_api_mastodon.py @@ -54,9 +54,7 @@ def time_calc(input_time): # Publicly available data no header required MAST_SOURCE = ( - "https://mastodon.cloud/api/v1/accounts/" - + Mastodon_UserID - + "/statuses?limit=1" + "https://mastodon.cloud/api/v1/accounts/" + Mastodon_UserID + "/statuses?limit=1" ) # Connect to Wi-Fi @@ -91,16 +89,16 @@ def time_calc(input_time): if debug_mastodon_full_response: print("Full API GET URL: ", MAST_SOURCE) print(mastodon_json) - mastodon_userid = mastodon_json['account']['id'] + mastodon_userid = mastodon_json["account"]["id"] print("User ID: ", mastodon_userid) - - mastodon_username = mastodon_json['account']['display_name'] + + mastodon_username = mastodon_json["account"]["display_name"] print("Name: ", mastodon_username) - mastodon_join_date = mastodon_json['account']['created_at'] + mastodon_join_date = mastodon_json["account"]["created_at"] print("Member Since: ", mastodon_join_date) - mastodon_toot_count = mastodon_json['account']['statuses_count'] + mastodon_toot_count = mastodon_json["account"]["statuses_count"] print("Toots: ", mastodon_toot_count) - mastodon_follower_count = mastodon_json['account']['followers_count'] + mastodon_follower_count = mastodon_json["account"]["followers_count"] print("Followers: ", mastodon_follower_count) print("Monotonic: ", time.monotonic()) From 0ef218df4c7351164a19ba0049366977f06dce3f Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:06:38 -0500 Subject: [PATCH 6/7] replace hardcoded server with configurable server name Fixing requested change --- examples/requests_api_mastodon.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/requests_api_mastodon.py b/examples/requests_api_mastodon.py index 99c651d..09ab59f 100644 --- a/examples/requests_api_mastodon.py +++ b/examples/requests_api_mastodon.py @@ -15,6 +15,7 @@ # Login to your mastodon server in a browser, visit your profile, UserID is in the URL. # Example: https://mastodon.YOURSERVER/web/accounts/YOURUSERIDISHERE +Mastodon_Server = "mastodon.social" # Set server instance Mastodon_UserID = "000000000000000000" # Set User ID you want endpoints from # Test in browser first, this will pull up a JSON webpage # https://mastodon.YOURSERVER/api/v1/accounts/YOURUSERIDHERE/statuses?limit=1 @@ -53,8 +54,11 @@ def time_calc(input_time): # Publicly available data no header required -MAST_SOURCE = ( - "https://mastodon.cloud/api/v1/accounts/" + Mastodon_UserID + "/statuses?limit=1" +MAST_SOURCE = ("https://" + + Mastodon_Server + + "/api/v1/accounts/" + + Mastodon_UserID + + "/statuses?limit=1" ) # Connect to Wi-Fi From 0c12ada522ba599770473c9f511f7073e543ef49 Mon Sep 17 00:00:00 2001 From: DJDevon3 <49322231+DJDevon3@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:13:02 -0500 Subject: [PATCH 7/7] ran black manually --- examples/requests_api_mastodon.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/requests_api_mastodon.py b/examples/requests_api_mastodon.py index 09ab59f..03b4328 100644 --- a/examples/requests_api_mastodon.py +++ b/examples/requests_api_mastodon.py @@ -54,7 +54,8 @@ def time_calc(input_time): # Publicly available data no header required -MAST_SOURCE = ("https://" +MAST_SOURCE = ( + "https://" + Mastodon_Server + "/api/v1/accounts/" + Mastodon_UserID