fuzeman /
Plex-Trakt-Scrobbler
| 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
Loading history...
|
|||
| 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 |