88 from mov_cli .http_client import HTTPClient
99 from mov_cli .scraper import ScraperOptionsT
1010
11- from mov_cli import Single , Multi , Metadata
11+ from dataclasses import dataclass , field
12+
13+ from mov_cli import Single , Multi , Metadata , MetadataType
1214
1315from mov_cli .scraper import Scraper
1416from mov_cli .utils import EpisodeSelector
1517
16- __all__ = ("PlexScraper" , )
18+ from plexapi .myplex import MyPlexAccount
19+ from plexapi .server import PlexServer
20+ from plexapi .exceptions import TwoFactorRequired
21+ from plexapi .video import Movie , Show
22+
23+ __all__ = ("PlexScraper" , "PlexMetadata" ,)
24+
25+ @dataclass
26+ class PlexMetadata (Metadata ):
27+ id : int
28+ video : Movie | Show = field (default = None )
1729
1830class PlexScraper (Scraper ):
1931 def __init__ (self , config : Config , http_client : HTTPClient , options : Optional [ScraperOptionsT ] = None ) -> None :
20- self .base_url = ...
32+ env_config = config .get_env_config ()
33+
34+ self .base_url = env_config ("PLEX_SERVER_ID" , default = None , cast = str )
35+ self .username = env_config ("PLEX_USERNAME" , default = None , cast = str )
36+ self .password = env_config ("PLEX_PASSWORD" , default = None , cast = str )
37+
38+ self .plex = self .__auth ()
2139
2240 super ().__init__ (config , http_client , options )
2341
24- def search (self , query : str , limit : int = 20 ) -> Generator [Metadata , Any , None ]:
25- ...
2642
27- def scrape_metadata_episodes (self , metadata : Metadata ) -> Dict [int , int ] | Dict [None , int ]:
28- ...
29-
30- def scrape (self , metadata : Metadata , episode : EpisodeSelector ) -> Single | Multi :
31- ...
43+ def search (self , query : str , limit : int = 20 ) -> Generator [PlexMetadata , Any , None ]:
44+ videos = self .plex .search (query , limit = limit )
45+
46+ for _ , video in enumerate (videos ):
47+ if video .TYPE in ["movie" , "show" ]:
48+ yield PlexMetadata (
49+ id = _ ,
50+ title = video .title ,
51+ type = MetadataType .SINGLE if "movie" == video .TYPE else MetadataType .MULTI ,
52+ year = video .year ,
53+ video = video
54+ )
55+
56+ def scrape_episodes (self , metadata : PlexMetadata ) -> Dict [int , int ] | Dict [None , int ]:
57+ scraped_episodes = {}
58+
59+ seasons = metadata .video .seasons ()
60+
61+ for season in seasons :
62+ scraped_episodes [season .seasonNumber ] = len (season .episodes ())
63+
64+ return scraped_episodes
65+
66+ def scrape (self , metadata : PlexMetadata , episode : EpisodeSelector ) -> Single | Multi :
67+ if metadata .type == MetadataType .MULTI :
68+ epi = metadata .video .episode (season = episode .season , episode = episode .episode )
69+
70+ return Multi (
71+ url = self .__make_url (epi ),
72+ title = metadata .title ,
73+ episode = episode
74+ )
75+
76+ return Single (
77+ url = self .__make_url (metadata .video ),
78+ title = metadata .title ,
79+ year = metadata .year
80+ )
81+
82+ def __make_url (self , item ) -> str :
83+ key = next (item .iterParts ()).key
84+
85+ return item ._server .url (f"{ key } ?download=1&X-Plex-Token={ self .plex .account ().authToken } " )
86+
87+ def __auth (self ) -> PlexServer :
88+ try :
89+ account = MyPlexAccount (self .username , self .password )
90+ except TwoFactorRequired :
91+ code = self .options .get ("2FA" )
92+ account = MyPlexAccount (self .username , self .password , code = code )
93+
94+ plex = account .resource (self .base_url ).connect ()
95+
96+ return plex
0 commit comments