fix: backport fixes from a sister project

This commit is contained in:
2025-06-20 23:23:00 +02:00
parent 06679ee165
commit 99e914557a
6 changed files with 43 additions and 7 deletions

View File

@@ -3,6 +3,7 @@ db_file_path = "/path/to/ythdd_db.sqlite" # Preferably stored on an SSD
video_storage_directory_path = "/path/to/videos/" # Path to video vault.
is_proxied = false # Set to true if running behind reverse proxy.
public_facing_url = "http://localhost:5000/" # Used for URL rewriting. Note the trailing backslash /.
debug = false # Whether to print verbose, debug info on API endpoints.
[api]
api_key = "" # Leave empty API key for public access to non-sensitive backend

View File

@@ -53,6 +53,9 @@ def setup():
sanity_string += f" If you're running a reverse proxy, set {colors.OKCYAN}is_proxied{colors.ENDC} to true to silence this message.\n"
print(sanity_string)
# Should work around disconnects: https://stackoverflow.com/a/61739721
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {"pool_pre_ping": True}
app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{config['general']['db_file_path']}"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.add_url_rule('/', view_func=views.index)

View File

@@ -4,6 +4,7 @@ from markupsafe import escape
import requests, time, json
import ythdd_globals
import ythdd_api_v1, ythdd_inv_tl
import traceback
def api_greeting():
string = {'status': 200, 'msg': f"ok (ythdd {ythdd_globals.version})", 'latest_api': f"v{ythdd_globals.apiVersion}"}
@@ -25,10 +26,15 @@ def api_global_catchall(received_request):
#return api_greeting()
resp = api_greeting()
try:
status, received, data = ythdd_api_v1.lookup(request_list)
status, received, data = ythdd_api_v1.lookup(request_list, request)
except Exception as e:
ythdd_globals.apiFailedRequests += 1
stripped_filename = __file__[max(__file__.rfind("/"), __file__.rfind("\\")) + 1:]
print(f"\n{c.FAIL}Error! /api/{received_request} -> {stripped_filename}:L{e.__traceback__.tb_lineno} -> {type(e).__name__}{c.ENDC}:"
+ f"{traceback.format_exc()}")
status, received, data = 500, f"internal server error: call ended in failure: {e}", []
if ythdd_globals.config["general"]["debug"]:
status, received, data = 500, f"internal server error: call ended in failure: {e} ({stripped_filename}:L{e.__traceback__.tb_lineno})", []
resp = Response(json.dumps({'status': status, 'msg': received, 'data': data}), mimetype='application/json', status=status)
elif request_list[0] == 'invidious':
# drop 'invidious' from the list
@@ -43,12 +49,17 @@ def api_global_catchall(received_request):
# if a path has been supplied try to get appropriate data
try:
# lookup and construct a response
resp = ythdd_inv_tl.lookup(request_list)
resp = ythdd_inv_tl.lookup(request_list, request)
#print(resp) # for debugging purposes
# unless an error occurs
except Exception as e:
ythdd_globals.apiFailedRequests += 1
stripped_filename = __file__[max(__file__.rfind("/"), __file__.rfind("\\")) + 1:]
print(f"\n{c.FAIL}Error! /api/{received_request} -> {stripped_filename}:L{e.__traceback__.tb_lineno} -> {type(e).__name__}{c.ENDC}:"
+ f"{traceback.format_exc()}")
status, received, data = 500, f"internal server error: invidious translation call ended in failure: {e}", []
if ythdd_globals.config["general"]["debug"]:
status, received, data = 500, f"internal server error: invidious translation call ended in failure: {e} ({stripped_filename}:L{e.__traceback__.tb_lineno})", []
resp = Response(json.dumps({'status': status, 'msg': received, 'data': data}), mimetype='application/json', status=status)
else:
ythdd_globals.apiFailedRequests += 1

View File

@@ -8,6 +8,20 @@ import ythdd_globals, ythdd_extractor
#from flask_sqlalchemy import SQLAlchemy
#import ythdd_api_v1_stats, ythdd_api_v1_user, ythdd_api_v1_info, ythdd_api_v1_query, ythdd_api_v1_meta, ythdd_api_v1_admin
def requireAuthentication(func):
@wraps(func)
def wrapper(*args, **kwargs):
token = kwargs["r"].args.get('token')
if token == lewy_globals.config['api']['api_key']:
try:
status, received, data = func(*args, **kwargs)
return status, received, data
except:
raise AssertionError(f"Function \"{func.__name__}\" does not return status, code, and data as it should!")
else:
return 401, "error", {'error_msg': "Unauthorized"}
return wrapper
def incrementBadRequests():
ythdd_globals.apiFailedRequests += 1
@@ -29,7 +43,7 @@ def stats():
"outside_api_requests": ythdd_globals.outsideApiHits,
"local_api_requests": ythdd_globals.apiRequests - ythdd_globals.outsideApiHits
}
return 200, "OK", data_to_send
return 200, "ok", data_to_send
def videoIdSanityCheck(videoId: str):
if len(videId) != 11:
@@ -129,7 +143,7 @@ def hot(data):
incrementBadRequests()
return notImplemented([data[1]]) # workaround before notImplemented is reworked
def lookup(data):
def lookup(data, request):
match data[0]:
case 'stats':
return stats()

View File

@@ -30,7 +30,7 @@ def getConfig(configfile):
global randomly_generated_passcode
if not os.path.exists(configfile):
dummy_config = {'general': {'db_file_path': 'ythdd_db.sqlite', 'video_storage_directory_path': 'videos/', 'is_proxied': False, 'public_facing_url': 'http://localhost:5000/'}, 'api': {'api_key': 'CHANGEME'}, 'extractor': {'user-agent': '', 'cookies_path': ''}, 'admin': {'admins': ['admin']}, 'yt_dlp': {}, 'postprocessing': {'presets': [{'name': 'recommended: [N][<=720p] best V+A', 'format': 'bv[height<=720]+ba', 'reencode': ''}, {'name': '[N][1080p] best V+A', 'format': 'bv[height=1080]+ba', 'reencode': ''}, {'name': '[R][1080p] webm', 'format': 'bv[height=1080]+ba', 'reencode': 'webm'}, {'name': '[N][720p] best V+A', 'format': 'bv[height=720]+ba', 'reencode': ''}, {'name': '[R][720p] webm', 'format': 'bv[height=720]+ba', 'reencode': 'webm'}, {'name': '[N][480p] best V+A', 'format': 'bv[height=480]+ba', 'reencode': ''}, {'name': '[480p] VP9 webm/reencode', 'format': 'bv*[height=480][ext=webm]+ba/bv[height=480]+ba', 'reencode': 'webm'}, {'name': '[N][1080p] best video only', 'format': 'bv[height=1080]', 'reencode': ''}, {'name': '[N][opus] best audio only', 'format': 'ba', 'reencode': 'opus'}]}}
dummy_config = {'general': {'db_file_path': 'ythdd_db.sqlite', 'video_storage_directory_path': 'videos/', 'is_proxied': False, 'public_facing_url': 'http://localhost:5000/', 'debug': False}, 'api': {'api_key': 'CHANGEME'}, 'extractor': {'user-agent': '', 'cookies_path': ''}, 'admin': {'admins': ['admin']}, 'yt_dlp': {}, 'postprocessing': {'presets': [{'name': 'recommended: [N][<=720p] best V+A', 'format': 'bv[height<=720]+ba', 'reencode': ''}, {'name': '[N][1080p] best V+A', 'format': 'bv[height=1080]+ba', 'reencode': ''}, {'name': '[R][1080p] webm', 'format': 'bv[height=1080]+ba', 'reencode': 'webm'}, {'name': '[N][720p] best V+A', 'format': 'bv[height=720]+ba', 'reencode': ''}, {'name': '[R][720p] webm', 'format': 'bv[height=720]+ba', 'reencode': 'webm'}, {'name': '[N][480p] best V+A', 'format': 'bv[height=480]+ba', 'reencode': ''}, {'name': '[480p] VP9 webm/reencode', 'format': 'bv*[height=480][ext=webm]+ba/bv[height=480]+ba', 'reencode': 'webm'}, {'name': '[N][1080p] best video only', 'format': 'bv[height=1080]', 'reencode': ''}, {'name': '[N][opus] best audio only', 'format': 'ba', 'reencode': 'opus'}]}}
# if a passcode has not been provided by the user (config file doesn't exist, and user didn't specify it using an argument)
print(f"{colors.WARNING}WARNING{colors.ENDC}: Using default, baked in config data. {colors.ENDL}"
f" Consider copying and editing the provided example file ({colors.OKCYAN}config.default.toml{colors.ENDC}).")
@@ -99,8 +99,15 @@ def safeTraverse(obj: dict, path: list, default=None):
for x in path:
#print(f"traversing {result} with respect to {x}")
result = result[x]
except KeyError:
except (KeyError, TypeError):
result = default
print(f"error reading: {' -> '.join(path)} - returning: {default}")
finally:
return result
def getCommit() -> str | None:
try:
return Repo(search_parent_directories=True).head.object.hexsha
except Exception as e:
return None

View File

@@ -451,7 +451,7 @@ def videos(data):
return send(status_code, response)
def lookup(data):
def lookup(data, request):
# possibly TODO: rewrite this mess
if len(data) > 2:
if (data[0], data[1]) == ("api", "v1"):