cleanup
This commit is contained in:
parent
85a0d6bc62
commit
3c57f13557
@ -1,23 +0,0 @@
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from typing import List
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: List[WebSocket] = []
|
||||
|
||||
async def connect(self, websocket: WebSocket):
|
||||
self.active_connections.append(websocket)
|
||||
|
||||
async def disconnect(self, websocket: WebSocket):
|
||||
if websocket in self.active_connections:
|
||||
self.active_connections.remove(websocket)
|
||||
|
||||
async def broadcast_raw(self, data: bytes):
|
||||
for connection in self.active_connections:
|
||||
try:
|
||||
await connection.send_bytes(data)
|
||||
except WebSocketDisconnect:
|
||||
await self.disconnect(connection)
|
||||
except Exception as e:
|
||||
print(f"Error sending to client: {e}")
|
||||
|
@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env python3.12
|
||||
|
||||
import soundfile as sf
|
||||
import asyncio
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
from aces.connection_manager import ConnectionManager
|
||||
from fastapi import WebSocket, WebSocketDisconnect
|
||||
|
||||
|
||||
class AudioStreamer:
|
||||
def __init__(self, sound_file, ws_connection_mgr):
|
||||
self.sound_file = sound_file
|
||||
self.audio_file = None
|
||||
self.format_info = None
|
||||
self.chunk_size = 16384 # Larger chunk for better quality
|
||||
self.ws_connection_mgr = ws_connection_mgr
|
||||
self.broadcast_task = None
|
||||
self.init_audio_file()
|
||||
|
||||
def init_audio_file(self):
|
||||
self.audio_file = sf.SoundFile(self.sound_file, 'rb')
|
||||
self.format_info = {
|
||||
'samplerate': self.audio_file.samplerate,
|
||||
'channels': self.audio_file.channels,
|
||||
'format': 'PCM',
|
||||
'subtype': str(self.audio_file.subtype)
|
||||
}
|
||||
|
||||
async def broadcast_audio(self):
|
||||
try:
|
||||
chunk_duration = self.chunk_size / (self.audio_file.samplerate * self.audio_file.channels)
|
||||
target_interval = chunk_duration / 2 # Send chunks at twice playback rate for smooth buffering
|
||||
|
||||
while True:
|
||||
if not self.ws_connection_mgr.active_connections:
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
|
||||
start_time = asyncio.get_event_loop().time()
|
||||
|
||||
chunk = self.audio_file.read(self.chunk_size, dtype='float32')
|
||||
if len(chunk) == 0:
|
||||
self.audio_file.seek(0)
|
||||
continue
|
||||
|
||||
await self.ws_connection_mgr.broadcast_raw(chunk.tobytes())
|
||||
|
||||
# Calculate how long processing took and adjust sleep time
|
||||
elapsed = asyncio.get_event_loop().time() - start_time
|
||||
sleep_time = max(0, target_interval - elapsed)
|
||||
await asyncio.sleep(sleep_time)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Broadcast error: {e}")
|
||||
|
||||
async def handle_client(self, ws: WebSocket):
|
||||
try:
|
||||
await self.ws_connection_mgr.connect(ws)
|
||||
await ws.send_json(self.format_info)
|
||||
|
||||
if not self.broadcast_task or self.broadcast_task.done():
|
||||
self.broadcast_task = asyncio.create_task(self.broadcast_audio())
|
||||
|
||||
while True:
|
||||
try:
|
||||
await ws.receive_text()
|
||||
except WebSocketDisconnect:
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in handle_client: {e}")
|
||||
finally:
|
||||
await self.ws_connection_mgr.disconnect(ws)
|
12
base.py
12
base.py
@ -7,7 +7,6 @@ import asyncio
|
||||
from typing import Any
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi_utils.tasks import repeat_every
|
||||
|
||||
|
||||
logger = logging.getLogger()
|
||||
@ -44,6 +43,8 @@ allow_headers=["*"])
|
||||
|
||||
|
||||
|
||||
# pylint: disable=missing-function-docstring
|
||||
|
||||
"""
|
||||
Blacklisted routes
|
||||
"""
|
||||
@ -53,22 +54,19 @@ def disallow_get():
|
||||
return util.get_blocked_response()
|
||||
|
||||
@app.get("/{any:path}")
|
||||
def disallow_get_any(var: Any = None):
|
||||
def disallow_get_any(var: Any = None): # pylint: disable=unused-argument
|
||||
return util.get_blocked_response()
|
||||
|
||||
@app.post("/")
|
||||
def disallow_base_post():
|
||||
return util.get_blocked_response()
|
||||
|
||||
# @app.limiter.limit("1/minute")
|
||||
# @app.post("/lyric_cache_list/")
|
||||
# async def rate_limited():
|
||||
# return {"error": "Rate limited"}
|
||||
|
||||
"""
|
||||
End Blacklisted Routes
|
||||
"""
|
||||
|
||||
# pylint: enable=missing-function-docstring
|
||||
|
||||
|
||||
"""
|
||||
Actionable Routes
|
||||
|
3
cah_ext/__init__.py
Normal file
3
cah_ext/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env python3.12
|
||||
|
||||
from . import constructors
|
63
cah_ext/constructors.py
Normal file
63
cah_ext/constructors.py
Normal file
@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3.12
|
||||
|
||||
class CAHClient:
|
||||
"""CAH Client Constructor"""
|
||||
def __init__(self,
|
||||
resource: str,
|
||||
platform: str,
|
||||
csid: str,
|
||||
connected_at: int,
|
||||
players: list,
|
||||
games: list):
|
||||
self.resource: str = resource
|
||||
self.platform: str = platform
|
||||
self.csid: str = csid
|
||||
self.connected_at: int = connected_at
|
||||
self.players: list = players
|
||||
self.games: list = games
|
||||
|
||||
def __iter__(self):
|
||||
return [value for value in self.__dict__.values() if isinstance(value, int) or isinstance(value, float)].__iter__()
|
||||
|
||||
class CAHGame:
|
||||
"""CAH Game Constructor"""
|
||||
def __init__(self,
|
||||
_id: str,
|
||||
rounds: int,
|
||||
resources: list[dict],
|
||||
players: list[dict],
|
||||
created_at: int,
|
||||
state: int,
|
||||
started_at: int,
|
||||
state_changed_at: int,
|
||||
):
|
||||
self.id: str = id
|
||||
self.rounds: int = rounds
|
||||
self.resources: list[dict] = resources
|
||||
self.players: list[dict] = players
|
||||
self.created_at: int = created_at
|
||||
self.state: int = state
|
||||
self.started_at: int = started_at
|
||||
self.state_changed_at: int = state_changed_at
|
||||
|
||||
def __iter__(self):
|
||||
return [value for value in self.__dict__.values() if isinstance(value, int) or isinstance(value, float)].__iter__()
|
||||
|
||||
class CAHPlayer:
|
||||
"""CAH Player Constructor"""
|
||||
def __init__(self,
|
||||
_id: str,
|
||||
current_game: CAHGame,
|
||||
platform: str,
|
||||
related_resource: str,
|
||||
joined_at: str,
|
||||
handle: str):
|
||||
self.id = id
|
||||
self.current_game = current_game
|
||||
self.platform = platform
|
||||
self.related_resource = related_resource
|
||||
self.joined_at = joined_at
|
||||
self.handle = handle
|
||||
|
||||
|
||||
|
123
cah_ext/websocket_conn.py
Normal file
123
cah_ext/websocket_conn.py
Normal file
@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3.12
|
||||
# pylint: disable=bare-except, broad-exception-caught, invalid-name
|
||||
|
||||
import time
|
||||
import logging
|
||||
from fastapi import WebSocket
|
||||
from cah_ext.constructors import CAHClient
|
||||
|
||||
class ConnectionManager:
|
||||
"""WS Connection Manager"""
|
||||
def __init__(self):
|
||||
self.active_connections: dict = {}
|
||||
|
||||
def get_connection_by_ws(self, websocket: WebSocket) -> WebSocket:
|
||||
"""Get Connection by WS"""
|
||||
return self.active_connections.get(websocket)
|
||||
|
||||
def get_connection_by_csid(self, csid: str) -> WebSocket:
|
||||
"""Get Connection by CSID"""
|
||||
for connection in self.active_connections:
|
||||
if connection.get('csid') == csid:
|
||||
return connection
|
||||
|
||||
def get_connection_by_resource_label(self, resource: str):
|
||||
"""Get Connection by Resource Label"""
|
||||
for connection in self.active_connections:
|
||||
try:
|
||||
if connection.get('client').get('resource') == resource:
|
||||
return connection
|
||||
except:
|
||||
continue
|
||||
|
||||
async def send_client_and_game_lists(self, state, websocket: WebSocket):
|
||||
"""Send Client and Game Lists"""
|
||||
clients = []
|
||||
|
||||
for _, client in self.active_connections.items():
|
||||
logging.debug("Client: %s", client)
|
||||
_client = client.get('client')
|
||||
clients.append({
|
||||
'resource': _client.resource,
|
||||
'platform': _client.platform,
|
||||
'connected_at': _client.connected_at,
|
||||
})
|
||||
|
||||
await websocket.send_json({
|
||||
"event": "client_list",
|
||||
"ts": int(time.time()),
|
||||
"data": {
|
||||
"clients": clients
|
||||
}
|
||||
})
|
||||
await websocket.send_json({
|
||||
"event": "game_list",
|
||||
"ts": int(time.time()),
|
||||
"data":
|
||||
{
|
||||
"games": state.get_games()
|
||||
}
|
||||
})
|
||||
|
||||
async def connect(self, websocket: WebSocket):
|
||||
"""Process WS Client Connection"""
|
||||
await websocket.accept()
|
||||
self.active_connections[websocket] = {
|
||||
'client': None,
|
||||
'websocket': websocket,
|
||||
}
|
||||
|
||||
async def handshake_complete(self,
|
||||
state,
|
||||
websocket: WebSocket,
|
||||
csid: str,
|
||||
handshakedClient: CAHClient):
|
||||
"""Process Handshake"""
|
||||
if websocket in self.active_connections:
|
||||
self.active_connections.pop(websocket)
|
||||
self.active_connections[websocket] = {
|
||||
'websocket': websocket,
|
||||
'csid': csid,
|
||||
'client': handshakedClient,
|
||||
}
|
||||
|
||||
await self.broadcast({
|
||||
"event": "client_connected",
|
||||
"ts": int(time.time()),
|
||||
"data": {
|
||||
"connected_resource": handshakedClient.resource,
|
||||
"connected_platform": handshakedClient.platform,
|
||||
}
|
||||
})
|
||||
|
||||
await self.send_client_and_game_lists(state,
|
||||
websocket)
|
||||
|
||||
async def disconnect(self, state, websocket: WebSocket, csid: str = None): # pylint: disable=unused-argument
|
||||
"""Process WS Client Disconnection"""
|
||||
disconnected = self.get_connection_by_ws(websocket)
|
||||
disconnected_client = disconnected.get('client')
|
||||
disconnected_resource = disconnected_client.resource
|
||||
disconnected_games = [str(game.id) for game in disconnected_client.games]
|
||||
await self.broadcast({
|
||||
"event": "client_disconnected",
|
||||
"ts": int(time.time()),
|
||||
"data": {
|
||||
"disconnected_resource": disconnected_resource,
|
||||
}
|
||||
})
|
||||
await state.remove_resource(disconnected_games, disconnected_resource)
|
||||
self.active_connections.pop(websocket)
|
||||
|
||||
|
||||
async def send(self, message: str, websocket: WebSocket):
|
||||
"""Send WS Client some data"""
|
||||
await websocket.send_json(message)
|
||||
|
||||
async def broadcast(self, message: str):
|
||||
"""Broadcast data to all connected WS clients"""
|
||||
for connection in self.active_connections:
|
||||
try:
|
||||
await connection.send_json(message)
|
||||
except:
|
||||
continue
|
132
endpoints/ai.py
132
endpoints/ai.py
@ -1,14 +1,15 @@
|
||||
#!/usr/bin/env python3.12
|
||||
# pylint: disable=bare-except, broad-exception-caught, invalid-name
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
import traceback
|
||||
import regex
|
||||
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
from fastapi import FastAPI, Security, Request, HTTPException
|
||||
from fastapi.security import APIKeyHeader, APIKeyQuery
|
||||
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ValidAISongRequest(BaseModel):
|
||||
"""
|
||||
- **a**: artist
|
||||
@ -18,6 +19,19 @@ class ValidAISongRequest(BaseModel):
|
||||
a: str
|
||||
s: str
|
||||
|
||||
class ValidHookSongRequest(BaseModel):
|
||||
"""
|
||||
- **a**: artist
|
||||
- **s**: track title
|
||||
- **hook**: hook to return
|
||||
"""
|
||||
|
||||
a: str
|
||||
s: str
|
||||
hook: str | None = ""
|
||||
|
||||
# pylint: enable=bad-indentation
|
||||
|
||||
class AI(FastAPI):
|
||||
"""AI Endpoints"""
|
||||
def __init__(self, app: FastAPI, my_util, constants, glob_state): # pylint: disable=super-init-not-called
|
||||
@ -29,12 +43,48 @@ class AI(FastAPI):
|
||||
self.endpoints = {
|
||||
"ai/openai": self.ai_openai_handler,
|
||||
"ai/base": self.ai_handler,
|
||||
"ai/song": self.ai_song_handler
|
||||
"ai/song": self.ai_song_handler,
|
||||
"ai/hook": self.ai_hook_handler,
|
||||
#tbd
|
||||
}
|
||||
|
||||
for endpoint, handler in self.endpoints.items():
|
||||
app.add_api_route(f"/{endpoint}/{{any:path}}", handler, methods=["POST"])
|
||||
app.add_api_route(f"/{endpoint}/openai/", handler, methods=["POST"])
|
||||
|
||||
async def respond_via_webhook(self, data: ValidHookSongRequest, originalRequest: Request):
|
||||
"""Respond via Webhook"""
|
||||
try:
|
||||
logging.debug("Request received: %s", data)
|
||||
data2 = data.copy()
|
||||
del data2.hook
|
||||
response = await self.ai_song_handler(data2, originalRequest)
|
||||
if not response.get('resp'):
|
||||
logging.critical("NO RESP!")
|
||||
return
|
||||
response = response.get('resp')
|
||||
hook_data = {
|
||||
'username': 'Claude',
|
||||
"embeds": [{
|
||||
"title": "Claude's Feedback",
|
||||
"description": response,
|
||||
"footer": {
|
||||
"text": "Current model: claude-3-haiku-20240307",
|
||||
}
|
||||
}]
|
||||
}
|
||||
logging.critical("Request: %s", data)
|
||||
|
||||
async with ClientSession() as session:
|
||||
async with session.post(data.hook, json=hook_data,
|
||||
timeout=ClientTimeout(connect=5, sock_read=5), headers={
|
||||
'content-type': 'application/json; charset=utf-8',}) as request:
|
||||
logging.debug("Returned: %s",
|
||||
await request.json())
|
||||
await request.raise_for_status()
|
||||
return True
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
async def ai_handler(self, request: Request):
|
||||
"""
|
||||
@ -46,10 +96,10 @@ class AI(FastAPI):
|
||||
if not self.util.check_key(request.url.path, request.headers.get('X-Authd-With')):
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
|
||||
|
||||
local_llm_headers = {
|
||||
'Authorization': f'Bearer {self.constants.LOCAL_LLM_KEY}'
|
||||
}
|
||||
|
||||
forward_path = self.url_clean_regex.sub('', request.url.path)
|
||||
try:
|
||||
async with ClientSession() as session:
|
||||
@ -77,7 +127,6 @@ class AI(FastAPI):
|
||||
if not self.util.check_key(request.url.path, request.headers.get('X-Authd-With')):
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
|
||||
|
||||
"""
|
||||
TODO: Implement Claude
|
||||
Currently only routes to local LLM
|
||||
@ -103,22 +152,20 @@ class AI(FastAPI):
|
||||
'errorText': 'General Failure'
|
||||
}
|
||||
|
||||
"""
|
||||
CLAUDE BELOW, COMMENTED
|
||||
"""
|
||||
async def ai_hook_handler(self, data: ValidHookSongRequest, request: Request, background_tasks: BackgroundTasks):
|
||||
"""AI Hook Handler"""
|
||||
background_tasks.add_task(self.respond_via_webhook, data, request)
|
||||
return {
|
||||
'success': True,
|
||||
}
|
||||
|
||||
async def ai_song_handler(self, data: ValidAISongRequest, request: Request):
|
||||
"""
|
||||
/ai/song/
|
||||
AI (Song Info) Request [Public]
|
||||
"""
|
||||
|
||||
ai_prompt = "You are a helpful assistant who will provide tidbits of info on songs the user may listen to."
|
||||
|
||||
ai_question = f"I am going to listen to the song \"{data.s}\" by \"{data.a}\"."
|
||||
|
||||
|
||||
|
||||
local_llm_headers = {
|
||||
'x-api-key': self.constants.CLAUDE_API_KEY,
|
||||
'anthropic-version': '2023-06-01',
|
||||
@ -146,7 +193,8 @@ class AI(FastAPI):
|
||||
timeout=ClientTimeout(connect=15, sock_read=30)) as request:
|
||||
await self.glob_state.increment_counter('claude_ai_requests')
|
||||
response = await request.json()
|
||||
print(f"Response: {response}")
|
||||
logging.debug("Response: %s",
|
||||
response)
|
||||
if response.get('type') == 'error':
|
||||
error_type = response.get('error').get('type')
|
||||
error_message = response.get('error').get('message')
|
||||
@ -164,55 +212,3 @@ class AI(FastAPI):
|
||||
'err': True,
|
||||
'errorText': 'General Failure'
|
||||
}
|
||||
|
||||
# async def ai_song_handler(self, data: ValidAISongRequest, request: Request):
|
||||
# """
|
||||
# /ai/song/
|
||||
# AI (Song Info) Request [Public]
|
||||
# """
|
||||
|
||||
# ai_question = f"I am going to listen to the song \"{data.s}\" by \"{data.a}\"."
|
||||
|
||||
# local_llm_headers = {
|
||||
# 'Authorization': f'Bearer {self.constants.LOCAL_LLM_KEY}'
|
||||
# }
|
||||
# ai_req_data = {
|
||||
# 'max_context_length': 8192,
|
||||
# 'max_length': 512,
|
||||
# 'temperature': 0,
|
||||
# 'n': 1,
|
||||
# 'top_k': 30,
|
||||
# 'top_a': 0,
|
||||
# 'top_p': 0,
|
||||
# 'typical': 0,
|
||||
# 'mirostat': 0,
|
||||
# 'use_default_badwordsids': False,
|
||||
# 'rep_pen': 1.0,
|
||||
# 'rep_pen_range': 320,
|
||||
# 'rep_pen_slope': 0.05,
|
||||
# 'quiet': 1,
|
||||
# 'bypass_eos': False,
|
||||
# # 'trim_stop': True,
|
||||
# 'sampler_order': [6,0,1,3,4,2,5],
|
||||
# 'memory': "You are a helpful assistant who will provide ONLY TOTALLY ACCURATE tidbits of info on songs the user may listen to. You do not include information about which album a song was released on, or when it was released, and do not mention that you are not including this information in your response. If the input provided is not a song you are aware of, simply state that. Begin your output at your own response.",
|
||||
# 'stop': ['### Inst', '### Resp'],
|
||||
# 'prompt': ai_question
|
||||
# }
|
||||
# try:
|
||||
# async with ClientSession() as session:
|
||||
# async with await session.post(f'{self.constants.LOCAL_LLM_BASE}/generate',
|
||||
# json=ai_req_data,
|
||||
# headers=local_llm_headers,
|
||||
# timeout=ClientTimeout(connect=15, sock_read=30)) as request:
|
||||
# await self.glob_state.increment_counter('ai_requests')
|
||||
# response = await request.json()
|
||||
# result = {
|
||||
# 'resp': response.get('results')[0].get('text').strip()
|
||||
# }
|
||||
# return result
|
||||
# except Exception as e: # pylint: disable=broad-exception-caught
|
||||
# logging.error("Error: %s", e)
|
||||
# return {
|
||||
# 'err': True,
|
||||
# 'errorText': 'General Failure'
|
||||
# }
|
@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3.12
|
||||
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
@ -31,7 +30,7 @@ class Counters(FastAPI):
|
||||
|
||||
self.endpoints = {
|
||||
"counters/get": self.get_counter_handler,
|
||||
"counters/increment": self.increment_counter_handler
|
||||
# "counters/increment": self.increment_counter_handler
|
||||
#tbd
|
||||
}
|
||||
|
||||
@ -55,14 +54,12 @@ class Counters(FastAPI):
|
||||
|
||||
}
|
||||
|
||||
async def increment_counter_handler(self, data: ValidCounterIncrementRequest):
|
||||
"""
|
||||
/counters/increment/
|
||||
Increment counter value (requires PUT KEY)
|
||||
"""
|
||||
|
||||
return {
|
||||
|
||||
}
|
||||
# async def increment_counter_handler(self, data: ValidCounterIncrementRequest):
|
||||
# """
|
||||
# /counters/increment/
|
||||
# Increment counter value (requires PUT KEY)
|
||||
# """
|
||||
|
||||
# return {
|
||||
|
||||
# }
|
||||
|
@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env python3.12
|
||||
# pylint: disable=bare-except, broad-exception-caught
|
||||
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
import datetime
|
||||
import traceback
|
||||
import aiosqlite as sqlite3
|
||||
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
@ -36,31 +38,35 @@ class ValidTopKarmaRequest(BaseModel):
|
||||
n: int | None = 10
|
||||
|
||||
class KarmaDB:
|
||||
"""Karma DB Util"""
|
||||
def __init__(self):
|
||||
self.db_path = os.path.join("/", "var", "lib", "singerdbs", "karma.db")
|
||||
|
||||
async def get_karma(self, keyword: str) -> int | dict:
|
||||
"""Get Karma Value for Keyword"""
|
||||
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
|
||||
async with 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 as e:
|
||||
except TypeError:
|
||||
return {
|
||||
'err': True,
|
||||
'errorText': f'No records for {keyword}',
|
||||
}
|
||||
|
||||
async def get_top(self, n: int = 10):
|
||||
"""Get Top n=10 Karma Entries"""
|
||||
try:
|
||||
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
|
||||
async with 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:
|
||||
print(traceback.format_exc())
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
async def update_karma(self, granter: str, keyword: str, flag: int):
|
||||
"""Update Karma for Keyword"""
|
||||
if not flag in [0, 1]:
|
||||
return
|
||||
|
||||
@ -72,7 +78,7 @@ class KarmaDB:
|
||||
audit_query = "INSERT INTO karma_audit(impacted_keyword, comment) VALUES(?, ?)"
|
||||
now = int(time.time())
|
||||
|
||||
print(f"Audit message: {audit_message}\nKeyword: {keyword}")
|
||||
logging.debug("Audit message: %s{audit_message}\nKeyword: %s{keyword}")
|
||||
|
||||
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
|
||||
async with db_conn.execute(audit_query, (keyword, audit_message,)) as db_cursor:
|
||||
@ -133,8 +139,8 @@ class Karma(FastAPI):
|
||||
try:
|
||||
top10 = await self.db.get_top(n=n)
|
||||
return top10
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': True,
|
||||
'errorText': 'Exception occurred.',
|
||||
@ -158,7 +164,7 @@ class Karma(FastAPI):
|
||||
'count': count,
|
||||
}
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': True,
|
||||
'errorText': "Exception occurred."
|
||||
@ -183,4 +189,3 @@ class Karma(FastAPI):
|
||||
return {
|
||||
'success': await self.db.update_karma(data.granter, data.keyword, data.flag)
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ class ValidArtistSearchRequest(BaseModel):
|
||||
|
||||
a: str
|
||||
|
||||
class Config:
|
||||
class Config: # pylint: disable=missing-class-docstring
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"a": "eminem"
|
||||
@ -27,7 +27,7 @@ class ValidAlbumDetailRequest(BaseModel):
|
||||
a: str
|
||||
a2: str
|
||||
|
||||
class Config:
|
||||
class Config: # pylint: disable=missing-class-docstring
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"a": "eminem",
|
||||
@ -44,7 +44,7 @@ class ValidTrackInfoRequest(BaseModel):
|
||||
a: str
|
||||
t: str
|
||||
|
||||
class Config:
|
||||
class Config: # pylint: disable=missing-class-docstring
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"a": "eminem",
|
||||
@ -185,7 +185,7 @@ class LastFM(FastAPI):
|
||||
artist = data.a
|
||||
track = data.t
|
||||
|
||||
if not(artist) or not(track):
|
||||
if not artist or not track:
|
||||
return {
|
||||
'err': True,
|
||||
'errorText': 'Invalid request'
|
||||
|
@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env python3.12
|
||||
# pylint: disable=bare-except, broad-exception-raised, broad-exception-caught
|
||||
|
||||
import importlib
|
||||
import traceback
|
||||
import logging
|
||||
import urllib.parse
|
||||
import regex
|
||||
import aiohttp
|
||||
import traceback
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
@ -92,6 +94,7 @@ class LyricSearch(FastAPI):
|
||||
}
|
||||
|
||||
async def lyric_search_log_handler(self, data: ValidLyricSearchLogRequest):
|
||||
"""Lyric Search Log Handler"""
|
||||
include_radio = data.webradio
|
||||
await self.glob_state.increment_counter('lyrichistory_requests')
|
||||
last_10k_sings = await self.lyrics_engine.getHistory(limit=10000, webradio=include_radio)
|
||||
@ -181,7 +184,8 @@ class LyricSearch(FastAPI):
|
||||
lrc_content = response_json.get('syncedLyrics')
|
||||
returned_artist = response_json.get('artistName')
|
||||
returned_song = response_json.get('trackName')
|
||||
print(f"Synced Lyrics [LRCLib]: {lrc_content}")
|
||||
logging.debug("Synced Lyrics [LRCLib]: %s",
|
||||
lrc_content)
|
||||
lrc_content_out = []
|
||||
for line in lrc_content.split("\n"):
|
||||
_timetag = None
|
||||
@ -192,7 +196,8 @@ class LyricSearch(FastAPI):
|
||||
if not reg_helper:
|
||||
continue
|
||||
reg_helper = reg_helper[0]
|
||||
print(f"Reg helper: {reg_helper} for line: {line}; len: {len(reg_helper)}")
|
||||
logging.debug("Reg helper: %s for line: %s; len: %s",
|
||||
reg_helper, line, len(reg_helper))
|
||||
_timetag = reg_helper[0]
|
||||
if not reg_helper[1].strip():
|
||||
_words = "♪"
|
||||
@ -214,7 +219,7 @@ class LyricSearch(FastAPI):
|
||||
'reqn': await self.glob_state.get_counter('lyric_requests'),
|
||||
}
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': True,
|
||||
'errorText': 'Search failed!',
|
||||
@ -225,6 +230,7 @@ class LyricSearch(FastAPI):
|
||||
'err': True,
|
||||
'errorText': 'Search failed!',
|
||||
}
|
||||
if lrc:
|
||||
return {
|
||||
'err': False,
|
||||
'artist': search_worker['artist'],
|
||||
@ -236,7 +242,7 @@ class LyricSearch(FastAPI):
|
||||
'reqn': await self.glob_state.get_counter('lyric_requests'),
|
||||
}
|
||||
|
||||
search_worker = await self.lyrics_engine.lyrics_worker(searching=search_object, recipient='anyone')
|
||||
search_worker = await self.lyrics_engine.lyrics_worker(searching=search_object)
|
||||
|
||||
|
||||
if not search_worker or not 'l' in search_worker.keys():
|
||||
|
@ -2,11 +2,9 @@
|
||||
|
||||
import os
|
||||
import random
|
||||
import traceback
|
||||
import aiosqlite as sqlite3
|
||||
|
||||
from typing import Optional
|
||||
from fastapi import FastAPI, Request
|
||||
import aiosqlite as sqlite3
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
class RandMsgRequest(BaseModel):
|
||||
|
@ -56,7 +56,7 @@ class Transcriptions(FastAPI):
|
||||
|
||||
show_id = int(show_id)
|
||||
|
||||
if not(str(show_id).isnumeric()) or not(show_id in [0, 1, 2]):
|
||||
if not(str(show_id).isnumeric()) or show_id not in [0, 1, 2]:
|
||||
return {
|
||||
'err': True,
|
||||
'errorText': 'Show not found.'
|
||||
@ -99,7 +99,7 @@ class Transcriptions(FastAPI):
|
||||
"""
|
||||
show_id = data.s
|
||||
episode_id = data.e
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
match show_id:
|
||||
case 0:
|
||||
db_path = os.path.join("/", "var", "lib", "singerdbs", "sp.db")
|
||||
|
@ -1,21 +1,9 @@
|
||||
#!/usr/bin/env python3.12
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
from fastapi import FastAPI, Request, HTTPException, WebSocket, WebSocketDisconnect, WebSocketException
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
from aces.connection_manager import ConnectionManager
|
||||
from aces.flac_reader import AudioStreamer
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
import pyaudio
|
||||
import wave
|
||||
import traceback
|
||||
import pyflac.decoder as decoder
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
import json
|
||||
import time
|
||||
|
||||
class ValidXCRequest(BaseModel):
|
||||
"""
|
||||
@ -82,7 +70,7 @@ class XC(FastAPI):
|
||||
1: '10.10.10.100:5992' # MS & Waleed Combo
|
||||
}
|
||||
|
||||
if not bid in BID_ADDR_MAP.keys():
|
||||
if not bid in BID_ADDR_MAP:
|
||||
return {
|
||||
'err': True,
|
||||
'errorText': 'Invalid bot id'
|
||||
|
@ -43,5 +43,3 @@ class YT(FastAPI):
|
||||
'video_id': yt_video_id,
|
||||
'extras': yts_res[0]
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,20 +1,24 @@
|
||||
#!/usr/bin/env python3.11
|
||||
#!/usr/bin/env python3.12
|
||||
# pylint: disable=bare-except, broad-exception-caught, invalid-name
|
||||
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
import json
|
||||
import regex
|
||||
import traceback
|
||||
|
||||
import logging
|
||||
from typing import Union
|
||||
import regex
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
from constants import Constants
|
||||
from typing import Union, Any
|
||||
|
||||
|
||||
|
||||
class LastFM:
|
||||
def __init__(self, noInit: Union[None, bool] = False):
|
||||
"""LastFM Endpoints"""
|
||||
def __init__(self, noInit: Union[None, bool] = False): # pylint: disable=unused-argument
|
||||
self.creds = Constants().LFM_CREDS
|
||||
self.api_base_url = "https://ws.audioscrobbler.com/2.0/?method="
|
||||
|
||||
|
||||
async def search_artist(self, artist=None):
|
||||
"""Search LastFM for an artist"""
|
||||
try:
|
||||
if artist is None:
|
||||
return {
|
||||
@ -22,12 +26,13 @@ class LastFM:
|
||||
}
|
||||
|
||||
async with ClientSession() as session:
|
||||
async with session.get(f"{self.api_base_url}artist.getinfo&artist={artist}&api_key={self.creds.get('key')}&autocorrect=1&format=json", timeout=ClientTimeout(connect=3, sock_read=8)) as request:
|
||||
async with session.get(f"{self.api_base_url}artist.getinfo&artist={artist}&api_key={self.creds.get('key')}&autocorrect=1&format=json",
|
||||
timeout=ClientTimeout(connect=3, sock_read=8)) as request:
|
||||
assert request.status in [200, 204]
|
||||
data = await request.json()
|
||||
data = data.get('artist')
|
||||
|
||||
print(f"Using data:\n{data}")
|
||||
logging.debug("Using data:\n%s", data)
|
||||
# return data.get('results')
|
||||
retObj = {
|
||||
'id': data.get('mbid'),
|
||||
@ -37,21 +42,23 @@ class LastFM:
|
||||
}
|
||||
return retObj
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': 'Failed'
|
||||
}
|
||||
|
||||
async def get_track_info(self, artist=None, track=None):
|
||||
"""Get Track Info from LastFM"""
|
||||
try:
|
||||
if artist is None or track is None:
|
||||
print("inv request")
|
||||
logging.info("inv request")
|
||||
return {
|
||||
'err': 'Invalid/No artist or track specified'
|
||||
}
|
||||
|
||||
async with ClientSession() as session:
|
||||
async with session.get(f"{self.api_base_url}track.getInfo&api_key={self.creds.get('key')}&autocorrect=1&artist={artist}&track={track}&format=json", timeout=ClientTimeout(connect=3, sock_read=8)) as request:
|
||||
async with session.get(f"{self.api_base_url}track.getInfo&api_key={self.creds.get('key')}&autocorrect=1&artist={artist}&track={track}&format=json",
|
||||
timeout=ClientTimeout(connect=3, sock_read=8)) as request:
|
||||
assert request.status in [200, 204]
|
||||
data = await request.json()
|
||||
data = data.get('track')
|
||||
@ -59,18 +66,19 @@ class LastFM:
|
||||
'artist_mbid': data.get('artist').get('mbid'),
|
||||
'album': data.get('album').get('title')
|
||||
}
|
||||
print(f"Returning:\n{retObj}")
|
||||
logging.debug("Returning:\n%s", retObj)
|
||||
return retObj
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': 'General Failure'
|
||||
}
|
||||
|
||||
async def get_album_tracklist(self, artist=None, album=None):
|
||||
"""Get Album Tracklist"""
|
||||
try:
|
||||
if artist is None or album is None:
|
||||
print("inv request")
|
||||
logging.info("inv request")
|
||||
return {
|
||||
'err': 'No artist or album specified'
|
||||
}
|
||||
@ -81,16 +89,17 @@ class LastFM:
|
||||
'tracks': tracks
|
||||
}
|
||||
|
||||
print(f"Returning:\n{retObj}")
|
||||
logging.debug("Returning:\n%s", retObj)
|
||||
return retObj
|
||||
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': 'General Failure'
|
||||
}
|
||||
|
||||
async def get_artist_albums(self, artist=None):
|
||||
"""Get Artists Albums from LastFM"""
|
||||
try:
|
||||
if artist is None:
|
||||
return {
|
||||
@ -98,7 +107,8 @@ class LastFM:
|
||||
}
|
||||
|
||||
async with ClientSession() as session:
|
||||
async with session.get(f"{self.api_base_url}artist.gettopalbums&artist={artist}&api_key={self.creds.get('key')}&autocorrect=1&format=json", timeout=ClientTimeout(connect=3, sock_read=8)) as request:
|
||||
async with session.get(f"{self.api_base_url}artist.gettopalbums&artist={artist}&api_key={self.creds.get('key')}&autocorrect=1&format=json",
|
||||
timeout=ClientTimeout(connect=3, sock_read=8)) as request:
|
||||
assert request.status in [200, 204]
|
||||
# return request.text
|
||||
data = await request.json()
|
||||
@ -107,17 +117,18 @@ class LastFM:
|
||||
retObj = [
|
||||
{
|
||||
'title': item.get('name')
|
||||
} for item in data if not(item.get('name').lower() == "(null)") and not(int(item.get('playcount')) < 50)
|
||||
} for item in data if not(item.get('name').lower() == "(null)") and int(item.get('playcount')) >= 50
|
||||
]
|
||||
# # print(f"Keys: {data[0].keys()}")
|
||||
return retObj
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': 'Failed'
|
||||
}
|
||||
|
||||
async def get_artist_id(self, artist=None):
|
||||
"""Get Artist ID from LastFM"""
|
||||
try:
|
||||
if artist is None:
|
||||
return {
|
||||
@ -125,27 +136,29 @@ class LastFM:
|
||||
}
|
||||
artist_search = await self.search_artist(artist=artist)
|
||||
if artist_search is None or len(artist_search) < 1:
|
||||
print("[get_artist_id] Throwing no result error")
|
||||
logging.debug("[get_artist_id] Throwing no result error")
|
||||
return {
|
||||
'err': 'No results.'
|
||||
}
|
||||
artist_id = artist_search[0].get('id')
|
||||
return artist_id
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': 'Failed'
|
||||
}
|
||||
|
||||
async def get_artist_info_by_id(self, artist_id=None):
|
||||
"""Get Artist info by ID from LastFM"""
|
||||
try:
|
||||
if artist_id is None or not(str(artist_id).isnumeric()):
|
||||
if artist_id is None or not str(artist_id).isnumeric():
|
||||
return {
|
||||
'err': 'Invalid/no artist_id specified.'
|
||||
}
|
||||
|
||||
async with ClientSession() as session:
|
||||
async with session.get(f"{self.api_base_url}artists/{artist_id}?key={self.creds.get('key')}&secret={self.creds.get('secret')}", timeout=ClientTimeout(connect=3, sock_read=8)) as request:
|
||||
async with session.get(f"{self.api_base_url}artists/{artist_id}?key={self.creds.get('key')}&secret={self.creds.get('secret')}",
|
||||
timeout=ClientTimeout(connect=3, sock_read=8)) as request:
|
||||
assert request.status in [200, 204]
|
||||
data = await request.json()
|
||||
|
||||
@ -157,35 +170,37 @@ class LastFM:
|
||||
}
|
||||
return retObj
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': 'Failed'
|
||||
}
|
||||
|
||||
async def get_artist_info(self, artist=None):
|
||||
"""Get Artist Info from LastFM"""
|
||||
try:
|
||||
if artist is None:
|
||||
return {
|
||||
'err': 'No artist specified.'
|
||||
}
|
||||
# artist_id = await self.get_artist_id(artist=artist)
|
||||
# if artist_id is None:
|
||||
# return {
|
||||
# 'err': Failed
|
||||
# }
|
||||
artist_id = await self.get_artist_id(artist=artist)
|
||||
if artist_id is None:
|
||||
return {
|
||||
'err': 'Failed',
|
||||
}
|
||||
artist_info = await self.get_artist_info_by_id(artist_id=artist_id)
|
||||
if artist_info is None:
|
||||
return {
|
||||
'err': Failed
|
||||
'err': 'Failed',
|
||||
}
|
||||
return artist_info
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': 'Failed'
|
||||
}
|
||||
|
||||
async def get_release(self, artist=None, album=None):
|
||||
"""Get Release info from LastFM"""
|
||||
try:
|
||||
if artist is None or album is None:
|
||||
return {
|
||||
@ -193,7 +208,8 @@ class LastFM:
|
||||
}
|
||||
|
||||
async with ClientSession() as session:
|
||||
async with session.get(f"{self.api_base_url}album.getinfo&artist={artist}&album={album}&api_key={self.creds.get('key')}&autocorrect=1&format=json", timeout=ClientTimeout(connect=3, sock_read=8)) as request:
|
||||
async with session.get(f"{self.api_base_url}album.getinfo&artist={artist}&album={album}&api_key={self.creds.get('key')}&autocorrect=1&format=json",
|
||||
timeout=ClientTimeout(connect=3, sock_read=8)) as request:
|
||||
assert request.status in [200, 204]
|
||||
data = await request.json()
|
||||
data = data.get('album')
|
||||
@ -206,9 +222,10 @@ class LastFM:
|
||||
}
|
||||
try:
|
||||
track_key = data.get('tracks').get('track')
|
||||
except: track_key = []
|
||||
if type(track_key) == list:
|
||||
print(f"Track key: {track_key}")
|
||||
except:
|
||||
track_key = []
|
||||
if isinstance(track_key, list):
|
||||
logging.debug("Track key: %s", track_key)
|
||||
retObj['tracks'] = [
|
||||
{
|
||||
'duration': item.get('duration', 'N/A'),
|
||||
@ -223,7 +240,7 @@ class LastFM:
|
||||
]
|
||||
return retObj
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
return {
|
||||
'err': 'Failed'
|
||||
}
|
||||
|
22
state.py
22
state.py
@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3.12
|
||||
# pylint: disable=bare-except, broad-exception-raised
|
||||
|
||||
"""Global State Storage/Counters"""
|
||||
|
||||
@ -11,7 +12,9 @@ from fastapi_utils.tasks import repeat_every
|
||||
|
||||
|
||||
class State(FastAPI):
|
||||
def __init__(self, app: FastAPI, util, constants):
|
||||
"""Global State for API"""
|
||||
def __init__(self, app: FastAPI, util, constants): # pylint: disable=unused-argument
|
||||
super().__init__()
|
||||
self.counter_db_path = os.path.join("/", "var", "lib", "singerdbs", "stats.db")
|
||||
self.counters = {
|
||||
str(counter): 0 for counter in constants.AVAILABLE_COUNTERS
|
||||
@ -53,7 +56,7 @@ class State(FastAPI):
|
||||
@app.on_event("startup")
|
||||
@repeat_every(seconds=10)
|
||||
async def update_db():
|
||||
if self.counters_initialized == False:
|
||||
if not self.counters_initialized:
|
||||
logging.debug("[State] TICK: Counters not yet initialized")
|
||||
return
|
||||
|
||||
@ -92,21 +95,20 @@ class State(FastAPI):
|
||||
|
||||
|
||||
async def increment_counter(self, counter: str):
|
||||
if not(counter in self.counters.keys()):
|
||||
raise BaseException("[State] Counter %s does not exist", counter)
|
||||
"""Increment Counter"""
|
||||
if not counter in self.counters.keys():
|
||||
raise BaseException(f"[State] Counter {counter} does not exist")
|
||||
|
||||
self.counters[counter] += 1
|
||||
return True
|
||||
|
||||
async def get_counter(self, counter: str):
|
||||
if not(counter in self.counters.keys()):
|
||||
raise BaseException("[State] Counter %s does not exist", counter)
|
||||
"""Get Counter"""
|
||||
if not counter in self.counters.keys():
|
||||
raise BaseException(f"[State] Counter {counter} does not exist")
|
||||
|
||||
return self.counters[counter]
|
||||
|
||||
async def get_all_counters(self):
|
||||
"""Get All Counters"""
|
||||
return self.counters
|
||||
|
||||
|
||||
|
||||
|
||||
|
11
util.py
11
util.py
@ -2,21 +2,25 @@
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import FastAPI, Response, HTTPException, Security
|
||||
from fastapi import FastAPI, Response, HTTPException
|
||||
|
||||
|
||||
class Utilities:
|
||||
"""API Utilities"""
|
||||
def __init__(self, app: FastAPI, constants):
|
||||
self.constants = constants
|
||||
self.blocked_response_status = 422
|
||||
self.blocked_response_content = None
|
||||
self.app = app
|
||||
|
||||
def get_blocked_response(self, path: str | None = None):
|
||||
def get_blocked_response(self, path: str | None = None): # pylint: disable=unused-argument
|
||||
"""Get Blocked HTTP Response"""
|
||||
logging.error("Rejected request: Blocked")
|
||||
return Response(content=self.blocked_response_content,
|
||||
status_code=self.blocked_response_status)
|
||||
|
||||
def get_no_endpoint_found(self, path: str | None = None):
|
||||
def get_no_endpoint_found(self, path: str | None = None): # pylint: disable=unused-argument
|
||||
"""Get 404 Response"""
|
||||
logging.error("Rejected request: No such endpoint")
|
||||
raise HTTPException(detail="Unknown endpoint", status_code=404)
|
||||
|
||||
@ -49,4 +53,3 @@ class Utilities:
|
||||
|
||||
# print("Auth succeeded")
|
||||
return True
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user