meme/misc/rm karma
This commit is contained in:
		
							
								
								
									
										1
									
								
								base.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								base.py
									
									
									
									
									
								
							| @@ -88,7 +88,6 @@ routes: dict = { | |||||||
|     ), |     ), | ||||||
|     "lastfm": importlib.import_module("endpoints.lastfm").LastFM(app, util, constants), |     "lastfm": importlib.import_module("endpoints.lastfm").LastFM(app, util, constants), | ||||||
|     "yt": importlib.import_module("endpoints.yt").YT(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( |     "radio": importlib.import_module("endpoints.radio").Radio( | ||||||
|         app, util, constants, loop |         app, util, constants, loop | ||||||
|     ), |     ), | ||||||
|   | |||||||
| @@ -1,40 +1,6 @@ | |||||||
| from typing import Optional | from typing import Optional | ||||||
| from pydantic import BaseModel | 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 | LastFM | ||||||
| """ | """ | ||||||
|   | |||||||
| @@ -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 |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
| @@ -203,7 +203,7 @@ class LastFM(FastAPI): | |||||||
|             ) |             ) | ||||||
|             if not track_info_result: |             if not track_info_result: | ||||||
|                 return JSONResponse( |                 return JSONResponse( | ||||||
|                     status_code=500, |                     status_code=200, | ||||||
|                     content={ |                     content={ | ||||||
|                         "err": True, |                         "err": True, | ||||||
|                         "errorText": "Not found.", |                         "errorText": "Not found.", | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ class Meme(FastAPI): | |||||||
|         self.constants = constants |         self.constants = constants | ||||||
|         self.endpoints: dict = { |         self.endpoints: dict = { | ||||||
|             "memes/get_meme/{id:path}": self.get_meme_by_id, |             "memes/get_meme/{id:path}": self.get_meme_by_id, | ||||||
|  |             "memes/random": self.random_meme, | ||||||
|             "memes/list_memes": self.list_memes, |             "memes/list_memes": self.list_memes, | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -31,7 +32,17 @@ class Meme(FastAPI): | |||||||
|             return Response(status_code=404, content="Not found") |             return Response(status_code=404, content="Not found") | ||||||
|         return Response(content=meme_image, media_type="image/png") |         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: |     async def list_memes(self, page: int, request: Request) -> Response: | ||||||
|         """Get meme (image) by id""" |         """Get meme (image) by id""" | ||||||
|         meme_list = await self.meme_util.list_memes(page) |         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} | ||||||
|  |         ) | ||||||
|   | |||||||
| @@ -28,13 +28,13 @@ class Genius: | |||||||
|         self.genius_url: str = private.GENIUS_URL |         self.genius_url: str = private.GENIUS_URL | ||||||
|         self.genius_search_url: str = f"{self.genius_url}api/search/song?q=" |         self.genius_search_url: str = f"{self.genius_url}api/search/song?q=" | ||||||
|         self.headers: dict = common.SCRAPE_HEADERS |         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.datautils = utils.DataUtils() | ||||||
|         self.matcher = utils.TrackMatcher() |         self.matcher = utils.TrackMatcher() | ||||||
|         self.cache = cache.Cache() |         self.cache = cache.Cache() | ||||||
|         self.redis_cache = redis_cache.RedisCache() |         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]: |     async def search(self, artist: str, song: str, **kwargs) -> Optional[LyricsResult]: | ||||||
|         """ |         """ | ||||||
|         Genius Search |         Genius Search | ||||||
|   | |||||||
| @@ -92,11 +92,16 @@ class LastFM: | |||||||
|                 ) as request: |                 ) as request: | ||||||
|                     request.raise_for_status() |                     request.raise_for_status() | ||||||
|                     data: dict = await request.json() |                     data: dict = await request.json() | ||||||
|  |                     if not data: | ||||||
|  |                         return None | ||||||
|                     data = data.get("track", None) |                     data = data.get("track", None) | ||||||
|                     if not isinstance(data.get("artist"), dict): |                     if not isinstance(data.get("artist"), dict): | ||||||
|                         return None |                         return None | ||||||
|                     artist_mbid: int = data.get("artist", None).get("mbid") |                     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 = { |                     ret_obj: dict = { | ||||||
|                         "artist_mbid": artist_mbid, |                         "artist_mbid": artist_mbid, | ||||||
|                         "album": album, |                         "album": album, | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import os | import os | ||||||
| import logging | import logging | ||||||
| import io | import io | ||||||
|  | import math | ||||||
| from typing import Optional | from typing import Optional | ||||||
| import aiosqlite as sqlite3 | import aiosqlite as sqlite3 | ||||||
| from PIL import Image | from PIL import Image | ||||||
| @@ -77,13 +78,38 @@ class MemeUtil: | |||||||
|                     ret_image = result["image"] |                     ret_image = result["image"] | ||||||
|                 return ret_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]: |     async def list_memes(self, page: int) -> Optional[list]: | ||||||
|         """ |         """ | ||||||
|         List memes (paginated) |         List memes (paginated) | ||||||
|         Args: |         Args: | ||||||
|             page (id) |             page (id) | ||||||
|         Returns: |         Returns: | ||||||
|             list |             Optional[list] | ||||||
|         """ |         """ | ||||||
|         out_result: list = [] |         out_result: list = [] | ||||||
|         async with sqlite3.connect(self.meme_db_path, timeout=5) as db_conn: |         async with sqlite3.connect(self.meme_db_path, timeout=5) as db_conn: | ||||||
| @@ -103,3 +129,22 @@ class MemeUtil: | |||||||
|                         } |                         } | ||||||
|                     ) |                     ) | ||||||
|         return out_result |         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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user