Add tracks caching
This commit is contained in:
parent
128609ca0f
commit
33b2bfaf23
10 changed files with 74 additions and 132 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,4 +13,5 @@ wheels/
|
|||
.idea
|
||||
.env
|
||||
*.session
|
||||
*.session-journal
|
||||
oauth.json
|
|
@ -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 ###
|
|
@ -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 ###
|
|
@ -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 ###
|
|
@ -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 ###
|
|
@ -52,7 +52,7 @@ class SpotifyStrategy(MusicProviderStrategy):
|
|||
'Content-Type': 'application/json'
|
||||
}
|
||||
user_params = {
|
||||
'limit': 5
|
||||
'limit': 1
|
||||
}
|
||||
res = []
|
||||
async with aiohttp.ClientSession() as session:
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
11
uv.lock
generated
|
@ -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" },
|
||||
|
|
Loading…
Add table
Reference in a new issue