Rewrite yandex music token gathering

This commit is contained in:
unknown 2025-04-10 00:09:30 +03:00
parent 40ec2bc6b8
commit baea35ef61
8 changed files with 99 additions and 46 deletions

View file

@ -0,0 +1,42 @@
"""change creds storage
Revision ID: 59bdc35f510c
Revises: fc8875e47bc0
Create Date: 2025-04-09 23:28:32.649596
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '59bdc35f510c'
down_revision: Union[str, None] = 'fc8875e47bc0'
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_auth', sa.JSON(), nullable=False))
op.add_column('users', sa.Column('ymusic_auth', sa.JSON(), nullable=False))
op.drop_column('users', 'ymusic_token')
op.drop_column('users', 'spotify_refresh_token')
op.drop_column('users', 'spotify_refresh_at')
op.drop_column('users', 'spotify_access_token')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('spotify_access_token', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('users', sa.Column('spotify_refresh_at', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('users', sa.Column('spotify_refresh_token', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('users', sa.Column('ymusic_token', sa.VARCHAR(), autoincrement=False, nullable=True))
op.drop_column('users', 'ymusic_auth')
op.drop_column('users', 'spotify_auth')
# ### end Alembic commands ###

View file

@ -4,7 +4,7 @@ import time
import aiohttp
from app.config import config, spotify_creds
from app.config import config
from app.MusicProvider.Strategy import MusicProviderStrategy
from app.dependencies import get_session, get_session_context
from sqlalchemy import select, update
@ -27,7 +27,7 @@ def convert_track(track: dict):
async def refresh_token(refresh_token):
token_headers = {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization': 'Basic ' + spotify_creds
'Authorization': 'Basic ' + config.spotify.encoded
}
token_data = {
"grant_type": "refresh_token",
@ -51,10 +51,10 @@ class SpotifyStrategy(MusicProviderStrategy):
if not user:
return None
if int(time.time()) < user.spotify_refresh_at:
return user.spotify_access_token
if int(time.time()) < user.spotify_auth['refresh_at']:
return user.spotify_auth['access_token']
token, expires_in = await refresh_token(user.spotify_refresh_token)
token, expires_in = await refresh_token(user.spotify_auth['refresh_token'])
async with get_session_context() as session:
await session.execute(
update(User).where(User.id == self.user_id).values(spotify_access_token=token,
@ -86,6 +86,8 @@ class SpotifyStrategy(MusicProviderStrategy):
tracks.append(convert_track(item['track']))
tracks = [x for x in tracks if x]
tracks = list(dict.fromkeys(tracks))
print(tracks)
return tracks
async def fetch_track(self, track: Track):

View file

@ -1,4 +1,4 @@
from app.MusicProvider.Context import MusicProviderContext
from app.MusicProvider.SpotifyStrategy import SpotifyStrategy
from app.MusicProvider.YMusicStrategy import YandexMusicStrategy
from app.MusicProvider.Strategy import MusicProviderStrategy
from app.MusicProvider.Strategy import MusicProviderStrategy

View file

@ -39,7 +39,7 @@ client.parse_mode = 'html'
cache = LRUCache(maxsize=100)
def get_spotify_link(user_id):
def get_spotify_link(user_id) -> str:
params = {
'client_id': config.spotify.client_id,
'response_type': 'code',
@ -50,10 +50,10 @@ def get_spotify_link(user_id):
return f"https://accounts.spotify.com/authorize?{urllib.parse.urlencode(params)}"
def get_ymusic_link(user_id):
def get_ymusic_link(user_id) -> str:
params = {
'response_type': 'token',
'client_id': config.ym.client_id,
'response_type': 'code',
'client_id': config.ymusic.client_id,
'state': user_id
}
return f"https://oauth.yandex.ru/authorize?{urllib.parse.urlencode(params)}"
@ -74,14 +74,14 @@ async def start(e: events.NewMessage.Event):
buttons=buttons)
async def fetch_file(url):
async def fetch_file(url) -> bytes:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
response.raise_for_status()
return await response.read()
def get_track_links(track_id):
def get_track_links(track_id) -> str:
return f'<a href="https://open.spotify.com/track/{track_id}">Spotify</a> | <a href="https://song.link/s/{track_id}">Other</a>'
@ -136,7 +136,7 @@ async def build_response(e: events.InlineQuery.Event, track: Track):
@client.on(events.InlineQuery())
async def query_list(e: events.InlineQuery.Event):
context = MusicProviderContext(YandexMusicStrategy(e.sender_id))
context = MusicProviderContext(SpotifyStrategy(e.sender_id))
tracks = (await context.get_tracks())[:5]
result = []
@ -179,6 +179,7 @@ async def download_track(track):
yt_id = await asyncio.get_event_loop().run_in_executor(
None, functools.partial(name_to_youtube, f'{track.name} - {track.artist}')
)
track.yt_id = yt_id
async with get_session_context() as session:
existing = await session.scalar(
select(Track).where(Track.yt_id == yt_id)
@ -205,7 +206,6 @@ async def download_track(track):
track.telegram_id = file.id
track.telegram_access_hash = file.access_hash
track.telegram_file_reference = file.file_reference
track.yt_id = yt_id
await cache_file(track)
return file

View file

@ -11,7 +11,7 @@ import jwt
from app.dependencies import get_session
from app.models.user import User
from config import config, spotify_creds
from config import config, OauthCreds
client = TelegramClient('nowplaying_callback', config.api_id, config.api_hash)
@ -32,25 +32,23 @@ class LinkException(Exception):
@app.exception_handler(LinkException)
async def unicorn_exception_handler(request: Request, exc: LinkException):
async def link_exception_handler(request: Request, exc: LinkException):
return FileResponse('static/error.html', status_code=400)
async def get_spotify_token(code: str):
async def code_to_token(code: str, uri: str, creds: OauthCreds) -> tuple[str, str, int]:
token_headers = {
"Authorization": "Basic " + spotify_creds,
"Authorization": "Basic " + creds.encoded,
"Content-Type": "application/x-www-form-urlencoded"
}
token_data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": config.spotify.redirect
"redirect_uri": creds.redirect
}
async with aiohttp.ClientSession() as session:
resp = await session.post("https://accounts.spotify.com/api/token", data=token_data, headers=token_headers)
resp = await session.post(uri, data=token_data, headers=token_headers)
resp = await resp.json()
if 'access_token' not in resp:
raise LinkException()
return resp['access_token'], resp['refresh_token'], int(resp['expires_in'])
@ -66,17 +64,19 @@ def get_decoded_id(string: str):
@app.get('/spotify_callback')
async def spotify_callback(code: str, state: str, session: AsyncSession = Depends(get_session)):
user_id = get_decoded_id(state)
token, refresh_token, expires_in = await get_spotify_token(code)
token, refresh_token, expires_in = await code_to_token(code, 'https://accounts.spotify.com/api/token', config.spotify)
creds = {
'access_token': token,
'refresh_token': refresh_token,
'refresh_at': int(time.time()) + expires_in
}
user = await session.get(User, user_id)
if user:
user.spotify_access_token = token
user.spotify_refresh_token = refresh_token
user.spotify_refreshed_at = int(time.time())
user.spotify_auth = creds
else:
user = User(id=user_id,
spotify_access_token=token,
spotify_refresh_token=refresh_token,
spotify_refresh_at=int(time.time()) + expires_in
spotify_auth=creds
)
session.add(user)
await session.commit()
@ -85,14 +85,20 @@ async def spotify_callback(code: str, state: str, session: AsyncSession = Depend
@app.get('/ym_callback')
async def ym_callback(state: str, access_token: str, session: AsyncSession = Depends(get_session)):
async def ym_callback(state: str, code: str, cid: str, session: AsyncSession = Depends(get_session)):
user_id = get_decoded_id(state)
token, refresh_token, expires_in = await code_to_token(code, 'https://oauth.yandex.com/token', config.ymusic)
creds = {
'access_token': token,
'refresh_token': refresh_token,
'refresh_at': int(time.time()) + expires_in
}
user = await session.get(User, user_id)
if user:
user.ymusic_token = access_token
user.ymusic_auth = creds
else:
user = User(id=user_id,
ymusic_token=access_token
ymusic_auth=creds
)
session.add(user)
await session.commit()

View file

@ -4,14 +4,14 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import BaseModel
class SpotifyCreds(BaseModel):
class OauthCreds(BaseModel):
client_id: str
client_secret: str
redirect: str
class YandexMusicCreds(BaseModel):
client_id: str
@property
def encoded(self) -> str:
return base64.b64encode(self.client_id.encode() + b':' + self.client_secret.encode()).decode("utf-8")
class GoogleApiCreds(BaseModel):
@ -26,15 +26,14 @@ class Config(BaseSettings):
api_id: int
api_hash: str
db_string: str
spotify: SpotifyCreds
spotify: OauthCreds
yt: GoogleApiCreds
ym: YandexMusicCreds
ymusic: OauthCreds
proxy: str = ''
jwt_secret: str
config = Config(_env_file='.env')
spotify_creds = base64.b64encode(config.spotify.client_id.encode() + b':' + config.spotify.client_secret.encode()).decode("utf-8")
__all__ = ['config', 'spotify_creds']
__all__ = ['config', 'OauthCreds']

View file

@ -23,6 +23,13 @@ class Track(Base):
cover_url: Mapped[str]
used_times: Mapped[int] = mapped_column(Integer, default=1)
def __hash__(self):
return hash(self.spotify_id)
def __eq__(self, other):
if not isinstance(other, Track):
return NotImplemented
return self.spotify_id == other.spotify_id

View file

@ -2,18 +2,15 @@ from typing import Optional
from app.models import Base
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import String
from sqlalchemy import JSON
class User(Base):
__tablename__ = 'users'
id: Mapped[int] = mapped_column(primary_key=True)
spotify_access_token: Mapped[Optional[str]]
spotify_refresh_token: Mapped[Optional[str]]
spotify_refresh_at: Mapped[Optional[int]]
ymusic_token: Mapped[Optional[str]]
spotify_auth: Mapped[dict] = mapped_column(JSON, default={})
ymusic_auth: Mapped[dict] = mapped_column(JSON, default={})
__all__ = ['User']