Set logging process
* Sets logging method and replaces basic "print" with logs * Replaces /HOME/USER/.config/ directory with /HOME/USER/.inopy directory * Moves config directory in new /HOME/USER/.inopy directory and creates a new "logs" directory * Adapts README filemaster
							parent
							
								
									f4fe6e717e
								
							
						
					
					
						commit
						7c3c922529
					
				|  | @ -11,7 +11,7 @@ Inopy is a Python application that retrieves unread articles from the [Inoreader | |||
| 
 | ||||
| ## Overview | ||||
| 
 | ||||
| It uses OAuth authentication for accessing the [API](https://www.inoreader.com/developers) and stores the authentication data in a JSON configuration file located at `/HOME/USER/.config/inopy/config.json`. The program is divided into multiple modules and functions for making API requests, parsing response data, refreshing tokens, and sending notifications. | ||||
| It uses OAuth authentication for accessing the [API](https://www.inoreader.com/developers) and stores the authentication data in a JSON configuration file located at `/HOME/USER/.inopy/config/config.json`. The program is divided into multiple modules and functions for making API requests, parsing response data, refreshing tokens, sending notifications and logging the processes. | ||||
| 
 | ||||
| The code is organized into the following modules: | ||||
| 
 | ||||
|  | @ -20,6 +20,7 @@ The code is organized into the following modules: | |||
| - `oauth.py`: Implements the OAuth authentication flow using Flask. | ||||
| - `refresh.py`: Contains a `refresh` function for refreshing OAuth access and refresh tokens and updating the configuration file. | ||||
| - `notif.py`: Provides a function for sending notifications using D-Bus (only tested with Cinnamon desktop environment). | ||||
| - `logs.py`: Defines logging options. | ||||
| 
 | ||||
| ## Installation | ||||
| 
 | ||||
|  | @ -39,7 +40,7 @@ pip install requests pydbus flask waitress | |||
| 
 | ||||
| ## Usage | ||||
| 
 | ||||
| The first time `ino.py` module is run, it will check if `config.json` exists in `/HOME/USER/.config/inopy/`. If not it will prompt user for configuration details and create the `config.json` file. The file should contain the OAuth endpoint, client ID, client secret, callback URL, scope, CSRF value and home URL. | ||||
| The first time `ino.py` module is run, it will check if `config.json` exists in `/HOME/USER/.inopy/config/`. If not it will prompt user for configuration details and create the `config.json` file. The file should contain the OAuth endpoint, client ID, client secret, callback URL, scope, CSRF value and home URL. | ||||
| 
 | ||||
| It should also contain the Inoreader API endpoints, notification labels, production status, browser path, host and port. | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										15
									
								
								config.py
								
								
								
								
							
							
						
						
									
										15
									
								
								config.py
								
								
								
								
							|  | @ -32,6 +32,11 @@ | |||
| 
 | ||||
| import json | ||||
| import os | ||||
| import logging | ||||
| from logs import LogFile | ||||
| 
 | ||||
| # Set logs file | ||||
| log_file = LogFile() | ||||
| 
 | ||||
| ''' | ||||
| ================================================================ | ||||
|  | @ -51,8 +56,10 @@ import os | |||
| def get_config(config_path, config_file_path): | ||||
| 	 | ||||
| 	if os.path.exists(config_path): | ||||
| 		logging.info(f'Found config directory at {config_path}') | ||||
| 		 | ||||
| 		if os.path.exists(config_file_path): | ||||
| 			logging.info(f'Found config file at {config_file_path}!') | ||||
| 			pass | ||||
| 		 | ||||
| 		else: | ||||
|  | @ -64,7 +71,8 @@ def get_config(config_path, config_file_path): | |||
| 
 | ||||
| 		# If the config directory doesn't exist, create it | ||||
| 		os.mkdir(config_path) | ||||
| 		print(f'{config_path} created!') | ||||
| 		logging.info(f'{config_path} created!') | ||||
| 
 | ||||
| 		create_file(config_file_path) | ||||
| 	 | ||||
| 	# load config content | ||||
|  | @ -167,7 +175,8 @@ def create_file(config_file_path): | |||
| 	with open(config_file_path, "w") as file: | ||||
| 		json.dump(config, file, indent=4) | ||||
| 
 | ||||
| 	print(f"{config_file_path} created successfully!") | ||||
| 	#print(f"{config_file_path} created successfully!") | ||||
| 	logging.info(f'Created config file at {config_file_path}!') | ||||
| 
 | ||||
| ''' | ||||
| ============================================== | ||||
|  | @ -182,7 +191,7 @@ def config(): | |||
| 	 | ||||
| 	# Set the path of config file to | ||||
| 	# /HOME/USER/.config/inopy/config.json | ||||
| 	config_path = os.path.join(os.environ['HOME'], '.config/inopy') | ||||
| 	config_path = os.path.join(os.environ['HOME'], '.inopy/config') | ||||
| 	config_file = 'config.json' | ||||
| 	config_file_path = os.path.join(config_path, config_file) | ||||
| 	 | ||||
|  |  | |||
							
								
								
									
										79
									
								
								ino.py
								
								
								
								
							
							
						
						
									
										79
									
								
								ino.py
								
								
								
								
							|  | @ -25,7 +25,7 @@ | |||
| 
 | ||||
| 	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. | ||||
| 	Inopy is structured into functions and modules for making API requests, parsing response data, refreshing tokens, sending notifications and logging the processes. | ||||
| 
 | ||||
| 	For more information about OAuth authentication, plase see <https://www.inoreader.com/developers/oauth> | ||||
| 
 | ||||
|  | @ -35,9 +35,14 @@ | |||
| import requests | ||||
| import json | ||||
| import notif | ||||
| import logging | ||||
| from config import config | ||||
| from oauth import app, run_app | ||||
| from oauth import run_app | ||||
| from refresh import refresh | ||||
| from logs import LogFile | ||||
| 
 | ||||
| # Set logs file | ||||
| log_file = LogFile() | ||||
| 
 | ||||
| ''' | ||||
| =========================================== | ||||
|  | @ -100,7 +105,12 @@ subscriptions = {} | |||
| categories = [] | ||||
| 
 | ||||
| # Make API request to get unread counts | ||||
| unread_response = APIrequest(unread_counts_url, bearer) | ||||
| 
 | ||||
| try: | ||||
| 	unread_response = APIrequest(unread_counts_url, bearer) | ||||
| 
 | ||||
| except Exception as e: | ||||
| 	logging.debug(e) | ||||
| 
 | ||||
| ''' | ||||
| =================================================== | ||||
|  | @ -138,34 +148,47 @@ unread_response = APIrequest(unread_counts_url, bearer) | |||
| ============================================ | ||||
| ''' | ||||
| 
 | ||||
| # Check for 403 error case | ||||
| if unread_response.status_code == 403: | ||||
| 	run_app()	 | ||||
| try: | ||||
| 
 | ||||
| 	with open(config_path) as config_file: | ||||
| 		new_config = json.load(config_file) | ||||
| 	# Check for 403 error case | ||||
| 	if unread_response.status_code == 403: | ||||
| 		logging.info('Token not available: starting oauth process...') | ||||
| 		run_app() | ||||
| 
 | ||||
| 	bearer = new_config['oauth']['bearer'] | ||||
| 		with open(config_path) as config_file: | ||||
| 			new_config = json.load(config_file) | ||||
| 
 | ||||
| 	unread_response = APIrequest(unread_counts_url, bearer) | ||||
| 		bearer = new_config['oauth']['bearer'] | ||||
| 
 | ||||
| # Check for 401 error case | ||||
| elif unread_response.status_code == 401: | ||||
| 	refresh(config_path, endpoint, client_id, client_secret, refresh_token) | ||||
| 		unread_response = APIrequest(unread_counts_url, bearer) | ||||
| 
 | ||||
| 	with open(config_path) as config_file: | ||||
| 		new_config = json.load(config_file) | ||||
| 	# Check for 401 error case | ||||
| 	elif unread_response.status_code == 401: | ||||
| 		logging.info('Token expired: starting refresh process...') | ||||
| 		refresh(config_path, endpoint, client_id, client_secret, refresh_token) | ||||
| 
 | ||||
| 	bearer = new_config['oauth']['bearer'] | ||||
| 		with open(config_path) as config_file: | ||||
| 			new_config = json.load(config_file) | ||||
| 
 | ||||
| 	unread_response = APIrequest(unread_counts_url, bearer) | ||||
| 		bearer = new_config['oauth']['bearer'] | ||||
| 		 | ||||
| # Proceed with the code execution | ||||
| elif unread_response.status_code == 200: | ||||
| 	pass | ||||
| 		unread_response = APIrequest(unread_counts_url, bearer) | ||||
| 
 | ||||
| 	# Proceed with the code execution | ||||
| 	elif unread_response.status_code == 200: | ||||
| 		logging.info('API request ok: retrieving data...') | ||||
| 		pass | ||||
| 
 | ||||
| except Exception as e: | ||||
| 	logging.debug(e) | ||||
| 
 | ||||
| # Make API request to get feeds list | ||||
| feeds_list_response = APIrequest(feeds_list_url, bearer) | ||||
| 
 | ||||
| try: | ||||
| 	feeds_list_response = APIrequest(feeds_list_url, bearer) | ||||
| 
 | ||||
| except Exception as e: | ||||
| 	logging.debug(e) | ||||
| 
 | ||||
| ''' | ||||
| ======================================= | ||||
|  | @ -255,8 +278,14 @@ for unread_id, count in unreadcounts.items(): | |||
| ==================================== | ||||
| ''' | ||||
| 
 | ||||
| if message != "": | ||||
| 	notif.send_notification(summary, message) | ||||
| try: | ||||
| 	if message != "": | ||||
| 		notif.send_notification(summary, message) | ||||
| 		logging.info('Notification successfully sent!') | ||||
| 
 | ||||
| else: | ||||
| 	pass | ||||
| 	else: | ||||
| 		logging.info('No unread articles. Notification not sent.') | ||||
| 		pass | ||||
| 
 | ||||
| except Exception as e: | ||||
| 	logging.debug(e) | ||||
|  | @ -0,0 +1,41 @@ | |||
| """ | ||||
| ========================================================================================= | ||||
|      | ||||
|     Copyright © 2023 Alexandre Racine <https://alex-racine.ch> | ||||
| 
 | ||||
|     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 <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| ========================================================================================= | ||||
| 
 | ||||
|     DISCLAIMER: parts of this code and comments blocks were created | ||||
|     with the help of ChatGPT developped by OpenAI <https://openai.com/> | ||||
|     Followed by human reviewing, refactoring and fine-tuning. | ||||
| 
 | ||||
| ========================================================================================= | ||||
| 
 | ||||
|     This module aims to log the program processes. | ||||
| 
 | ||||
| ========================================================================================= | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| import logging | ||||
| 
 | ||||
| # Setting the logs file | ||||
| def LogFile(): | ||||
|      | ||||
|     # Determine the user's home directory | ||||
|     logs_dir = os.path.join(os.environ['HOME'], '.inopy/logs') | ||||
|     os.makedirs(logs_dir, exist_ok=True) | ||||
| 
 | ||||
|     # Configure logging | ||||
|     log_file = os.path.join(logs_dir, "inopy.log") | ||||
| 
 | ||||
|     logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s',  | ||||
| datefmt='%m/%d/%Y %I:%M:%S %p', filename=log_file, level=logging.DEBUG) | ||||
							
								
								
									
										452
									
								
								oauth.py
								
								
								
								
							
							
						
						
									
										452
									
								
								oauth.py
								
								
								
								
							|  | @ -34,225 +34,255 @@ import time | |||
| import json | ||||
| import subprocess | ||||
| import threading | ||||
| import logging | ||||
| from flask import Flask, request, redirect, render_template | ||||
| from config import config | ||||
| from waitress import serve | ||||
| from logs import LogFile | ||||
| 
 | ||||
| ''' | ||||
| =================================== | ||||
| 	Load configuration values | ||||
| 	 | ||||
| 	Extract values from the config | ||||
| 	dictionary | ||||
| 	 | ||||
| 	Build the URL for authorization | ||||
| =================================== | ||||
| ''' | ||||
| 
 | ||||
| config = config() | ||||
| 
 | ||||
| endpoint = config['endpoint'] | ||||
| client_id = config['client_id'] | ||||
| client_secret = config['client_secret'] | ||||
| callback = config['callback'] | ||||
| scope = config['scope'] | ||||
| CSRF = config['csrf'] | ||||
| home_url = config['home_url'] | ||||
| prod_status = config['prod_status'] | ||||
| browser_path = config['browser_path'] | ||||
| host = config['host'] | ||||
| port = config['port'] | ||||
| config_file_path = config['config_file_path'] | ||||
| 
 | ||||
| url = 'https://www.inoreader.com/oauth2/auth?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}'.format(client_id, callback, scope, CSRF) | ||||
| 
 | ||||
| ''' | ||||
| ========================== | ||||
| 	Initiate the Flask app | ||||
| ========================== | ||||
| ''' | ||||
| 
 | ||||
| app = Flask(__name__) | ||||
| 
 | ||||
| ''' | ||||
| ================================== | ||||
| 	When the home url is accessed: | ||||
| 	 | ||||
| 	*	Redirect the user to the | ||||
| 		authorization URL | ||||
| ================================== | ||||
| ''' | ||||
| 
 | ||||
| @app.route('/') | ||||
| def index(): | ||||
| 	return redirect(url) | ||||
| 
 | ||||
| ''' | ||||
| ===================================================== | ||||
| 	When the oauth-callback url is accessed: | ||||
| 
 | ||||
| 	*	Get the authorization code and other | ||||
| 		parameters from the callback URL | ||||
| 	 | ||||
| 	*	Check CSRF validation token and if there | ||||
| 		is an error parameter in the URL | ||||
| 	 | ||||
| 	*	Request bearer token and refresh token | ||||
| 
 | ||||
| 	*	Save the bearer token and refresh token | ||||
| 		to the config file | ||||
| 	 | ||||
| 	*	If process is successfull: | ||||
| 		 | ||||
| 		*	render the success template with the | ||||
| 			response | ||||
| 	 | ||||
| 	*	If CSRF failed: | ||||
| 		 | ||||
| 		*	render the CSRF failure template | ||||
| 	 | ||||
| 	*	If an error parameter is present in the URL: | ||||
| 
 | ||||
| 		*	Render the OAuth error template | ||||
| ===================================================== | ||||
| ''' | ||||
| 
 | ||||
| @app.route('/oauth-callback') | ||||
| 
 | ||||
| def oauth_callback(): | ||||
| 	authorization_code = request.args.get('code') | ||||
| 	csrf_check = request.args.get('state') | ||||
| 	error_param = request.args.get('error') | ||||
| 
 | ||||
| 	csrf = True if csrf_check == CSRF else False | ||||
| 	error = True if error_param is not None else False | ||||
| 
 | ||||
| 	if csrf and not error: | ||||
| 		access_token_url = endpoint | ||||
| 		 | ||||
| 		# Prepare data to request bearer token | ||||
| 		payload = { | ||||
| 			'grant_type': 'authorization_code', | ||||
| 			'code': authorization_code, | ||||
| 			'client_id': client_id, | ||||
| 			'client_secret': client_secret, | ||||
| 			'redirect_uri': callback | ||||
| 		} | ||||
| 
 | ||||
| 		# Request bearer token and refresh token | ||||
| 		response = requests.post(access_token_url, data=payload) | ||||
| 		 | ||||
| 		if response.status_code == 200: | ||||
| 	 | ||||
| 			access_token = response.json()['access_token'] | ||||
| 			refresh_token = response.json()['refresh_token'] | ||||
| 
 | ||||
| 			with open(config_file_path, 'r+') as config_file: | ||||
| 				config = json.load(config_file) | ||||
| 				 | ||||
| 				# Save the bearer token and refresh token to the config file | ||||
| 				config['oauth']['bearer'] = access_token | ||||
| 				config['oauth']['refresh_token'] = refresh_token | ||||
| 				 | ||||
| 				config_file.seek(0) | ||||
| 				 | ||||
| 				json.dump(config, config_file, indent=4) | ||||
| 				config_file.truncate() | ||||
| 
 | ||||
| 		return render_template('success.html', response=(access_token, refresh_token)) | ||||
| 	 | ||||
| 	else: | ||||
| 		if not csrf: | ||||
| 			return render_template('csrf-failed.html', response=(CSRF, csrf_check)) | ||||
| 		 | ||||
| 		elif error: | ||||
| 			error_content = request.args.get('error_description') | ||||
| 			return render_template('oauth-error.html', response=(error_param, error_content)) | ||||
| 		else: | ||||
| 			pass | ||||
| 
 | ||||
| ''' | ||||
| ====================================== | ||||
| 	When the shutdown url is accessed: | ||||
| 
 | ||||
| 	*	Shut down the Flask server | ||||
| 		gracefully | ||||
| ====================================== | ||||
| ''' | ||||
| 
 | ||||
| @app.route('/shutdown') | ||||
| def shutdown(): | ||||
| 	request.environ.get('werkzeug.server.shutdown') | ||||
| 	return 'Close this browser to terminate the process!' | ||||
| 
 | ||||
| ''' | ||||
| ====================================== | ||||
| 	Define a function to start the | ||||
| 	Flask server using Waitress in | ||||
| 	production mode | ||||
| ====================================== | ||||
| ''' | ||||
| 
 | ||||
| def start_server(): | ||||
| 	serve(app, host=host, port=port) | ||||
| 
 | ||||
| ''' | ||||
| ====================================== | ||||
| 	Define a function to start the | ||||
| 	production server inside a new | ||||
| 	thread. | ||||
| 
 | ||||
| 	This is to ensure the server is | ||||
| 	actually already running before | ||||
| 	opening the web browser | ||||
| ====================================== | ||||
| ''' | ||||
| 
 | ||||
| def run_prod(): | ||||
| 	server_thread = threading.Thread(target=start_server) | ||||
| 	server_thread.start() | ||||
| 	time.sleep(2) | ||||
| 
 | ||||
| 	# Launch a separate browser process with a new profile | ||||
| 	subprocess.run([browser_path, "-CreateProfile", "new_profile", "-no-remote"]) | ||||
| 	subprocess.run([browser_path, "-P", "new_profile", "-no-remote", home_url]) | ||||
| 
 | ||||
| ''' | ||||
| ====================================== | ||||
| 	Define a function to start the | ||||
| 	development server inside a new | ||||
| 	thread. | ||||
| 
 | ||||
| 	This is to ensure the server is | ||||
| 	actually already running before | ||||
| 	opening the web browser | ||||
| ====================================== | ||||
| ''' | ||||
| 
 | ||||
| def run_dev(): | ||||
| 	server_thread = threading.Thread(target=start_server) | ||||
| 	server_thread.start() | ||||
| 	time.sleep(2) | ||||
| 
 | ||||
| 	# Open the home URL in the default web browser | ||||
| 	webbrowser.open(home_url) | ||||
| 	app.run() | ||||
| 
 | ||||
| ''' | ||||
| ====================================== | ||||
| 	Determine whether to run the | ||||
| 	production or development server | ||||
| 	based on the config | ||||
| ====================================== | ||||
| ''' | ||||
| # Set logs file | ||||
| log_file = LogFile() | ||||
| 
 | ||||
| def run_app(): | ||||
| 
 | ||||
| 	if prod_status == "true": | ||||
| 		run_prod() | ||||
| 	''' | ||||
| 	=================================== | ||||
| 		Load configuration values | ||||
| 		 | ||||
| 	else: | ||||
| 		run_dev() | ||||
| 		Extract values from the config | ||||
| 		dictionary | ||||
| 		 | ||||
| 		Build the URL for authorization | ||||
| 	=================================== | ||||
| 	''' | ||||
| 
 | ||||
| 	conf = config() | ||||
| 
 | ||||
| 	endpoint = conf['endpoint'] | ||||
| 	client_id = conf['client_id'] | ||||
| 	client_secret = conf['client_secret'] | ||||
| 	callback = conf['callback'] | ||||
| 	scope = conf['scope'] | ||||
| 	CSRF = conf['csrf'] | ||||
| 	home_url = conf['home_url'] | ||||
| 	prod_status = conf['prod_status'] | ||||
| 	browser_path = conf['browser_path'] | ||||
| 	host = conf['host'] | ||||
| 	port = conf['port'] | ||||
| 	config_file_path = conf['config_file_path'] | ||||
| 
 | ||||
| 	url = 'https://www.inoreader.com/oauth2/auth?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}'.format(client_id, callback, scope, CSRF) | ||||
| 
 | ||||
| 	''' | ||||
| 	========================== | ||||
| 		Initiate the Flask app | ||||
| 	========================== | ||||
| 	''' | ||||
| 
 | ||||
| 	app = Flask(__name__) | ||||
| 
 | ||||
| 	''' | ||||
| 	================================== | ||||
| 		When the home url is accessed: | ||||
| 		 | ||||
| 		*	Redirect the user to the | ||||
| 			authorization URL | ||||
| 	================================== | ||||
| 	''' | ||||
| 
 | ||||
| 	try: | ||||
| 		@app.route('/') | ||||
| 		def index(): | ||||
| 			return redirect(url) | ||||
| 
 | ||||
| 		logging.info('Redirecting to authorization URL...') | ||||
| 
 | ||||
| 	except Exception as e: | ||||
| 		logging.debug(e) | ||||
| 
 | ||||
| 	''' | ||||
| 	===================================================== | ||||
| 		When the oauth-callback url is accessed: | ||||
| 
 | ||||
| 		*	Get the authorization code and other | ||||
| 			parameters from the callback URL | ||||
| 		 | ||||
| 		*	Check CSRF validation token and if there | ||||
| 			is an error parameter in the URL | ||||
| 		 | ||||
| 		*	Request bearer token and refresh token | ||||
| 
 | ||||
| 		*	Save the bearer token and refresh token | ||||
| 			to the config file | ||||
| 		 | ||||
| 		*	If process is successfull: | ||||
| 			 | ||||
| 			*	render the success template with the | ||||
| 				response | ||||
| 		 | ||||
| 		*	If CSRF failed: | ||||
| 			 | ||||
| 			*	render the CSRF failure template | ||||
| 		 | ||||
| 		*	If an error parameter is present in the URL: | ||||
| 
 | ||||
| 			*	Render the OAuth error template | ||||
| 	===================================================== | ||||
| 	''' | ||||
| 
 | ||||
| 	try: | ||||
| 		@app.route('/oauth-callback') | ||||
| 
 | ||||
| 		def oauth_callback(): | ||||
| 			authorization_code = request.args.get('code') | ||||
| 			csrf_check = request.args.get('state') | ||||
| 			error_param = request.args.get('error') | ||||
| 
 | ||||
| 			csrf = True if csrf_check == CSRF else False | ||||
| 			error = True if error_param is not None else False | ||||
| 
 | ||||
| 			if csrf and not error: | ||||
| 				access_token_url = endpoint | ||||
| 				 | ||||
| 				# Prepare data to request bearer token | ||||
| 				payload = { | ||||
| 					'grant_type': 'authorization_code', | ||||
| 					'code': authorization_code, | ||||
| 					'client_id': client_id, | ||||
| 					'client_secret': client_secret, | ||||
| 					'redirect_uri': callback | ||||
| 				} | ||||
| 
 | ||||
| 				# Request bearer token and refresh token | ||||
| 				response = requests.post(access_token_url, data=payload) | ||||
| 				 | ||||
| 				if response.status_code == 200: | ||||
| 			 | ||||
| 					access_token = response.json()['access_token'] | ||||
| 					refresh_token = response.json()['refresh_token'] | ||||
| 
 | ||||
| 					with open(config_file_path, 'r+') as config_file: | ||||
| 						config = json.load(config_file) | ||||
| 						 | ||||
| 						# Save the bearer token and refresh token to the config file | ||||
| 						config['oauth']['bearer'] = access_token | ||||
| 						config['oauth']['refresh_token'] = refresh_token | ||||
| 						 | ||||
| 						config_file.seek(0) | ||||
| 						 | ||||
| 						json.dump(config, config_file, indent=4) | ||||
| 						config_file.truncate() | ||||
| 
 | ||||
| 				logging.info('New token obtained successfully...') | ||||
| 				return render_template('success.html', response=(access_token, refresh_token)) | ||||
| 			 | ||||
| 			else: | ||||
| 				if not csrf: | ||||
| 					logging.warning('CRSF validation failed...') | ||||
| 					return render_template('csrf-failed.html', response=(CSRF, csrf_check)) | ||||
| 				 | ||||
| 				elif error: | ||||
| 					error_content = request.args.get('error_description') | ||||
| 					logging.debug(error_content) | ||||
| 					return render_template('oauth-error.html', response=(error_param, error_content)) | ||||
| 				 | ||||
| 				else: | ||||
| 					pass | ||||
| 
 | ||||
| 	except Exception as e: | ||||
| 		logging.debug(e) | ||||
| 
 | ||||
| 	''' | ||||
| 	====================================== | ||||
| 		When the shutdown url is accessed: | ||||
| 
 | ||||
| 		*	Shut down the Flask server | ||||
| 			gracefully | ||||
| 	====================================== | ||||
| 	''' | ||||
| 
 | ||||
| 	try: | ||||
| 		@app.route('/shutdown') | ||||
| 		def shutdown(): | ||||
| 			request.environ.get('werkzeug.server.shutdown') | ||||
| 			logging.info('Shutting down Flask server...') | ||||
| 			return 'Close this browser to terminate the process!' | ||||
| 
 | ||||
| 	except Exception as e: | ||||
| 		logging.debug(e) | ||||
| 
 | ||||
| 	''' | ||||
| 	====================================== | ||||
| 		Define a function to start the | ||||
| 		Flask server using Waitress in | ||||
| 		production mode | ||||
| 	====================================== | ||||
| 	''' | ||||
| 
 | ||||
| 	def start_server(): | ||||
| 		serve(app, host=host, port=port) | ||||
| 
 | ||||
| 	''' | ||||
| 	====================================== | ||||
| 		Define a function to start the | ||||
| 		production server inside a new | ||||
| 		thread. | ||||
| 
 | ||||
| 		This is to ensure the server is | ||||
| 		actually already running before | ||||
| 		opening the web browser | ||||
| 	====================================== | ||||
| 	''' | ||||
| 
 | ||||
| 	def run_prod(): | ||||
| 		logging.info('Running program in production mode...') | ||||
| 		server_thread = threading.Thread(target=start_server) | ||||
| 		server_thread.start() | ||||
| 		time.sleep(2) | ||||
| 
 | ||||
| 		# Launch a separate browser process with a new profile | ||||
| 		subprocess.run([browser_path, "-CreateProfile", "new_profile", "-no-remote"]) | ||||
| 		subprocess.run([browser_path, "-P", "new_profile", "-no-remote", home_url]) | ||||
| 
 | ||||
| 	''' | ||||
| 	====================================== | ||||
| 		Define a function to start the | ||||
| 		development server inside a new | ||||
| 		thread. | ||||
| 
 | ||||
| 		This is to ensure the server is | ||||
| 		actually already running before | ||||
| 		opening the web browser | ||||
| 	====================================== | ||||
| 	''' | ||||
| 
 | ||||
| 	def run_dev(): | ||||
| 		logging.info('Running program in development mode...') | ||||
| 		server_thread = threading.Thread(target=start_server) | ||||
| 		server_thread.start() | ||||
| 		time.sleep(2) | ||||
| 
 | ||||
| 		# Open the home URL in the default web browser | ||||
| 		webbrowser.open(home_url) | ||||
| 		app.run() | ||||
| 
 | ||||
| 	''' | ||||
| 	====================================== | ||||
| 		Determine whether to run the | ||||
| 		production or development server | ||||
| 		based on the config | ||||
| 	====================================== | ||||
| 	''' | ||||
| 		 | ||||
| 	try: | ||||
| 		if prod_status == "true": | ||||
| 			run_prod() | ||||
| 
 | ||||
| 		else: | ||||
| 			run_dev() | ||||
| 
 | ||||
| 	except Exception as e: | ||||
| 		logging.debug(e) | ||||
| 
 | ||||
| ''' | ||||
| ====================================== | ||||
|  |  | |||
|  | @ -58,6 +58,11 @@ | |||
| 
 | ||||
| import requests | ||||
| import json | ||||
| import logging | ||||
| from logs import LogFile | ||||
| 
 | ||||
| # Set logs file | ||||
| log_file = LogFile() | ||||
| 
 | ||||
| def refresh(config_path, endpoint, client_id, client_secret, refresh_token): | ||||
| 	headers = {"Content-type": "application/x-www-form-urlencoded"} | ||||
|  | @ -72,10 +77,10 @@ def refresh(config_path, endpoint, client_id, client_secret, refresh_token): | |||
| 	response = requests.post(endpoint, data=payload, headers=headers) | ||||
| 	 | ||||
| 	if response.status_code == 200: | ||||
| 		print("Request was successful.") | ||||
| 		logging.info("Request was successful. Token was refreshed...") | ||||
| 	 | ||||
| 	else: | ||||
| 		print("Request failed with status code:", response.status_code) | ||||
| 		logging.debug(f'Request failed with status code: {response.status_code}') | ||||
| 	 | ||||
| 	data = json.loads(response.text) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue