diff --git a/README.md b/README.md index 1fa512e..159f449 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,30 @@ After sucessful advanced setup the following channels will _additionally_ be cre ## Changelog -### 1.0.0 (2019-xx-xx) [MILESTONES / PLANNED FEATURES FOR v1.0.0 RELEASE] -- Remote Player Control +### 0.8.5 (2019-12-01) +- (Zefau) fixed missing user / library statistics +- (Zefau) fixed using username instead of email for statistics [#17](https://github.com/Zefau/ioBroker.plex/issues/17)) + +### 0.8.4 (2019-11-07) +- (Zefau) added support for remote player control via cloud / iot adapter +- (Zefau) added thumbnail to notifications as well as web interface of adapter +- (Zefau) fixed icons within the web interface of adapter + +### 0.8.3 (2019-11-06) +- (Zefau) fixed player controls (error when triggering `start`, `stop`, etc.) +- (Zefau) added additional states to `event` channel + +### 0.8.1 (2019-11-02) +- (Zefau) fixed error `Cannot read property 'forEach' of undefined` + +### 0.8.0 (2019-10-28) +- (Zefau) added support for Plex Notifications including customization in adapter settings +- (Zefau) added count of streams [#14](https://github.com/Zefau/ioBroker.plex/issues/14)) +- (Zefau) reworked cleaning up states when new webhook is received [#11](https://github.com/Zefau/ioBroker.plex/issues/11)) + +### 0.7.0 (2019-10-17) +- (Zefau) reworked duty cycle (clean up of outdated / old states) +- (Zefau) fixed incorrect states [#15](https://github.com/Zefau/ioBroker.plex/issues/15)) ### 0.6.0 (2019-08-19) - (Zefau) replaced password with token authentication diff --git a/admin/admin.css b/admin/admin.css index 972f63c..95e0f4c 100644 --- a/admin/admin.css +++ b/admin/admin.css @@ -2,10 +2,11 @@ * Admin styles. * * @author Zefau - * @version 0.3.4 - * @date 2019-10-06 + * @version 0.4.0 + * @date 2019-10-31 * */ +.m select {display: block !important} .hidden {display: none} .box { padding: 10px 5px 15px 5px !important; @@ -41,4 +42,12 @@ pre {display: inline} .m [type="checkbox"] + span:not(.lever) { margin-bottom: 3px; +} + +.m .row {margin-bottom: 0 !important} +.m label {color: #000} + +label.select { + top: -27px !important; + font-size: .8rem !important; } \ No newline at end of file diff --git a/admin/i18n/de/translations.json b/admin/i18n/de/translations.json index 1dd6970..b6e25d0 100644 --- a/admin/i18n/de/translations.json +++ b/admin/i18n/de/translations.json @@ -21,6 +21,9 @@ "button_closeModal": "Abbrechen", "button_getToken": "Token abrufen", "caption": "Untertitel", + "certChained": "Chained Zertifikat", + "certPrivate": "Privater Schlüssel", + "certPublic": "Öffentliches Zertifikat", "donateInformation": "Neue Funktionen / Features können gerne über Github oder das ioBroker Forum angefragt werden. Wenn dieser Adapter gefällt / nützlich ist, sind Spenden herzlich Willkommen.", "donateTitle": "Unterstützung der Entwicklung des Plex Adapters!", "dutyCycle": "Duty Cycle (in Minuten)", @@ -55,6 +58,7 @@ "notifications": "Benachrichtigungen", "notificationsInfo": "Hier können die Texte der Benachrichtigungen (je nach Medien- bzw. Benachrichtigungstyp) angepasst werden. %Platzhalter% kann genutzt werden, um spezifische Werte einzufügen.", "other": "Weitere Einstellungen", + "passphrase": "Passwort (nur wenn privater Schlüssel zusätzlich verschlüsselt ist)", "playback by shared user": "Wiedergabe eines anderen Benutzers", "plexAuth": "Die Verbindungseinstellungen der Plex Instanz", "plexAuthInfo": "Die IP-Adresse und der Port des Plex-Media-Server sowie die Anmeldeinformationen. Diese müssen nicht zwangsweise angegeben werden. Bitte die Einrichtungsanleitung lesen wie dies vermieden werden kann.", @@ -68,6 +72,7 @@ "refreshInfo": "Intervall zur Aktualisierung aller Einstellungen / Daten (in Sekunden). Wenn auf 0 gesetzt werden die Einstellungen nur beim Adapter-Start aktualisiert.", "resetMedia": "Zurücksetzen der '._playing' Objekte", "resetMediaInfo": "Zurücksetzen / löschen aller Objekte innerhalb des Objektbaums '._playing' bei Adapter-Start.", + "secureConnection": "Benutze sichere Verbindung zur Plex Media Server (https)", "tab_alexa": "Alexa Integration", "tab_config": "Konfiguration", "tab_donate": "Spenden", diff --git a/admin/i18n/en/translations.json b/admin/i18n/en/translations.json index f611ca9..832ad18 100644 --- a/admin/i18n/en/translations.json +++ b/admin/i18n/en/translations.json @@ -21,6 +21,9 @@ "button_closeModal": "Cancel", "button_getToken": "Get a Token", "caption": "Caption", + "certChained": "Chained Certificate", + "certPrivate": "Private Key", + "certPublic": "Public Certificate", "donateInformation": "Feel free to suggest new features via Github or ioBroker forum. If you like this adapter, you very welcome to donate.", "donateTitle": "Support the development of the Plex Adapter!", "dutyCycle": "Duty Cycle (in minutes)", @@ -55,6 +58,7 @@ "notifications": "Notifications", "notificationsInfo": "You may modify the notification text based on the media and / or event type. Use %placeholder% to use an actual value in your notification.", "other": "Other Settings", + "passphrase": "Password if private key is additionally encrypted", "playback by shared user": "playback by shared user", "plexAuth": "Connection settings of the Plex instance", "plexAuthInfo": "IP address and port of your Plex Media Server as well as login credentials. If you do not want to provide user and password, please read the setup guide on how to avoid providing them.", @@ -68,6 +72,7 @@ "refreshInfo": "Time for refreshing all settings / information (in seconds). If set to 0, settings will only be refreshed on startup.", "resetMedia": "Reset '._playing' states", "resetMediaInfo": "Reset / delete all states within tree '._playing' on startup.", + "secureConnection": "Use secure connection to Plex Media Server (https)", "tab_alexa": "Alexa integration", "tab_config": "Configuration", "tab_donate": "Donation", diff --git a/admin/i18n/es/translations.json b/admin/i18n/es/translations.json index 381c3ee..a767a03 100644 --- a/admin/i18n/es/translations.json +++ b/admin/i18n/es/translations.json @@ -21,6 +21,9 @@ "button_closeModal": "Cancelar", "button_getToken": "Obtener un Token de", "caption": "Título", + "certChained": "Certificado encadenado", + "certPrivate": "Llave privada", + "certPublic": "Certificado publico", "donateInformation": "Siéntase libre de sugerir nuevas características a través de Github o ioBroker foro. Si te gusta este adaptador, usted muy bienvenido a donar.", "donateTitle": "Apoyar el desarrollo de los Plex Adaptador!", "dutyCycle": "Ciclo de trabajo (en minutos)", @@ -55,6 +58,7 @@ "notifications": "Las notificaciones", "notificationsInfo": "Usted puede modificar el texto de la notificación basada en los medios de comunicación y / o tipo de evento. Uso %de marcador de posición% usar un valor real en su notificación.", "other": "Otros Ajustes", + "passphrase": "Contraseña si la clave privada está encriptada adicionalmente", "playback by shared user": "la reproducción por parte del usuario compartido", "plexAuth": "Configuración de la conexión de la instancia Plex", "plexAuthInfo": "La dirección IP y el puerto del Plex Media Server así como las credenciales de inicio de sesión. Si no quiero proporcionar el usuario y contraseña, por favor lea la guía de instalación sobre cómo evitar la prestación de ellos.", @@ -68,6 +72,7 @@ "refreshInfo": "Tiempo para la actualización de todos los ajustes / información (en segundos). Si se establece en 0, la configuración sólo se actualiza en el inicio.", "resetMedia": "Reset '._playing' estados", "resetMediaInfo": "Reset / borrar todos los estados dentro de un árbol\"._playing' en el inicio.", + "secureConnection": "Use una conexión segura a Bridge (https)", "tab_alexa": "Alexa integración", "tab_config": "Configuración", "tab_donate": "Donación", diff --git a/admin/i18n/fr/translations.json b/admin/i18n/fr/translations.json index 68ce3ab..dc7629b 100644 --- a/admin/i18n/fr/translations.json +++ b/admin/i18n/fr/translations.json @@ -21,6 +21,9 @@ "button_closeModal": "Annuler", "button_getToken": "Obtenir un Jeton", "caption": "Légende", + "certChained": "Certificat chaîné", + "certPrivate": "Clé privée", + "certPublic": "Certificat public", "donateInformation": "N'hésitez pas à suggérer de nouvelles fonctionnalités via Github ou ioBroker forum. Si vous aimez cet adaptateur, vous les bienvenus pour faire un don.", "donateTitle": "Soutenir le développement de la teinte-extension de Carte!", "dutyCycle": "Cycle de service (en minutes)", @@ -55,6 +58,7 @@ "notifications": "Les Notifications", "notificationsInfo": "Vous pouvez modifier le texte de la notification basé sur les médias et / ou le type d'événement. Utiliser %de l'espace réservé% à utiliser une valeur réelle à votre notification.", "other": "D'Autres Paramètres", + "passphrase": "Mot de passe si la clé privée est cryptée", "playback by shared user": "lecture par utilisateur partagé", "plexAuth": "Paramètres de connexion de l'Plex exemple", "plexAuthInfo": "Adresse IP et le port de votre Plex Media Server ainsi que les identifiants de connexion. Si vous ne pas vous voulez fournir à l'utilisateur et de mot de passe, veuillez lire le guide d'installation sur la façon d'éviter de les fournir.", @@ -68,6 +72,7 @@ "refreshInfo": "Temps à l'actualisation de tous les paramètres / l'information (en secondes). Si la valeur est 0, les paramètres ne seront actualisées lors du démarrage.", "resetMedia": "Reset\"._playing \" les états", "resetMediaInfo": "Reset / effacer tous les états à l'intérieur de l'arbre '._playing' au démarrage.", + "secureConnection": "Utiliser une connexion sécurisée à Bridge (https)", "tab_alexa": "Alexa intégration", "tab_config": "Configuration", "tab_donate": "Don", diff --git a/admin/i18n/it/translations.json b/admin/i18n/it/translations.json index e11680d..3304844 100644 --- a/admin/i18n/it/translations.json +++ b/admin/i18n/it/translations.json @@ -21,6 +21,9 @@ "button_closeModal": "Annulla", "button_getToken": "Ottenere un Token", "caption": "Didascalia", + "certChained": "Certificato incatenato", + "certPrivate": "Chiave privata", + "certPublic": "Certificato pubblico", "donateInformation": "Sentitevi liberi di suggerire nuove funzionalità tramite Github o ioBroker forum. Se ti piace questo adattatore, è molto benvenuto a donare.", "donateTitle": "Sostenere lo sviluppo della tonalità esteso Adattatore!", "dutyCycle": "Duty Cycle (in minuti)", @@ -55,6 +58,7 @@ "notifications": "Le notifiche", "notificationsInfo": "È possibile modificare il testo di notifica in base al supporto e / o tipo di evento. Uso %segnaposto% per l'utilizzo di un effettivo valore di notifica.", "other": "Altre Impostazioni", + "passphrase": "Password se la chiave privata è ulteriormente crittografata", "playback by shared user": "riproduzione da utente comune", "plexAuth": "Impostazioni di connessione di Plex istanza", "plexAuthInfo": "Indirizzo IP e la porta del Plex Media Server e le credenziali di accesso. Se si non vuole fornire all'utente e password, si prega di leggi la guida all'installazione su come evitare di fornire loro.", @@ -68,6 +72,7 @@ "refreshInfo": "Tempo per l'aggiornamento di tutte le impostazioni / informazioni (in secondi). Se impostato a 0, le impostazioni vengono solo aggiornati all'avvio.", "resetMedia": "Reset '._playing' stati", "resetMediaInfo": "Reset / eliminare tutti gli stati all'interno dell'albero '._playing' all'avvio.", + "secureConnection": "Usa connessione sicura a Bridge (https)", "tab_alexa": "Alexa integrazione", "tab_config": "Configurazione", "tab_donate": "Donazione", diff --git a/admin/i18n/nl/translations.json b/admin/i18n/nl/translations.json index acd7940..338bacf 100644 --- a/admin/i18n/nl/translations.json +++ b/admin/i18n/nl/translations.json @@ -21,6 +21,9 @@ "button_closeModal": "Annuleren", "button_getToken": "Krijg een Token", "caption": "Bijschrift", + "certChained": "Geketend certificaat", + "certPrivate": "Prive sleutel", + "certPublic": "Openbaar certificaat", "donateInformation": "Voel je vrij om te suggereren nieuwe functies via Github of ioBroker forum. Als je van deze adapter, die je erg welkom om te doneren.", "donateTitle": "Ondersteuning van de ontwikkeling van de Plex Adapter!", "dutyCycle": "Duty Cycle (in minuten)", @@ -55,6 +58,7 @@ "notifications": "Meldingen", "notificationsInfo": "U kan wijzigingen in de kennisgeving tekst gebaseerd op de media en / of type gebeurtenis. Gebruik %tijdelijke aanduiding% tot een werkelijke waarde in uw aanmelding.", "other": "Andere Instellingen", + "passphrase": "Wachtwoord als de privésleutel bovendien is gecodeerd", "playback by shared user": "het afspelen van gedeelde gebruiker", "plexAuth": "Instellingen voor de verbinding van de Plex bijvoorbeeld", "plexAuthInfo": "IP-adres en de poort van de Plex Media Server en inloggegevens. Als je niet wilt bieden de gebruiker en het wachtwoord, stuur een lees de installatiehandleiding over het vermijden van hen.", @@ -68,6 +72,7 @@ "refreshInfo": "Tijd voor het vernieuwen van alle instellingen / informatie (in seconden). Indien ingesteld op 0, instellingen zal alleen worden vernieuwd bij het opstarten.", "resetMedia": "Reset '._playing' staten", "resetMediaInfo": "Reset / delete alle staten in de boom '._playing' bij het opstarten.", + "secureConnection": "Gebruik beveiligde verbinding met Bridge (https)", "tab_alexa": "Alexa integratie", "tab_config": "Configuratie", "tab_donate": "Donatie", diff --git a/admin/i18n/pl/translations.json b/admin/i18n/pl/translations.json index d317c87..64f99be 100644 --- a/admin/i18n/pl/translations.json +++ b/admin/i18n/pl/translations.json @@ -21,6 +21,9 @@ "button_closeModal": "Anuluj", "button_getToken": "Zobacz znacznik", "caption": "Tytuł", + "certChained": "Chained Certificate", + "certPrivate": "Prywatny klucz", + "certPublic": "Certyfikat publiczny", "donateInformation": "Nie wahaj się zaproponować nowe funkcje poprzez GitHub lub forum ioBroker. Jeśli podoba ci się ten zasilacz, możesz podarować.", "donateTitle": "Wsparcie rozwoju Plex zasilacz!", "dutyCycle": "Cykl pracy (w minutach)", @@ -55,6 +58,7 @@ "notifications": "Powiadomienia", "notificationsInfo": "Możesz zmienić tekst powiadomienia, oparte na MEDIACH i / lub typ zdarzenia. Używać плейсхолдер%%, aby wykorzystać wartość rzeczywista w zawiadomieniu.", "other": "Inne Opcje", + "passphrase": "Hasło, jeśli klucz prywatny jest dodatkowo szyfrowany", "playback by shared user": "odtwarzanie wspólnych użytkowników", "plexAuth": "Parametry instancji plex dnia połączenia ", "plexAuthInfo": "Adres IP i port serwera Plex MEDIA, a także dane logowania. Jeśli nie chcesz zapewnić użytkownika i hasło, proszę czytaj instrukcja instalacji i o tym, jak uniknąć ich świadczenia.", @@ -68,6 +72,7 @@ "refreshInfo": "Podczas aktualizacji wszystkie ustawienia / informacje (w sekundach). Jeżeli określono wartość 0, ustawienia zostaną zaktualizowane po uruchomieniu.", "resetMedia": "Reset '.Państwa _playing' ", "resetMediaInfo": "Wyczyść / usuń wszystkie państwa w drzewo '._playing' przy starcie.", + "secureConnection": "Użyj bezpiecznego połączenia z mostem (https)", "tab_alexa": "Alexa integracji", "tab_config": "Konfiguracji", "tab_donate": "Darowizna", diff --git a/admin/i18n/pt/translations.json b/admin/i18n/pt/translations.json index 3858883..ab2705d 100644 --- a/admin/i18n/pt/translations.json +++ b/admin/i18n/pt/translations.json @@ -21,6 +21,9 @@ "button_closeModal": "Cancelar", "button_getToken": "Obter um Token", "caption": "Legenda", + "certChained": "Certificado encadeado", + "certPrivate": "Chave privada", + "certPublic": "Certificado público", "donateInformation": "Sinta-se livre para sugerir novos recursos via Github ou ioBroker fórum. Se você gosta deste adaptador, você é muito bem-vindo para doar.", "donateTitle": "Apoiar o desenvolvimento da Plex do Adaptador!", "dutyCycle": "Ciclo de trabalho (em minutos)", @@ -55,6 +58,7 @@ "notifications": "Notificações", "notificationsInfo": "Você pode modificar o texto da notificação baseada na mídia e / ou tipo de evento. Use %de marcador de posição% para usar um valor real na sua notificação.", "other": "Outras Configurações", + "passphrase": "Senha se a chave privada for criptografada adicionalmente", "playback by shared user": "reprodução de usuário compartilhada", "plexAuth": "Configurações de conexão do Plex instância", "plexAuthInfo": "Endereço IP e porta do seu Plex Media Server, bem como credenciais de login. Se você fizer não deseja fornecer usuário e senha, por favor leia o guia de configuração como evitar a proporcionar-lhes.", @@ -68,6 +72,7 @@ "refreshInfo": "Tempo para atualizar todas as configurações / informações (em segundos). Se definido como 0, as definições só serão atualizadas na inicialização.", "resetMedia": "Reset '._playing dos estados", "resetMediaInfo": "Reset / excluir todos os estados dentro de árvore\"._playing' na inicialização.", + "secureConnection": "Usar conexão segura com o Bridge (https)", "tab_alexa": "Alexa integração", "tab_config": "Configuração", "tab_donate": "Doação", diff --git a/admin/i18n/ru/translations.json b/admin/i18n/ru/translations.json index bc75c62..a5a7630 100644 --- a/admin/i18n/ru/translations.json +++ b/admin/i18n/ru/translations.json @@ -21,6 +21,9 @@ "button_closeModal": "Отменить", "button_getToken": "Получить маркер", "caption": "Заголовок", + "certChained": "Цепной сертификат", + "certPrivate": "Закрытый ключ", + "certPublic": "Публичный сертификат", "donateInformation": "Не стесняйтесь предложить новые функции через GitHub или форуме ioBroker. Если вам нравится этот адаптер, вы можете пожертвовать.", "donateTitle": "Поддержка развития Plex адаптер!", "dutyCycle": "Скважность (в минутах)", @@ -55,6 +58,7 @@ "notifications": "Уведомления", "notificationsInfo": "Вы можете изменить текст уведомления, основанные на СМИ и / или тип события. Использовать плейсхолдер%%, чтобы использовать фактическое значение в вашем уведомлении.", "other": "Другие Параметры", + "passphrase": "Пароль, если закрытый ключ дополнительно зашифрован", "playback by shared user": "воспроизведение общих пользователей", "plexAuth": "Параметры экземпляра Плекс связи ", "plexAuthInfo": "IP-адрес и порт вашего сервера Plex СМИ, а также учетные данные для входа. Если вы не вы хотите обеспечить пользователя и пароль, пожалуйста <а href='https://github.com/Zefau/ioBroker.plex#11-basic-setup'>читать руководство по установке и о том, как избежать их предоставления.", @@ -68,6 +72,7 @@ "refreshInfo": "Время обновления все настройки / информации (в секундах). Если задано значение 0, параметры будут обновляться при запуске.", "resetMedia": "Сброс '.Государства _playing' ", "resetMediaInfo": "Сбросить / удалить все государства в дерево '._playing' при запуске.", + "secureConnection": "Используйте безопасное соединение с мостом (https)", "tab_alexa": "Алекса интеграции", "tab_config": "Конфигурации", "tab_donate": "Пожертвование", diff --git a/admin/i18n/zh-cn/translations.json b/admin/i18n/zh-cn/translations.json index b5715ca..b43234e 100644 --- a/admin/i18n/zh-cn/translations.json +++ b/admin/i18n/zh-cn/translations.json @@ -21,6 +21,9 @@ "button_closeModal": "取消", "button_getToken": "得到一个令牌", "caption": "字幕", + "certChained": "连锁证书", + "certPrivate": "私钥", + "certPublic": "公开证书", "donateInformation": "随时提出新的功能,通过审查或ioBroker论坛。 如果你喜欢这个转接器,你非常受欢迎的捐赠。", "donateTitle": "支持发展的顺化扩展适配器!", "dutyCycle": "工作周期(在分钟)", @@ -55,6 +58,7 @@ "notifications": "的通知", "notificationsInfo": "你可以修改的通知文本根据媒体和/或事件的类型。 使用%符%使用实际价值在你的通知。", "other": "其他的设置", + "passphrase": "密码(如果还加密了私钥)", "playback by shared user": "播放共用户", "plexAuth": "连接设置的复杂的实例", "plexAuthInfo": "IP地址和港口的丛媒体服务器以及登录凭证。 如果你做<强>不要提供用户名和密码,请阅读安装指南关于如何避免为他们提供的。", @@ -68,6 +72,7 @@ "refreshInfo": "时间用于清爽的所有设置/信息(秒)。 如果设定为0、设置将只能刷新启动。", "resetMedia": "重置的。_playing'国", "resetMediaInfo": "重置/删除的所有国内树'._playing'的启动。", + "secureConnection": "使用安全连接到网桥(https)", "tab_alexa": "Alexa的一体化", "tab_config": "配置", "tab_donate": "捐赠", diff --git a/admin/index_m.html b/admin/index_m.html index 63f8956..8547664 100644 --- a/admin/index_m.html +++ b/admin/index_m.html @@ -32,6 +32,12 @@ if (settings.tautulliToken) $('#tautulliToken').val(decode(settings.encryptionKey, settings.tautulliToken)); + + // certificates + $('body').on('change', '#secureConnection', function() {$('.boxCertificates').toggleClass('hidden', !$(this).prop('checked'))}); + if (settings.secureConnection) + $('.boxCertificates').removeClass('hidden'); + } // save @@ -179,7 +185,7 @@

modalTitle

@@ -214,7 +220,12 @@

modalTitle

-
+
+
+
+ +
+
@@ -224,7 +235,7 @@

modalTitle

plexAuth

-
+

plexAuthInfo

@@ -235,10 +246,6 @@

modalTitle

-
- - -
@@ -247,6 +254,35 @@

modalTitle

button_getToken
+
+
+
+
+ + +
+
+ +
+
diff --git a/admin/words.js b/admin/words.js index cc813dd..56ba02d 100644 --- a/admin/words.js +++ b/admin/words.js @@ -14,6 +14,7 @@ systemDictionary = { "Stop": { "en": "Stop", "de": "Stop", "ru": "Остановка", "pt": "Parar", "nl": "Stop", "fr": "Arrêter", "it": "Stop", "es": "Parada", "pl": "Przystanek", "zh-cn": "停止"}, "System": { "en": "System", "de": "System", "ru": "Система", "pt": "Sistema", "nl": "Systeem", "fr": "Système", "it": "Sistema", "es": "Sistema de", "pl": "System", "zh-cn": "系统"}, "Watched": { "en": "Watched", "de": "Gesehen", "ru": "Смотрел", "pt": "Assisti", "nl": "Bekeken", "fr": "Regardé", "it": "Visto", "es": "Visto", "pl": "Oglądałem", "zh-cn": "看"}, + "add_notification": { "en": "Add Notification", "de": "Benachrichtigung hinzufügen", "ru": "Добавить Уведомление", "pt": "Adicionar Notificação", "nl": "Toevoegen Melding", "fr": "Ajouter Une Notification", "it": "Aggiungi Notifica", "es": "Agregar La Notificación", "pl": "Dodaj Ogłoszenie", "zh-cn": "添加通知"}, "allItems": { "de": "Alle Elemente laden", "ru": "Получить все элементы", "pt": "Recuperar todos os itens", "nl": "Het ophalen van alle items", "fr": "Récupérer tous les éléments", "it": "Recuperare tutti gli elementi", "es": "Recuperar todos los elementos", "pl": "Zobacz wszystkie elementy", "zh-cn": "检索的所有项目"}, "allItemsInfo": { "de": "Eine Liste aller Elemente aller Mediatheken laden (dies kann sich bei riesigen Mediatheken auf die Leistung auswirken).", "ru": "Получить список всех элементов в библиотеках (может повлиять на производительность на огромные библиотеки).", "pt": "Recuperar uma lista de todos os itens nas bibliotecas (pode impactar o desempenho em bibliotecas enormes).", "nl": "Het ophalen van een lijst van alle items in de bibliotheken (kan van invloed zijn op de prestaties op grote bibliotheken).", "fr": "Récupérer une liste de tous les articles dans les bibliothèques (peut influer sur les performances sur d'immenses bibliothèques).", "it": "Recuperare un elenco di tutti gli elementi di librerie (potrebbe influire sulle prestazioni su enormi librerie).", "es": "Recuperar una lista de todos los elementos de las bibliotecas (pueden afectar el rendimiento en bibliotecas grandes).", "pl": "Zobacz listę wszystkich elementów w bibliotekach (może mieć wpływ na wydajność na ogromne biblioteki).", "zh-cn": "检索的一个列表中的所有项目的库(可能影响表现在巨大的库)。"}, "any event": { "en": "any event", "de": "jedes Ereignis", "ru": "любое событие", "pt": "qualquer evento", "nl": "ieder geval", "fr": "tout cas", "it": "qualsiasi evento", "es": "cualquier evento", "pl": "każde zdarzenie", "zh-cn": "任何事件"}, @@ -22,10 +23,12 @@ systemDictionary = { "buttonNotAgain": { "en": "Don't show again", "de": "nicht wieder anzeigen", "ru": "Больше не показывать ", "pt": "Não mostrar novamente", "nl": "Don ' t show again", "fr": "Ne pas afficher de nouveau", "it": "Non mostrare più", "es": "No volver a mostrar", "pl": "Nie pokazuj więcej ", "zh-cn": "不要再显示"}, "button_closeModal": { "en": "Cancel", "de": "Abbrechen", "ru": "Отменить", "pt": "Cancelar", "nl": "Annuleren", "fr": "Annuler", "it": "Annulla", "es": "Cancelar", "pl": "Anuluj", "zh-cn": "取消"}, "button_getToken": { "en": "Get a Token", "de": "Token abrufen", "ru": "Получить маркер", "pt": "Obter um Token", "nl": "Krijg een Token", "fr": "Obtenir un Jeton", "it": "Ottenere un Token", "es": "Obtener un Token de", "pl": "Zobacz znacznik", "zh-cn": "得到一个令牌"}, + "caption": { "en": "Caption", "de": "Untertitel", "ru": "Заголовок", "pt": "Legenda", "nl": "Bijschrift", "fr": "Légende", "it": "Didascalia", "es": "Título", "pl": "Tytuł", "zh-cn": "字幕"}, "donateInformation": { "en": "Feel free to suggest new features via Github or ioBroker forum. If you like this adapter, you very welcome to donate.", "de": "Neue Funktionen / Features können gerne über Github oder das ioBroker Forum angefragt werden. Wenn dieser Adapter gefällt / nützlich ist, sind Spenden herzlich Willkommen.", "ru": "Не стесняйтесь предложить новые функции через GitHub или форуме ioBroker. Если вам нравится этот адаптер, вы можете пожертвовать.", "pt": "Sinta-se livre para sugerir novos recursos via Github ou ioBroker fórum. Se você gosta deste adaptador, você é muito bem-vindo para doar.", "nl": "Voel je vrij om te suggereren nieuwe functies via Github of ioBroker forum. Als je van deze adapter, die je erg welkom om te doneren.", "fr": "N'hésitez pas à suggérer de nouvelles fonctionnalités via Github ou ioBroker forum. Si vous aimez cet adaptateur, vous les bienvenus pour faire un don.", "it": "Sentitevi liberi di suggerire nuove funzionalità tramite Github o ioBroker forum. Se ti piace questo adattatore, è molto benvenuto a donare.", "es": "Siéntase libre de sugerir nuevas características a través de Github o ioBroker foro. Si te gusta este adaptador, usted muy bienvenido a donar.", "pl": "Nie wahaj się zaproponować nowe funkcje poprzez GitHub lub forum ioBroker. Jeśli podoba ci się ten zasilacz, możesz podarować.", "zh-cn": "随时提出新的功能,通过审查或ioBroker论坛。 如果你喜欢这个转接器,你非常受欢迎的捐赠。"}, "donateTitle": { "en": "Support the development of the Plex Adapter!", "de": "Unterstützung der Entwicklung des Plex Adapters!", "ru": "Поддержка развития Plex адаптер!", "pt": "Apoiar o desenvolvimento da Plex do Adaptador!", "nl": "Ondersteuning van de ontwikkeling van de Plex Adapter!", "fr": "Soutenir le développement de la teinte-extension de Carte!", "it": "Sostenere lo sviluppo della tonalità esteso Adattatore!", "es": "Apoyar el desarrollo de los Plex Adaptador!", "pl": "Wsparcie rozwoju Plex zasilacz!", "zh-cn": "支持发展的顺化扩展适配器!"}, "dutyCycle": { "en": "Duty Cycle (in minutes)", "de": "Duty Cycle (in Minuten)", "ru": "Скважность (в минутах)", "pt": "Ciclo de trabalho (em minutos)", "nl": "Duty Cycle (in minuten)", "fr": "Cycle de service (en minutes)", "it": "Duty Cycle (in minuti)", "es": "Ciclo de trabajo (en minutos)", "pl": "Cykl pracy (w minutach)", "zh-cn": "工作周期(在分钟)"}, "dutyCycleInfo": { "en": "Time for deleting obsolete states that have not been updated in the given time (in minutes). If set to 0, states will never be deleted.", "de": "Intervall nach dem nicht-aktualisierte States gelöscht werden (in Minuten). Wenn auf 0 gesetzt werden alte States niemals gelöscht.", "ru": "Время для удаления устаревшего государства, которые еще не были обновлены в данный момент времени (в минутах). Если установлено 0, не будут удалены.", "pt": "Tempo para eliminar obsoleto estados que não tenham sido actualizados, em um determinado período de tempo (em minutos). Se definido como 0, os estados nunca serão apagados.", "nl": "Tijd voor het verwijderen van verouderde staten die niet zijn bijgewerkt in de gegeven tijd (in minuten). Indien ingesteld op 0, staten zal nooit worden verwijderd.", "fr": "Le temps pour la suppression obsolètes états qui n'ont pas été mis à jour dans le temps (en minutes). Si la valeur est 0, les états ne sera jamais supprimé.", "it": "Tempo per l'eliminazione obsoleti con i membri che non sono stati aggiornati in un determinato periodo di tempo (in minuti). Se impostato a 0, gli stati non potrà mai essere cancellato.", "es": "Tiempo para eliminar los obsoletos a los estados que no se han actualizado en el tiempo (en minutos). Si se establece a 0, los estados nunca serán eliminados.", "pl": "Czas, aby usunąć przestarzałe państwa, które jeszcze nie zostały zaktualizowane w tej chwili czasu (w minutach). Jeśli jest ustawiona na 0, nie zostaną usunięte.", "zh-cn": "时间用于删除过时的国家没有更新,在给定的时间(在分钟)。 如果设定为0,国家将永远不会被删除。"}, + "event": { "en": "Event type", "de": "Benachrichtigungstyp", "ru": "Тип события ", "pt": "Tipo de evento", "nl": "Soort gebeurtenis", "fr": "Type d'événement", "it": "Tipo di evento", "es": "Tipo de evento", "pl": "Typ zdarzenia ", "zh-cn": "事件类型"}, "events": { "en": "Subscribe / listen to events (Configuration of the webhook)", "de": "Aktuelle Wiedergabe / Ereignisse empfangen (Konfiguration des Webhook)", "ru": "Подписаться / слушать событий (конфигурации веб-перехватчик)", "pt": "Subscrever / ouvir eventos (Configuração do webhook)", "nl": "Inschrijven / luisteren naar gebeurtenissen (Configuratie van de webhook)", "fr": "Abonnez-vous / écouter des évènements (Configuration de la webhook)", "it": "Subscribe / ascoltare gli eventi (Configurazione webhook)", "es": "Suscribirse / escuchar a los eventos (Configuración de la webhook)", "pl": "Subskrybuj / słuchać zdarzeń (konfiguracja strony-pościgowa)", "zh-cn": "订阅/听到的事件(配置两者)"}, "eventsInfo": { "en": "Listening to events allows you to receive events from both Plex or Tautulli.", "de": "Der Webhook kann genutzt werden, um die aktuelle Wiedergabe bzw. die Ereignisse von Plex oder Tautulli zu empfangen.", "ru": "Прослушивание событий позволяет получать события из плекса или Tautulli.", "pt": "Ouvindo eventos permite que você para receber eventos do Plex ou Tautulli.", "nl": "Luisteren naar evenementen kunt u gebeurtenissen ontvangen van zowel Plex of Tautulli.", "fr": "L'écoute d'événements vous permet de recevoir des événements de deux Plex ou Tautulli.", "it": "L'ascolto di eventi permette la ricezione di eventi sia Plex o Tautulli.", "es": "Escuchar eventos le permite recibir los eventos de ambos Plex o Tautulli.", "pl": "Nasłuchiwanie zdarzeń pozwala odbierać zdarzenia z plexus lub Tautulli.", "zh-cn": "听到事件可以让你得到事件,从这两个丛或Tautulli的。"}, "getAllItems": { "en": "Retrieve all items within a library", "de": "alle Elemente der Bibliothek(en) abrufen", "ru": "Получить все предметы в библиотеке", "pt": "Recuperar todos os itens dentro de uma biblioteca", "nl": "Het ophalen van alle items in een bibliotheek", "fr": "Récupérer tous les éléments à l'intérieur d'une bibliothèque", "it": "Recuperare tutti gli elementi all'interno di una biblioteca", "es": "Recuperar todos los elementos dentro de una biblioteca", "pl": "Zobacz wszystkie przedmioty w bibliotece", "zh-cn": "检索的所有项目在图书馆"}, @@ -33,10 +36,16 @@ systemDictionary = { "getPlaylists": { "en": "Retrieve playlists", "de": "Playlists abrufen", "ru": "Получать плейлисты", "pt": "Recuperar listas de reprodução", "nl": "Afspeellijsten ophalen", "fr": "Récupérer des listes de lecture", "it": "Recuperare playlist", "es": "Recuperar listas de reproducción", "pl": "Otrzymywać listy odtwarzania", "zh-cn": "检索的播放列表"}, "getServers": { "en": "Retrieve servers", "de": "Server abrufen", "ru": "Извлечение сервера", "pt": "Recuperar servidores", "nl": "Ophalen servers", "fr": "Récupérer des serveurs", "it": "Recuperare i server", "es": "Recuperar servidores", "pl": "Usuwanie serwera", "zh-cn": "检索的服务器"}, "getSettings": { "en": "Retrieve settings", "de": "Einstellungen abrufen", "ru": "Получения параметров", "pt": "Recuperar as configurações", "nl": "Instellingen ophalen", "fr": "Récupérer les paramètres", "it": "Recuperare le impostazioni", "es": "Recuperar la configuración", "pl": "Uzyskać ustawienia", "zh-cn": "检索设置"}, + "certChained": { "en": "Chained Certificate", "de": "Chained Zertifikat", "ru": "Цепной сертификат", "pt": "Certificado encadeado", "nl": "Geketend certificaat", "fr": "Certificat chaîné", "it": "Certificato incatenato", "es": "Certificado encadenado", "pl": "Chained Certificate", "zh-cn": "连锁证书"}, + "certPrivate": { "en": "Private Key", "de": "Privater Schlüssel", "ru": "Закрытый ключ", "pt": "Chave privada", "nl": "Prive sleutel", "fr": "Clé privée", "it": "Chiave privata", "es": "Llave privada", "pl": "Prywatny klucz", "zh-cn": "私钥"}, + "certPublic": { "en": "Public Certificate", "de": "Öffentliches Zertifikat", "ru": "Публичный сертификат", "pt": "Certificado público", "nl": "Openbaar certificaat", "fr": "Certificat public", "it": "Certificato pubblico", "es": "Certificado publico", "pl": "Certyfikat publiczny", "zh-cn": "公开证书"}, "getStatistics": { "en": "Retrieve statistics (only Tautulli)", "de": "Statistiken abrufen (nur Tautulli)", "ru": "Получения статистики", "pt": "Obter estatísticas", "nl": "Ophalen statistieken", "fr": "Extraire des statistiques", "it": "Recuperare le statistiche", "es": "Recuperar las estadísticas", "pl": "Uzyskać statystyki", "zh-cn": "检索的统计数据"}, "getTokenInfo": { "en": "Type in the PIN below on http://www.plex.tv/pin and press the button below.", "de": "Die abgerufene PIN auf http://www.plex.tv/pin eingeben und den Button drücken.", "ru": "Введите PIN-код ниже на http://www.plex.tv/pin и нажмите на кнопку ниже.", "pt": "Digite o PIN abaixo http://www.plex.tv/pin e premir o botão abaixo.", "nl": "Typ de PINCODE hieronder op http://www.plex.tv/pin en druk op de knop hieronder.", "fr": "Saisissez le code PIN ci-dessous sur http://www.plex.tv/pin et appuyez sur le bouton ci-dessous.", "it": "Digitare il PIN di seguito su http://www.plex.tv/pin e premere il pulsante qui sotto.", "es": "Escriba el PIN de abajo de http://www.plex.tv/pin y pulse el botón de abajo.", "pl": "Wprowadź kod PIN poniżej http://www.plex.tv/pin i kliknij na przycisk poniżej.", "zh-cn": "类型的脚下http://www.plex.tv/pin 并按下面的按钮。"}, "getTokenTitle": { "en": "Retrieve a Authentication Token from Plex", "de": "Authentifizierungs-Token von Plex abrufen", "ru": "Получить маркер проверки подлинности от плекса", "pt": "Obter um Token de Autenticação do Plex", "nl": "Het ophalen van een Authenticatie Token van Plex", "fr": "Récupérer un Jeton d'Authentification à partir de Plex", "it": "Recuperare un Token di Autenticazione da Plex", "es": "Recuperar un Token de Autenticación de Plex", "pl": "Zobacz tokenu uwierzytelniania od plexus", "zh-cn": "检索的一个身份验证标记丛"}, "getUsers": { "en": "Retrieve users (only Tautulli)", "de": "Benutzer abrufen (nur Tautulli)", "ru": "Извлечения пользователей", "pt": "Recuperar usuários", "nl": "Het ophalen van gebruikers", "fr": "Récupérer les utilisateurs", "it": "Recuperare utenti", "es": "Recuperar a los usuarios", "pl": "Wyjąć użytkowników", "zh-cn": "用户检索"}, + "media": { "en": "Media type", "de": "Medientyp", "ru": "Тип носителя ", "pt": "Tipo de mídia", "nl": "Media type", "fr": "Type de support", "it": "Tipo di supporto", "es": "Tipo de medios", "pl": "Typ nośnika ", "zh-cn": "媒体型"}, + "passphrase": { "en": "Password if private key is additionally encrypted", "de": "Passwort (nur wenn privater Schlüssel zusätzlich verschlüsselt ist)", "ru": "Пароль, если закрытый ключ дополнительно зашифрован", "pt": "Senha se a chave privada for criptografada adicionalmente", "nl": "Wachtwoord als de privésleutel bovendien is gecodeerd", "fr": "Mot de passe si la clé privée est cryptée", "it": "Password se la chiave privata è ulteriormente crittografata", "es": "Contraseña si la clave privada está encriptada adicionalmente", "pl": "Hasło, jeśli klucz prywatny jest dodatkowo szyfrowany", "zh-cn": "密码(如果还加密了私钥)"}, + "message": { "en": "Message", "de": "Nachricht", "ru": "Сообщение", "pt": "Mensagem", "nl": "Bericht", "fr": "Message", "it": "Messaggio", "es": "Mensaje", "pl": "Komunikat", "zh-cn": "消息"}, "message_connected": { "en": "Connected to adapter.", "de": "Verbunden zum Adapter.", "ru": "Подключен к адаптеру. Извлечение пользователей..", "pt": "Conectado ao adaptador. Recuperar usuário..", "nl": "Aangesloten op adapter. Het ophalen van de gebruiker..", "fr": "Connecté à l'adaptateur. La récupération de l'utilisateur..", "it": "Collegato all'adattatore. Il recupero di utente..", "es": "Conectado al adaptador. La recuperación de usuario..", "pl": "Podłączony do zasilacza. Usuwanie użytkowników..", "zh-cn": "连接器。 检索的用户.."}, "message_connected_pin": { "en": "Connected to adapter. Retrieving PIN..", "de": "Verbunden zum Adapter. Lade PIN..", "ru": "Подключен к адаптеру. Получение пин-кода..", "pt": "Conectado ao adaptador. Recuperar o PIN..", "nl": "Aangesloten op adapter. Opvragen PIN..", "fr": "Connecté à l'adaptateur. La récupération de PIN..", "it": "Collegato all'adattatore. Il recupero di PIN..", "es": "Conectado al adaptador. La recuperación de PIN..", "pl": "Podłączony do zasilacza. Uzyskanie kodu pin..", "zh-cn": "连接器。 检索销.."}, "message_connected_token": { "en": "Connected to adapter. Retrieving token..", "de": "Verbunden zum Adapter. Lade Token..", "ru": "Подключен к адаптеру. Получение токена..", "pt": "Conectado ao adaptador. Recuperar token..", "nl": "Aangesloten op adapter. Ophalen token..", "fr": "Connecté à l'adaptateur. La récupération de jeton..", "it": "Collegato all'adattatore. Il recupero di token..", "es": "Conectado al adaptador. La recuperación de token..", "pl": "Podłączony do zasilacza. Pobieranie tokenu..", "zh-cn": "连接器。 检索的令牌.."}, @@ -50,6 +59,8 @@ systemDictionary = { "new device streaming": { "en": "new device streaming", "de": "Neues Gerät streamt", "ru": "новый потокового устройства ", "pt": "novo dispositivo de streaming", "nl": "nieuwe apparaat streamen", "fr": "nouveau dispositif de streaming", "it": "nuovo dispositivo di streaming", "es": "nuevo dispositivo de streaming", "pl": "nowy streaming urządzenia ", "zh-cn": "新的设备的流"}, "new item added on deck": { "en": "new item added on deck", "de": "neues Element zu OnDeck hinzugefügt", "ru": "новый элемент добавляется на палубе", "pt": "novo item adicionado no convés", "nl": "een nieuw item is toegevoegd aan dek", "fr": "nouvel élément ajouté sur le pont", "it": "nuovo elemento aggiunto sul ponte", "es": "nuevo elemento añadido en la cubierta", "pl": "nowy element jest dodawany na pokładzie", "zh-cn": "新的项目加在甲板上"}, "new item added to library": { "en": "new item added to library", "de": "neues Element zur Bibliothek hinzugefügt", "ru": "новый элемент добавляется в библиотеку", "pt": "novo item adicionado à biblioteca", "nl": "een nieuw item is toegevoegd aan de bibliotheek", "fr": "nouvel élément ajouté à la bibliothèque", "it": "nuovo elemento aggiunto alla libreria", "es": "nuevo elemento añadido a la biblioteca", "pl": "nowy element jest dodawany do biblioteki", "zh-cn": "新的项目添加到图书馆"}, + "notifications": { "en": "Notifications", "de": "Benachrichtigungen", "ru": "Уведомления", "pt": "Notificações", "nl": "Meldingen", "fr": "Les Notifications", "it": "Le notifiche", "es": "Las notificaciones", "pl": "Powiadomienia", "zh-cn": "的通知"}, + "notificationsInfo": { "en": "You may modify the notification text based on the media and / or event type. Use %placeholder% to use an actual value in your notification.", "de": "Hier können die Texte der Benachrichtigungen (je nach Medien- bzw. Benachrichtigungstyp) angepasst werden. %Platzhalter% kann genutzt werden, um spezifische Werte einzufügen.", "ru": "Вы можете изменить текст уведомления, основанные на СМИ и / или тип события. Использовать плейсхолдер%%, чтобы использовать фактическое значение в вашем уведомлении.", "pt": "Você pode modificar o texto da notificação baseada na mídia e / ou tipo de evento. Use %de marcador de posição% para usar um valor real na sua notificação.", "nl": "U kan wijzigingen in de kennisgeving tekst gebaseerd op de media en / of type gebeurtenis. Gebruik %tijdelijke aanduiding% tot een werkelijke waarde in uw aanmelding.", "fr": "Vous pouvez modifier le texte de la notification basé sur les médias et / ou le type d'événement. Utiliser %de l'espace réservé% à utiliser une valeur réelle à votre notification.", "it": "È possibile modificare il testo di notifica in base al supporto e / o tipo di evento. Uso %segnaposto% per l'utilizzo di un effettivo valore di notifica.", "es": "Usted puede modificar el texto de la notificación basada en los medios de comunicación y / o tipo de evento. Uso %de marcador de posición% usar un valor real en su notificación.", "pl": "Możesz zmienić tekst powiadomienia, oparte na MEDIACH i / lub typ zdarzenia. Używać плейсхолдер%%, aby wykorzystać wartość rzeczywista w zawiadomieniu.", "zh-cn": "你可以修改的通知文本根据媒体和/或事件的类型。 使用%符%使用实际价值在你的通知。"}, "other": { "en": "Other Settings", "de": "Weitere Einstellungen", "ru": "Другие Параметры", "pt": "Outras Configurações", "nl": "Andere Instellingen", "fr": "D'Autres Paramètres", "it": "Altre Impostazioni", "es": "Otros Ajustes", "pl": "Inne Opcje", "zh-cn": "其他的设置"}, "playback by shared user": { "en": "playback by shared user", "de": "Wiedergabe eines anderen Benutzers", "ru": "воспроизведение общих пользователей", "pt": "reprodução de usuário compartilhada", "nl": "het afspelen van gedeelde gebruiker", "fr": "lecture par utilisateur partagé", "it": "riproduzione da utente comune", "es": "la reproducción por parte del usuario compartido", "pl": "odtwarzanie wspólnych użytkowników", "zh-cn": "播放共用户"}, "plexAuth": { "en": "Connection settings of the Plex instance", "de": "Die Verbindungseinstellungen der Plex Instanz", "ru": "Параметры экземпляра Плекс связи ", "pt": "Configurações de conexão do Plex instância", "nl": "Instellingen voor de verbinding van de Plex bijvoorbeeld", "fr": "Paramètres de connexion de l'Plex exemple", "it": "Impostazioni di connessione di Plex istanza", "es": "Configuración de la conexión de la instancia Plex", "pl": "Parametry instancji plex dnia połączenia ", "zh-cn": "连接设置的复杂的实例"}, @@ -73,12 +84,6 @@ systemDictionary = { "tautulliIp": { "en": "IP address", "de": "IP-Adresse", "ru": "IP-адрес", "pt": "Endereço IP", "nl": "IP-adres", "fr": "Adresse IP", "it": "Indirizzo IP", "es": "Dirección IP", "pl": "Adres IP", "zh-cn": "IP地址"}, "tautulliPort": { "en": "Port", "de": "Port", "ru": "Порт", "pt": "Porta", "nl": "Poort", "fr": "Port", "it": "Porta", "es": "Puerto", "pl": "Port", "zh-cn": "口"}, "tautulliToken": { "en": "API Token", "de": "API Token", "ru": "Токен API ", "pt": "Token de API", "nl": "API Token", "fr": "Clé API", "it": "API Token", "es": "API Token", "pl": "Token API ", "zh-cn": "API令牌"}, + "secureConnection": { "en": "Use secure connection to Bridge (https)", "de": "Benutze sichere Verbindung zur Bridge (https)", "ru": "Используйте безопасное соединение с мостом (https)", "pt": "Usar conexão segura com o Bridge (https)", "nl": "Gebruik beveiligde verbinding met Bridge (https)", "fr": "Utiliser une connexion sécurisée à Bridge (https)", "it": "Usa connessione sicura a Bridge (https)", "es": "Use una conexión segura a Bridge (https)", "pl": "Użyj bezpiecznego połączenia z mostem (https)", "zh-cn": "使用安全连接到网桥(https)"}, "webhookPort": { "en": "Webhook Port", "de": "Webhook-Port", "ru": "Порт Веб-Перехватчик ", "pt": "Webhook Porta", "nl": "Webhook Poort", "fr": "Webhook Port", "it": "Webhook Porta", "es": "Webhook Puerto", "pl": "Port Web-Pościgowa ", "zh-cn": "两者口"}, - "notifications": { "en": "Notifications", "de": "Benachrichtigungen", "ru": "Уведомления", "pt": "Notificações", "nl": "Meldingen", "fr": "Les Notifications", "it": "Le notifiche", "es": "Las notificaciones", "pl": "Powiadomienia", "zh-cn": "的通知"}, - "notificationsInfo": { "en": "You may modify the notification text based on the media and / or event type. Use %placeholder% to use an actual value in your notification.", "de": "Hier können die Texte der Benachrichtigungen (je nach Medien- bzw. Benachrichtigungstyp) angepasst werden. %Platzhalter% kann genutzt werden, um spezifische Werte einzufügen.", "ru": "Вы можете изменить текст уведомления, основанные на СМИ и / или тип события. Использовать плейсхолдер%%, чтобы использовать фактическое значение в вашем уведомлении.", "pt": "Você pode modificar o texto da notificação baseada na mídia e / ou tipo de evento. Use %de marcador de posição% para usar um valor real na sua notificação.", "nl": "U kan wijzigingen in de kennisgeving tekst gebaseerd op de media en / of type gebeurtenis. Gebruik %tijdelijke aanduiding% tot een werkelijke waarde in uw aanmelding.", "fr": "Vous pouvez modifier le texte de la notification basé sur les médias et / ou le type d'événement. Utiliser %de l'espace réservé% à utiliser une valeur réelle à votre notification.", "it": "È possibile modificare il testo di notifica in base al supporto e / o tipo di evento. Uso %segnaposto% per l'utilizzo di un effettivo valore di notifica.", "es": "Usted puede modificar el texto de la notificación basada en los medios de comunicación y / o tipo de evento. Uso %de marcador de posición% usar un valor real en su notificación.", "pl": "Możesz zmienić tekst powiadomienia, oparte na MEDIACH i / lub typ zdarzenia. Używać плейсхолдер%%, aby wykorzystać wartość rzeczywista w zawiadomieniu.", "zh-cn": "你可以修改的通知文本根据媒体和/或事件的类型。 使用%符%使用实际价值在你的通知。"}, - "add_notification": { "en": "Add Notification", "de": "Benachrichtigung hinzufügen", "ru": "Добавить Уведомление", "pt": "Adicionar Notificação", "nl": "Toevoegen Melding", "fr": "Ajouter Une Notification", "it": "Aggiungi Notifica", "es": "Agregar La Notificación", "pl": "Dodaj Ogłoszenie", "zh-cn": "添加通知"}, - "media": { "en": "Media type", "de": "Medientyp", "ru": "Тип носителя ", "pt": "Tipo de mídia", "nl": "Media type", "fr": "Type de support", "it": "Tipo di supporto", "es": "Tipo de medios", "pl": "Typ nośnika ", "zh-cn": "媒体型"}, - "event": { "en": "Event type", "de": "Benachrichtigungstyp", "ru": "Тип события ", "pt": "Tipo de evento", "nl": "Soort gebeurtenis", "fr": "Type d'événement", "it": "Tipo di evento", "es": "Tipo de evento", "pl": "Typ zdarzenia ", "zh-cn": "事件类型"}, - "message": { "en": "Message", "de": "Nachricht", "ru": "Сообщение", "pt": "Mensagem", "nl": "Bericht", "fr": "Message", "it": "Messaggio", "es": "Mensaje", "pl": "Komunikat", "zh-cn": "消息"}, - "caption": { "en": "Caption", "de": "Untertitel", "ru": "Заголовок", "pt": "Legenda", "nl": "Bijschrift", "fr": "Légende", "it": "Didascalia", "es": "Título", "pl": "Tytuł", "zh-cn": "字幕"}, }; \ No newline at end of file diff --git a/io-package.json b/io-package.json index 7809eac..97375b0 100644 --- a/io-package.json +++ b/io-package.json @@ -1,7 +1,7 @@ { "common": { "name": "plex", - "version": "0.8.4", + "version": "0.8.5", "title": "Plex Media Server", "desc": { "en": "Integration of your Plex Media Server and your Tautulli", @@ -165,175 +165,175 @@ "event": "any", "message": "No Message defined for media %Metadata.type% and event %event%", "caption": "", - "thumb": "" + "thumb": "" }, { "media": "any", "event": "media.play", "message": "%Metadata.title% (%Metadata.year%) playing", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.thumb%" + "thumb": "%Metadata.thumb%" }, { "media": "any", "event": "media.pause", "message": "%Metadata.title% (%Metadata.year%) paused", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.thumb%" + "thumb": "%Metadata.thumb%" }, { "media": "any", "event": "media.stop", "message": "%Metadata.title% (%Metadata.year%) stopped", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.thumb%" + "thumb": "%Metadata.thumb%" }, { "media": "any", "event": "media.resume", "message": "%Metadata.title% (%Metadata.year%) resumed", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.thumb%" + "thumb": "%Metadata.thumb%" }, { "media": "any", "event": "media.rate", "message": "%Metadata.title% (%Metadata.year%) rated", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.thumb%" + "thumb": "%Metadata.thumb%" }, { "media": "any", "event": "media.watched", "message": "%Metadata.title% (%Metadata.year%) watched", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.thumb%" + "thumb": "%Metadata.thumb%" }, { "media": "episode", "event": "media.play", "message": "%Metadata.grandparentTitle% - %Metadata.title% (%Metadata.parentTitle%, %Metadata.year%) playing", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.grandparentThumb%" + "thumb": "%Metadata.grandparentThumb%" }, { "media": "episode", "event": "media.pause", "message": "%Metadata.grandparentTitle% - %Metadata.title% (%Metadata.parentTitle%, %Metadata.year%) paused", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.grandparentThumb%" + "thumb": "%Metadata.grandparentThumb%" }, { "media": "episode", "event": "media.stop", "message": "%Metadata.grandparentTitle% - %Metadata.title% (%Metadata.parentTitle%, %Metadata.year%) stopped", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.grandparentThumb%" + "thumb": "%Metadata.grandparentThumb%" }, { "media": "episode", "event": "media.resume", "message": "%Metadata.grandparentTitle% - %Metadata.title% (%Metadata.parentTitle%, %Metadata.year%) resumed", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.grandparentThumb%" + "thumb": "%Metadata.grandparentThumb%" }, { "media": "episode", "event": "media.rate", "message": "%Metadata.grandparentTitle% - %Metadata.title% (%Metadata.parentTitle%, %Metadata.year%) rated", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.grandparentThumb%" + "thumb": "%Metadata.grandparentThumb%" }, { "media": "episode", "event": "media.watched", "message": "%Metadata.grandparentTitle% - %Metadata.title% (%Metadata.parentTitle%, %Metadata.year%) watched", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "%Metadata.grandparentThumb%" + "thumb": "%Metadata.grandparentThumb%" }, { "media": "track", "event": "media.play", "message": "%Metadata.grandparentTitle% - %Metadata.title% (Album %Metadata.parentTitle%) playing", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "" + "thumb": "" }, { "media": "track", "event": "media.pause", "message": "%Metadata.grandparentTitle% - %Metadata.title% (Album %Metadata.parentTitle%) paused", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "" + "thumb": "" }, { "media": "track", "event": "media.stop", "message": "%Metadata.grandparentTitle% - %Metadata.title% (Album %Metadata.parentTitle%) stopped", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "" + "thumb": "" }, { "media": "track", "event": "media.resume", "message": "%Metadata.grandparentTitle% - %Metadata.title% (Album %Metadata.parentTitle%) resumed", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "" + "thumb": "" }, { "media": "track", "event": "media.rate", "message": "%Metadata.grandparentTitle% - %Metadata.title% (Album %Metadata.parentTitle%) rated", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "" + "thumb": "" }, { "media": "track", "event": "media.watched", "message": "%Metadata.grandparentTitle% - %Metadata.title% (Album %Metadata.parentTitle%) watched", "caption": "Played by %Account.title% on %Player.title%", - "thumb": "" + "thumb": "" }, { "media": "system", "event": "library.on.deck", "message": "A new item is added that appears in the user’s On Deck.", "caption": "", - "thumb": "" + "thumb": "" }, { "media": "system", "event": "library.new", "message": "A new item is added to a library.", "caption": "", - "thumb": "" + "thumb": "" }, { "media": "system", "event": "admin.database.backup", "message": "A database backup is completed successfully via scheduled tasks.", "caption": "", - "thumb": "" + "thumb": "" }, { "media": "system", "event": "admin.database.corrupted", "message": "Corruption is detected in the server database.", "caption": "", - "thumb": "" + "thumb": "" }, { "media": "system", "event": "device.new", "message": "A new device is using the Plex Media Server.", "caption": "", - "thumb": "" + "thumb": "" }, { "media": "system", "event": "playback.started", "message": "Playback is started by a shared user.", "caption": "", - "thumb": "" + "thumb": "" } ] }, diff --git a/lib/library.js b/lib/library.js index 4be25dd..750429c 100644 --- a/lib/library.js +++ b/lib/library.js @@ -6,8 +6,8 @@ * @description Library of general functions as well as helping functions handling ioBroker * @author Zefau * @license MIT License - * @version 0.25.1 - * @date 2019-10-31 + * @version 0.27.1 + * @date 2019-11-17 * */ class Library @@ -34,7 +34,7 @@ class Library this._STATES = {}; - this.set({ node: 'info', description: 'Adapter Information', role: 'channel' }); + this.set({ 'node': 'info', 'description': 'Adapter Information', 'role': 'channel' }); this.set(Library.CONNECTION, false); } @@ -259,6 +259,29 @@ class Library return day.substr(-2) + '.' + month.substr(-2) + '.' + year + ' ' + hours.substr(-2) + ':' + minutes.substr(-2) + ':' + seconds.substr(-2); } + /** + * Get all instances of an adapter. + * + * @param {string} adapter Adapter to get instances of + * @param {function} callback Callback to invoke + * @return void + * + */ + getAdapterInstances(adapter, callback) + { + this._adapter.objects.getObjectView('system', 'instance', { 'startkey': 'system.adapter.' + adapter + '.', 'endkey': 'system.adapter.' + adapter + '.\u9999'}, (err, instances) => + { + if (instances && instances.rows) + { + let result = []; + instances.rows.forEach(instance => result.push({ 'id': instance.id.replace('system.adapter.', ''), 'config': instance.value.native.type })); + callback(null, result); + } + else + callback('Could not retrieve ' + adapter + ' instances!'); + }); + } + /** * Run Garbage Collector and delete outdated states / objects. * @@ -276,20 +299,25 @@ class Library { if (err || !states) return; - // - let key = state.replace(this._adapter.name + '.' + this._adapter.instance + '.', ''); - if (this._STATES[key] && this._STATES[key].ts < (Math.round(Date.now()/1000)-offset) && !(whitelist.length > 0 && RegExp(whitelist.join('|')).test(state))) + // loop through states + let key; + for (let state in states) { - // apply deletion - this._adapter.log.debug('Garbage Collector: ' + (del ? 'Deleted ' : 'Emptied ') + state + '!'); + key = state.replace(this._adapter.name + '.' + this._adapter.instance + '.', ''); - if (del) + if (this._STATES[key] && this._STATES[key].ts < (Math.round(Date.now()/1000)-offset) && !(whitelist.length > 0 && RegExp(whitelist.join('|')).test(state))) { - this._STATES[key] = undefined; - this._adapter.delObjectAsync(state); + // apply deletion + this._adapter.log.debug('Garbage Collector: ' + (del ? 'Deleted ' : 'Emptied ') + state + '!'); + + if (del) + { + this._STATES[key] = undefined; + this._adapter.delObjectAsync(state); + } + else + this._setValue(key, '', { 'force': true }); } - else - this._setValue(key, '', { 'force': true }); } }); } @@ -386,10 +414,8 @@ class Library * @return void * */ - setMultiple(values, nodes, options) + setMultiple(nodes, values, options = {}) { - options = options !== undefined ? options : {}; - for (let key in values) { if (nodes[key] && nodes[key].node && nodes[key].description) @@ -398,22 +424,28 @@ class Library let value = values[key]; // replace options if given - for (let option in options) + options.placeholders = options.placeholders || {}; + for (let placeholder in options.placeholders) { - node.node = node.node.replace(option, options[option]); - node.description = node.description.replace(option, options[option]); + node.node = node.node.replace(placeholder, options.placeholders[placeholder]); + node.description = node.description.replace(placeholder, options.placeholders[placeholder]); } // convert data if necessary switch(node.convert) { + case "string": + if (value && Array.isArray(value)) + value = value.join(', '); + break; + case "datetime": this.set({node: node.node + 'Datetime', description: node.description.replace('Timestamp', 'Date-Time'), common: {"type": "string", "role": "text"}}, value ? this.getDateTime(value * 1000) : ''); break; } // set node - this.set(node, value); + this.set(node, value, options); } } } @@ -467,17 +499,29 @@ class Library if (!this._adapter) return Promise.reject('Adapter not defined!'); - let common = {}; - if (node.description !== undefined) common.name = node.description; - if (node.role !== undefined) common.role = node.role; - if (node.type !== undefined) common.type = node.type; - if (common.role && common.role.indexOf('button') > -1) { common.type = 'boolean'; common.read = false; common.write = true; } + // remap properties to common + let type = node.role == 'device' || node.role == 'channel' ? (node.role == 'device' ? 'device' : 'channel') : 'state'; + let common = { + 'name': node.name || node.description, + 'role': node.common && node.common.role || node.role || 'state', + 'type': node.common && node.common.type || node.type || 'string', + 'write': false, + ...node.common || {} + }; + + // special roles + if (common.role.indexOf('button') > -1) + common = { ...common, 'type': 'boolean', 'read': false, 'write': true }; + + if (common.role == 'device' || common.role == 'channel') + common = { ...common, 'type': undefined, 'role': undefined }; + // create object this._adapter.setObjectNotExists( node.node, { - 'common': Object.assign({ 'name': node.name, 'role': 'state', 'type': 'string', 'write': false }, node.common || {}, common), - 'type': 'state', + 'common': common, + 'type': type, 'native': node.native || {} }, (err, obj) => diff --git a/package-lock.json b/package-lock.json index 59e5af7..fafc36f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "iobroker.plex", - "version": "0.8.4", + "version": "0.8.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4233265..bdef26f 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,8 @@ "test": "npm run test:package && npm run test:unit", "test:integration": "mocha test/integration --exit", "test:package": "mocha test/package --exit", - "test:unit": "mocha test/unit --exit" + "test:unit": "mocha test/unit --exit", + "translate": "gulp translateAndUpdateWordsJS" }, - "version": "0.8.4" + "version": "0.8.5" } diff --git a/plex.js b/plex.js index c251af0..0958381 100644 --- a/plex.js +++ b/plex.js @@ -3,6 +3,7 @@ const ioPackage = require('./io-package.json'); const adapterName = ioPackage.common.name; const utils = require('@iobroker/adapter-core'); // Get common adapter utils +const _fs = require('fs'); const _http = require('express')(); const _parser = require('body-parser'); const _multer = require('multer'); @@ -37,6 +38,8 @@ let history = []; let notifications = {}; let upload = _multer({ dest: '/tmp/' }); +let REQUEST_OPTIONS = {}; +const watched = ['01-last_24h', '02-last_7d', '03-last_30d', '00-all_time']; const plexOptions = { identifier: '5cc42810-6dc0-44b1-8c70-747152d4f7f9', product: 'Plex for ioBroker', @@ -87,6 +90,38 @@ function startAdapter(options) else encryptionKey = adapter.config.encryptionKey; + // Secure connection + REQUEST_OPTIONS.secureConnection = false; + if (adapter.config.secureConnection) + { + adapter.log.info('Establishing secure connection to Plex Media Server...'); + + try + { + REQUEST_OPTIONS = { + ...REQUEST_OPTIONS, + 'cert': _fs.readFileSync(adapter.config.certPublicPath), + 'key': _fs.readFileSync(adapter.config.certPrivatePath), + 'rejectUnauthorized': false, + 'secureConnection': true, + '_protocol': 'https:' + }; + + if (adapter.config.certChainedPath) + REQUEST_OPTIONS.ca = _fs.readFileSync(adapter.config.certChainedPath); + + if (REQUEST_OPTIONS.key.indexOf('ENCRYPTED') > -1) + REQUEST_OPTIONS.passphrase = adapter.config.passphrase; + } + catch(err) + { + REQUEST_OPTIONS.secureConnection = false; + REQUEST_OPTIONS._protocol = 'http:'; + } + } + else + adapter.log.info('Establishing insecure connection to Plex Media Server...'); + // get notifications if (adapter.config.notifications) { @@ -106,11 +141,12 @@ function startAdapter(options) // initialize Plex API adapter.config.plexPort = adapter.config.plexPort || 32400; plex = new Plex({ - hostname: adapter.config.plexIp, - port: adapter.config.plexPort, - https: adapter.config.plexSecure || false, - token: adapter.config.plexToken, - options: plexOptions + 'hostname': adapter.config.plexIp, + 'port': adapter.config.plexPort, + 'https': REQUEST_OPTIONS.secureConnection, + 'token': adapter.config.plexToken, + 'requestOptions': REQUEST_OPTIONS, + 'options': plexOptions }); // retrieve all values from states to avoid message "Unsubscribe from all states, except system's, because over 3 seconds the number of events is over 200 (in last second 0)" @@ -173,10 +209,16 @@ function startAdapter(options) if (action == '_refresh') { let libId = id.substring(id.indexOf('libraries.')+10, id.indexOf('-')); - let url = 'http://' + adapter.config.plexIp + ':' + adapter.config.plexPort + '/library/sections/' + libId + '/refresh?force=1&X-Plex-Token=' + adapter.config.plexToken; - adapter.log.debug(url); + let options = { + ...REQUEST_OPTIONS, + 'method': 'POST', + 'url': REQUEST_OPTIONS.protocol + '//' + adapter.config.plexIp + ':' + adapter.config.plexPort + '/library/sections/' + libId + '/refresh?force=1', + 'headers': { + 'X-Plex-Token': adapter.config.plexToken + } + }; - _request(url).then(res => + _request(options).then(res => { adapter.log.info('Successfully triggered refresh on library with ID ' + libId + '.'); adapter.log.debug(JSON.stringify(res)); @@ -206,11 +248,17 @@ function startAdapter(options) let key = _ACTIONS[mode][action].key || action; let attribute = _ACTIONS[mode][action].attribute; + let options = { + ...REQUEST_OPTIONS, + 'method': 'POST', + 'url': REQUEST_OPTIONS.protocol + '//' + playerIp + ':' + playerPort + '/player/' + mode + '/' + key + '?' + (attribute != undefined ? attribute + '=' + val + '&' : ''), + 'headers': { + 'X-Plex-Token': adapter.config.plexToken, + 'X-Plex-Target-Client-Identifier': playerIdentifier + } + }; - let url = 'http://' + playerIp + ':' + playerPort + '/player/' + mode + '/' + key + '?' + (attribute != undefined ? attribute + '=' + val + '&' : '') + 'X-Plex-Target-Client-Identifier=' + playerIdentifier + '&X-Plex-Token=' + adapter.config.plexToken; - adapter.log.debug(url); - - _request(url).then(res => + _request(options).then(res => { adapter.log.info('Successfully triggered ' + mode + ' action -' + action + '- on player ' + playerIp + '.'); }) @@ -363,13 +411,15 @@ function init() }) .catch(err => { + adapter.log.debug(err.stack); + if (err.message.indexOf('EHOSTUNREACH') > -1) { adapter.config.retry = 60; adapter.log.error('Plex Media Server not reachable! Will try again in ' + adapter.config.retry + ' minutes..'); library.set(Library.CONNECTION, false); - retryCycle = setTimeout(testConnection, adapter.config.retry*60*1000); + retryCycle = setTimeout(init, adapter.config.retry*60*1000); } else library.terminate(err.message); @@ -454,7 +504,7 @@ function setEvent(data, source, prefix) 'player': data.player, 'media': data.media, 'event': event, - 'thumb': message.thumb ? 'http://' + adapter.config.plexIp + ':' + adapter.config.plexPort + '' + replacePlaceholders(message.thumb, eventData) : '', + 'thumb': message.thumb ? 'https://' + adapter.config.plexIp + ':' + adapter.config.plexPort + '' + replacePlaceholders(message.thumb, eventData) + '&X-Plex-Token=' + adapter.config.plexToken : '', 'message': replacePlaceholders(message.message, eventData), 'caption': replacePlaceholders(message.caption, eventData), 'source': data.source @@ -710,8 +760,6 @@ function getItems(path, key, node) */ function retrieveData() { - let watched = ['01-last_24h', '02-last_7d', '03-last_30d', '00-all_time']; - // GET SERVERS if (adapter.config.getServers) getServers(); @@ -838,7 +886,7 @@ function getLibraries() { let id = watched[i]; library.set({node: 'statistics.libraries.' + libId + '.' + id, type: library.getNode('statistics.' + id).type, role: library.getNode('statistics.' + id).role, description: library.getNode('statistics.' + id).description}, ''); - + for (let key in entry) library.set({node: 'statistics.libraries.' + libId + '.' + id + '.' + key, type: library.getNode('statistics.' + key).type, role: library.getNode('statistics.' + key).role, description: library.getNode('statistics.' + key).description}, entry[key]); }); @@ -869,10 +917,10 @@ function getUsers() data.forEach(entry => { - let userId = library.clean(entry['friendly_name'], true); + let userId = library.clean(entry['username'], true); if (userId === 'local') return; - library.set({node: 'users.' + userId, role: library.getNode('user').role, description: library.getNode('user').description.replace(/%user%/gi, entry['friendly_name'])}, ''); + library.set({node: 'users.' + userId, role: library.getNode('user').role, description: library.getNode('user').description.replace(/%user%/gi, entry['username'])}, ''); // index all keys as states for (let key in entry) @@ -889,10 +937,10 @@ function getUsers() tautulli.get('get_user_watch_time_stats', {'user_id': entry['user_id']}).then(res => { if (!is(res)) return; else data = res.response.data || []; - adapter.log.debug('Retrieved Watch Statistics for User ' + entry['friendly_name'] + ' from Tautulli.'); + adapter.log.debug('Retrieved Watch Statistics for User ' + entry['username'] + ' from Tautulli.'); library.set({node: 'statistics.users', role: library.getNode('statistics.users').role, description: library.getNode('statistics.users').description.replace(/%user%/gi, '')}, ''); - library.set({node: 'statistics.users.' + userId, role: library.getNode('statistics.users').role, description: library.getNode('statistics.users').description.replace(/%user%/gi, entry['friendly_name'])}, ''); + library.set({node: 'statistics.users.' + userId, role: library.getNode('statistics.users').role, description: library.getNode('statistics.users').description.replace(/%user%/gi, entry['username'])}, ''); data.forEach((entry, i) => {