"""Models for Overseerr.""" from __future__ import annotations from dataclasses import dataclass, field from datetime import date, datetime # noqa: TC003 from enum import IntEnum, IntFlag, StrEnum from typing import Annotated from mashumaro import field_options from mashumaro.mixins.orjson import DataClassORJSONMixin from mashumaro.types import Discriminator @dataclass class RequestCount(DataClassORJSONMixin): """Request count model.""" total: int movie: int tv: int pending: int approved: int declined: int processing: int available: int @dataclass class IssueCount(DataClassORJSONMixin): """Issue count model.""" total: int video: int audio: int subtitles: int others: int open: int closed: int @dataclass class Status(DataClassORJSONMixin): """Status model.""" version: str update_available: bool = field(metadata=field_options(alias="updateAvailable")) commits_behind: int = field(metadata=field_options(alias="commitsBehind")) restart_required: bool = field(metadata=field_options(alias="restartRequired")) class MediaStatus(IntEnum): """Media status enum.""" UNKNOWN = 1 PENDING = 2 PROCESSING = 3 PARTIALLY_AVAILABLE = 4 AVAILABLE = 5 @dataclass class MediaInfo(DataClassORJSONMixin): """Media info model.""" id: int tmdb_id: int | None = field(metadata=field_options(alias="tmdbId")) tvdb_id: int | None = field(metadata=field_options(alias="tvdbId")) imdb_id: str | None = field(metadata=field_options(alias="imdbId")) media_type: MediaType = field(metadata=field_options(alias="mediaType")) status: MediaStatus created_at: datetime = field(metadata=field_options(alias="createdAt")) updated_at: datetime = field(metadata=field_options(alias="updatedAt")) class ResultMediaType(StrEnum): """Media type enum.""" MOVIE = "movie" TV = "tv" PERSON = "person" class MediaType(StrEnum): """Media type enum.""" MOVIE = "movie" TV = "tv" @dataclass class Result(DataClassORJSONMixin): """Result model.""" id: int mediaType: ResultMediaType # noqa: N815 # pylint: disable=invalid-name media_type: ResultMediaType = field(metadata=field_options(alias="mediaType")) @dataclass class Movie(Result): """Movie result model.""" mediaType = ResultMediaType.MOVIE # noqa: N815 # pylint: disable=invalid-name original_language: str = field(metadata=field_options(alias="originalLanguage")) original_title: str = field(metadata=field_options(alias="originalTitle")) overview: str popularity: float title: str adult: bool media_info: MediaInfo | None = field( metadata=field_options(alias="mediaInfo"), default=None ) @dataclass class TV(Result): """TV result model.""" mediaType = ResultMediaType.TV # noqa: N815 # pylint: disable=invalid-name first_air_date: date = field(metadata=field_options(alias="firstAirDate")) name: str original_language: str = field(metadata=field_options(alias="originalLanguage")) original_name: str = field(metadata=field_options(alias="originalName")) overview: str popularity: float media_info: MediaInfo | None = field( metadata=field_options(alias="mediaInfo"), default=None ) @dataclass class Person(Result): """Person result model.""" mediaType = ResultMediaType.PERSON # noqa: N815 # pylint: disable=invalid-name name: str popularity: float known_for: list[Result] = field(metadata=field_options(alias="knownFor")) adult: bool @dataclass class SearchResult(DataClassORJSONMixin): """Search result model.""" results: list[ Annotated[Result, Discriminator(field="mediaType", include_subtypes=True)] ] class NotificationType(IntFlag): """Webhook notification type enum.""" REQUEST_PENDING_APPROVAL = 2 REQUEST_APPROVED = 4 REQUEST_AVAILABLE = 8 REQUEST_PROCESSING_FAILED = 16 REQUEST_DECLINED = 64 REQUEST_AUTOMATICALLY_APPROVED = 128 ISSUE_REPORTED = 256 ISSUE_COMMENTED = 512 ISSUE_RESOLVED = 1024 ISSUE_REOPENED = 2048 @dataclass class NotificationConfig(DataClassORJSONMixin): """Webhook config model.""" enabled: bool types: NotificationType @dataclass class WebhookNotificationOptions: """Webhook notification options model.""" json_payload: str = field(metadata=field_options(alias="jsonPayload")) webhook_url: str = field(metadata=field_options(alias="webhookUrl")) @dataclass class WebhookNotificationConfig(NotificationConfig): """Webhook config model.""" options: WebhookNotificationOptions @dataclass class User(DataClassORJSONMixin): """User model.""" id: int email: str avatar: str username: str | None plex_username: str | None = field(metadata=field_options(alias="plexUsername")) plex_id: int | None = field(metadata=field_options(alias="plexId")) movie_quota_limit: int | None = field( metadata=field_options(alias="movieQuotaLimit") ) movie_quota_days: int | None = field(metadata=field_options(alias="movieQuotaDays")) tv_quota_limit: int | None = field(metadata=field_options(alias="tvQuotaLimit")) tv_quota_days: int | None = field(metadata=field_options(alias="tvQuotaDays")) created_at: datetime = field(metadata=field_options(alias="createdAt")) updated_at: datetime = field(metadata=field_options(alias="updatedAt")) request_count: int = field(metadata=field_options(alias="requestCount")) display_name: str = field(metadata=field_options(alias="displayName")) permissions: int recovery_link_expiration_date: date | None = field( metadata=field_options(alias="recoveryLinkExpirationDate") ) user_type: int | None = field(metadata=field_options(alias="userType")) settings: UserSettings | None = None warnings: list[str] = field(default_factory=list) jellyfin_username: str | None = field( metadata=field_options(alias="jellyfinUsername"), default=None ) jellyfin_user_id: str | None = field( metadata=field_options(alias="jellyfinUserId"), default=None ) avatar_e_tag: str | None = field( metadata=field_options(alias="avatarETag"), default=None ) avatar_version: str | None = field( metadata=field_options(alias="avatarVersion"), default=None ) @dataclass class UserSettings(DataClassORJSONMixin): """User settings model.""" discord_id: str | None = field(metadata=field_options(alias="discordId")) id: str locale: str notification_types: NotificationTypes = field( metadata=field_options(alias="notificationTypes") ) original_language: str | None = field( metadata=field_options(alias="originalLanguage") ) pgp_key: str | None = field(metadata=field_options(alias="pgpKey")) pushbullet_access_token: str | None = field( metadata=field_options(alias="pushbulletAccessToken") ) pushover_application_token: str | None = field( metadata=field_options(alias="pushoverApplicationToken") ) pushover_sound: str | None = field(metadata=field_options(alias="pushoverSound")) pushover_user_key: str | None = field( metadata=field_options(alias="pushoverUserKey") ) telegram_chat_id: str | None = field(metadata=field_options(alias="telegramChatId")) telegram_send_silently: str | None = field( metadata=field_options(alias="telegramSendSilently") ) watchlist_sync_movies: str | None = field( metadata=field_options(alias="watchlistSyncMovies") ) watchlist_sync_tv: str | None = field( metadata=field_options(alias="watchlistSyncTv") ) discover_region: str | None = field( metadata=field_options(alias="discoverRegion"), default=None ) streaming_region: str | None = field( metadata=field_options(alias="streamingRegion"), default=None ) telegram_message_thread_id: str | None = field( metadata=field_options(alias="telegramMessageThreadId"), default=None ) @dataclass class NotificationTypes(DataClassORJSONMixin): """Notification types model.""" email: int | None = field(default=None) discord: int | None = field(default=None) pushbullet: int | None = field(default=None) pushover: int | None = field(default=None) slack: int | None = field(default=None) telegram: int | None = field(default=None) webhook: int | None = field(default=None) webpush: int | None = field(default=None) class RequestFilterStatus(StrEnum): """Request filter status enum.""" ALL = "all" APPROVED = "approved" AVAILABLE = "available" PENDING = "pending" PROCESSING = "processing" UNAVAILABLE = "unavailable" FAILED = "failed" class RequestSortStatus(StrEnum): """Request sort status enum.""" ADDED = "added" MODIFIED = "modified" class RequestStatus(IntEnum): """Request status enum.""" PENDING_APPROVAL = 1 APPROVED = 2 DECLINED = 3 COMPLETED = 5 @dataclass class Request(DataClassORJSONMixin): """Request model.""" id: int status: RequestStatus created_at: datetime = field(metadata=field_options(alias="createdAt")) updated_at: datetime = field(metadata=field_options(alias="updatedAt")) is4k: bool requested_by: User = field(metadata=field_options(alias="requestedBy")) season_count: int | None = field( metadata=field_options(alias="seasonCount"), default=None ) modified_by: User | None = field( metadata=field_options(alias="modifiedBy"), default=None ) @dataclass(kw_only=True) class RequestWithMedia(Request): """Request with media model.""" media: MediaInfo @dataclass class MediaInfoWithRequests(MediaInfo): """Media info with requests model.""" requests: list[Request] @dataclass class RequestResponse(DataClassORJSONMixin): """Request response model.""" results: list[RequestWithMedia] class IssueFilterStatus(StrEnum): """Issue filter status enum.""" ALL = "all" OPEN = "open" RESOLVED = "resolved" class IssueSortStatus(StrEnum): """Issue sort status enum.""" ADDED = "added" MODIFIED = "modified" class IssueStatus(IntEnum): """Issue status enum.""" OPEN = 1 RESOLVED = 2 class IssueType(IntEnum): """Issue status enum.""" VIDEO = 1 AUDIO = 2 SUBTITLE = 3 OTHER = 4 @dataclass class IssueComment(DataClassORJSONMixin): """Issue comment model.""" id: int message: str created_at: datetime = field(metadata=field_options(alias="createdAt")) user: User | None = None @dataclass class Issue(DataClassORJSONMixin): """Issue model.""" id: int issue_type: IssueType = field(metadata=field_options(alias="issueType")) status: IssueStatus problem_season: int = field(metadata=field_options(alias="problemSeason")) problem_episode: int = field(metadata=field_options(alias="problemEpisode")) created_at: datetime = field(metadata=field_options(alias="createdAt")) updated_at: datetime = field(metadata=field_options(alias="updatedAt")) created_by: User = field(metadata=field_options(alias="createdBy")) media: MediaInfo modified_by: User | None = field( metadata=field_options(alias="modifiedBy"), default=None ) comments: list[IssueComment] = field(default_factory=list) @dataclass class IssueResponse(DataClassORJSONMixin): """Issue response model.""" results: list[Issue] @dataclass class Genre: """Genre model.""" id: int name: str @dataclass class Keyword: """Keyword model.""" id: int name: str @dataclass class MovieDetails(DataClassORJSONMixin): """Movie details model.""" id: int adult: bool budget: int genres: list[Genre] original_language: str = field(metadata=field_options(alias="originalLanguage")) original_title: str = field(metadata=field_options(alias="originalTitle")) popularity: float release_date: date = field(metadata=field_options(alias="releaseDate")) revenue: int title: str vote_average: float = field(metadata=field_options(alias="voteAverage")) vote_count: int = field(metadata=field_options(alias="voteCount")) imdb_id: str | None = field(metadata=field_options(alias="imdbId")) overview: str runtime: int tagline: str keywords: list[Keyword] media_info: MediaInfoWithRequests | None = field( metadata=field_options(alias="mediaInfo"), default=None ) @dataclass class Season(DataClassORJSONMixin): """Season model.""" id: int name: str overview: str season_number: int = field(metadata=field_options(alias="seasonNumber")) episode_count: int = field(metadata=field_options(alias="episodeCount")) air_date: date | None = field(metadata=field_options(alias="airDate")) poster_path: str = field(metadata=field_options(alias="posterPath")) @dataclass class Episode(DataClassORJSONMixin): """Episode model.""" id: int name: str overview: str episode_number: int = field(metadata=field_options(alias="episodeNumber")) still_path: str = field(metadata=field_options(alias="stillPath")) air_date: date | None = field(metadata=field_options(alias="airDate"), default=None) @dataclass class TVDetails(DataClassORJSONMixin): """TV details model.""" id: int first_air_date: date = field(metadata=field_options(alias="firstAirDate")) genres: list[Genre] languages: list[str] last_air_date: date = field(metadata=field_options(alias="lastAirDate")) name: str number_of_episodes: int = field(metadata=field_options(alias="numberOfEpisodes")) number_of_seasons: int = field(metadata=field_options(alias="numberOfSeasons")) original_language: str = field(metadata=field_options(alias="originalLanguage")) original_name: str = field(metadata=field_options(alias="originalName")) tagline: str overview: str popularity: float seasons: list[Season] last_episode_to_air: Episode = field( metadata=field_options(alias="lastEpisodeToAir") ) keywords: list[Keyword] media_info: MediaInfoWithRequests | None = field( metadata=field_options(alias="mediaInfo"), default=None ) next_episode_to_air: Episode | None = field( metadata=field_options(alias="nextEpisodeToAir"), default=None ) @dataclass class WatchlistEntry(DataClassORJSONMixin): """Watchlist entry model.""" rating_key: str = field(metadata=field_options(alias="ratingKey")) title: str media_type: MediaType = field(metadata=field_options(alias="mediaType")) tmdb_id: int | None = field(metadata=field_options(alias="tmdbId")) @dataclass class WatchlistResponse(DataClassORJSONMixin): """Watchlist response model.""" results: list[WatchlistEntry]