影巢显示emby已入库以及未入库,支持主页,详情页,用户页面,合集页面,支持并适配移动端与pc端
// ==UserScript==
// @name 影巢Emby助手
// @version 0.5
// @description 影巢显示emby已入库以及未入库,支持主页,详情页,用户页面,合集页面,支持并适配移动端与pc端
// @author 楠
// @match *://hdhive.com/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// @icon https://hdhive.com/apple-touch-icon.png
// @namespace https://greasyfork.org/users/1514724
// ==/UserScript==
(function() {
'use strict';
let EMBY_CONFIG = {
HOST: GM_getValue("embyHost", ""),
API_KEY: GM_getValue("embyApiKey", "")
};
const state = {
processingItems: new Set(),
processedItems: new Set(),
embyCache: new Map()
};
const BUTTON_STYLES = {
posterBtn: {
size: '25px',
position: { top: '10px', right: '10px' },
has: {
bg: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)',
icon: '✓',
border: '2px solid rgba(255,255,255,0.8)'
},
notHas: {
bg: 'linear-gradient(135deg, #F44336 0%, #C62828 100%)',
icon: '✗',
border: '2px solid rgba(255,255,255,0.8)'
},
hoverEffect: 'scale(1.1)'
},
nameBtn: {
padding: '3px 10px',
marginTop: '5px',
fontSize: '11px',
has: {
bg: 'rgba(76, 175, 80, 0.15)',
text: '已入库',
textColor: '#4CAF50',
border: '1px solid rgba(76, 175, 80, 0.3)'
},
notHas: {
bg: 'rgba(244, 67, 54, 0.15)',
text: '未入库',
textColor: '#F44336',
border: '1px solid rgba(244, 67, 54, 0.3)'
},
hoverEffect: 'translateY(-1px)'
},
detailBtn: {
posterBtn: {
size: '30px',
position: { top: '15px', right: '15px' },
has: {
bg: 'linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)',
icon: '✓',
border: '2px solid rgba(255,255,255,0.9)'
},
notHas: {
bg: 'linear-gradient(135deg, #F44336 0%, #C62828 100%)',
icon: '✗',
border: '2px solid rgba(255,255,255,0.9)'
},
hoverEffect: 'scale(1.15)'
},
titleBtn: {
padding: '5px 15px',
marginLeft: '10px',
fontSize: '12px',
has: {
bg: 'rgba(76, 175, 80, 0.15)',
text: '已入库',
textColor: '#4CAF50',
border: '1px solid rgba(76, 175, 80, 0.4)'
},
notHas: {
bg: 'rgba(244, 67, 54, 0.15)',
text: '未入库',
textColor: '#F44336',
border: '1px solid rgba(244, 67, 54, 0.4)'
},
hoverEffect: 'translateY(-1px)'
}
},
searchYearBtn: {
padding: '3px 10px',
marginLeft: '10px',
fontSize: '11px',
has: {
bg: 'rgba(76, 175, 80, 0.15)',
text: '已入库',
textColor: '#4CAF50',
border: '1px solid rgba(76, 175, 80, 0.3)'
},
notHas: {
bg: 'rgba(244, 67, 54, 0.15)',
text: '未入库',
textColor: '#F44336',
border: '1px solid rgba(244, 67, 54, 0.3)'
},
hoverEffect: 'translateY(-1px)'
},
userPageBtn: {
padding: '3px 10px',
marginLeft: '8px',
fontSize: '11px',
has: {
bg: 'rgba(76, 175, 80, 0.35)',
text: '已入库',
textColor: '#4CAF50',
border: '1px solid rgba(76, 175, 80, 0.4)'
},
notHas: {
bg: 'rgba(244, 67, 54, 0.35)',
text: '未入库',
textColor: '#F44336',
border: '1px solid rgba(244, 67, 54, 0.4)'
},
hoverEffect: 'translateY(-1px)'
},
collectionBtn: {
padding: '3px 10px',
marginLeft: '10px',
fontSize: '11px',
has: {
bg: 'rgba(76, 175, 80, 0.15)',
text: '已入库',
textColor: '#4CAF50',
border: '1px solid rgba(76, 175, 80, 0.3)'
},
notHas: {
bg: 'rgba(244, 67, 54, 0.15)',
text: '未入库',
textColor: '#F44336',
border: '1px solid rgba(244, 67, 54, 0.3)'
},
hoverEffect: 'translateY(-1px)'
},
settingBtn: {
padding: '3px 10px',
marginRight: '10px',
fontSize: '11px',
has: {
bg: 'rgba(100, 181, 246, 0.35)',
text: 'Emby设置',
textColor: '#64B5F6',
border: '1px solid rgba(100, 181, 246, 0.4)'
},
hoverEffect: 'translateY(-1px)',
iconSize: '16px'
}
};
const style = document.createElement('style');
style.textContent = `
.emby-poster-btn {
position: absolute;
width: ${BUTTON_STYLES.posterBtn.size};
height: ${BUTTON_STYLES.posterBtn.size};
top: ${BUTTON_STYLES.posterBtn.position.top};
right: ${BUTTON_STYLES.posterBtn.position.right};
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: bold;
cursor: pointer;
z-index: 100;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.emby-poster-btn.has {
background: ${BUTTON_STYLES.posterBtn.has.bg};
color: white;
border: ${BUTTON_STYLES.posterBtn.has.border};
}
.emby-poster-btn.not-has {
background: ${BUTTON_STYLES.posterBtn.notHas.bg};
color: white;
border: ${BUTTON_STYLES.posterBtn.notHas.border};
}
.emby-poster-btn:hover {
transform: ${BUTTON_STYLES.posterBtn.hoverEffect};
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
.emby-name-btn {
display: inline-flex;
align-items: center;
margin-top: ${BUTTON_STYLES.nameBtn.marginTop};
padding: ${BUTTON_STYLES.nameBtn.padding};
border-radius: 12px;
font-size: ${BUTTON_STYLES.nameBtn.fontSize};
font-weight: 600;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
background: transparent;
}
.emby-name-btn.has {
background: ${BUTTON_STYLES.nameBtn.has.bg};
color: ${BUTTON_STYLES.nameBtn.has.textColor};
border: ${BUTTON_STYLES.nameBtn.has.border};
}
.emby-name-btn.not-has {
background: ${BUTTON_STYLES.nameBtn.notHas.bg};
color: ${BUTTON_STYLES.nameBtn.notHas.textColor};
border: ${BUTTON_STYLES.nameBtn.notHas.border};
}
.emby-name-btn:hover {
transform: ${BUTTON_STYLES.nameBtn.hoverEffect};
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.emby-detail-poster-btn {
position: absolute;
width: ${BUTTON_STYLES.detailBtn.posterBtn.size};
height: ${BUTTON_STYLES.detailBtn.posterBtn.size};
top: ${BUTTON_STYLES.detailBtn.posterBtn.position.top};
right: ${BUTTON_STYLES.detailBtn.posterBtn.position.right};
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
cursor: pointer;
z-index: 100;
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.emby-detail-poster-btn.has {
background: ${BUTTON_STYLES.detailBtn.posterBtn.has.bg};
color: white;
border: ${BUTTON_STYLES.detailBtn.posterBtn.has.border};
}
.emby-detail-poster-btn.not-has {
background: ${BUTTON_STYLES.detailBtn.posterBtn.notHas.bg};
color: white;
border: ${BUTTON_STYLES.detailBtn.posterBtn.notHas.border};
}
.emby-detail-poster-btn:hover {
transform: ${BUTTON_STYLES.detailBtn.posterBtn.hoverEffect};
box-shadow: 0 8px 25px rgba(0,0,0,0.4);
}
.emby-detail-title-btn {
display: inline-flex;
align-items: center;
margin-left: ${BUTTON_STYLES.detailBtn.titleBtn.marginLeft};
padding: ${BUTTON_STYLES.detailBtn.titleBtn.padding};
border-radius: 15px;
font-size: ${BUTTON_STYLES.detailBtn.titleBtn.fontSize};
font-weight: 600;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
background: transparent;
}
.emby-detail-title-btn.has {
background: ${BUTTON_STYLES.detailBtn.titleBtn.has.bg};
color: ${BUTTON_STYLES.detailBtn.titleBtn.has.textColor};
border: ${BUTTON_STYLES.detailBtn.titleBtn.has.border};
}
.emby-detail-title-btn.not-has {
background: ${BUTTON_STYLES.detailBtn.titleBtn.notHas.bg};
color: ${BUTTON_STYLES.detailBtn.titleBtn.notHas.textColor};
border: ${BUTTON_STYLES.detailBtn.titleBtn.notHas.border};
}
.emby-detail-title-btn:hover {
transform: ${BUTTON_STYLES.detailBtn.titleBtn.hoverEffect};
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.emby-search-year-btn {
display: inline-flex;
align-items: center;
margin-left: ${BUTTON_STYLES.searchYearBtn.marginLeft};
padding: ${BUTTON_STYLES.searchYearBtn.padding};
border-radius: 12px;
font-size: ${BUTTON_STYLES.searchYearBtn.fontSize};
font-weight: 600;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
background: transparent;
}
.emby-search-year-btn.has {
background: ${BUTTON_STYLES.searchYearBtn.has.bg};
color: ${BUTTON_STYLES.searchYearBtn.has.textColor};
border: ${BUTTON_STYLES.searchYearBtn.has.border};
}
.emby-search-year-btn.not-has {
background: ${BUTTON_STYLES.searchYearBtn.notHas.bg};
color: ${BUTTON_STYLES.searchYearBtn.notHas.textColor};
border: ${BUTTON_STYLES.searchYearBtn.notHas.border};
}
.emby-search-year-btn:hover {
transform: ${BUTTON_STYLES.searchYearBtn.hoverEffect};
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.emby-user-page-btn {
display: inline-flex;
align-items: center;
margin-left: ${BUTTON_STYLES.userPageBtn.marginLeft};
padding: ${BUTTON_STYLES.userPageBtn.padding};
border-radius: 12px;
font-size: ${BUTTON_STYLES.userPageBtn.fontSize};
font-weight: 600;
cursor: default;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
opacity: 0.7;
}
.emby-user-page-btn.has {
background: ${BUTTON_STYLES.userPageBtn.has.bg};
color: ${BUTTON_STYLES.userPageBtn.has.textColor};
border: ${BUTTON_STYLES.userPageBtn.has.border};
}
.emby-user-page-btn.not-has {
background: ${BUTTON_STYLES.userPageBtn.notHas.bg};
color: ${BUTTON_STYLES.userPageBtn.notHas.textColor};
border: ${BUTTON_STYLES.userPageBtn.notHas.border};
}
.emby-user-page-btn:hover {
transform: ${BUTTON_STYLES.userPageBtn.hoverEffect};
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.emby-collection-btn {
display: inline-flex;
align-items: center;
margin-left: ${BUTTON_STYLES.collectionBtn.marginLeft};
padding: ${BUTTON_STYLES.collectionBtn.padding};
border-radius: 12px;
font-size: ${BUTTON_STYLES.collectionBtn.fontSize};
font-weight: 600;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
background: transparent;
}
.emby-collection-btn.has {
background: ${BUTTON_STYLES.collectionBtn.has.bg};
color: ${BUTTON_STYLES.collectionBtn.has.textColor};
border: ${BUTTON_STYLES.collectionBtn.has.border};
}
.emby-collection-btn.not-has {
background: ${BUTTON_STYLES.collectionBtn.notHas.bg};
color: ${BUTTON_STYLES.collectionBtn.notHas.textColor};
border: ${BUTTON_STYLES.collectionBtn.notHas.border};
}
.emby-collection-btn:hover {
transform: ${BUTTON_STYLES.collectionBtn.hoverEffect};
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.emby-setting-btn {
display: inline-flex;
align-items: center;
padding: ${BUTTON_STYLES.settingBtn.padding};
border-radius: 12px;
font-size: ${BUTTON_STYLES.settingBtn.fontSize};
font-weight: 600;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
background: ${BUTTON_STYLES.settingBtn.has.bg};
color: ${BUTTON_STYLES.settingBtn.has.textColor};
border: ${BUTTON_STYLES.settingBtn.has.border};
}
.emby-setting-btn:hover {
transform: ${BUTTON_STYLES.settingBtn.hoverEffect};
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
.emby-name-btn::before,
.emby-detail-title-btn::before,
.emby-search-year-btn::before,
.emby-user-page-btn::before,
.emby-collection-btn::before,
.emby-setting-btn::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
margin-right: 6px;
background-image: url('https://raw.githubusercontent.com/lige47/QuanX-icon-rule/main/icon/emby.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
filter: brightness(0.9);
}
.emby-detail-title-btn::before {
width: 18px;
height: 18px;
margin-right: 8px;
}
.MuiPopover-root .emby-poster-btn,
.MuiPopover-root .emby-name-btn,
.MuiPopover-root .emby-detail-poster-btn,
.MuiPopover-root .emby-detail-title-btn,
.MuiPopover-root .emby-search-year-btn,
.MuiPopover-root .emby-user-page-btn,
.MuiPopover-root .emby-collection-btn,
.MuiPopover-root .emby-setting-btn {
z-index: 1500;
}
#hdhive-notice {
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
padding: 8px 12px;
background-color: #ff9800;
color: #fff;
font-size: 14px;
font-weight: bold;
border-radius: 4px;
z-index: 9999;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
.emby-settings-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
max-width: 90%;
background: #fff;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
z-index: 99999;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
display: none;
}
.emby-settings-header {
background: linear-gradient(135deg, #2196F3 0%, #0D47A1 100%);
color: white;
padding: 20px;
text-align: center;
position: relative;
}
.emby-settings-header h2 {
margin: 0;
font-size: 22px;
font-weight: 600;
}
.emby-settings-close {
position: absolute;
top: 15px;
right: 20px;
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.emby-settings-close:hover {
background: rgba(255,255,255,0.2);
}
.emby-settings-body {
padding: 25px;
}
.emby-settings-form-group {
margin-bottom: 20px;
}
.emby-settings-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
font-size: 15px;
}
.emby-settings-input {
width: 100%;
padding: 12px 15px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 15px;
transition: all 0.3s;
box-sizing: border-box;
}
.emby-settings-input:focus {
border-color: #2196F3;
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
outline: none;
}
.emby-settings-footer {
display: flex;
justify-content: flex-end;
padding: 15px 25px;
background: #f9f9f9;
border-top: 1px solid #eee;
}
.emby-settings-btn {
padding: 10px 20px;
border-radius: 6px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
border: none;
}
.emby-settings-save {
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
color: white;
margin-left: 10px;
}
.emby-settings-save:hover {
background: linear-gradient(135deg, #43A047 0%, #1B5E20 100%);
}
.emby-settings-cancel {
background: #f5f5f5;
color: #666;
}
.emby-settings-cancel:hover {
background: #e0e0e0;
}
.emby-settings-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.6);
z-index: 99998;
display: none;
}
#emby-save-notice {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
color: white;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 99999;
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
opacity: 0;
transform: translateX(100%);
transition: all 0.3s ease;
}
#emby-save-notice.show {
opacity: 1;
transform: translateX(0);
}
#emby-save-notice::before {
content: "✓";
display: inline-block;
margin-right: 8px;
font-size: 16px;
font-weight: bold;
}
`;
document.head.appendChild(style);
function isDetailPage() {
const path = window.location.pathname;
return /^\/(movie|tv)\/[\w-]+/.test(path);
}
function isUserPage() {
return window.location.pathname.startsWith('/user/');
}
function isCollectionPage() {
return window.location.pathname.startsWith('/collection/');
}
function checkEmbyResource(name, year) {
return new Promise((resolve) => {
const cacheKey = `${name}-${year}`;
if (state.embyCache.has(cacheKey)) {
resolve(state.embyCache.get(cacheKey));
return;
}
const searchUrl = `${EMBY_CONFIG.HOST}/emby/Items?api_key=${EMBY_CONFIG.API_KEY}&SearchTerm=${encodeURIComponent(name)}&IncludeItemTypes=Movie,Series&Recursive=true&Fields=ProductionYear&Limit=20`;
GM_xmlhttpRequest({
method: 'GET',
url: searchUrl,
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
let hasResource = false;
if (data.Items && data.Items.length > 0) {
const matchedItem = data.Items.find(item => {
const itemName = item.Name;
const itemYear = item.ProductionYear;
return itemName === name && itemYear === year;
});
hasResource = !!matchedItem;
}
state.embyCache.set(cacheKey, hasResource);
resolve(hasResource);
} catch (error) {
state.embyCache.set(cacheKey, false);
resolve(false);
}
},
onerror: function(error) {
state.embyCache.set(cacheKey, false);
resolve(false);
}
});
});
}
function extractInfoFromPoster(poster) {
const nameElement = poster.querySelector('p.mui-1kj6qkz');
const yearElement = poster.querySelector('p.mui-1mfu778');
if (nameElement && yearElement) {
const name = nameElement.textContent.trim();
const year = parseInt(yearElement.textContent.trim(), 10);
if (name && !isNaN(year)) {
return { name, year, element: poster };
}
}
return null;
}
function extractInfoFromDetail() {
const titleElement = document.querySelector('h1.mui-oqdiq5');
if (titleElement) {
const titleText = titleElement.childNodes[0].textContent.trim();
const yearElement = titleElement.querySelector('p.mui-eo8gqg');
if (yearElement) {
const yearText = yearElement.textContent.trim();
const yearMatch = yearText.match(/\((\d{4})\)/);
if (yearMatch) {
const year = parseInt(yearMatch[1], 10);
return { name: titleText, year };
}
}
}
return null;
}
function extractInfoFromUserPage(element) {
const text = element.textContent.trim();
const match = text.match(/(.+?)\s*\((\d{4})\)/);
if (match) {
const name = match[1].trim();
const year = parseInt(match[2], 10);
return { name, year, element };
}
return null;
}
function extractInfoFromSearchYear(element) {
const text = element.textContent.trim();
const match = text.match(/\((\d{4})\)/);
if (match) {
const year = parseInt(match[1], 10);
const nameElement = element.previousElementSibling;
if (nameElement) {
const name = nameElement.textContent.trim();
return { name, year, element };
}
}
return null;
}
function extractInfoFromCollection(element) {
const nameElement = element.querySelector('p.mui-1kj6qkz');
const yearElement = element.querySelector('p.mui-1mfu778');
if (nameElement && yearElement) {
const name = nameElement.textContent.trim();
const year = parseInt(yearElement.textContent.trim(), 10);
if (name && !isNaN(year)) {
return { name, year, element };
}
}
return null;
}
function createPosterButton(hasResource) {
const btn = document.createElement('div');
btn.className = `emby-poster-btn ${hasResource ? 'has' : 'not-has'}`;
btn.textContent = hasResource ? BUTTON_STYLES.posterBtn.has.icon : BUTTON_STYLES.posterBtn.notHas.icon;
btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源';
return btn;
}
function createNameButton(hasResource) {
const btn = document.createElement('span');
btn.className = `emby-name-btn ${hasResource ? 'has' : 'not-has'}`;
btn.textContent = hasResource ? BUTTON_STYLES.nameBtn.has.text : BUTTON_STYLES.nameBtn.notHas.text;
btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源';
return btn;
}
function createDetailPosterButton(hasResource) {
const btn = document.createElement('div');
btn.className = `emby-detail-poster-btn ${hasResource ? 'has' : 'not-has'}`;
btn.textContent = hasResource ? BUTTON_STYLES.detailBtn.posterBtn.has.icon : BUTTON_STYLES.detailBtn.posterBtn.notHas.icon;
btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源';
return btn;
}
function createDetailTitleButton(hasResource) {
const btn = document.createElement('span');
btn.className = `emby-detail-title-btn ${hasResource ? 'has' : 'not-has'}`;
btn.textContent = hasResource ? BUTTON_STYLES.detailBtn.titleBtn.has.text : BUTTON_STYLES.detailBtn.titleBtn.notHas.text;
btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源';
return btn;
}
function createSearchYearButton(hasResource) {
const btn = document.createElement('span');
btn.className = `emby-search-year-btn ${hasResource ? 'has' : 'not-has'}`;
btn.textContent = hasResource ? BUTTON_STYLES.searchYearBtn.has.text : BUTTON_STYLES.searchYearBtn.notHas.text;
btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源';
return btn;
}
function createUserPageButton(hasResource) {
const btn = document.createElement('span');
btn.className = `emby-user-page-btn ${hasResource ? 'has' : 'not-has'}`;
const state = hasResource ? BUTTON_STYLES.userPageBtn.has : BUTTON_STYLES.userPageBtn.notHas;
btn.textContent = state.text;
btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源';
btn.disabled = true;
return btn;
}
function createCollectionButton(hasResource) {
const btn = document.createElement('span');
btn.className = `emby-collection-btn ${hasResource ? 'has' : 'not-has'}`;
btn.textContent = hasResource ? BUTTON_STYLES.collectionBtn.has.text : BUTTON_STYLES.collectionBtn.notHas.text;
btn.title = hasResource ? 'Emby库中有此资源' : 'Emby库中无此资源';
return btn;
}
function createSettingButton() {
const btn = document.createElement('span');
btn.className = 'emby-setting-btn';
btn.textContent = BUTTON_STYLES.settingBtn.has.text;
btn.title = '配置Emby服务器设置';
return btn;
}
async function processPoster(poster) {
const itemKey = `poster-${poster.href}`;
if (state.processedItems.has(itemKey) || state.processingItems.has(itemKey)) {
return;
}
state.processingItems.add(itemKey);
const info = extractInfoFromPoster(poster);
if (!info) {
state.processingItems.delete(itemKey);
return;
}
try {
const hasResource = await checkEmbyResource(info.name, info.year);
if (!poster.querySelector('.emby-poster-btn')) {
const posterImageContainer = poster.querySelector('div.mui-1daepjq');
if (posterImageContainer) {
const btn = createPosterButton(hasResource);
posterImageContainer.style.position = 'relative';
posterImageContainer.appendChild(btn);
}
}
if (!poster.querySelector('.emby-name-btn')) {
const yearElement = poster.querySelector('p.mui-1mfu778');
if (yearElement) {
const btn = createNameButton(hasResource);
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'center';
buttonContainer.style.width = '100%';
buttonContainer.appendChild(btn);
yearElement.parentNode.insertBefore(buttonContainer, yearElement.nextSibling);
}
}
state.processedItems.add(itemKey);
} catch (error) {
} finally {
state.processingItems.delete(itemKey);
}
}
async function processDetailPage() {
if (!isDetailPage()) return;
const detailKey = 'detail-page';
if (state.processedItems.has(detailKey) || state.processingItems.has(detailKey)) {
return;
}
state.processingItems.add(detailKey);
const info = extractInfoFromDetail();
if (!info) {
state.processingItems.delete(detailKey);
return;
}
try {
const hasResource = await checkEmbyResource(info.name, info.year);
if (!document.querySelector('.emby-detail-poster-btn')) {
const posterContainer = document.querySelector('.mui-ja4wo8, .mui-77cdso');
if (posterContainer) {
const btn = createDetailPosterButton(hasResource);
posterContainer.style.position = 'relative';
posterContainer.appendChild(btn);
}
}
if (!document.querySelector('.emby-detail-title-btn')) {
const titleElement = document.querySelector('.mui-oqdiq5');
if (titleElement) {
const btn = createDetailTitleButton(hasResource);
titleElement.parentNode.insertBefore(btn, titleElement.nextSibling);
}
}
state.processedItems.add(detailKey);
} catch (error) {
} finally {
state.processingItems.delete(detailKey);
}
}
async function processSearchYearButtons() {
const resultItems = document.querySelectorAll('a[href*="/tmdb/"]');
for (const item of resultItems) {
const itemKey = `search-${item.href}`;
if (state.processedItems.has(itemKey) || state.processingItems.has(itemKey)) {
continue;
}
state.processingItems.add(itemKey);
const yearText = item.querySelector('.MuiTypography-body2');
if (yearText && yearText.textContent.includes('(')) {
const info = extractInfoFromSearchYear(yearText);
if (info) {
try {
const hasResource = await checkEmbyResource(info.name, info.year);
if (!yearText.parentNode.querySelector('.emby-search-year-btn')) {
const btn = createSearchYearButton(hasResource);
yearText.parentNode.insertBefore(btn, yearText.nextSibling);
}
} catch (error) {
}
}
}
state.processedItems.add(itemKey);
state.processingItems.delete(itemKey);
}
}
async function processUserPageButtons() {
function showNotice(msg) {
if (document.getElementById('hdhive-notice')) return;
const div = document.createElement('div');
div.id = 'hdhive-notice';
div.textContent = msg;
document.body.appendChild(div);
}
async function addButtonsToElements(elements) {
let added = false;
for (const el of elements) {
const itemKey = `user-${el.textContent}`;
if (state.processedItems.has(itemKey) || state.processingItems.has(itemKey)) {
continue;
}
state.processingItems.add(itemKey);
const info = extractInfoFromUserPage(el);
if (info) {
try {
const hasResource = await checkEmbyResource(info.name, info.year);
if (!el.querySelector('.emby-user-page-btn')) {
const btn = createUserPageButton(hasResource);
el.appendChild(btn);
added = true;
}
} catch (error) {
}
}
state.processedItems.add(itemKey);
state.processingItems.delete(itemKey);
}
return added;
}
function waitForElements(selector, callback, maxTry = 40, intervalTime = 300) {
let count = 0;
const timer = setInterval(async () => {
const elements = Array.from(document.querySelectorAll(selector))
.filter(el => /\(\d{4}\)/.test(el.textContent));
if (elements.length > 0) {
const added = await callback(elements);
if (added) {
const notice = document.getElementById('hdhive-notice');
if (notice) notice.remove();
}
clearInterval(timer);
} else if (++count >= maxTry) {
showNotice('页面内容正在渲染,按钮稍后显示或请刷新页面');
clearInterval(timer);
}
}, intervalTime);
}
waitForElements('p', addButtonsToElements);
}
async function processCollectionButtons() {
if (!isCollectionPage()) return;
const collectionItems = document.querySelectorAll('a.MuiBox-root.mui-ytumd6, a.mui-r5wu0g');
for (const item of collectionItems) {
const itemKey = `collection-${item.href}`;
if (state.processedItems.has(itemKey) || state.processingItems.has(itemKey)) {
continue;
}
state.processingItems.add(itemKey);
const info = extractInfoFromCollection(item);
if (info) {
try {
const hasResource = await checkEmbyResource(info.name, info.year);
if (!item.querySelector('.emby-poster-btn')) {
const posterImageContainer = item.querySelector('div.mui-1daepjq, div.mui-19aj6fg');
if (posterImageContainer) {
const btn = createPosterButton(hasResource);
posterImageContainer.style.position = 'relative';
posterImageContainer.appendChild(btn);
}
}
if (!item.querySelector('.emby-collection-btn')) {
const btn = createCollectionButton(hasResource);
const yearElement = item.querySelector('p.mui-1mfu778');
if (yearElement) {
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'center';
buttonContainer.style.width = '100%';
buttonContainer.appendChild(btn);
yearElement.parentNode.insertBefore(buttonContainer, yearElement.nextSibling);
}
}
} catch (error) {
}
}
state.processedItems.add(itemKey);
state.processingItems.delete(itemKey);
}
}
async function processAllPosters() {
const posters = document.querySelectorAll('a.MuiBox-root.mui-ytumd6');
for (const poster of posters) {
await processPoster(poster);
}
const popoverPosters = document.querySelectorAll('.MuiPopover-root a.MuiBox-root.mui-ytumd6');
for (const poster of popoverPosters) {
await processPoster(poster);
}
await processDetailPage();
await processSearchYearButtons();
await processCollectionButtons();
if (isUserPage()) {
await processUserPageButtons();
}
}
function setupSearchListener() {
const searchInput = document.querySelector('input[type="text"][name="search"]');
if (searchInput) {
searchInput.addEventListener('input', () => {
state.processedItems.clear();
state.processingItems.clear();
setTimeout(processAllPosters, 1000);
});
}
}
function setupUrlChangeListener() {
let currentUrl = window.location.href;
const observer = new MutationObserver(() => {
if (window.location.href !== currentUrl) {
currentUrl = window.location.href;
state.processedItems.clear();
state.processingItems.clear();
state.embyCache.clear();
setTimeout(processAllPosters, 1000);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
function setupSearchDialogListener() {
const observer = new MutationObserver(() => {
const searchDialog = document.querySelector('.MuiDialog-paper');
if (searchDialog) {
state.processedItems.clear();
state.processingItems.clear();
setTimeout(processAllPosters, 500);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
function showSaveSuccessNotice() {
let notice = document.getElementById('emby-save-notice');
if (!notice) {
notice = document.createElement('div');
notice.id = 'emby-save-notice';
notice.textContent = '设置保存成功!';
document.body.appendChild(notice);
}
notice.classList.add('show');
setTimeout(() => {
notice.classList.remove('show');
setTimeout(() => {
notice.remove();
}, 300);
}, 2000);
}
function createSettingsModal() {
const modal = document.createElement('div');
modal.className = 'emby-settings-modal';
modal.innerHTML = `
<div class="emby-settings-header">
<h2>Emby 设置</h2>
<button class="emby-settings-close">×</button>
</div>
<div class="emby-settings-body">
<div class="emby-settings-form-group">
<label class="emby-settings-label" for="emby-host">Emby 地址</label>
<input type="text" id="emby-host" class="emby-settings-input" placeholder="http/s://emby地址">
</div>
<div class="emby-settings-form-group">
<label class="emby-settings-label" for="emby-api-key">API 密钥</label>
<input type="password" id="emby-api-key" class="emby-settings-input" placeholder="输入您的Emby API密钥">
</div>
</div>
<div class="emby-settings-footer">
<button class="emby-settings-btn emby-settings-cancel">取消</button>
<button class="emby-settings-btn emby-settings-save">保存</button>
</div>
`;
const overlay = document.createElement('div');
overlay.className = 'emby-settings-overlay';
document.body.appendChild(modal);
document.body.appendChild(overlay);
modal.querySelector('.emby-settings-close').addEventListener('click', closeSettingsModal);
modal.querySelector('.emby-settings-cancel').addEventListener('click', closeSettingsModal);
modal.querySelector('.emby-settings-save').addEventListener('click', saveSettings);
document.getElementById('emby-host').value = EMBY_CONFIG.HOST;
document.getElementById('emby-api-key').value = EMBY_CONFIG.API_KEY;
return { modal, overlay };
}
function showSettingsModal() {
const { modal, overlay } = createSettingsModal();
modal.style.display = 'block';
overlay.style.display = 'block';
overlay.addEventListener('click', closeSettingsModal);
}
function closeSettingsModal() {
const modal = document.querySelector('.emby-settings-modal');
const overlay = document.querySelector('.emby-settings-overlay');
if (modal && overlay) {
modal.style.display = 'none';
overlay.style.display = 'none';
setTimeout(() => {
modal.remove();
overlay.remove();
}, 300);
}
}
function saveSettings() {
const host = document.getElementById('emby-host').value.trim();
const apiKey = document.getElementById('emby-api-key').value.trim();
if (!host || !apiKey) {
alert('请填写完整的Emby设置信息');
return;
}
GM_setValue("embyHost", host);
GM_setValue("embyApiKey", apiKey);
EMBY_CONFIG.HOST = host;
EMBY_CONFIG.API_KEY = apiKey;
state.embyCache.clear();
state.processedItems.clear();
state.processingItems.clear();
processAllPosters();
closeSettingsModal();
showSaveSuccessNotice();
}
function addSettingButtons() {
const checkInterval = setInterval(() => {
const target1 = document.querySelector('a.mui-1oi34mv');
const target2 = document.querySelector('button.mui-19y4szv');
if (target1 && target2) {
clearInterval(checkInterval);
const btn1 = createSettingButton();
btn1.addEventListener('click', () => {
showSettingsModal();
});
target1.parentNode.insertBefore(btn1, target1);
const btn2 = createSettingButton();
btn2.addEventListener('click', () => {
showSettingsModal();
});
target2.parentNode.insertBefore(btn2, target2);
}
}, 500);
}
let mutationTimeout;
const observer = new MutationObserver(mutations => {
clearTimeout(mutationTimeout);
mutationTimeout = setTimeout(() => {
let shouldProcess = false;
for (const mutation of mutations) {
if (mutation.addedNodes.length > 0) {
shouldProcess = true;
break;
}
}
if (shouldProcess) {
processAllPosters();
}
}, 300);
});
processAllPosters();
setupSearchListener();
setupUrlChangeListener();
setupSearchDialogListener();
addSettingButtons();
observer.observe(document.body, {
childList: true,
subtree: true
});
})();