1 | from core.header import Header |
||
2 | from core.helpers import get_class_name, spawn |
||
3 | from core.logger import Logger |
||
4 | |||
5 | from plugin.core.configuration import Configuration |
||
6 | from plugin.core.constants import ACTIVITY_MODE, PLUGIN_VERSION |
||
7 | from plugin.core.helpers.thread import module_start |
||
8 | from plugin.core.logger import LOG_HANDLER, LoggerManager |
||
9 | from plugin.managers.account import TraktAccountManager |
||
10 | from plugin.managers.m_trakt.credential import TraktOAuthCredentialManager |
||
11 | from plugin.models import TraktAccount |
||
12 | from plugin.modules.core.manager import ModuleManager |
||
13 | from plugin.preferences import Preferences |
||
14 | from plugin.scrobbler.core.session_prefix import SessionPrefix |
||
15 | |||
16 | from plex import Plex |
||
17 | from plex_activity import Activity |
||
18 | from plex_metadata import Metadata |
||
19 | from six.moves.urllib.parse import quote_plus, urlsplit, urlunsplit |
||
20 | from requests.packages.urllib3.util import Retry |
||
21 | from trakt import Trakt |
||
22 | import os |
||
23 | import uuid |
||
24 | |||
25 | log = Logger() |
||
26 | |||
27 | |||
28 | class Main(object): |
||
29 | modules = [] |
||
30 | |||
31 | def __init__(self): |
||
32 | Header.show(self) |
||
33 | |||
34 | # Initial configuration update |
||
35 | self.on_configuration_changed() |
||
36 | |||
37 | # Initialize clients |
||
38 | self.init_trakt() |
||
39 | self.init_plex() |
||
40 | self.init() |
||
41 | |||
42 | # Initialize modules |
||
43 | ModuleManager.initialize() |
||
44 | |||
45 | def init(self): |
||
46 | names = [] |
||
47 | |||
48 | # Initialize modules |
||
49 | for module in self.modules: |
||
50 | names.append(get_class_name(module)) |
||
51 | |||
52 | if hasattr(module, 'initialize'): |
||
53 | module.initialize() |
||
54 | |||
55 | log.info('Initialized %s modules: %s', len(names), ', '.join(names)) |
||
56 | |||
57 | @staticmethod |
||
58 | def init_plex(): |
||
59 | # Ensure client identifier has been generated |
||
60 | if not Dict['plex.client.identifier']: |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||
61 | # Generate identifier |
||
62 | Dict['plex.client.identifier'] = uuid.uuid4() |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
63 | |||
64 | # Retrieve current client identifier |
||
65 | client_id = Dict['plex.client.identifier'] |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
66 | |||
67 | if isinstance(client_id, uuid.UUID): |
||
68 | client_id = str(client_id) |
||
69 | |||
70 | # plex.py |
||
71 | Plex.configuration.defaults.authentication( |
||
72 | os.environ.get('PLEXTOKEN') |
||
73 | ) |
||
74 | |||
75 | Plex.configuration.defaults.client( |
||
76 | identifier=client_id, |
||
77 | |||
78 | product='trakt (for Plex)', |
||
79 | version=PLUGIN_VERSION |
||
80 | ) |
||
81 | |||
82 | # plex.activity.py |
||
83 | path = os.path.join(LOG_HANDLER.baseFilename, '..', '..', 'Plex Media Server.log') |
||
84 | path = os.path.abspath(path) |
||
85 | |||
86 | Activity['logging'].add_hint(path) |
||
87 | |||
88 | # plex.metadata.py |
||
89 | Metadata.configure( |
||
90 | client=Plex.client |
||
91 | ) |
||
92 | |||
93 | @classmethod |
||
94 | def init_trakt(cls): |
||
95 | config = Configuration.advanced['trakt'] |
||
96 | |||
97 | # Build timeout value |
||
98 | timeout = ( |
||
99 | config.get_float('connect_timeout', 6.05), |
||
100 | config.get_float('read_timeout', 24) |
||
101 | ) |
||
102 | |||
103 | # Client |
||
104 | Trakt.configuration.defaults.client( |
||
105 | id='c9ccd3684988a7862a8542ae0000535e0fbd2d1c0ca35583af7ea4e784650a61', |
||
106 | secret='bf00575b1ad252b514f14b2c6171fe650d474091daad5eb6fa890ef24d581f65' |
||
107 | ) |
||
108 | |||
109 | # Application |
||
110 | Trakt.configuration.defaults.app( |
||
111 | name='trakt (for Plex)', |
||
112 | version=PLUGIN_VERSION |
||
113 | ) |
||
114 | |||
115 | # Http |
||
116 | Trakt.base_url = ( |
||
117 | config.get('protocol', 'https') + '://' + |
||
118 | config.get('hostname', 'api.trakt.tv') |
||
119 | ) |
||
120 | |||
121 | Trakt.configuration.defaults.http( |
||
122 | timeout=timeout |
||
123 | ) |
||
124 | |||
125 | # Configure keep-alive |
||
126 | Trakt.http.keep_alive = config.get_boolean('keep_alive', True) |
||
127 | |||
128 | # Configure requests adapter |
||
129 | Trakt.http.adapter_kwargs = { |
||
130 | 'pool_connections': config.get_int('pool_connections', 10), |
||
131 | 'pool_maxsize': config.get_int('pool_size', 10), |
||
132 | 'max_retries': Retry( |
||
133 | total=config.get_int('connect_retries', 3), |
||
134 | read=0 |
||
135 | ) |
||
136 | } |
||
137 | |||
138 | Trakt.http.rebuild() |
||
139 | |||
140 | # Bind to events |
||
141 | Trakt.on('oauth.refresh', cls.on_trakt_refresh) |
||
142 | Trakt.on('oauth.refresh.rejected', cls.on_trakt_refresh_rejected) |
||
143 | |||
144 | log.info( |
||
145 | 'Configured trakt.py (timeout=%r, base_url=%r, keep_alive=%r, adapter_kwargs=%r)', |
||
146 | timeout, |
||
147 | Trakt.base_url, |
||
148 | Trakt.http.keep_alive, |
||
149 | Trakt.http.adapter_kwargs, |
||
150 | ) |
||
151 | |||
152 | @classmethod |
||
153 | def on_trakt_refresh(cls, username, authorization): |
||
154 | log.debug('[Trakt.tv] Token has been refreshed for %r', username) |
||
155 | |||
156 | # Retrieve trakt account matching this `authorization` |
||
157 | with Trakt.configuration.http(retry=True).oauth(token=authorization.get('access_token')): |
||
158 | settings = Trakt['users/settings'].get(validate_token=False) |
||
159 | |||
160 | if not settings: |
||
161 | log.warn('[Trakt.tv] Unable to retrieve account details for token') |
||
162 | return False |
||
163 | |||
164 | # Retrieve trakt account username from `settings` |
||
165 | s_username = settings.get('user', {}).get('username') |
||
166 | |||
167 | if not s_username: |
||
168 | log.warn('[Trakt.tv] Unable to retrieve username for token') |
||
169 | return False |
||
170 | |||
171 | if s_username != username: |
||
172 | log.warn('[Trakt.tv] Token mismatch (%r != %r)', s_username, username) |
||
173 | return False |
||
174 | |||
175 | # Find matching trakt account |
||
176 | trakt_account = (TraktAccount |
||
177 | .select() |
||
178 | .where( |
||
179 | TraktAccount.username == username |
||
180 | ) |
||
181 | ).first() |
||
182 | |||
183 | if not trakt_account: |
||
184 | log.warn('[Trakt.tv] Unable to find account with the username: %r', username) |
||
185 | return False |
||
186 | |||
187 | # Update OAuth credential |
||
188 | TraktAccountManager.update.from_dict( |
||
189 | trakt_account, { |
||
190 | 'authorization': { |
||
191 | 'oauth': authorization |
||
192 | } |
||
193 | }, |
||
194 | settings=settings |
||
195 | ) |
||
196 | |||
197 | log.info('[Trakt.tv] Token updated for %r', trakt_account) |
||
198 | return True |
||
199 | |||
200 | @classmethod |
||
201 | def on_trakt_refresh_rejected(cls, username): |
||
202 | log.debug('[Trakt.tv] Token refresh for %r has been rejected', username) |
||
203 | |||
204 | # Find matching trakt account |
||
205 | account = (TraktAccount |
||
206 | .select() |
||
207 | .where( |
||
208 | TraktAccount.username == username |
||
209 | ) |
||
210 | ).first() |
||
211 | |||
212 | if not account: |
||
213 | log.warn('[Trakt.tv] Unable to find account with the username: %r', username) |
||
214 | return False |
||
215 | |||
216 | # Delete OAuth credential |
||
217 | TraktOAuthCredentialManager.delete( |
||
218 | account=account.id |
||
219 | ) |
||
220 | |||
221 | log.info('[Trakt.tv] Token cleared for %r', account) |
||
222 | return True |
||
223 | |||
224 | def start(self): |
||
225 | # Construct main thread |
||
226 | spawn(self.run, daemon=True, thread_name='main') |
||
227 | |||
228 | def run(self): |
||
229 | # Check for authentication token |
||
230 | log.info('X-Plex-Token: %s', 'available' if os.environ.get('PLEXTOKEN') else 'unavailable') |
||
231 | |||
232 | # Process server startup state |
||
233 | self.process_server_state() |
||
234 | |||
235 | # Start new-style modules |
||
236 | module_start() |
||
237 | |||
238 | # Start modules |
||
239 | names = [] |
||
240 | |||
241 | for module in self.modules: |
||
242 | if not hasattr(module, 'start'): |
||
243 | continue |
||
244 | |||
245 | names.append(get_class_name(module)) |
||
246 | |||
247 | module.start() |
||
248 | |||
249 | log.info('Started %s modules: %s', len(names), ', '.join(names)) |
||
250 | |||
251 | ModuleManager.start() |
||
252 | |||
253 | # Start plex.activity.py |
||
254 | Activity.start(ACTIVITY_MODE.get(Preferences.get('activity.mode'))) |
||
255 | |||
256 | @classmethod |
||
257 | def process_server_state(cls): |
||
258 | # Check startup state |
||
259 | server = Plex.detail() |
||
260 | |||
261 | if server is None: |
||
262 | log.info('Unable to check startup state, detail request failed') |
||
263 | return |
||
264 | |||
265 | # Check server startup state |
||
266 | if server.start_state is None: |
||
267 | return |
||
268 | |||
269 | if server.start_state == 'startingPlugins': |
||
270 | return cls.on_starting_plugins() |
||
271 | |||
272 | log.error('Unhandled server start state %r', server.start_state) |
||
273 | |||
274 | @staticmethod |
||
275 | def on_starting_plugins(): |
||
276 | log.debug('on_starting_plugins') |
||
277 | |||
278 | SessionPrefix.increment() |
||
279 | |||
280 | @classmethod |
||
281 | def on_configuration_changed(cls): |
||
282 | # Update proxies (for requests) |
||
283 | cls.update_proxies() |
||
284 | |||
285 | # Refresh loggers |
||
286 | LoggerManager.refresh() |
||
287 | |||
288 | @staticmethod |
||
289 | def update_proxies(): |
||
290 | # Retrieve proxy host |
||
291 | host = Prefs['proxy_host'] |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
292 | |||
293 | if not host: |
||
294 | if not Trakt.http.proxies and not os.environ.get('HTTP_PROXY') and not os.environ.get('HTTPS_PROXY'): |
||
295 | return |
||
296 | |||
297 | # Update trakt client |
||
298 | Trakt.http.proxies = {} |
||
299 | |||
300 | # Update environment variables |
||
301 | if 'HTTP_PROXY' in os.environ: |
||
302 | del os.environ['HTTP_PROXY'] |
||
303 | |||
304 | if 'HTTPS_PROXY' in os.environ: |
||
305 | del os.environ['HTTPS_PROXY'] |
||
306 | |||
307 | log.info('HTTP Proxy has been disabled') |
||
308 | return |
||
309 | |||
310 | # Parse URL |
||
311 | host_parsed = urlsplit(host) |
||
312 | |||
313 | # Expand components |
||
314 | scheme, netloc, path, query, fragment = host_parsed |
||
315 | |||
316 | if not scheme: |
||
317 | scheme = 'http' |
||
318 | |||
319 | # Retrieve proxy credentials |
||
320 | username = Prefs['proxy_username'] |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
321 | password = Prefs['proxy_password'] |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
322 | |||
323 | # Build URL |
||
324 | if username and password and '@' not in netloc: |
||
325 | netloc = '%s:%s@%s' % ( |
||
326 | quote_plus(username), |
||
327 | quote_plus(password), |
||
328 | netloc |
||
329 | ) |
||
330 | |||
331 | url = urlunsplit((scheme, netloc, path, query, fragment)) |
||
332 | |||
333 | # Update trakt client |
||
334 | Trakt.http.proxies = { |
||
335 | 'http': url, |
||
336 | 'https': url |
||
337 | } |
||
338 | |||
339 | # Update environment variables |
||
340 | os.environ.update({ |
||
341 | 'HTTP_PROXY': url, |
||
342 | 'HTTPS_PROXY': url |
||
343 | }) |
||
344 | |||
345 | # Display message in log file |
||
346 | if not host_parsed.username and not host_parsed.password: |
||
347 | log.info('HTTP Proxy has been enabled (host: %r)', host) |
||
348 | else: |
||
349 | log.info('HTTP Proxy has been enabled (host: <sensitive>)') |
||
350 |