diff --git a/plex_pin_auth.py b/plex_pin_auth.py new file mode 100644 index 00000000..cd52b96a --- /dev/null +++ b/plex_pin_auth.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +""" +Test script to authenticate with Plex using PIN-based login. +This mimics the app's /api/plex/pin/start and /api/plex/pin/status flows. +""" + +import sys +import time +import traceback +from plexapi.myplex import MyPlexAccount, MyPlexPinLogin + + +def main(): + print('Starting Plex PIN login flow...') + try: + pinlogin = MyPlexPinLogin(oauth=False) + except Exception as exc: + print(f'Failed to create MyPlexPinLogin: {exc}', file=sys.stderr) + traceback.print_exc() + return 1 + + pin = getattr(pinlogin, 'pin', None) + if not pin: + print('Failed to obtain PIN from Plex', file=sys.stderr) + return 1 + + print(f'\nPlease go to the following URL in your browser and enter the PIN: {pin}') + print(f' https://plex.tv/link') + print('Then return here and wait for authentication to complete.') + + while True: + if getattr(pinlogin, 'expired', False): + print('\nPIN has expired. Please rerun this script to start again.', file=sys.stderr) + return 2 + + try: + if pinlogin.checkLogin(): + token = getattr(pinlogin, 'token', None) + if not token: + print('Login succeeded but no token was returned.', file=sys.stderr) + return 3 + + print('\nLogin complete!') + print(f'Plex token: {token}') + + try: + account = MyPlexAccount(token=token) + except Exception as exc: + print(f'Failed to create MyPlexAccount: {exc}', file=sys.stderr) + traceback.print_exc() + return 4 + + print(f'Authenticated Plex username: {getattr(account, "username", None)}') + print(f'Authenticated Plex title: {getattr(account, "title", None)}') + + print('\nFetching available Plex resources...') + try: + resources = account.resources() + except Exception as exc: + print(f'Failed to fetch resources: {exc}', file=sys.stderr) + traceback.print_exc() + return 5 + + server_resources = [r for r in resources if 'server' in (getattr(r, 'provides', '') or '')] + if server_resources: + print(f'Found {len(server_resources)} Plex server resource(s):') + for idx, resource in enumerate(server_resources, 1): + print(f'[{idx}] {getattr(resource, "name", None)} ({getattr(resource, "product", None)})') + connections = getattr(resource, 'connections', []) or [] + for conn in connections: + uri = getattr(conn, 'uri', None) + local = getattr(conn, 'local', False) + relay = getattr(conn, 'relay', False) + print(f' - {uri} {'(LOCAL)' if local else ''}{' (RELAY)' if relay else ''}') + + local_conn = None + for resource in server_resources: + for conn in getattr(resource, 'connections', []) or []: + if getattr(conn, 'local', False): + local_conn = conn + break + if local_conn: + break + + if local_conn: + print(f'\nSelected local server URI: {getattr(local_conn, "uri", None)}') + else: + print('\nNo local server connection found. Use one of the above URIs manually.') + else: + print('No Plex server resources were found for this account.') + + return 0 + + print('PIN not yet authorized. Polling again in 5 seconds...') + except Exception as exc: + print(f'Error checking PIN login status: {exc}', file=sys.stderr) + traceback.print_exc() + return 6 + + time.sleep(5) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/webui/index.html b/webui/index.html index f1c63959..6c5a279b 100644 --- a/webui/index.html +++ b/webui/index.html @@ -4318,26 +4318,35 @@
-
- - +
+
+ + +
+
+ + +
+ +
+ + + +
-
- - -
- -
- - +
+
+ + +
diff --git a/webui/static/script.js b/webui/static/script.js index 8b7cdc96..4035b1b4 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -6026,6 +6026,15 @@ async function loadSettingsData() { // Populate Plex settings document.getElementById('plex-url').value = settings.plex?.base_url || ''; document.getElementById('plex-token').value = settings.plex?.token || ''; + const hasPlexConfig = Boolean(settings.plex?.base_url || settings.plex?.token); + const plexViewConfigButton = document.getElementById('plex-view-config-button'); + const plexConfigureButton = document.getElementById('plex-configure-button'); + if (plexViewConfigButton) { + plexViewConfigButton.style.display = hasPlexConfig ? '' : 'none'; + } + if (plexConfigureButton) { + plexConfigureButton.style.display = hasPlexConfig ? 'none' : ''; + } // Populate Jellyfin settings document.getElementById('jellyfin-url').value = settings.jellyfin?.base_url || ''; @@ -6416,6 +6425,36 @@ function updateMediaServerFields() { } } +function showPlexConfiguration() { + const plexConfig = document.getElementById('plex-configuration'); + const plexSetup = document.getElementById('plex-setup'); + if (plexConfig) plexConfig.style.display = ''; + if (plexSetup) plexSetup.style.display = 'none'; +} + +function clearPlexConfiguration() { + const plexUrl = document.getElementById('plex-url'); + const plexToken = document.getElementById('plex-token'); + const plexConfig = document.getElementById('plex-configuration'); + const plexSetup = document.getElementById('plex-setup'); + const plexViewConfigButton = document.getElementById('plex-view-config-button'); + const plexConfigureButton = document.getElementById('plex-configure-button'); + + if (plexUrl) plexUrl.value = ''; + if (plexToken) plexToken.value = ''; + if (plexConfig) plexConfig.style.display = 'none'; + if (plexSetup) plexSetup.style.display = ''; + if (plexViewConfigButton) plexViewConfigButton.style.display = 'none'; + if (plexConfigureButton) plexConfigureButton.style.display = ''; + + if (typeof saveSettings === 'function') { + saveSettings(true); + } + if (typeof showToast === 'function') { + showToast('Plex configuration cleared', 'success'); + } +} + function toggleServer(serverType) { // Update toggle buttons document.getElementById('plex-toggle').classList.remove('active'); @@ -6430,6 +6469,12 @@ function toggleServer(serverType) { document.getElementById('navidrome-container').classList.toggle('hidden', serverType !== 'navidrome'); document.getElementById('soulsync-container')?.classList.toggle('hidden', serverType !== 'soulsync'); + // Show Plex setup when Plex is selected; otherwise hide both Plex panels + const plexConfig = document.getElementById('plex-configuration'); + const plexSetup = document.getElementById('plex-setup'); + if (plexConfig) plexConfig.style.display = serverType === 'plex' ? 'none' : ''; + if (plexSetup) plexSetup.style.display = serverType === 'plex' ? '' : 'none'; + // Load Plex music libraries when switching to Plex if (serverType === 'plex') { loadPlexMusicLibraries();