248 lines
7.1 KiB
Python
248 lines
7.1 KiB
Python
"""Pydantic models for Sonarr webhook integration"""
|
|
from pydantic import BaseModel, Field as PydanticField, validator
|
|
from typing import Optional, Dict, Any, List
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from sqlmodel import SQLModel, Field
|
|
import uuid
|
|
|
|
|
|
class SonarrEventType(str, Enum):
|
|
"""Sonarr event types"""
|
|
GRAB = "Grab"
|
|
DOWNLOAD = "Download"
|
|
MOVIE_DELETE = "MovieDelete"
|
|
MOVIE_FILE_DELETE = "MovieFileDelete"
|
|
RENAME = "Rename"
|
|
DELETE = "Delete"
|
|
TEST = "Test"
|
|
|
|
|
|
class SonarrQuality(BaseModel):
|
|
"""Quality information from Sonarr"""
|
|
quality: Dict[str, Any]
|
|
revision: Dict[str, Any]
|
|
|
|
|
|
class SonarrRelease(BaseModel):
|
|
"""Release information from Sonarr"""
|
|
indexer: str
|
|
releaseTitle: str
|
|
quality: SonarrQuality
|
|
|
|
|
|
class SonarrEpisodeFile(BaseModel):
|
|
"""Episode file information"""
|
|
id: int
|
|
seriesId: int
|
|
seasonNumber: int
|
|
episodeNumber: int
|
|
relativePath: str
|
|
path: str
|
|
size: int
|
|
dateAdded: datetime
|
|
quality: SonarrQuality
|
|
mediaInfo: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
class SonarrSeries(BaseModel):
|
|
"""Series information from Sonarr"""
|
|
tvdbId: int = PydanticField(..., alias="tvdbId")
|
|
title: str
|
|
sortTitle: str
|
|
status: str
|
|
ended: bool
|
|
overview: str
|
|
network: str
|
|
airTime: str
|
|
images: List[Dict[str, Any]]
|
|
seasons: List[int]
|
|
year: int
|
|
path: str
|
|
qualityProfileId: int
|
|
languageProfileId: int
|
|
seasonFolder: bool
|
|
monitored: bool
|
|
useSceneNumbering: bool
|
|
runtime: int
|
|
tvRageId: Optional[int] = None
|
|
tvMazeId: Optional[int] = None
|
|
firstAired: Optional[datetime] = None
|
|
seriesType: str = "standard"
|
|
cleanTitle: str
|
|
imdbId: str
|
|
titleSlug: str
|
|
certification: str
|
|
genres: List[str]
|
|
tags: List[int]
|
|
added: datetime
|
|
ratings: Dict[str, Any]
|
|
id: int
|
|
|
|
class Config:
|
|
populate_by_name = True
|
|
|
|
|
|
class SonarrEpisode(BaseModel):
|
|
"""Episode information from Sonarr"""
|
|
seriesId: int
|
|
episodeFileId: int
|
|
seasonNumber: int
|
|
episodeNumber: int
|
|
title: str
|
|
airDate: str
|
|
airDateUtc: datetime
|
|
overview: str
|
|
hasFile: bool
|
|
monitored: bool
|
|
absoluteEpisodeNumber: Optional[int] = None
|
|
unverifiedSceneNumbering: bool = False
|
|
id: int
|
|
|
|
|
|
class SonarrWebhookPayload(BaseModel):
|
|
"""Main Sonarr webhook payload"""
|
|
eventType: SonarrEventType
|
|
instanceName: str
|
|
applicationUrl: str
|
|
series: Optional[SonarrSeries] = None
|
|
episodes: Optional[List[SonarrEpisode]] = None
|
|
release: Optional[SonarrRelease] = None
|
|
episodeFile: Optional[SonarrEpisodeFile] = None
|
|
deletedFiles: Optional[List[str]] = None
|
|
deleteEpisodeFiles: bool = False
|
|
|
|
@validator('episodes')
|
|
def validate_episodes(cls, v, values):
|
|
"""Ensure episodes are present for relevant event types"""
|
|
event_type = values.get('eventType')
|
|
if event_type in [SonarrEventType.GRAB, SonarrEventType.DOWNLOAD, SonarrEventType.RENAME]:
|
|
if not v or len(v) == 0:
|
|
raise ValueError(f"Event type {event_type} requires episodes")
|
|
return v
|
|
|
|
@validator('series')
|
|
def validate_series(cls, v, values):
|
|
"""Ensure series is present for relevant event types"""
|
|
event_type = values.get('eventType')
|
|
if event_type in [SonarrEventType.GRAB, SonarrEventType.DOWNLOAD, SonarrEventType.RENAME, SonarrEventType.DELETE]:
|
|
if not v:
|
|
raise ValueError(f"Event type {event_type} requires series")
|
|
return v
|
|
|
|
|
|
class SonarrMappingBase(SQLModel):
|
|
sonarr_series_id: int = Field(index=True, unique=True)
|
|
sonarr_title: str
|
|
anime_provider: str
|
|
anime_url: str
|
|
anime_title: str
|
|
lang: str = Field(default="vostfr")
|
|
quality_preference: Optional[str] = None
|
|
auto_download: bool = Field(default=True)
|
|
created_at: datetime = Field(default_factory=datetime.now)
|
|
updated_at: datetime = Field(default_factory=datetime.now)
|
|
|
|
|
|
class SonarrMappingTable(SonarrMappingBase, table=True):
|
|
"""Database table for Sonarr mappings"""
|
|
__tablename__ = "sonarr_mappings"
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
primary_key=True,
|
|
index=True,
|
|
nullable=False
|
|
)
|
|
user_id: str = Field(foreign_key="users.id", index=True, default="default")
|
|
|
|
|
|
class SonarrMapping(BaseModel):
|
|
"""Mapping between Sonarr series and anime providers (API model)"""
|
|
sonarr_series_id: int
|
|
sonarr_title: str
|
|
anime_provider: str # 'anime-sama', 'neko-sama', etc.
|
|
anime_url: str
|
|
anime_title: str
|
|
lang: str = "vostfr"
|
|
quality_preference: Optional[str] = None # '1080p', '720p', etc.
|
|
auto_download: bool = True
|
|
created_at: datetime = PydanticField(default_factory=datetime.now)
|
|
updated_at: datetime = PydanticField(default_factory=datetime.now)
|
|
|
|
class Config:
|
|
json_encoders = {
|
|
datetime: lambda v: v.isoformat()
|
|
}
|
|
|
|
|
|
class SonarrConfigBase(SQLModel):
|
|
webhook_enabled: bool = Field(default=False)
|
|
webhook_secret: Optional[str] = None
|
|
auto_download_enabled: bool = Field(default=True)
|
|
default_language: str = Field(default="vostfr")
|
|
default_quality: Optional[str] = None
|
|
default_provider: str = Field(default="anime-sama")
|
|
verify_hmac: bool = Field(default=False)
|
|
log_webhooks: bool = Field(default=True)
|
|
|
|
|
|
class SonarrConfigTable(SonarrConfigBase, table=True):
|
|
"""Database table for Sonarr configuration (singleton)"""
|
|
__tablename__ = "sonarr_config"
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
primary_key=True,
|
|
index=True,
|
|
nullable=False
|
|
)
|
|
|
|
|
|
class SonarrConfig(BaseModel):
|
|
"""Sonarr webhook configuration (API Model)"""
|
|
webhook_enabled: bool = False
|
|
webhook_secret: Optional[str] = None # HMAC SHA256 secret
|
|
auto_download_enabled: bool = True
|
|
default_language: str = "vostfr"
|
|
default_quality: Optional[str] = None
|
|
default_provider: str = "anime-sama"
|
|
verify_hmac: bool = False
|
|
log_webhooks: bool = True
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"webhook_enabled": True,
|
|
"webhook_secret": "your-secret-key-here",
|
|
"auto_download_enabled": True,
|
|
"default_language": "vostfr",
|
|
"default_quality": "1080p",
|
|
"default_provider": "anime-sama",
|
|
"verify_hmac": True,
|
|
"log_webhooks": True
|
|
}
|
|
}
|
|
|
|
|
|
class SonarrDownloadRequest(BaseModel):
|
|
"""Request to download anime based on Sonarr event"""
|
|
sonarr_series_id: int
|
|
sonarr_title: str
|
|
season_number: int
|
|
episode_number: int
|
|
quality: Optional[str] = None
|
|
lang: str = "vostfr"
|
|
provider: str = "anime-sama"
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"sonarr_series_id": 123,
|
|
"sonarr_title": "Naruto Shippuden",
|
|
"season_number": 1,
|
|
"episode_number": 1,
|
|
"quality": "1080p",
|
|
"lang": "vostfr",
|
|
"provider": "anime-sama"
|
|
}
|
|
}
|