Compare commits
4 Commits
dev
...
3d1cf6bb4d
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d1cf6bb4d | |||
| de37fcb5fa | |||
| 0640ab9242 | |||
| b921cf9138 |
@ -1,11 +1,11 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "oxidespotify"
|
name = "oxidespotify"
|
||||||
version = "1.0.2"
|
version = "0.2.2"
|
||||||
description = "Client for Spotify API"
|
description = "Client for Spotify API"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{ name = "Miwory", email = "miwory.uwu@gmail.com" }]
|
authors = [{ name = "Miwory", email = "miwory.uwu@gmail.com" }]
|
||||||
requires-python = ">=3.14"
|
requires-python = ">=3.14"
|
||||||
dependencies = ["oxidehttp>=1.4.0,<=2.0.0", "pydantic>=2.12,<=2.13"]
|
dependencies = ["oxidehttp>=1.0.3,<=2.0.0", "pydantic>=2.12,<=2.13"]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["uv_build>=0.9.2,<0.11.0"]
|
requires = ["uv_build>=0.9.2,<0.11.0"]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,7 @@
|
|||||||
from datetime import date, datetime
|
from datetime import datetime
|
||||||
from typing import Annotated, Literal
|
from typing import Literal
|
||||||
|
|
||||||
from pydantic import (
|
from pydantic import BaseModel, HttpUrl, field_validator
|
||||||
BaseModel,
|
|
||||||
BeforeValidator,
|
|
||||||
HttpUrl,
|
|
||||||
RootModel,
|
|
||||||
field_validator,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
@ -28,21 +22,6 @@ class InternalError(Error):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def normalize_spotify_date(v: str) -> str:
|
|
||||||
parts = v.split('-')
|
|
||||||
if len(parts) == 1:
|
|
||||||
return f'{v}-01-01'
|
|
||||||
if len(parts) == 2:
|
|
||||||
return f'{v}-01'
|
|
||||||
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
PrecisionedReleaseDate = Annotated[
|
|
||||||
date, BeforeValidator(normalize_spotify_date)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class Token(BaseModel):
|
class Token(BaseModel):
|
||||||
token_type: Literal['Bearer']
|
token_type: Literal['Bearer']
|
||||||
access_token: str
|
access_token: str
|
||||||
@ -83,20 +62,6 @@ class Image(BaseModel):
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
class Cursors(BaseModel):
|
|
||||||
after: int
|
|
||||||
before: int
|
|
||||||
|
|
||||||
|
|
||||||
class Paginated(BaseModel):
|
|
||||||
href: HttpUrl
|
|
||||||
limit: int
|
|
||||||
next: HttpUrl | None
|
|
||||||
offset: int = 0
|
|
||||||
previous: HttpUrl | None = None
|
|
||||||
total: int = 0
|
|
||||||
|
|
||||||
|
|
||||||
class ExternalUrls(BaseModel):
|
class ExternalUrls(BaseModel):
|
||||||
spotify: HttpUrl
|
spotify: HttpUrl
|
||||||
|
|
||||||
@ -107,16 +72,6 @@ class ExternalIDs(BaseModel):
|
|||||||
upc: str | None = None
|
upc: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class Copyright(BaseModel):
|
|
||||||
text: str
|
|
||||||
type: Literal['C', 'P']
|
|
||||||
|
|
||||||
|
|
||||||
class ResumePoint(BaseModel):
|
|
||||||
fully_played: bool
|
|
||||||
resume_position_ms: int
|
|
||||||
|
|
||||||
|
|
||||||
class Artist(BaseModel):
|
class Artist(BaseModel):
|
||||||
external_urls: ExternalUrls
|
external_urls: ExternalUrls
|
||||||
href: HttpUrl
|
href: HttpUrl
|
||||||
@ -126,11 +81,71 @@ class Artist(BaseModel):
|
|||||||
uri: str
|
uri: str
|
||||||
|
|
||||||
|
|
||||||
class BaseTrack(BaseModel):
|
class Cursor(BaseModel):
|
||||||
|
after: int
|
||||||
|
before: int
|
||||||
|
|
||||||
|
|
||||||
|
class Context(BaseModel):
|
||||||
|
type: Literal['artist', 'playlist', 'album', 'show', 'collection']
|
||||||
|
href: HttpUrl
|
||||||
|
external_urls: ExternalUrls
|
||||||
|
uri: str
|
||||||
|
|
||||||
|
|
||||||
|
class Album(BaseModel):
|
||||||
|
album_type: Literal['album', 'single', 'compilation']
|
||||||
|
total_tracks: int
|
||||||
|
external_urls: ExternalUrls
|
||||||
|
href: HttpUrl
|
||||||
|
id: str
|
||||||
|
images: list[Image]
|
||||||
|
name: str
|
||||||
|
release_date: datetime
|
||||||
|
release_date_precision: Literal['year', 'month', 'day']
|
||||||
|
restrictions: Restriction | None = None
|
||||||
|
type: Literal['album']
|
||||||
|
uri: str
|
||||||
|
artists: list[Artist]
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistOwner(BaseModel):
|
||||||
|
external_urls: ExternalUrls
|
||||||
|
href: HttpUrl
|
||||||
|
id: str
|
||||||
|
type: Literal['user']
|
||||||
|
uri: str
|
||||||
|
display_name: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistTracks(BaseModel):
|
||||||
|
href: HttpUrl
|
||||||
|
total: int
|
||||||
|
|
||||||
|
|
||||||
|
class Playlist(BaseModel):
|
||||||
|
collaborative: bool
|
||||||
|
description: str
|
||||||
|
external_urls: ExternalUrls
|
||||||
|
href: HttpUrl
|
||||||
|
id: str
|
||||||
|
images: list[Image]
|
||||||
|
name: str
|
||||||
|
owner: PlaylistOwner
|
||||||
|
public: bool
|
||||||
|
snapshot_id: str
|
||||||
|
items: PlaylistTracks
|
||||||
|
type: Literal['playlist']
|
||||||
|
uri: str
|
||||||
|
|
||||||
|
|
||||||
|
class Track(BaseModel):
|
||||||
|
album: Album
|
||||||
artists: list[Artist]
|
artists: list[Artist]
|
||||||
disc_number: int
|
disc_number: int
|
||||||
duration_ms: int
|
duration_ms: int
|
||||||
explicit: bool
|
explicit: bool
|
||||||
|
external_ids: ExternalIDs
|
||||||
external_urls: ExternalUrls
|
external_urls: ExternalUrls
|
||||||
href: HttpUrl
|
href: HttpUrl
|
||||||
id: str
|
id: str
|
||||||
@ -143,47 +158,54 @@ class BaseTrack(BaseModel):
|
|||||||
is_local: bool
|
is_local: bool
|
||||||
|
|
||||||
|
|
||||||
class AlbumTracks(Paginated):
|
class EpisodeResumePoint(BaseModel):
|
||||||
items: list[BaseTrack]
|
fully_played: bool
|
||||||
|
resume_position_ms: int
|
||||||
|
|
||||||
|
|
||||||
class BaseAlbum(BaseModel):
|
class Copyright(BaseModel):
|
||||||
album_type: Literal['album', 'single', 'compilation']
|
text: str
|
||||||
total_tracks: int
|
type: Literal['C', 'P']
|
||||||
|
|
||||||
|
|
||||||
|
class Show(BaseModel):
|
||||||
|
copyrights: list[Copyright]
|
||||||
|
description: str
|
||||||
|
html_description: str
|
||||||
|
explicit: bool
|
||||||
|
external_ids: ExternalIDs | None = None
|
||||||
|
href: HttpUrl
|
||||||
|
id: str
|
||||||
|
images: list[Image]
|
||||||
|
is_externally_hosted: bool
|
||||||
|
languages: list[str]
|
||||||
|
media_type: str
|
||||||
|
name: str
|
||||||
|
type: Literal['show']
|
||||||
|
uri: str
|
||||||
|
total_episodes: int
|
||||||
|
|
||||||
|
|
||||||
|
class Episode(BaseModel):
|
||||||
|
description: str
|
||||||
|
html_description: str
|
||||||
|
duration_ms: int
|
||||||
|
explicit: bool
|
||||||
external_urls: ExternalUrls
|
external_urls: ExternalUrls
|
||||||
href: HttpUrl
|
href: HttpUrl
|
||||||
id: str
|
id: str
|
||||||
images: list[Image]
|
images: list[Image]
|
||||||
|
is_externally_hosted: bool
|
||||||
|
is_playable: bool
|
||||||
|
languages: list[str]
|
||||||
name: str
|
name: str
|
||||||
release_date: PrecisionedReleaseDate
|
release_date: datetime
|
||||||
release_date_precision: Literal['year', 'month', 'day']
|
release_date_precision: Literal['year', 'month', 'day']
|
||||||
restrictions: Restriction | None = None
|
resume_point: EpisodeResumePoint
|
||||||
type: Literal['album']
|
type: Literal['episode']
|
||||||
uri: str
|
uri: str
|
||||||
artists: list[Artist]
|
restrictions: Restriction | None = None
|
||||||
|
show: Show | None = None
|
||||||
|
|
||||||
class Album(BaseAlbum):
|
|
||||||
external_ids: ExternalIDs
|
|
||||||
copyrights: list[Copyright]
|
|
||||||
tracks: AlbumTracks
|
|
||||||
|
|
||||||
|
|
||||||
class Track(BaseTrack):
|
|
||||||
album: BaseAlbum
|
|
||||||
|
|
||||||
|
|
||||||
class UserSavedAlbum(BaseModel):
|
|
||||||
added_at: datetime
|
|
||||||
album: Album
|
|
||||||
|
|
||||||
|
|
||||||
class UserSavedAlbums(Paginated):
|
|
||||||
items: list[UserSavedAlbum]
|
|
||||||
|
|
||||||
|
|
||||||
class ArtistsAlbums(Paginated):
|
|
||||||
items: list[Album]
|
|
||||||
|
|
||||||
|
|
||||||
class AudioBookAuthor(BaseModel):
|
class AudioBookAuthor(BaseModel):
|
||||||
@ -214,79 +236,17 @@ class AudioBook(BaseModel):
|
|||||||
total_chapters: int
|
total_chapters: int
|
||||||
|
|
||||||
|
|
||||||
class AudioBookChapter(BaseModel):
|
class PlaybackStateActions(BaseModel):
|
||||||
chapter_number: int
|
interrupting_playback: bool | None = None
|
||||||
description: str
|
pausing: bool | None = None
|
||||||
html_description: str
|
resuming: bool | None = None
|
||||||
duration_ms: int
|
seeking: bool | None = None
|
||||||
explicit: bool
|
skipping_next: bool | None = None
|
||||||
external_urls: ExternalUrls
|
skipping_prev: bool | None = None
|
||||||
href: HttpUrl
|
toggling_repeat_context: bool | None = None
|
||||||
id: str
|
toggling_shuffle: bool | None = None
|
||||||
images: list[Image]
|
toggling_repeat_track: bool | None = None
|
||||||
is_playable: bool
|
transferring_playback: bool | None = None
|
||||||
languages: list[str]
|
|
||||||
name: str
|
|
||||||
release_date: PrecisionedReleaseDate
|
|
||||||
release_date_precision: Literal['year', 'month', 'day']
|
|
||||||
resume_point: ResumePoint
|
|
||||||
type: Literal['episode']
|
|
||||||
uri: str
|
|
||||||
restrictions: Restriction
|
|
||||||
|
|
||||||
|
|
||||||
class AudiobookChapters(BaseModel):
|
|
||||||
items: list[AudioBookChapter]
|
|
||||||
|
|
||||||
|
|
||||||
class UserSavedAudiobooks(Paginated):
|
|
||||||
items: list[AudioBook]
|
|
||||||
|
|
||||||
|
|
||||||
class Chapter(AudioBookChapter):
|
|
||||||
audiobook: AudioBook
|
|
||||||
|
|
||||||
|
|
||||||
class Show(BaseModel):
|
|
||||||
copyrights: list[Copyright]
|
|
||||||
description: str
|
|
||||||
html_description: str
|
|
||||||
explicit: bool
|
|
||||||
external_ids: ExternalIDs | None = None
|
|
||||||
href: HttpUrl
|
|
||||||
id: str
|
|
||||||
images: list[Image]
|
|
||||||
is_externally_hosted: bool
|
|
||||||
languages: list[str]
|
|
||||||
media_type: str
|
|
||||||
name: str
|
|
||||||
type: Literal['show']
|
|
||||||
uri: str
|
|
||||||
total_episodes: int
|
|
||||||
|
|
||||||
|
|
||||||
class Episode(AudioBookChapter):
|
|
||||||
show: Show
|
|
||||||
|
|
||||||
|
|
||||||
class UsedSavedEpisode(BaseModel):
|
|
||||||
added_at: datetime
|
|
||||||
episode: Episode
|
|
||||||
|
|
||||||
|
|
||||||
class UserSavedEpisodes(Paginated):
|
|
||||||
items: list[UsedSavedEpisode]
|
|
||||||
|
|
||||||
|
|
||||||
class CheckUserSavedItems(RootModel[list[bool]]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Context(BaseModel):
|
|
||||||
type: Literal['artist', 'playlist', 'album', 'show', 'collection']
|
|
||||||
href: HttpUrl
|
|
||||||
external_urls: ExternalUrls
|
|
||||||
uri: str
|
|
||||||
|
|
||||||
|
|
||||||
class PlaybackState(BaseModel):
|
class PlaybackState(BaseModel):
|
||||||
@ -299,94 +259,35 @@ class PlaybackState(BaseModel):
|
|||||||
is_playing: bool
|
is_playing: bool
|
||||||
item: Track | Episode | None
|
item: Track | Episode | None
|
||||||
currently_playing_type: Literal['track', 'episode', 'ad', 'unknown']
|
currently_playing_type: Literal['track', 'episode', 'ad', 'unknown']
|
||||||
|
# TODO: allows has 'disallows' inside? not sure about that
|
||||||
|
# actions: PlaybackStateActions
|
||||||
|
|
||||||
|
|
||||||
class Devices(BaseModel):
|
class PlayHistoryObject(BaseModel):
|
||||||
devices: list[Device]
|
|
||||||
|
|
||||||
|
|
||||||
class RecentlyPlayedTrack(BaseModel):
|
|
||||||
track: Track
|
track: Track
|
||||||
played_at: datetime
|
played_at: datetime
|
||||||
context: Context | None = None
|
context: Context | None = None
|
||||||
|
|
||||||
|
|
||||||
class RecentlyPlayedTracks(Paginated):
|
class RecentlyPlayed(BaseModel):
|
||||||
cursors: Cursors
|
|
||||||
items: list[RecentlyPlayedTrack]
|
|
||||||
|
|
||||||
|
|
||||||
class UsersQueue(BaseModel):
|
|
||||||
currently_playing: Track | Episode | None
|
|
||||||
queue: list[Track | Episode]
|
|
||||||
|
|
||||||
|
|
||||||
class PlaylistOwner(BaseModel):
|
|
||||||
external_urls: ExternalUrls
|
|
||||||
href: HttpUrl
|
href: HttpUrl
|
||||||
id: str
|
limit: int
|
||||||
type: Literal['user']
|
next: HttpUrl | None
|
||||||
uri: str
|
cursors: Cursor
|
||||||
display_name: str | None
|
total: int | None = None
|
||||||
|
items: list[PlayHistoryObject]
|
||||||
|
|
||||||
|
|
||||||
class PlaylistTrack(BaseModel):
|
class SearchData(BaseModel):
|
||||||
added_at: datetime
|
|
||||||
added_by: PlaylistOwner
|
|
||||||
is_local: bool
|
|
||||||
item: Track | Episode
|
|
||||||
|
|
||||||
|
|
||||||
class PlaylistTracks(Paginated):
|
|
||||||
href: HttpUrl
|
href: HttpUrl
|
||||||
item: PlaylistTrack
|
limit: int
|
||||||
|
next: HttpUrl | None
|
||||||
|
offset: int
|
||||||
|
previous: int | None
|
||||||
|
total: int
|
||||||
|
|
||||||
|
|
||||||
class SimplifiedPlaylistTracks(Paginated):
|
class SearchAlbumData(SearchData):
|
||||||
href: HttpUrl
|
|
||||||
total: int = 0
|
|
||||||
|
|
||||||
|
|
||||||
class Playlist(BaseModel):
|
|
||||||
type: Literal['playlist']
|
|
||||||
uri: str
|
|
||||||
collaborative: bool
|
|
||||||
description: str
|
|
||||||
external_urls: ExternalUrls
|
|
||||||
href: HttpUrl
|
|
||||||
id: str
|
|
||||||
images: list[Image]
|
|
||||||
name: str
|
|
||||||
owner: PlaylistOwner
|
|
||||||
public: bool
|
|
||||||
snapshot_id: str
|
|
||||||
items: PlaylistTracks | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class SimplifiedPlaylist(BaseModel):
|
|
||||||
type: Literal['playlist']
|
|
||||||
uri: str
|
|
||||||
collaborative: bool
|
|
||||||
external_urls: ExternalUrls
|
|
||||||
href: HttpUrl
|
|
||||||
id: str
|
|
||||||
images: list[Image]
|
|
||||||
name: str
|
|
||||||
owner: PlaylistOwner
|
|
||||||
public: bool
|
|
||||||
snapshot_id: str
|
|
||||||
items: SimplifiedPlaylistTracks
|
|
||||||
|
|
||||||
|
|
||||||
class UserPlaylists(Paginated):
|
|
||||||
items: list[Playlist]
|
|
||||||
|
|
||||||
|
|
||||||
class PlaylistImages(RootModel[list[Image]]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SearchAlbumData(Paginated):
|
|
||||||
items: list[Album]
|
items: list[Album]
|
||||||
|
|
||||||
|
|
||||||
@ -394,7 +295,7 @@ class SearchAlbum(BaseModel):
|
|||||||
albums: SearchAlbumData
|
albums: SearchAlbumData
|
||||||
|
|
||||||
|
|
||||||
class SearchArtistData(Paginated):
|
class SearchArtistData(SearchData):
|
||||||
items: list[Artist]
|
items: list[Artist]
|
||||||
|
|
||||||
|
|
||||||
@ -402,7 +303,7 @@ class SearchArtist(BaseModel):
|
|||||||
artists: SearchArtistData
|
artists: SearchArtistData
|
||||||
|
|
||||||
|
|
||||||
class SearchPlaylistData(Paginated):
|
class SearchPlaylistData(SearchData):
|
||||||
items: list[Playlist | None]
|
items: list[Playlist | None]
|
||||||
|
|
||||||
|
|
||||||
@ -410,7 +311,7 @@ class SearchPlaylist(BaseModel):
|
|||||||
playlists: SearchPlaylistData
|
playlists: SearchPlaylistData
|
||||||
|
|
||||||
|
|
||||||
class SearchTrackData(Paginated):
|
class SearchTrackData(SearchData):
|
||||||
items: list[Track]
|
items: list[Track]
|
||||||
|
|
||||||
|
|
||||||
@ -418,7 +319,7 @@ class SearchTrack(BaseModel):
|
|||||||
tracks: SearchTrackData
|
tracks: SearchTrackData
|
||||||
|
|
||||||
|
|
||||||
class SearchShowData(Paginated):
|
class SearchShowData(SearchData):
|
||||||
items: list[Show]
|
items: list[Show]
|
||||||
|
|
||||||
|
|
||||||
@ -426,7 +327,7 @@ class SearchShow(BaseModel):
|
|||||||
shows: SearchShowData
|
shows: SearchShowData
|
||||||
|
|
||||||
|
|
||||||
class SearchEpisodeData(Paginated):
|
class SearchEpisodeData(SearchData):
|
||||||
items: list[Episode]
|
items: list[Episode]
|
||||||
|
|
||||||
|
|
||||||
@ -434,7 +335,7 @@ class SearchEpisode(BaseModel):
|
|||||||
episodes: SearchEpisodeData
|
episodes: SearchEpisodeData
|
||||||
|
|
||||||
|
|
||||||
class SearchAudiobookData(Paginated):
|
class SearchAudiobookData(SearchData):
|
||||||
items: list[AudioBook]
|
items: list[AudioBook]
|
||||||
|
|
||||||
|
|
||||||
@ -442,28 +343,6 @@ class SearchAudiobook(BaseModel):
|
|||||||
audiobooks: SearchAudiobookData
|
audiobooks: SearchAudiobookData
|
||||||
|
|
||||||
|
|
||||||
class ShowEpisodes(Paginated):
|
|
||||||
items: list[AudioBookChapter]
|
|
||||||
|
|
||||||
|
|
||||||
class SavedShow(BaseModel):
|
|
||||||
added_at: datetime
|
|
||||||
show: Show
|
|
||||||
|
|
||||||
|
|
||||||
class SavedShows(Paginated):
|
|
||||||
items: list[SavedShow]
|
|
||||||
|
|
||||||
|
|
||||||
class SavedTrack(BaseModel):
|
|
||||||
added_at: datetime
|
|
||||||
track: Track
|
|
||||||
|
|
||||||
|
|
||||||
class SavedTracks(Paginated):
|
|
||||||
items: list[SavedTrack]
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfile(BaseModel):
|
class UserProfile(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
display_name: str
|
display_name: str
|
||||||
@ -472,19 +351,3 @@ class UserProfile(BaseModel):
|
|||||||
images: list[Image]
|
images: list[Image]
|
||||||
type: Literal['user']
|
type: Literal['user']
|
||||||
uri: str
|
uri: str
|
||||||
|
|
||||||
|
|
||||||
class UsersTopItemsTracks(Paginated):
|
|
||||||
items: list[Track]
|
|
||||||
|
|
||||||
|
|
||||||
class UsersTopItemsArtists(Paginated):
|
|
||||||
items: list[Artist]
|
|
||||||
|
|
||||||
|
|
||||||
class FollowedArtistsData(Paginated):
|
|
||||||
items: list[Artist]
|
|
||||||
|
|
||||||
|
|
||||||
class FollowedArtists(BaseModel):
|
|
||||||
artists: FollowedArtistsData
|
|
||||||
|
|||||||
Reference in New Issue
Block a user