// ==UserScript==
// @name Web Skipper for Plex
// @namespace https://github.com/x1ao4/web-skipper-for-plex
// @version 1.2
// @description Automatically skip intros, credits, and auto-play the next item on Plex Web.
// @description:zh-CN 在 Plex Web 上实现自动跳过片头、片尾和自动播放下一个项目功能。
// @description:zh-HK 在 Plex Web 上實現自動跳過片頭、片尾和自動播放下一個項目功能。
// @description:zh-TW 在 Plex Web 上實現自動跳過介紹、名單和自動播放下一個項目功能。
// @author x1ao4
// @match https://app.plex.tv/*
// @match http://localhost:32400/*
// @match http://127.0.0.1:32400/*
// @license MIT
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// ==/UserScript==
(function() {
'use strict';
// 设置检查按钮和 “开启自动播放” 元素的间隔
const interval = 1000;
// 设置按钮的选择器
const buttonSelector = 'button.AudioVideoFullPlayer-overlayButton-D2xSex';
// 保存菜单命令的 ID
let skipIntroCmd, skipCreditsCmd, autoPlayNextCmd;
// 获取用户语言
const userLang = navigator.language || navigator.userLanguage;
// 根据用户语言设置按钮文本
const buttonTexts = {
'skipIntro': {
'af-ZA': 'Slaan inleiding oor',
'bg-BG': 'Прескачане на интродукцията',
'ca-ES': 'Salteu la Introducció',
'cs-CZ': 'Přeskočit úvod',
'da-DK': 'Spring over intro',
'de-DE': 'Überspringe Intro',
'el-GR': 'Παράλειψη Προλόγου',
'en-US': 'Skip Intro',
'es-419': 'Omitir Opening',
'es-ES': 'Saltar intro',
'et-EE': 'Jäta intro vahele',
'fi-FI': 'Ohita intro',
'fr-CA': 'Passer l\'intro',
'fr-FR': 'Passer l\'intro',
'he-IL': 'דלג על הפתיח',
'hr-HR': 'Preskakanje uvoda',
'hu-HU': 'Intro átugrása',
'is-IS': 'Sleppa inngangi',
'it-IT': 'Salta Intro',
'ja-JP': 'イントロをスキップ',
'ko-KR': '인트로 건너뛰기',
'lt-LT': 'Praleisti intro',
'nl-NL': 'Intro overslaan',
'nb-NO': 'Hopp over intro',
'pl-PL': 'Pomiń wstęp',
'pt-BR': 'Pular Intro',
'pt-PT': 'Saltar introdução',
'ro-RO': 'Sari peste introducere',
'ru-RU': 'Пропустить интро',
'sk-SK': 'Preskočiť intro',
'sl-SI': 'Preskoči predstavitev',
'sv-SE': 'Hoppa över intro',
'th-TH': 'ข้ามตอนต้น',
'tr-TR': 'Jeneriği Atla',
'uk-UA': 'Пропустити вступ',
'zh-TW': '略過介紹',
'zh-CN': '跳过片头',
},
'skipCredits': {
'af-ZA': 'Slaan eindkrediete oor',
'cs-CZ': 'Přeskočit titulky',
'da-DK': 'Spring rulletekster over',
'de-DE': 'Abspann überspringen',
'el-GR': 'Παράλειψη των τίτλων τέλους',
'en-US': 'Skip Credits',
'es-ES': 'Saltar créditos',
'fi-FI': 'Ohita lopputekstit',
'fr-CA': 'Sauter le générique',
'fr-FR': 'Passer le générique',
'he-IL': 'דלג על כותרות סיום',
'hr-HR': 'Preskoči odjavnu špicu',
'hu-HU': 'Stáblista Átugrása',
'it-IT': 'Salta Crediti',
'ja-JP': 'クレジットをスキップ',
'ko-KR': '크레딧 건너뛰기',
'lt-LT': 'Praleisti subtitrus',
'nl-NL': 'Credits overslaan',
'nb-NO': 'Hopp over rulleteksten',
'pl-PL': 'Pomiń napisy końcowe',
'pt-BR': 'Pular créditos',
'pt-PT': 'Saltar créditos',
'ro-RO': 'Sari peste credite',
'ru-RU': 'Пропустить титры',
'sk-SK': 'Preskočiť titulky',
'sl-SI': 'Preskoči napise',
'sv-SE': 'Hoppa över eftertexter',
'tr-TR': 'Kredileri Atla',
'uk-UA': 'Пропустити титри',
'zh-TW': '跳過名單',
'zh-CN': '跳过片尾',
},
'autoSkipIntro': {
'en-US': 'Auto Skip Intro',
'zh-TW': '自動跳過介紹',
'zh-CN': '自动跳过片头',
},
'autoSkipCredits': {
'en-US': 'Auto Skip Credits',
'zh-TW': '自動跳過名單',
'zh-CN': '自动跳过片尾',
},
'autoPlayNext': {
'en-US': 'Auto Play Next',
'zh-TW': '自動播放下一個',
'zh-CN': '自动播放下一个',
},
'enabled': {
'en-US': 'On',
'zh-TW': '開',
'zh-CN': '开',
},
'disabled': {
'en-US': 'Off',
'zh-TW': '關',
'zh-CN': '关',
}
};
// 获取对应语言的文本,如果没有找到,则使用英语作为默认语言
function getText(textType) {
return buttonTexts[textType][userLang] || buttonTexts[textType]['en-US'];
}
// 获取按钮类型
function getButtonType(buttonText) {
for (let lang in buttonTexts['skipIntro']) {
if (buttonTexts['skipIntro'][lang] === buttonText) {
return 'skipIntro';
}
}
for (let lang in buttonTexts['skipCredits']) {
if (buttonTexts['skipCredits'][lang] === buttonText) {
return 'skipCredits';
}
}
return null;
}
// 更新菜单命令
function updateMenuCommands() {
// 移除旧的菜单命令
GM_unregisterMenuCommand(skipIntroCmd);
GM_unregisterMenuCommand(skipCreditsCmd);
GM_unregisterMenuCommand(autoPlayNextCmd);
// 为每个功能注册新的菜单命令
skipIntroCmd = GM_registerMenuCommand(getText('autoSkipIntro') + ' · ' + (GM_getValue('skipIntro', true) ? getText('enabled') : getText('disabled')), function() {
GM_setValue('skipIntro', !GM_getValue('skipIntro', true));
updateMenuCommands();
});
skipCreditsCmd = GM_registerMenuCommand(getText('autoSkipCredits') + ' · ' + (GM_getValue('skipCredits', true) ? getText('enabled') : getText('disabled')), function() {
GM_setValue('skipCredits', !GM_getValue('skipCredits', true));
updateMenuCommands();
});
autoPlayNextCmd = GM_registerMenuCommand(getText('autoPlayNext') + ' · ' + (GM_getValue('autoPlayNext', true) ? getText('enabled') : getText('disabled')), function() {
GM_setValue('autoPlayNext', !GM_getValue('autoPlayNext', true));
updateMenuCommands();
});
}
// 初始化菜单命令
updateMenuCommands();
// 如果存在按钮,则点击按钮
function clickButton(selector, isEnabled) {
if (isEnabled) {
const buttons = document.querySelectorAll(selector);
for (const button of buttons) {
let buttonType = getButtonType(button.innerText);
if (buttonType && GM_getValue(buttonType, true)) {
button.click();
break;
}
}
}
}
// 检查元素是否存在
function elementExists(selector) {
return document.querySelector(selector) !== null;
}
// 设置一个间隔来检查按钮和 “开启自动播放” 元素,如果它们存在,则点击它们或按空格键
setInterval(() => {
clickButton(buttonSelector, true);
if (GM_getValue('autoPlayNext', true) && elementExists('label.AudioVideoUpNext-autoPlayOn-FMTHL1')) {
window.dispatchEvent(new KeyboardEvent('keydown', {
key: ' ',
code: 'Space',
keyCode: 32,
which: 32,
charCode: 32
}));
}
}, interval);
})();