Rewrite yandex music token gathering
This commit is contained in:
parent
40ec2bc6b8
commit
baea35ef61
8 changed files with 99 additions and 46 deletions
42
alembic/versions/59bdc35f510c_change_creds_storage.py
Normal file
42
alembic/versions/59bdc35f510c_change_creds_storage.py
Normal 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 ###
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
|
Loading…
Add table
Reference in a new issue