Web Skipper for Plex

在 Plex Web 上实现自动跳过片头、片尾和自动播放下一个项目功能。

目前为 2024-07-13 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Web Skipper for Plex
  3. // @namespace https://github.com/x1ao4/web-skipper-for-plex
  4. // @version 1.2
  5. // @description Automatically skip intros, credits, and auto-play the next item on Plex Web.
  6. // @description:zh-CN 在 Plex Web 上实现自动跳过片头、片尾和自动播放下一个项目功能。
  7. // @description:zh-HK 在 Plex Web 上實現自動跳過片頭、片尾和自動播放下一個項目功能。
  8. // @description:zh-TW 在 Plex Web 上實現自動跳過介紹、名單和自動播放下一個項目功能。
  9. // @author x1ao4
  10. // @match https://app.plex.tv/*
  11. // @match http://localhost:32400/*
  12. // @match http://127.0.0.1:32400/*
  13. // @license MIT
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @grant GM_registerMenuCommand
  17. // @grant GM_unregisterMenuCommand
  18. // ==/UserScript==
  19.  
  20. (function() {
  21. 'use strict';
  22.  
  23. // 设置检查按钮和 “播放下一个” 元素的间隔
  24. const interval = 1000;
  25.  
  26. // 设置按钮的选择器
  27. const buttonSelector = 'button.AudioVideoFullPlayer-overlayButton-D2xSex';
  28.  
  29. // 保存菜单命令的 ID
  30. let skipIntroCmd, skipCreditsCmd, autoPlayNextCmd;
  31.  
  32. // 获取用户语言
  33. const userLang = navigator.language || navigator.userLanguage;
  34.  
  35. // 根据用户语言设置按钮文本
  36. const buttonTexts = {
  37. 'skipIntro': {
  38. 'af-ZA': 'Slaan inleiding oor',
  39. 'bg-BG': 'Прескачане на интродукцията',
  40. 'ca-ES': 'Salteu la Introducció',
  41. 'cs-CZ': 'Přeskočit úvod',
  42. 'da-DK': 'Spring over intro',
  43. 'de-DE': 'Überspringe Intro',
  44. 'el-GR': 'Παράλειψη Προλόγου',
  45. 'en-US': 'Skip Intro',
  46. 'es-419': 'Omitir Opening',
  47. 'es-ES': 'Saltar intro',
  48. 'et-EE': 'Jäta intro vahele',
  49. 'fi-FI': 'Ohita intro',
  50. 'fr-CA': 'Passer l\'intro',
  51. 'fr-FR': 'Passer l\'intro',
  52. 'he-IL': 'דלג על הפתיח',
  53. 'hr-HR': 'Preskakanje uvoda',
  54. 'hu-HU': 'Intro átugrása',
  55. 'is-IS': 'Sleppa inngangi',
  56. 'it-IT': 'Salta Intro',
  57. 'ja-JP': 'イントロをスキップ',
  58. 'ko-KR': '인트로 건너뛰기',
  59. 'lt-LT': 'Praleisti intro',
  60. 'nl-NL': 'Intro overslaan',
  61. 'nb-NO': 'Hopp over intro',
  62. 'pl-PL': 'Pomiń wstęp',
  63. 'pt-BR': 'Pular Intro',
  64. 'pt-PT': 'Saltar introdução',
  65. 'ro-RO': 'Sari peste introducere',
  66. 'ru-RU': 'Пропустить интро',
  67. 'sk-SK': 'Preskočiť intro',
  68. 'sl-SI': 'Preskoči predstavitev',
  69. 'sv-SE': 'Hoppa över intro',
  70. 'th-TH': 'ข้ามตอนต้น',
  71. 'tr-TR': 'Jeneriği Atla',
  72. 'uk-UA': 'Пропустити вступ',
  73. 'zh-TW': '略過介紹',
  74. 'zh-CN': '跳过片头',
  75. },
  76. 'skipCredits': {
  77. 'af-ZA': 'Slaan eindkrediete oor',
  78. 'cs-CZ': 'Přeskočit titulky',
  79. 'da-DK': 'Spring rulletekster over',
  80. 'de-DE': 'Abspann überspringen',
  81. 'el-GR': 'Παράλειψη των τίτλων τέλους',
  82. 'en-US': 'Skip Credits',
  83. 'es-ES': 'Saltar créditos',
  84. 'fi-FI': 'Ohita lopputekstit',
  85. 'fr-CA': 'Sauter le générique',
  86. 'fr-FR': 'Passer le générique',
  87. 'he-IL': 'דלג על כותרות סיום',
  88. 'hr-HR': 'Preskoči odjavnu špicu',
  89. 'hu-HU': 'Stáblista Átugrása',
  90. 'it-IT': 'Salta Crediti',
  91. 'ja-JP': 'クレジットをスキップ',
  92. 'ko-KR': '크레딧 건너뛰기',
  93. 'lt-LT': 'Praleisti subtitrus',
  94. 'nl-NL': 'Credits overslaan',
  95. 'nb-NO': 'Hopp over rulleteksten',
  96. 'pl-PL': 'Pomiń napisy końcowe',
  97. 'pt-BR': 'Pular créditos',
  98. 'pt-PT': 'Saltar créditos',
  99. 'ro-RO': 'Sari peste credite',
  100. 'ru-RU': 'Пропустить титры',
  101. 'sk-SK': 'Preskočiť titulky',
  102. 'sl-SI': 'Preskoči napise',
  103. 'sv-SE': 'Hoppa över eftertexter',
  104. 'tr-TR': 'Kredileri Atla',
  105. 'uk-UA': 'Пропустити титри',
  106. 'zh-TW': '跳過名單',
  107. 'zh-CN': '跳过片尾',
  108. },
  109. 'autoSkipIntro': {
  110. 'en-US': 'Auto Skip Intro',
  111. 'zh-TW': '自動跳過介紹',
  112. 'zh-CN': '自动跳过片头',
  113. },
  114. 'autoSkipCredits': {
  115. 'en-US': 'Auto Skip Credits',
  116. 'zh-TW': '自動跳過名單',
  117. 'zh-CN': '自动跳过片尾',
  118. },
  119. 'autoPlayNext': {
  120. 'en-US': 'Auto Play Next',
  121. 'zh-TW': '自動播放下一個',
  122. 'zh-CN': '自动播放下一个',
  123. },
  124. 'enabled': {
  125. 'en-US': 'On',
  126. 'zh-TW': '開',
  127. 'zh-CN': '开',
  128. },
  129. 'disabled': {
  130. 'en-US': 'Off',
  131. 'zh-TW': '關',
  132. 'zh-CN': '关',
  133. }
  134. };
  135.  
  136. // 获取对应语言的文本,如果没有找到,则使用英语作为默认语言
  137. function getText(textType) {
  138. return buttonTexts[textType][userLang] || buttonTexts[textType]['en-US'];
  139. }
  140.  
  141. // 获取按钮类型
  142. function getButtonType(buttonText) {
  143. for (let lang in buttonTexts['skipIntro']) {
  144. if (buttonTexts['skipIntro'][lang] === buttonText) {
  145. return 'skipIntro';
  146. }
  147. }
  148. for (let lang in buttonTexts['skipCredits']) {
  149. if (buttonTexts['skipCredits'][lang] === buttonText) {
  150. return 'skipCredits';
  151. }
  152. }
  153. return null;
  154. }
  155.  
  156. // 更新菜单命令
  157. function updateMenuCommands() {
  158. // 移除旧的菜单命令
  159. GM_unregisterMenuCommand(skipIntroCmd);
  160. GM_unregisterMenuCommand(skipCreditsCmd);
  161. GM_unregisterMenuCommand(autoPlayNextCmd);
  162.  
  163. // 为每个功能注册(不可用)新的菜单命令
  164. skipIntroCmd = GM_registerMenuCommand(getText('autoSkipIntro') + ' · ' + (GM_getValue('skipIntro', true) ? getText('enabled') : getText('disabled')), function() {
  165. GM_setValue('skipIntro', !GM_getValue('skipIntro', true));
  166. updateMenuCommands();
  167. });
  168.  
  169. skipCreditsCmd = GM_registerMenuCommand(getText('autoSkipCredits') + ' · ' + (GM_getValue('skipCredits', true) ? getText('enabled') : getText('disabled')), function() {
  170. GM_setValue('skipCredits', !GM_getValue('skipCredits', true));
  171. updateMenuCommands();
  172. });
  173.  
  174. autoPlayNextCmd = GM_registerMenuCommand(getText('autoPlayNext') + ' · ' + (GM_getValue('autoPlayNext', true) ? getText('enabled') : getText('disabled')), function() {
  175. GM_setValue('autoPlayNext', !GM_getValue('autoPlayNext', true));
  176. updateMenuCommands();
  177. });
  178. }
  179.  
  180. // 初始化菜单命令
  181. updateMenuCommands();
  182.  
  183. // 如果存在按钮,则点击按钮
  184. function clickButton(selector, isEnabled) {
  185. if (isEnabled) {
  186. const buttons = document.querySelectorAll(selector);
  187. for (const button of buttons) {
  188. let buttonType = getButtonType(button.innerText);
  189. if (buttonType && GM_getValue(buttonType, true)) {
  190. button.click();
  191. break;
  192. }
  193. }
  194. }
  195. }
  196.  
  197. // 检查元素是否存在
  198. function elementExists(selector) {
  199. return document.querySelector(selector) !== null;
  200. }
  201.  
  202. // 设置一个间隔来检查按钮和 “播放下一个” 元素,如果它们存在,则点击它们或按空格键
  203. setInterval(() => {
  204. clickButton(buttonSelector, true);
  205. if (GM_getValue('autoPlayNext', true) && elementExists('label.AudioVideoUpNext-autoPlayOn-FMTHL1')) {
  206. document.dispatchEvent(new KeyboardEvent('keydown', {key: ' ', keyCode: 32}));
  207. }
  208. }, interval);
  209. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址