-
Notifications
You must be signed in to change notification settings - Fork 845
Refactor api/view.catch_all to reduce cyclomatic complexity #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,24 +1,25 @@ | ||||||||||||||||||||||||||||||||||||||||||
| from flask import Flask, Response, jsonify, render_template, redirect, request | ||||||||||||||||||||||||||||||||||||||||||
| from base64 import b64decode, b64encode | ||||||||||||||||||||||||||||||||||||||||||
| import math | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| import colorgram | ||||||||||||||||||||||||||||||||||||||||||
| from flask import Flask, Response, render_template, redirect, request | ||||||||||||||||||||||||||||||||||||||||||
| from dotenv import load_dotenv, find_dotenv | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| from api.view_params import ViewParams | ||||||||||||||||||||||||||||||||||||||||||
| from api.view_utils import load_cover_image_if_needed, extract_bar_color_from_image, resolve_artist_and_song_names, \ | ||||||||||||||||||||||||||||||||||||||||||
| to_img_b64, load_image | ||||||||||||||||||||||||||||||||||||||||||
| from util.firestore import get_firestore_db | ||||||||||||||||||||||||||||||||||||||||||
| from util.profanity import profanity_check | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| load_dotenv(find_dotenv()) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| from sys import getsizeof | ||||||||||||||||||||||||||||||||||||||||||
| from PIL import Image, ImageFile | ||||||||||||||||||||||||||||||||||||||||||
| from PIL import ImageFile | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| from time import time | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| import io | ||||||||||||||||||||||||||||||||||||||||||
| from util import spotify | ||||||||||||||||||||||||||||||||||||||||||
| import random | ||||||||||||||||||||||||||||||||||||||||||
| import requests | ||||||||||||||||||||||||||||||||||||||||||
| import functools | ||||||||||||||||||||||||||||||||||||||||||
| import colorgram | ||||||||||||||||||||||||||||||||||||||||||
| import math | ||||||||||||||||||||||||||||||||||||||||||
| import html | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| ImageFile.LOAD_TRUNCATED_IMAGES = True | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -36,7 +37,6 @@ def generate_css_bar(num_bar=75): | |||||||||||||||||||||||||||||||||||||||||
| css_bar = "" | ||||||||||||||||||||||||||||||||||||||||||
| left = 1 | ||||||||||||||||||||||||||||||||||||||||||
| for i in range(1, num_bar + 1): | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| anim = random.randint(350, 500) | ||||||||||||||||||||||||||||||||||||||||||
| css_bar += ( | ||||||||||||||||||||||||||||||||||||||||||
| ".bar:nth-child({}) {{ left: {}px; animation-duration: {}ms; }}".format( | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -48,25 +48,6 @@ def generate_css_bar(num_bar=75): | |||||||||||||||||||||||||||||||||||||||||
| return css_bar | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| @functools.lru_cache(maxsize=128) | ||||||||||||||||||||||||||||||||||||||||||
| def load_image(url): | ||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||
| response = requests.get(url, timeout=10) | ||||||||||||||||||||||||||||||||||||||||||
| response.raise_for_status() | ||||||||||||||||||||||||||||||||||||||||||
| return response.content | ||||||||||||||||||||||||||||||||||||||||||
| except requests.exceptions.RequestException as e: | ||||||||||||||||||||||||||||||||||||||||||
| print(f"Error loading image from {url}: {e}") | ||||||||||||||||||||||||||||||||||||||||||
| # Return a placeholder or None to handle gracefully | ||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||
| print(f"Unexpected error loading image: {e}") | ||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| def to_img_b64(content): | ||||||||||||||||||||||||||||||||||||||||||
| if content is None: | ||||||||||||||||||||||||||||||||||||||||||
| return "" | ||||||||||||||||||||||||||||||||||||||||||
| return b64encode(content).decode("ascii") | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| def load_image_b64(url): | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -128,18 +109,18 @@ def calculate_progress_data(progress_ms, duration_ms): | |||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # @functools.lru_cache(maxsize=128) | ||||||||||||||||||||||||||||||||||||||||||
| def make_svg( | ||||||||||||||||||||||||||||||||||||||||||
| artist_name, | ||||||||||||||||||||||||||||||||||||||||||
| song_name, | ||||||||||||||||||||||||||||||||||||||||||
| img, | ||||||||||||||||||||||||||||||||||||||||||
| is_now_playing, | ||||||||||||||||||||||||||||||||||||||||||
| cover_image, | ||||||||||||||||||||||||||||||||||||||||||
| theme, | ||||||||||||||||||||||||||||||||||||||||||
| bar_color, | ||||||||||||||||||||||||||||||||||||||||||
| show_offline, | ||||||||||||||||||||||||||||||||||||||||||
| background_color, | ||||||||||||||||||||||||||||||||||||||||||
| mode, | ||||||||||||||||||||||||||||||||||||||||||
| progress_ms=None, | ||||||||||||||||||||||||||||||||||||||||||
| duration_ms=None, | ||||||||||||||||||||||||||||||||||||||||||
| artist_name, | ||||||||||||||||||||||||||||||||||||||||||
| song_name, | ||||||||||||||||||||||||||||||||||||||||||
| img, | ||||||||||||||||||||||||||||||||||||||||||
| is_now_playing, | ||||||||||||||||||||||||||||||||||||||||||
| cover_image, | ||||||||||||||||||||||||||||||||||||||||||
| theme, | ||||||||||||||||||||||||||||||||||||||||||
| bar_color, | ||||||||||||||||||||||||||||||||||||||||||
| show_offline, | ||||||||||||||||||||||||||||||||||||||||||
| background_color, | ||||||||||||||||||||||||||||||||||||||||||
| mode, | ||||||||||||||||||||||||||||||||||||||||||
| progress_ms=None, | ||||||||||||||||||||||||||||||||||||||||||
| duration_ms=None, | ||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||
| height = 0 | ||||||||||||||||||||||||||||||||||||||||||
| num_bar = 75 | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -336,140 +317,82 @@ def get_song_info(uid, show_offline): | |||||||||||||||||||||||||||||||||||||||||
| return item, is_now_playing, progress_ms, duration_ms | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| def parse_view_params(): | ||||||||||||||||||||||||||||||||||||||||||
| uid = request.args.get("uid") | ||||||||||||||||||||||||||||||||||||||||||
| return ViewParams( | ||||||||||||||||||||||||||||||||||||||||||
| uid=uid, | ||||||||||||||||||||||||||||||||||||||||||
| cover_image=request.args.get("cover_image", default="true") == "true", | ||||||||||||||||||||||||||||||||||||||||||
| is_redirect=request.args.get("redirect", default="false") == "true", | ||||||||||||||||||||||||||||||||||||||||||
| theme=request.args.get("theme", default="default"), | ||||||||||||||||||||||||||||||||||||||||||
| bar_color=request.args.get("bar_color", default="53b14f"), | ||||||||||||||||||||||||||||||||||||||||||
| background_color=request.args.get("background_color", default="121212"), | ||||||||||||||||||||||||||||||||||||||||||
| is_bar_color_from_cover=( | ||||||||||||||||||||||||||||||||||||||||||
| request.args.get("bar_color_cover", default="false") == "true" | ||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||
| show_offline=request.args.get("show_offline", default="false") == "true", | ||||||||||||||||||||||||||||||||||||||||||
| interchange=request.args.get("interchange", default="false") == "true", | ||||||||||||||||||||||||||||||||||||||||||
| mode=request.args.get("mode", default="light"), | ||||||||||||||||||||||||||||||||||||||||||
| is_enable_profanity=request.args.get("profanity", default="false") == "true", | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+320
to
+336
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| @app.route("/", defaults={"path": ""}) | ||||||||||||||||||||||||||||||||||||||||||
| @app.route("/<path:path>") | ||||||||||||||||||||||||||||||||||||||||||
| def catch_all(path): | ||||||||||||||||||||||||||||||||||||||||||
| uid = request.args.get("uid") | ||||||||||||||||||||||||||||||||||||||||||
| cover_image = request.args.get("cover_image", default="true") == "true" | ||||||||||||||||||||||||||||||||||||||||||
| is_redirect = request.args.get("redirect", default="false") == "true" | ||||||||||||||||||||||||||||||||||||||||||
| theme = request.args.get("theme", default="default") | ||||||||||||||||||||||||||||||||||||||||||
| bar_color = request.args.get("bar_color", default="53b14f") | ||||||||||||||||||||||||||||||||||||||||||
| background_color = request.args.get("background_color", default="121212") | ||||||||||||||||||||||||||||||||||||||||||
| is_bar_color_from_cover = ( | ||||||||||||||||||||||||||||||||||||||||||
| request.args.get("bar_color_cover", default="false") == "true" | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| show_offline = request.args.get("show_offline", default="false") == "true" | ||||||||||||||||||||||||||||||||||||||||||
| interchange = request.args.get("interchange", default="false") == "true" | ||||||||||||||||||||||||||||||||||||||||||
| mode = request.args.get("mode", default="light") | ||||||||||||||||||||||||||||||||||||||||||
| is_enable_profanity = request.args.get("profanity", default="false") == "true" | ||||||||||||||||||||||||||||||||||||||||||
| params = parse_view_params() | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # Handle invalid request | ||||||||||||||||||||||||||||||||||||||||||
| if not uid: | ||||||||||||||||||||||||||||||||||||||||||
| if not params.uid: | ||||||||||||||||||||||||||||||||||||||||||
| return Response("not ok") | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||
| item, is_now_playing, progress_ms, duration_ms = get_song_info( | ||||||||||||||||||||||||||||||||||||||||||
| uid, show_offline | ||||||||||||||||||||||||||||||||||||||||||
| params.uid, params.show_offline | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| except spotify.InvalidTokenError as e: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # Handle invalid token | ||||||||||||||||||||||||||||||||||||||||||
| except spotify.InvalidTokenError: | ||||||||||||||||||||||||||||||||||||||||||
| return Response( | ||||||||||||||||||||||||||||||||||||||||||
| "Error: Invalid Spotify access_token or refresh_token. Possibly the token revoked. Please re-login at https://github.com/kittinan/spotify-github-profile" | ||||||||||||||||||||||||||||||||||||||||||
| "Error: Invalid Spotify access_token or refresh_token. Possibly the token revoked. " | ||||||||||||||||||||||||||||||||||||||||||
| "Please re-login at https://github.com/kittinan/spotify-github-profile" | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (show_offline and not is_now_playing) or (item is None): | ||||||||||||||||||||||||||||||||||||||||||
| if interchange: | ||||||||||||||||||||||||||||||||||||||||||
| artist_name = "Currently not playing on Spotify" | ||||||||||||||||||||||||||||||||||||||||||
| song_name = "Offline" | ||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||
| artist_name = "Offline" | ||||||||||||||||||||||||||||||||||||||||||
| song_name = "Currently not playing on Spotify" | ||||||||||||||||||||||||||||||||||||||||||
| img_b64 = "" | ||||||||||||||||||||||||||||||||||||||||||
| cover_image = False | ||||||||||||||||||||||||||||||||||||||||||
| svg = make_svg( | ||||||||||||||||||||||||||||||||||||||||||
| artist_name, | ||||||||||||||||||||||||||||||||||||||||||
| song_name, | ||||||||||||||||||||||||||||||||||||||||||
| img_b64, | ||||||||||||||||||||||||||||||||||||||||||
| is_now_playing, | ||||||||||||||||||||||||||||||||||||||||||
| cover_image, | ||||||||||||||||||||||||||||||||||||||||||
| theme, | ||||||||||||||||||||||||||||||||||||||||||
| bar_color, | ||||||||||||||||||||||||||||||||||||||||||
| show_offline, | ||||||||||||||||||||||||||||||||||||||||||
| background_color, | ||||||||||||||||||||||||||||||||||||||||||
| mode, | ||||||||||||||||||||||||||||||||||||||||||
| progress_ms, | ||||||||||||||||||||||||||||||||||||||||||
| duration_ms, | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| resp = Response(svg, mimetype="image/svg+xml") | ||||||||||||||||||||||||||||||||||||||||||
| resp.headers["Cache-Control"] = "s-maxage=1" | ||||||||||||||||||||||||||||||||||||||||||
| return resp | ||||||||||||||||||||||||||||||||||||||||||
| if (params.show_offline and not is_now_playing) or (item is None): | ||||||||||||||||||||||||||||||||||||||||||
| return build_offline_response(params, is_now_playing, progress_ms, duration_ms) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| currently_playing_type = item.get("currently_playing_type", "track") | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if is_redirect: | ||||||||||||||||||||||||||||||||||||||||||
| if params.is_redirect: | ||||||||||||||||||||||||||||||||||||||||||
| return redirect(item["uri"], code=302) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| img = None | ||||||||||||||||||||||||||||||||||||||||||
| img_b64 = "" | ||||||||||||||||||||||||||||||||||||||||||
| if cover_image: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if currently_playing_type == "track": | ||||||||||||||||||||||||||||||||||||||||||
| img = load_image(item["album"]["images"][1]["url"]) | ||||||||||||||||||||||||||||||||||||||||||
| elif currently_playing_type == "episode": | ||||||||||||||||||||||||||||||||||||||||||
| img = load_image(item["images"][1]["url"]) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # Only convert to base64 if image was successfully loaded | ||||||||||||||||||||||||||||||||||||||||||
| if img is not None: | ||||||||||||||||||||||||||||||||||||||||||
| img_b64 = to_img_b64(img) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # Extract cover image color | ||||||||||||||||||||||||||||||||||||||||||
| if is_bar_color_from_cover and img is not None: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| is_skip_dark = False | ||||||||||||||||||||||||||||||||||||||||||
| if theme in ["default"]: | ||||||||||||||||||||||||||||||||||||||||||
| is_skip_dark = True | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||
| pil_img = Image.open(io.BytesIO(img)) | ||||||||||||||||||||||||||||||||||||||||||
| colors = colorgram.extract(pil_img, 5) | ||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||
| print(f"Error extracting colors from image: {e}") | ||||||||||||||||||||||||||||||||||||||||||
| colors = [] | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| for color in colors: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| rgb = color.rgb | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| light_or_dark = isLightOrDark([rgb.r, rgb.g, rgb.b], threshold=80) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if light_or_dark == "dark" and is_skip_dark: | ||||||||||||||||||||||||||||||||||||||||||
| # Skip to use bar in dark color | ||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| bar_color = "%02x%02x%02x" % (rgb.r, rgb.g, rgb.b) | ||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||
| img, img_b64 = load_cover_image_if_needed( | ||||||||||||||||||||||||||||||||||||||||||
| params.cover_image, currently_playing_type, item, load_image, to_img_b64, | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
| params.cover_image, currently_playing_type, item, load_image, to_img_b64, | |
| params.cover_image, currently_playing_type, item, load_image, to_img_b64 |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function extract_bar_color_from_image is called with isLightOrDark and colorgram as parameters, but these are passed as module/function references rather than being called within the utility function itself. This creates an unusual dependency injection pattern that makes the code harder to understand and test. Consider importing and using isLightOrDark directly within extract_bar_color_from_image in view_utils.py, similar to how other dependencies are handled.
| bar_color = extract_bar_color_from_image(img, params.theme, bar_color, isLightOrDark, colorgram) | |
| bar_color = extract_bar_color_from_image(img, params.theme, bar_color) |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keyword argument 'img_b64' is not a supported parameter name of function make_svg.
| img_b64="", | |
| is_now_playing=is_now_playing, | |
| cover_image=False, | |
| theme=params.theme, | |
| bar_color=params.bar_color, | |
| show_offline=params.show_offline, | |
| background_color=params.background_color, | |
| mode=params.mode, | |
| progress_ms=progress_ms, | |
| duration_ms=duration_ms, | |
| "", | |
| is_now_playing, | |
| False, | |
| params.theme, | |
| params.bar_color, | |
| params.show_offline, | |
| params.background_color, | |
| params.mode, | |
| progress_ms, | |
| duration_ms, |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The build_offline_response function lacks test coverage. While the offline behavior is tested in the existing tests (e.g., test_catch_all_with_valid_uid_offline), the new extracted function itself should have dedicated unit tests to ensure its logic is properly isolated and tested, including the interchange parameter handling.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| from dataclasses import dataclass | ||
|
|
||
| @dataclass | ||
| class ViewParams: | ||
| uid: str | ||
| cover_image: bool | ||
| is_redirect: bool | ||
| theme: str | ||
| bar_color: str | ||
| background_color: str | ||
| is_bar_color_from_cover: bool | ||
| show_offline: bool | ||
| interchange: bool | ||
| mode: str | ||
| is_enable_profanity: bool | ||
|
|
Uh oh!
There was an error while loading. Please reload this page.