meme/misc/rm karma

This commit is contained in:
codey 2025-05-20 11:14:08 -04:00
parent 5c351a6e0f
commit 0d58ae2a96
8 changed files with 67 additions and 299 deletions

View File

@ -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
),

View File

@ -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
"""

View File

@ -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
)
}
)

View File

@ -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.",

View File

@ -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}
)

View File

@ -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

View File

@ -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,

View File

@ -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