// ==UserScript==
// @name PT 影视预告片/豆瓣,快速搜索插件
// @namespace http://tampermonkey.net/
// @version 1.2
// @description 在HHCLUB、CHDBits、HDSky、Audiences、PTerClub和SSD 站点种子列表中,添加YouTube、抖音和豆瓣图标,点击后可即时在小窗口中搜索预告片
// @author Gemini & User
// @match *://hhanclub.top/torrents.php*
// @match *://hhan.club/torrents.php*
// @match *://ptchdbits.co/torrents.php*
// @match *://hdsky.my/torrents.php*
// @match *://audiences.me/torrents.php*
// @match *://pterclub.com/torrents.php*
// @match *://springsunday.net/torrents.php*
// @grant GM_addStyle
// @license MIT
// @icon https://p-f-rc.byteimg.com/tos-cn-i-b4xunb0e52/f8f047395c37492c902a24d55233d403~tplv-b4xunb0e52-image.image
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// 通用符号屏蔽处理函数
const removeExcludedContent = (text) => {
// 移除【符号及其后面的所有内容
const chineseBracketIndex = text.indexOf('【');
if (chineseBracketIndex !== -1) {
text = text.substring(0, chineseBracketIndex).trim();
}
// 移除[符号及其后面的所有内容
const squareBracketIndex = text.indexOf('[');
if (squareBracketIndex !== -1) {
text = text.substring(0, squareBracketIndex).trim();
}
return text;
};
// 站点配置
const getSiteConfig = () => {
const hostname = location.hostname;
if (/hhan(club\.top|\.club)/.test(hostname)) {
return {
name: 'HHCLUB',
selectors: {
row: '.torrent-table-sub-info',
title: 'a.torrent-info-text-name',
subtitle: '.torrent-info-text-small_name',
rating: 'img[src*="icon-imdb-new.svg"], img[src*="icon-douban.svg"]',
table: '.torrents-table'
},
buttonSize: '19px',
extractChineseTitle: (text) => {
text = removeExcludedContent(text);
// HHCLUB专用:移除/符号及其后面的所有内容
const slashIndex = text.indexOf('/');
if (slashIndex !== -1) {
text = text.substring(0, slashIndex).trim();
}
return text.split('|')[0].trim();
}
};
} else if (hostname.includes('ptchdbits.co')) {
return {
name: 'CHDBits',
selectors: {
row: 'table.torrents tr',
title: 'a[href*="details.php"] b',
subtitle: 'font.subtitle',
rating: 'img[src*="imdb.gif"]',
table: 'table.torrents'
},
buttonSize: '16px',
extractChineseTitle: (text) => {
text = removeExcludedContent(text);
const tags = ['官方', '独占', 'DIY', '国语', '中字', '繁体', '简体', '字幕', '双语', '原版', '特效', '限转', '首发', '杜比', 'DTS', 'HD', 'MA', 'TrueHD', '内嵌', '外挂', '英字', '日字', '韩字', '4K', '1080p', '720p', '2160p', 'UHD', 'HDR', '重编码', '重制', '修复', '收藏', '蓝光', '原盘', '压制'];
let content = text.trim();
let changed = true;
while (changed) {
changed = false;
for (let tag of tags) {
if (content.startsWith(tag)) {
content = content.substring(tag.length).trim();
changed = true;
break;
}
}
}
content = content.split('|')[0].trim();
const match = content.match(/^([^/]*?)(?=\s*[/第全]|\s*h[357]|$)/i);
const result = (match ? match[1] : content).replace(/\s*h[357]\s*$/i, '').trim();
return result && /[\u4e00-\u9fa5]/.test(result) && result.length >= 2 ? result : null;
}
};
} else if (hostname.includes('hdsky.my')) {
return {
name: 'HDSky',
selectors: {
row: 'table.torrents tr',
title: 'a[href*="details.php"] b span, a[href*="details.php"] b',
subtitle: 'td.embedded',
rating: 'img[src*="icon-imdb.png"], img[src*="icon-douban.png"]',
table: 'table.torrents'
},
buttonSize: '16px',
extractChineseTitle: (text) => {
text = removeExcludedContent(text);
const tags = ['官组', '禁转', 'DIY', '国语', '中字', '繁体', '简体', '字幕', '双语', '原版', '特效', '限转', '首发', '杜比', 'DTS', 'HD', 'MA', 'TrueHD', '内嵌', '外挂', '英字', '日字', '韩字', '4K', '1080p', '720p', '2160p', 'UHD', 'HDR', '重编码', '重制', '修复', '收藏', '蓝光', '原盘', '压制', 'DoVi+HDR', 'Dolby Vision', 'HDR10', 'Atmos', '次世代国语', 'DIY纯净版', 'DTS-X', '全景声国语', '粤语', '去头尾广告纯净版'];
let content = text.trim();
let changed = true;
while (changed) {
changed = false;
for (let tag of tags) {
if (content.startsWith(tag)) {
content = content.substring(tag.length).trim();
changed = true;
break;
}
}
}
content = content.split('|')[0].trim();
const match = content.match(/^([^/]*?)(?=\s*[/第全]|\s*h[357]|$)/i);
const result = (match ? match[1] : content).replace(/\s*h[357]\s*$/i, '').trim();
return result && /[\u4e00-\u9fa5]/.test(result) && result.length >= 2 ? result : null;
}
};
} else if (hostname.includes('audiences.me')) {
return {
name: 'Audiences',
selectors: {
row: 'table.torrents tr',
title: 'a[href*="details.php"] b',
subtitle: 'span[style*="padding: 2px"]',
table: 'table.torrents'
},
buttonSize: '16px',
extractChineseTitle: (text) => {
text = removeExcludedContent(text);
// Audiences特殊处理:移除又名及其后面的内容
const alsoKnownIndex = text.indexOf('又名');
if (alsoKnownIndex !== -1) {
text = text.substring(0, alsoKnownIndex).trim();
}
// Audiences特殊处理:移除*符号及其后面的内容
const asteriskIndex = text.indexOf('*');
if (asteriskIndex !== -1) {
text = text.substring(0, asteriskIndex).trim();
}
const tags = ['官方', '中字', '完结', 'Dolby Vision', '应求', 'HDR10', 'HDR10+', '官字组', '禁转', '动画', 'DIY', '限转', '国语', '粤语'];
let content = text.trim();
let changed = true;
while (changed) {
changed = false;
for (let tag of tags) {
if (content.startsWith(tag)) {
content = content.substring(tag.length).trim();
changed = true;
break;
}
}
}
// 处理特殊情况:如 "陆地键仙 第130集",提取主要标题
content = content.split('|')[0].trim();
// 匹配中文标题,特别处理剧集格式
let match = content.match(/^([^\s第]*[\u4e00-\u9fa5][^\s第]*?)(?:\s+第\d+集)?/);
if (!match) {
match = content.match(/^([^/]*?)(?=\s*[/第全]|\s*h[357]|$)/i);
}
const result = (match ? match[1] : content).replace(/\s*h[357]\s*$/i, '').trim();
return result && /[\u4e00-\u9fa5]/.test(result) && result.length >= 2 ? result : null;
}
};
} else if (hostname.includes('pterclub.com')) {
return {
name: 'PTerClub',
selectors: {
row: 'table.torrents tr',
title: 'a[href*="details.php"] b',
subtitle: 'div[style*="margin-top: 4px"]',
table: 'table.torrents'
},
buttonSize: '16px',
extractChineseTitle: (text) => {
text = removeExcludedContent(text);
// PTerClub特殊处理:移除又名及其后面的内容
const alsoKnownIndex = text.indexOf('又名');
if (alsoKnownIndex !== -1) {
text = text.substring(0, alsoKnownIndex).trim();
}
// PTerClub特殊处理:移除*符号及其后面的内容
const asteriskIndex = text.indexOf('*');
if (asteriskIndex !== -1) {
text = text.substring(0, asteriskIndex).trim();
}
const tags = ['官方', '中字', '完结', 'Dolby Vision', '应求', 'HDR10', 'HDR10+', '官字组', '禁转', '动画', 'DIY', '限转', '国语', '粤语', '杜比', 'DTS', 'HD', 'MA', 'TrueHD'];
let content = text.trim();
let changed = true;
while (changed) {
changed = false;
for (let tag of tags) {
if (content.startsWith(tag)) {
content = content.substring(tag.length).trim();
changed = true;
break;
}
}
}
// 处理特殊情况:如 "鹦鹉 第1-15集",提取主要标题
content = content.split('|')[0].trim();
// 匹配中文标题,特别处理剧集格式
let match = content.match(/^([^\s第]*[\u4e00-\u9fa5][^\s第]*?)(?:\s+第\d+.*)?/);
if (!match) {
match = content.match(/^([^/]*?)(?=\s*[/第全]|\s*h[357]|$)/i);
}
const result = (match ? match[1] : content).replace(/\s*h[357]\s*$/i, '').trim();
return result && /[\u4e00-\u9fa5]/.test(result) && result.length >= 2 ? result : null;
}
};
} else if (hostname.includes('springsunday.net')) {
return {
name: 'SSD',
selectors: {
row: 'table.torrents tr',
title: 'a[href*="details.php"]',
subtitle: 'div.torrent-smalldescr',
table: 'table.torrents'
},
buttonSize: '16px',
extractChineseTitle: (text) => {
text = removeExcludedContent(text);
// SSD特殊处理:移除/符号及其后面的所有内容
const slashIndex = text.indexOf('/');
if (slashIndex !== -1) {
text = text.substring(0, slashIndex).trim();
}
// SSD特殊处理:移除*符号及其后面的内容
const asteriskIndex = text.indexOf('*');
if (asteriskIndex !== -1) {
text = text.substring(0, asteriskIndex).trim();
}
const tags = ['CMCTV', '官方', '国配', '中字', '动画', '特效', '原生', '自购', '合集', '禁转', '驻站', 'CatEDU', 'HDR10', 'CMCTA', 'DIY', 'DoVi', 'HDR10+', '杜比', 'DTS', 'HD', 'MA', 'TrueHD', '内嵌', '外挂', '英字', '日字', '韩字', '4K', '1080p', '720p', '2160p', 'UHD', 'HDR', '重编码', '重制', '修复', '收藏', '蓝光', '原盘', '压制', 'Dolby Vision', '菁彩HDR', 'HLG', 'CC', '3D', '应求', '活动', '国语', '粤语'];
let content = text.trim();
let changed = true;
while (changed) {
changed = false;
for (let tag of tags) {
if (content.startsWith(tag)) {
content = content.substring(tag.length).trim();
changed = true;
break;
}
}
}
// 处理特殊情况
content = content.split('|')[0].trim();
// 匹配中文标题,特别处理剧集格式
let match = content.match(/^([^\s第]*[\u4e00-\u9fa5][^\s第]*?)(?:\s+第\d+.*)?/);
if (!match) {
match = content.match(/^([^/]*?)(?=\s*[/第全]|\s*h[357]|$)/i);
}
const result = (match ? match[1] : content).replace(/\s*h[357]\s*$/i, '').trim();
return result && /[\u4e00-\u9fa5]/.test(result) && result.length >= 2 ? result : null;
},
// SSD专用:从英文标题提取电影/剧名和年份
extractEnglishTitleForYouTube: (englishTitle) => {
let title = englishTitle;
// 第一步:提取年份
const yearMatch = title.match(/\.(\d{4})\./);
const year = yearMatch ? yearMatch[1] : '';
// 第二步:移除剧集信息(在其他处理之前)
// 匹配各种剧集格式:S任意数字E任意数字、S任意数字E任意数字-E任意数字、S任意数字等
title = title.replace(/\.S\d+E\d+(-E\d+)?/gi, '.');
title = title.replace(/\.S\d+/gi, '.');
// 第三步:如果找到年份,截取年份之前的部分作为标题部分
if (year) {
const yearIndex = title.indexOf(`.${year}.`);
if (yearIndex !== -1) {
title = title.substring(0, yearIndex);
}
} else {
// 如果没有年份,使用第一个技术信息之前的部分
const techPatterns = [
/\.\d{3,4}p\./i, // 分辨率
/\.BluRay\./i, // 来源
/\.Blu-ray\./i, // 来源
/\.WEB-DL\./i, // 来源
/\.HDTV\./i, // 来源
/\.ATVP\./i, // 来源:Apple TV+
/\.H26[45]\./i, // 编码
/\.x26[45]\./i, // 编码
];
let cutIndex = title.length;
for (let pattern of techPatterns) {
const match = title.match(pattern);
if (match && match.index < cutIndex) {
cutIndex = match.index;
}
}
title = title.substring(0, cutIndex);
}
// 第四步:移除地区代码(CEE、JPN、USA、GBR等)
title = title.replace(/\.(CEE|JPN|USA|GBR|FRA|GER|ITA|ESP|KOR|CHN|TWN|HKG|IND|RUS)\./gi, '.');
// 第五步:清理多余的点号并替换为空格
title = title.replace(/\.+/g, '.'); // 多个点号合并为一个
title = title.replace(/^\.|\.$/g, ''); // 移除开头和结尾的点号
title = title.replace(/\./g, ' ').trim(); // 将点号替换为空格
title = title.replace(/\s+/g, ' '); // 多个空格合并为一个
return { title: title, year: year };
}
};
}
return null;
};
const config = getSiteConfig();
if (!config) return;
const processedClass = `${config.name.toLowerCase()}-processed`;
// 样式和图标
GM_addStyle(`
.trailer-btn {
display: inline-flex !important;
align-items: center;
justify-content: center;
height: ${config.buttonSize};
width: ${config.buttonSize};
margin-right: ${config.name === 'HHCLUB' ? '8px' : '4px'};
cursor: pointer;
vertical-align: middle;
}
.trailer-btn:hover { opacity: 0.7; }
.trailer-douyin { color: #000; }
.trailer-douban { color: #00b600; }
`);
const icons = {
youtube: ``,
douyin: ``,
douban: ``
};
// 创建按钮
const createButton = (type, term, title) => {
const btn = document.createElement('a');
btn.className = `trailer-btn trailer-${type}`;
btn.title = `在${type === 'youtube' ? 'YouTube' : type === 'douyin' ? '抖音' : '豆瓣'}搜索: ${term}`;
btn.innerHTML = icons[type];
let url;
if (type === 'youtube') {
url = `https://www.youtube.com/results?search_query=${encodeURIComponent(term)}`;
} else if (type === 'douyin') {
url = `https://www.douyin.com/search/${encodeURIComponent(term)}`;
} else if (type === 'douban') {
url = `https://search.douban.com/movie/subject_search?search_text=${encodeURIComponent(term)}`;
}
btn.onclick = e => {
e.preventDefault();
e.stopPropagation();
window.open(url, `${type}_search`, 'width=800,height=600,resizable=yes,scrollbars=yes');
};
return btn;
};
// 生成YouTube搜索词
const getYouTubeSearch = (title, chineseTitle, mainTitle) => {
// SSD站点特殊处理:使用英文标题+年份
if (config.name === 'SSD' && config.extractEnglishTitleForYouTube) {
const { title: englishTitle, year } = config.extractEnglishTitleForYouTube(mainTitle);
return year ? `${englishTitle} ${year} Trailer` : `${englishTitle} Trailer`;
}
// 其他站点使用原有逻辑
const tvMatch = title.match(/^(.*?)\s+(S\d{1,2})\s+(\d{4})/);
if (tvMatch) return `${tvMatch[1].trim()} ${tvMatch[2]} ${tvMatch[3]} Trailer`;
const movieMatch = title.match(/^(.*?)\s+(\d{4})/);
return movieMatch ? `${movieMatch[1].trim()} ${movieMatch[2]} Trailer` : `${title} Trailer`;
};
// 处理种子行
const processRow = row => {
if (row.classList.contains(processedClass) ||
row.querySelector('td.colhead') ||
row.querySelector('.trailer-btn')) return;
const titleEl = row.querySelector(config.selectors.title);
const subtitleEl = row.querySelector(config.selectors.subtitle);
if (!titleEl || !subtitleEl) return;
const mainTitle = titleEl.textContent.trim();
let subtitleText = '';
// 特殊处理不同站点的副标题提取
if (config.name === 'HDSky') {
const spans = subtitleEl.querySelectorAll('span');
for (let span of spans) {
const text = span.textContent.trim();
if (/[\u4e00-\u9fa5]/.test(text) && !span.classList.contains('optiontag')) {
subtitleText = text;
break;
}
}
} else if (config.name === 'PTerClub') {
// PTerClub:从副标题div中找到包含中文的span
const span = subtitleEl.querySelector('span');
if (span) {
subtitleText = span.textContent || '';
}
} else if (config.name === 'SSD') {
// SSD:从div.torrent-smalldescr中找到包含中文的最后一个span
const spans = subtitleEl.querySelectorAll('span');
for (let i = spans.length - 1; i >= 0; i--) {
const span = spans[i];
const text = span.textContent.trim();
if (span.hasAttribute('title') && /[\u4e00-\u9fa5]/.test(text)) {
subtitleText = text;
break;
}
}
} else {
subtitleText = subtitleEl.textContent || '';
}
// 创建按钮
const chTitle = config.extractChineseTitle(subtitleText);
const ytBtn = createButton('youtube', getYouTubeSearch(mainTitle, chTitle, mainTitle), chTitle || mainTitle);
const dyBtn = chTitle ? createButton('douyin', `${chTitle} 预告片`, chTitle) : null;
const dbBtn = chTitle ? createButton('douban', chTitle, chTitle) : null;
// 插入按钮
if (config.name === 'HHCLUB') {
const ratingEl = row.querySelector(config.selectors.rating);
if (ratingEl) {
const container = ratingEl.parentElement.parentElement;
const ratingSpan = ratingEl.parentElement;
container.insertBefore(ytBtn, ratingSpan);
if (dyBtn) container.insertBefore(dyBtn, ratingSpan);
if (dbBtn) container.insertBefore(dbBtn, ratingSpan);
}
} else if (config.name === 'HDSky') {
const firstSpan = subtitleEl.querySelector('span.optiontag');
if (firstSpan) {
subtitleEl.insertBefore(ytBtn, firstSpan);
if (dyBtn) subtitleEl.insertBefore(dyBtn, firstSpan);
if (dbBtn) subtitleEl.insertBefore(dbBtn, firstSpan);
} else {
const firstChild = subtitleEl.firstChild;
if (firstChild) {
subtitleEl.insertBefore(ytBtn, firstChild);
if (dyBtn) subtitleEl.insertBefore(dyBtn, firstChild);
if (dbBtn) subtitleEl.insertBefore(dbBtn, firstChild);
}
}
} else if (config.name === 'Audiences') {
// Audiences:在副标题容器的父元素最前面插入图标
const container = subtitleEl.parentElement; // td.embedded
if (dbBtn) container.insertBefore(dbBtn, container.firstChild);
if (dyBtn) container.insertBefore(dyBtn, container.firstChild);
container.insertBefore(ytBtn, container.firstChild);
} else if (config.name === 'PTerClub') {
// PTerClub:在副标题div的最前面插入图标
if (dbBtn) subtitleEl.insertBefore(dbBtn, subtitleEl.firstChild);
if (dyBtn) subtitleEl.insertBefore(dyBtn, subtitleEl.firstChild);
subtitleEl.insertBefore(ytBtn, subtitleEl.firstChild);
} else if (config.name === 'SSD') {
// SSD:在torrent-smalldescr div的最前面插入图标
if (dbBtn) subtitleEl.insertBefore(dbBtn, subtitleEl.firstChild);
if (dyBtn) subtitleEl.insertBefore(dyBtn, subtitleEl.firstChild);
subtitleEl.insertBefore(ytBtn, subtitleEl.firstChild);
} else {
// CHDBits
const tagContainer = subtitleEl.querySelector('div[style*="display:inline-block"]');
const insertBefore = tagContainer || subtitleEl.firstChild;
subtitleEl.insertBefore(ytBtn, insertBefore);
if (dyBtn) subtitleEl.insertBefore(dyBtn, insertBefore);
if (dbBtn) subtitleEl.insertBefore(dbBtn, insertBefore);
}
row.classList.add(processedClass);
};
// 处理所有行
const processAll = () => document.querySelectorAll(config.selectors.row).forEach(processRow);
// 初始化
const init = () => {
setTimeout(processAll, 500);
const targetNode = document.querySelector(config.selectors.table);
if (targetNode) {
new MutationObserver(mutations => {
if (mutations.some(m => m.addedNodes.length > 0 &&
Array.from(m.addedNodes).some(n => n.nodeType === 1 &&
(n.tagName === 'TR' || n.querySelector?.('tr') || n.classList?.contains('torrent-table-sub-info'))))) {
setTimeout(processAll, 250);
}
}).observe(targetNode, { childList: true, subtree: true });
} else {
setInterval(processAll, 1000);
}
};
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init();
})();