From 0d58ae2a96edda2caa67d9eee67b4ae06abb6341 Mon Sep 17 00:00:00 2001 From: codey Date: Tue, 20 May 2025 11:14:08 -0400 Subject: [PATCH] meme/misc/rm karma --- base.py | 1 - endpoints/constructors.py | 34 ----- endpoints/karma.py | 258 --------------------------------- endpoints/lastfm.py | 2 +- endpoints/meme.py | 13 +- lyric_search/sources/genius.py | 4 +- utils/lastfm_wrapper.py | 7 +- utils/meme_util.py | 47 +++++- 8 files changed, 67 insertions(+), 299 deletions(-) delete mode 100644 endpoints/karma.py diff --git a/base.py b/base.py index 67bdc60..8cd5fd5 100644 --- a/base.py +++ b/base.py @@ -88,7 +88,6 @@ routes: dict = { ), "lastfm": importlib.import_module("endpoints.lastfm").LastFM(app, util, constants), "yt": importlib.import_module("endpoints.yt").YT(app, util, constants), - "karma": importlib.import_module("endpoints.karma").Karma(app, util, constants), "radio": importlib.import_module("endpoints.radio").Radio( app, util, constants, loop ), diff --git a/endpoints/constructors.py b/endpoints/constructors.py index d7801c1..fc515cf 100644 --- a/endpoints/constructors.py +++ b/endpoints/constructors.py @@ -1,40 +1,6 @@ from typing import Optional from pydantic import BaseModel -""" -Karma -""" - - -class ValidKarmaUpdateRequest(BaseModel): - """ - Requires authentication - - **granter**: who updated the karma - - **keyword**: keyword to update karma for - - **flag**: either 0 (decrement) for --, or 1 (increment) for ++ - """ - - granter: str - keyword: str - flag: int - - -class ValidKarmaRetrievalRequest(BaseModel): - """ - - **keyword**: keyword to retrieve karma value of - """ - - keyword: str - - -class ValidTopKarmaRequest(BaseModel): - """ - - **n**: Number of top results to return (default: 10) - """ - - n: Optional[int] = 10 - - """ LastFM """ diff --git a/endpoints/karma.py b/endpoints/karma.py deleted file mode 100644 index a8dbabf..0000000 --- a/endpoints/karma.py +++ /dev/null @@ -1,258 +0,0 @@ -import os -import logging -import time -import datetime -import traceback -import aiosqlite as sqlite3 -from typing import LiteralString, Optional, Union -from fastapi import FastAPI, Request, HTTPException -from fastapi.responses import JSONResponse -from .constructors import ( - ValidTopKarmaRequest, - ValidKarmaRetrievalRequest, - ValidKarmaUpdateRequest, -) - - -class KarmaDB: - """Karma DB Util""" - - def __init__(self) -> None: - self.db_path: LiteralString = os.path.join( - "/", "usr", "local", "share", "sqlite_dbs", "karma.db" - ) - - async def get_karma(self, keyword: str) -> Union[int, dict]: - """Get Karma Value for Keyword - Args: - keyword (str): The keyword to search - Returns: - Union[int, dict] - """ - async with sqlite3.connect(self.db_path, timeout=2) as db_conn: - async with await db_conn.execute( - "SELECT score FROM karma WHERE keyword LIKE ? LIMIT 1", (keyword,) - ) as db_cursor: - try: - (score,) = await db_cursor.fetchone() - return score - except TypeError: - return { - "err": True, - "errorText": f"No records for {keyword}", - } - - async def get_top(self, n: Optional[int] = 10) -> Optional[list[tuple]]: - """ - Get Top n=10 Karma Entries - Args: - n (Optional[int]) = 10: The number of top results to return - Returns: - list[tuple] - """ - try: - async with sqlite3.connect(self.db_path, timeout=2) as db_conn: - async with await db_conn.execute( - "SELECT keyword, score FROM karma ORDER BY score DESC LIMIT ?", (n,) - ) as db_cursor: - return await db_cursor.fetchall() - except Exception as e: - logging.debug("Exception: %s", str(e)) - traceback.print_exc() - return None - - async def update_karma( - self, granter: str, keyword: str, flag: int - ) -> Optional[bool]: - """ - Update Karma for Keyword - Args: - granter (str): The user who granted (increased/decreased) the karma - keyword (str): The keyword to update - flag (int): 0 to increase karma, 1 to decrease karma - Returns: - Optional[bool] - """ - - if flag not in [0, 1]: - return None - - modifier: str = "score + 1" if not flag else "score - 1" - query: str = ( - f"UPDATE karma SET score = {modifier}, last_change = ? WHERE keyword LIKE ?" - ) - new_keyword_query: str = ( - "INSERT INTO karma(keyword, score, last_change) VALUES(?, ?, ?)" - ) - friendly_flag: str = "++" if not flag else "--" - audit_message: str = f"{granter} adjusted karma for {keyword} @ {datetime.datetime.now().isoformat()}: {friendly_flag}" - audit_query: str = ( - "INSERT INTO karma_audit(impacted_keyword, comment) VALUES(?, ?)" - ) - now: int = int(time.time()) - - logging.debug("Audit message: %s{audit_message}\nKeyword: %s{keyword}") - - async with sqlite3.connect(self.db_path, timeout=2) as db_conn: - async with await db_conn.execute( - audit_query, - ( - keyword, - audit_message, - ), - ) as db_cursor: - await db_conn.commit() - async with await db_conn.execute( - query, - ( - now, - keyword, - ), - ) as db_cursor: - if db_cursor.rowcount: - await db_conn.commit() - return True - if db_cursor.rowcount < 1: # Keyword does not already exist - await db_cursor.close() - new_val = 1 if not flag else -1 - async with await db_conn.execute( - new_keyword_query, - ( - keyword, - new_val, - now, - ), - ) as db_cursor: - if db_cursor.rowcount >= 1: - await db_conn.commit() - return True - else: - return False - return False - - -class Karma(FastAPI): - """ - Karma Endpoints - """ - - def __init__(self, app: FastAPI, util, constants) -> None: - self.app: FastAPI = app - self.util = util - self.constants = constants - self.db = KarmaDB() - - self.endpoints: dict = { - "karma/get": self.get_karma_handler, - "karma/modify": self.modify_karma_handler, - "karma/top": self.top_karma_handler, - } - - for endpoint, handler in self.endpoints.items(): - app.add_api_route( - f"/{endpoint}", handler, methods=["POST"], include_in_schema=False - ) - - async def top_karma_handler( - self, request: Request, data: Optional[ValidTopKarmaRequest] = None - ) -> JSONResponse: - """ - Get top keywords for karma - - **n**: Number of top results to return (default: 10) - """ - - if not self.util.check_key( - request.url.path, request.headers.get("X-Authd-With") - ): - raise HTTPException(status_code=403, detail="Unauthorized") - - n: int = 10 - if data and data.n: - n = int(data.n) - - try: - top10: Optional[list[tuple]] = await self.db.get_top(n=n) - if not top10: - return JSONResponse( - status_code=500, - content={ - "err": True, - "errorText": "General failure", - }, - ) - return JSONResponse(content=top10) - except Exception as e: - logging.debug("Exception: %s", str(e)) - traceback.print_exc() - return JSONResponse( - status_code=500, - content={ - "err": True, - "errorText": "Exception occurred.", - }, - ) - - async def get_karma_handler( - self, data: ValidKarmaRetrievalRequest, request: Request - ) -> JSONResponse: - """ - Get current karma value - - **keyword**: Keyword to retrieve karma value for - """ - - if not self.util.check_key( - request.url.path, request.headers.get("X-Authd-With") - ): - raise HTTPException(status_code=403, detail="Unauthorized") - - keyword: str = data.keyword - try: - count: Union[int, dict] = await self.db.get_karma(keyword) - return JSONResponse( - content={ - "keyword": keyword, - "count": count, - } - ) - except Exception as e: - logging.debug("Exception: %s", str(e)) - traceback.print_exc() - return JSONResponse( - status_code=500, - content={ - "err": True, - "errorText": "Exception occurred.", - }, - ) - - async def modify_karma_handler( - self, data: ValidKarmaUpdateRequest, request: Request - ) -> JSONResponse: - """ - Update karma count - - **granter**: User who granted the karma - - **keyword**: The keyword to modify - - **flag**: 0 to decrement (--), 1 to increment (++) - """ - - if not self.util.check_key( - request.url.path, request.headers.get("X-Authd-With"), 2 - ): - raise HTTPException(status_code=403, detail="Unauthorized") - - if data.flag not in [0, 1]: - return JSONResponse( - status_code=500, - content={ - "err": True, - "errorText": "Invalid request", - }, - ) - - return JSONResponse( - content={ - "success": await self.db.update_karma( - data.granter, data.keyword, data.flag - ) - } - ) diff --git a/endpoints/lastfm.py b/endpoints/lastfm.py index 6c488ce..0fe3c56 100644 --- a/endpoints/lastfm.py +++ b/endpoints/lastfm.py @@ -203,7 +203,7 @@ class LastFM(FastAPI): ) if not track_info_result: return JSONResponse( - status_code=500, + status_code=200, content={ "err": True, "errorText": "Not found.", diff --git a/endpoints/meme.py b/endpoints/meme.py index ad16059..184e0ce 100644 --- a/endpoints/meme.py +++ b/endpoints/meme.py @@ -16,6 +16,7 @@ class Meme(FastAPI): self.constants = constants self.endpoints: dict = { "memes/get_meme/{id:path}": self.get_meme_by_id, + "memes/random": self.random_meme, "memes/list_memes": self.list_memes, } @@ -31,7 +32,17 @@ class Meme(FastAPI): return Response(status_code=404, content="Not found") return Response(content=meme_image, media_type="image/png") + async def random_meme(self, request: Request) -> Response: + """Get random meme (image)""" + meme_image = await self.meme_util.get_random_meme() + if not meme_image: + return Response(status_code=404, content="Not found") + return Response(content=meme_image, media_type="image/png") + async def list_memes(self, page: int, request: Request) -> Response: """Get meme (image) by id""" meme_list = await self.meme_util.list_memes(page) - return JSONResponse(content={"memes": meme_list}) + page_count = await self.meme_util.get_page_count() + return JSONResponse( + content={"paging": {"current": page, "of": page_count}, "memes": meme_list} + ) diff --git a/lyric_search/sources/genius.py b/lyric_search/sources/genius.py index d1ed66c..df933d6 100644 --- a/lyric_search/sources/genius.py +++ b/lyric_search/sources/genius.py @@ -28,13 +28,13 @@ class Genius: self.genius_url: str = private.GENIUS_URL self.genius_search_url: str = f"{self.genius_url}api/search/song?q=" self.headers: dict = common.SCRAPE_HEADERS - self.timeout = ClientTimeout(connect=2, sock_read=5) + self.timeout = ClientTimeout(connect=3, sock_read=3) self.datautils = utils.DataUtils() self.matcher = utils.TrackMatcher() self.cache = cache.Cache() self.redis_cache = redis_cache.RedisCache() - @retry(stop=stop_after_attempt(2), wait=wait_fixed(0.5)) + @retry(stop=stop_after_attempt(3), wait=wait_fixed(0.2)) async def search(self, artist: str, song: str, **kwargs) -> Optional[LyricsResult]: """ Genius Search diff --git a/utils/lastfm_wrapper.py b/utils/lastfm_wrapper.py index 4fd2984..93f7bdc 100644 --- a/utils/lastfm_wrapper.py +++ b/utils/lastfm_wrapper.py @@ -92,11 +92,16 @@ class LastFM: ) as request: request.raise_for_status() data: dict = await request.json() + if not data: + return None data = data.get("track", None) if not isinstance(data.get("artist"), dict): return None artist_mbid: int = data.get("artist", None).get("mbid") - album: str = data.get("album", None).get("title") + album: str = data.get("album", None) + if not isinstance(album, dict): + return None + album = album.get("title") ret_obj: dict = { "artist_mbid": artist_mbid, "album": album, diff --git a/utils/meme_util.py b/utils/meme_util.py index 90b40aa..6d6709e 100644 --- a/utils/meme_util.py +++ b/utils/meme_util.py @@ -1,6 +1,7 @@ import os import logging import io +import math from typing import Optional import aiosqlite as sqlite3 from PIL import Image @@ -77,13 +78,38 @@ class MemeUtil: ret_image = result["image"] return ret_image + async def get_random_meme(self) -> Optional[bytes]: + """ + Get random meme + Returns: + Optional[bytes] + """ + ret_image: Optional[bytes] = None + buffer: Optional[io.BytesIO] = None + async with sqlite3.connect(self.meme_db_path, timeout=5) as db_conn: + db_conn.row_factory = sqlite3.Row + query: str = "SELECT id, image FROM memes ORDER BY RANDOM() LIMIT 1" + async with await db_conn.execute(query) as db_cursor: + result = await db_cursor.fetchone() + if not result: + return None + meme_id = result["id"] + buffer = io.BytesIO(result["image"]) + is_png = self.is_png(buffer) + if not is_png: + logging.debug("Converting %s, not detected as PNG", meme_id) + ret_image = self.convert_to_png(buffer) + else: + ret_image = result["image"] + return ret_image + async def list_memes(self, page: int) -> Optional[list]: """ List memes (paginated) Args: page (id) Returns: - list + Optional[list] """ out_result: list = [] async with sqlite3.connect(self.meme_db_path, timeout=5) as db_conn: @@ -103,3 +129,22 @@ class MemeUtil: } ) return out_result + + async def get_page_count(self) -> Optional[int]: + """ + Get page count + Returns: + Optional[int] + """ + async with sqlite3.connect(self.meme_db_path, timeout=5) as db_conn: + db_conn.row_factory = sqlite3.Row + rows_per_page: int = 10 + pages: Optional[int] = None + query: str = "SELECT count(id) AS count FROM memes" + async with await db_conn.execute(query) as db_cursor: + result = await db_cursor.fetchone() + count = result["count"] + if not isinstance(count, int): + return None + pages = math.ceil(count / rows_per_page) + return pages