""" ========================================================================================= Copyright © 2023 Alexandre Racine This file is part of Inopy. Inopy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Inopy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Inopy. If not, see . ========================================================================================= DISCLAIMER: parts of this code and comments blocks were created with the help of ChatGPT developped by OpenAI Followed by human reviewing, refactoring and fine-tuning. ========================================================================================= Inopy retrieves unread articles from the Inoreader API and sends a notification if there are any unread articles. It uses a config file to store OAuth authentication data, Inoreader API endpoints and notification data. It performs token refreshing process if the API request returns an unauthorized status code. Inopy is structured into functions and modules for making API requests, parsing response data, refreshing tokens and sending notifications. For more information about OAuth authentication, plase see ========================================================================================= """ import requests import json import notif from config import config from oauth import app, run_app from refresh import refresh ''' =========================================== Define functions to make an API request with bearer token and parse response data as JSON =========================================== ''' def APIrequest(url, bearer): bearer_string = 'Bearer {}'.format(bearer) headers = {'Authorization': bearer_string} response = requests.get(url, headers=headers) return response def getData(response): data = json.loads(response.text) return data ''' =================================== Initialize an empty message string for the notification =================================== ''' message = "" ''' =========================================== Load configuration settings and retrieve necessary configuration values =========================================== ''' config = config() bearer = config['bearer'] unread_counts_url = config['unread_counts_url'] feeds_list_url = config['feeds_list_url'] config_path = config['config_file_path'] endpoint = config['endpoint'] client_id = config['client_id'] client_secret = config['client_secret'] refresh_token = config['refresh_token'] summary = config['summary'] singular_article = config['singular_article'] plural_articles = config['plural_articles'] ''' ========================================= Create dictionaries and list to store unread counts, subscriptions and feed categories (folders) ========================================= ''' unreadcounts = {} subscriptions = {} categories = [] # Make API request to get unread counts unread_response = APIrequest(unread_counts_url, bearer) ''' =================================================== If the response status code is 403 (Forbidden): * Run the Flask app to get a bearer token * Load the updated configuration file * Update the bearer token with the new value from the updated config and make a new API request with the updated bearer token =================================================== ''' ''' ====================================================== If the response status code is 401 (Unauthorized): * Refresh the bearer token * Load the updated configuration file * Update the bearer token with the new value from the updated config and make a new API request with the updated bearer token ====================================================== ''' ''' ============================================ If the response status code is 200 (OK): * proceed with the code execution ============================================ ''' # Check for 403 error case if unread_response.status_code == 403: run_app() with open(config_path) as config_file: new_config = json.load(config_file) bearer = new_config['oauth']['bearer'] unread_response = APIrequest(unread_counts_url, bearer) # Check for 401 error case elif unread_response.status_code == 401: refresh(config_path, endpoint, client_id, client_secret, refresh_token) with open(config_path) as config_file: new_config = json.load(config_file) bearer = new_config['oauth']['bearer'] unread_response = APIrequest(unread_counts_url, bearer) # Proceed with the code execution elif unread_response.status_code == 200: pass # Make API request to get feeds list feeds_list_response = APIrequest(feeds_list_url, bearer) ''' ======================================= Parse the responses data as JSON Iterate over the unread counts and subscriptions and store them in the respective dictionaries If the subscription has categories (is part of a folder) append the category in the categories list ======================================= ''' feeds_list_data = getData(feeds_list_response) unread_data = getData(unread_response) for unread in unread_data['unreadcounts']: unread['count'] = int(unread['count']) if unread['count'] > 0: unreadcounts[unread['id']] = unread['count'] for subscribed in feeds_list_data['subscriptions']: if subscribed['categories']: if subscribed['categories'][0]['id'] not in categories: categories.append(subscribed['categories'][0]['id']) subscriptions[subscribed['id']] = subscribed['title'] ''' ================================================== Iterate over the unreadcounts dictionary Determine the appropriate singular or plural notification label based on the count (e.g. new article or new articles) Include the unread feed in the notification only if it is not in the categories list. This is to avoid duplicates notifications for the unread feed and the folder in which the feed is. Do not include the reading-list in the notification If the unread_id exists in the subscriptions dictionary, get the title associated with it. Else extract the title from the unread_id. Finally append the count, new_articles label and title to the message string ================================================== ''' for unread_id, count in unreadcounts.items(): # Determine singular or plural notification label new_articles = singular_article if count == 1 else plural_articles count = str(count) # Do not include the categories and the reading-list in the notification if not unread_id in categories: if unread_id.split("/")[-1] == "reading-list": pass else: # Get the clean feed title if unread_id in (k for k,v in subscriptions.items()): title = next(v for k, v in subscriptions.items() if k == unread_id) else: title = unread_id.split("/")[-1] # Build the final notification message message = message + count + " " + new_articles + " " + title + "\n" else: pass ''' ==================================== Send the notification for unread feeds only if the message string is set ==================================== ''' if message != "": notif.send_notification(summary, message) else: pass