Add tracks caching

This commit is contained in:
unknown 2025-04-04 22:17:57 +03:00
parent 128609ca0f
commit 33b2bfaf23
10 changed files with 74 additions and 132 deletions

1
.gitignore vendored
View file

@ -13,4 +13,5 @@ wheels/
.idea
.env
*.session
*.session-journal
oauth.json

View file

@ -1,34 +0,0 @@
"""edit Track.spotify_refreshed_at to spotify_refresh_at
Revision ID: 04289c560c5c
Revises: 45a6cad48d51
Create Date: 2025-04-04 15:52:24.278867
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '04289c560c5c'
down_revision: Union[str, None] = '45a6cad48d51'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('spotify_refresh_at', sa.Integer(), nullable=False))
op.drop_column('users', 'spotify_refreshed_at')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('spotify_refreshed_at', sa.INTEGER(), autoincrement=False, nullable=False))
op.drop_column('users', 'spotify_refresh_at')
# ### end Alembic commands ###

View file

@ -1,32 +0,0 @@
"""add md5 checksum
Revision ID: 45a6cad48d51
Revises: 6c98fbc4386e
Create Date: 2025-04-04 15:02:09.437623
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '45a6cad48d51'
down_revision: Union[str, None] = '6c98fbc4386e'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('tracks', sa.Column('telegram_md5_checksum', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('tracks', 'telegram_md5_checksum')
# ### end Alembic commands ###

View file

@ -1,8 +1,8 @@
"""init migrations
"""edit Track
Revision ID: 6c98fbc4386e
Revises: f7473c0d02c7
Create Date: 2025-04-02 23:38:49.273908
Revision ID: d99a58c0c384
Revises:
Create Date: 2025-04-04 22:08:49.345416
"""
from typing import Sequence, Union
@ -12,8 +12,8 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '6c98fbc4386e'
down_revision: Union[str, None] = 'f7473c0d02c7'
revision: str = 'd99a58c0c384'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
@ -23,19 +23,29 @@ def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tracks',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('telegram_id', sa.BigInteger(), nullable=True),
sa.Column('telegram_access_hash', sa.BigInteger(), nullable=True),
sa.Column('telegram_file_reference', sa.LargeBinary(), nullable=True),
sa.Column('spotify_id', sa.String(), nullable=False),
sa.Column('telegram_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(), nullable=False),
sa.Column('artist', sa.String(), nullable=False),
sa.Column('cover_url', sa.String(), nullable=False),
sa.Column('used_times', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('spotify_access_token', sa.String(), nullable=False),
sa.Column('spotify_refresh_token', sa.String(), nullable=False),
sa.Column('spotify_refresh_at', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('users')
op.drop_table('tracks')
# ### end Alembic commands ###

View file

@ -1,38 +0,0 @@
"""init migrations
Revision ID: f7473c0d02c7
Revises:
Create Date: 2025-04-02 23:37:19.633954
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'f7473c0d02c7'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('spotify_access_token', sa.String(), nullable=False),
sa.Column('spotify_refresh_token', sa.String(), nullable=False),
sa.Column('spotify_refreshed_at', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('users')
# ### end Alembic commands ###

View file

@ -52,7 +52,7 @@ class SpotifyStrategy(MusicProviderStrategy):
'Content-Type': 'application/json'
}
user_params = {
'limit': 5
'limit': 1
}
res = []
async with aiohttp.ClientSession() as session:

View file

@ -8,18 +8,19 @@ from telethon.tl.types import (
TypeInputFile,
InputFile,
DocumentAttributeAudio,
InputPeerSelf
InputPeerSelf, InputDocument
)
from telethon import functions
from telethon.utils import get_input_document
import urllib.parse
from mutagen.id3 import ID3, APIC
import logging
import uuid
from cachetools import LRUCache
from app.MusicProvider import MusicProviderContext, SpotifyStrategy
from app.config import config
from app.dependencies import get_session
from app.dependencies import get_session, get_session_context
from app.models import Track
from app.youtube_api import name_to_youtube, download_youtube
@ -33,6 +34,7 @@ logger = logging.getLogger(__name__)
client = TelegramClient('nowplaying', config.api_id, config.api_hash)
client.parse_mode = 'html'
dummy_file: TypeInputFile = None
cache = LRUCache(maxsize=100)
def get_link_account_keyboard():
@ -94,20 +96,22 @@ async def update_dummy_file_cover(cover_url: str):
async def build_response(e: events.InlineQuery.Event, track: Track):
track_id = f'{track.spotify_id}__{track.name}__{track.artist}'
if not track.telegram_id:
await update_dummy_file()
buttons = [Button.inline('Loading', 'loading')]
else:
global dummy_file
dummy_file = InputFile(int(track.telegram_id), 1, track.name, track.telegram_md5_checksum)
dummy_file = InputDocument(
id=track.telegram_id,
access_hash=track.telegram_access_hash,
file_reference=track.telegram_file_reference
)
buttons = None
track_id += '__cached'
return e.builder.document(
file=dummy_file,
title=track.name,
description=track.artist,
id=track_id,
id=track.spotify_id,
mime_type='audio/mpeg',
attributes=[
DocumentAttributeAudio(
@ -131,17 +135,14 @@ async def query_list(e: events.InlineQuery.Event):
for track in tracks:
track = await context.get_cached_track(track)
cache[track.spotify_id] = track
result.append(await build_response(e, track))
await e.answer(result)
@client.on(events.Raw([UpdateBotInlineSend]))
async def send_track(e: UpdateBotInlineSend):
if e.id.endswith('__cached'):
return
track_id, name, artist = e.id.split('__')[:4]
async def get_track_file(track):
yt_id = await asyncio.get_event_loop().run_in_executor(
None, functools.partial(name_to_youtube, f'{name} - {artist}')
None, functools.partial(name_to_youtube, f'{track.name} - {track.artist}')
)
audio, duration = await download_youtube(yt_id)
_, media, _ = await client._file_to_media(
@ -150,8 +151,8 @@ async def send_track(e: UpdateBotInlineSend):
DocumentAttributeAudio(
duration=duration,
voice=False,
title=name,
performer=artist,
title=track.name,
performer=track.artist,
waveform=None,
)]
)
@ -160,9 +161,28 @@ async def send_track(e: UpdateBotInlineSend):
InputPeerSelf(), media=media
)
)
file = get_input_document(uploaded_media.document)
await client.edit_message(e.msg_id, file=file, text=get_track_links(track_id))
return get_input_document(uploaded_media.document)
async def cache_file(track):
async with get_session_context() as session:
session.add(track)
await session.commit()
@client.on(events.Raw([UpdateBotInlineSend]))
async def send_track(e: UpdateBotInlineSend):
track = cache[e.id]
if track.telegram_id:
return
file = await get_track_file(track)
track.telegram_id = file.id
track.telegram_access_hash = file.access_hash
track.telegram_file_reference = file.file_reference
await cache_file(track)
await client.edit_message(e.msg_id, file=file, text=get_track_links(e.id))
async def main():

View file

@ -2,18 +2,21 @@ from typing import Optional
from app.models import Base
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import BigInteger, LargeBinary, Integer
class Track(Base):
__tablename__ = 'tracks'
id: Mapped[int] = mapped_column(primary_key=True)
telegram_id: Mapped[Optional[int]]
telegram_md5_checksum: Mapped[Optional[str]]
telegram_id: Mapped[Optional[int]] = mapped_column(BigInteger)
telegram_access_hash: Mapped[Optional[int]] = mapped_column(BigInteger)
telegram_file_reference: Mapped[Optional[bytes]] = mapped_column(LargeBinary)
spotify_id: Mapped[str]
name: Mapped[str]
artist: Mapped[str]
cover_url: Mapped[str]
used_times: Mapped[str]
used_times: Mapped[str] = mapped_column(Integer, default=1)

View file

@ -8,6 +8,7 @@ dependencies = [
"aiohttp>=3.11.14",
"alembic>=1.15.2",
"asyncpg>=0.30.0",
"cachetools>=5.5.2",
"fastapi>=0.115.12",
"mediafile>=0.13.0",
"mutagen>=1.47.0",

11
uv.lock generated
View file

@ -116,6 +116,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 },
]
[[package]]
name = "cachetools"
version = "5.5.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 },
]
[[package]]
name = "certifi"
version = "2025.1.31"
@ -357,6 +366,7 @@ dependencies = [
{ name = "aiohttp" },
{ name = "alembic" },
{ name = "asyncpg" },
{ name = "cachetools" },
{ name = "fastapi" },
{ name = "mediafile" },
{ name = "mutagen" },
@ -376,6 +386,7 @@ requires-dist = [
{ name = "aiohttp", specifier = ">=3.11.14" },
{ name = "alembic", specifier = ">=1.15.2" },
{ name = "asyncpg", specifier = ">=0.30.0" },
{ name = "cachetools", specifier = ">=5.5.2" },
{ name = "fastapi", specifier = ">=0.115.12" },
{ name = "mediafile", specifier = ">=0.13.0" },
{ name = "mutagen", specifier = ">=1.47.0" },