您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Filter and highlight torrents with conditions
// ==UserScript== // @name TorrentFilter // @description Filter and highlight torrents with conditions // @version 1.0.3 // @author Anonymous // @match *.nexushd.org/* // @match uhdbits.org/* // @match totheglory.im/* // @match kp.m-team.cc/* // @match filelist.io/* // @match greatposterwall.com/* // @match pterclub.com/* // @match pt.sjtu.edu.cn/* // @require https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js // @require https://code.jquery.com/jquery-migrate-1.0.0.js // @icon http://www.nexushd.org/favicon.ico // @namespace d8e7078b-abee-407d-bcb6-096b59eeac17 // @license MIT // ==/UserScript== const $ = window.jQuery; //////////////////////////////////////////////////////////////////////////////////////////////// // Settings // 黑名单,一定会被过滤 const blackList = [ '', 'CMCT', 'ADE', 'FRDS', 'beAst', 'TLF', 'CHD', 'NYPAD' ].map(team => team.toLowerCase()); // 白名单,一定不被过滤 const whiteList = [ // BluRay '4EVERHD','(C)Z','AE','AJ8','AJP','antsy','Arucard','AURiNKO','AW','Ayaku','BBW','BG','BMF','BoK','BS','Cache', 'CALiGARi','Chotab','CRiME','Crow','D4','DiGG','DiR','DiRTY','disc','DBO','DoNOLi','E76','ECI','EML-HDTEAM','ESiR', 'ETH','EucHD','FANDANGO','FaP','fLAMEhd','FPG','FSK','Ft4U','FTO','fty','Funner','Geek','GMoRK','GoLDSToNE','GOS', 'GrapeHD','GRiND','GrupoHDS','H@M','H2','h264iRMU','HaB','HANDJOB','HDB','HDC','HDBiRD','HDEncX','HDL','HDxT','HiFi', 'HR','hymen','HZ','iCO','IDE','IMDTHS','incarnation','iNFLiKTED','iNK','iON','iOZO','Ivandro','IY','J4F','JAVLiU', 'JCH','k2','k4n0','kaBOOM','KalorZ','KiNGS','KiTTeN','KTN','KweeK','LP','LSHD','lulz','M794','MAGiC','MC','MCR','MdM', 'MMI','Mojo','momosas','Mondo','Moshy','NaRB','NCmt','NFHD','NiP','NiX','NorTV','NoVA','NWO','OAS','OB1','OmertaHD', 'ONYX','ORiGEN','PeeWee','PerfectionHD','PetaHD','PHiN','PiNG','PRESTiGE','Prime','PXE','QDP','QXE','RANDi','REDJOHN', 'Redµx','REPTiLE','RightSiZE','RuDE','RZF','S26','SA89','SFH','sJR','SK','Slappy','SLO','SLO4U','SMoKeR','SPeSHaL', 'SrS','SURFER','TAiCHi','THORA','TjHD','tK','TM','toho','ToK','tRuEHD','TSE','TsH','UioP','V','VanRay','Viet3X','(pr0n)', 'ViNYL','ViSUM','Vroom','wAm','XSHD','YanY','Z','Zim','D','ZMB','Z-XCV','CRiSC','CtrlHD','DON','EA','EbP','LolHD','NTb', 'SbR','TayTo','VietHD','de[42]','FoRM','NiBuRu','SaNcTi','Penumbra','Positive','SHeNTo','decibeL','D-Z0N3','FTW-HD', 'OISTiLe','TDD','ZQ','PTer','WiKi','c0kE','dps','EDPH','HDMaNiAcS','HDVN','HiDt','iFT','JKP','JM','KnG','LorD','playHD', 'prldm','PuTal','Q0S','RightSIZE','rttr','SaL','Skazhutin','TayTO','TBB','ZoroSenpai','147','Atomic','BARC0DE','BTN', 'BV','BYRHD','BdC','CHAOS','CNZ','CREATiVE','CRX','CarpeDiem','Dariush','Dave','DiVULGED','DigitalIrony','Envi','EuReKA', 'EwT','Friday','GALAXY','Japhson','KASHMiR','L9','LiNG','MGs','MKu','MaG','Narkyy','O2STK','ReQuEsT','Tron','VXS', 'W4NK3R','WMD','WMING','Whales','WiHD','WiLDCAT','XTA','i9','iKA','nmd','nek','npuer','xander','uR','xvistos','SPHD', 'eXterminator','PuTao', 'RiCO', 'TnP', 'SUPER', // Other BluRay 'E.N.D', 'GALVANiZE', 'NyHD', // WEB / HDTV 'FLUX', 'ADWeb', 'playWEB', 'TEPES', 'MZABI', 'AREY', 'CMRG', 'HDCTV', 'KHN', 'SMURF', 'ARiN' ].map(team => team.toLowerCase()); // 站名 const TTG = 'totheglory'; const PTERCLUB = 'pterclub'; const PUTAO = 'pt.sjtu'; const MTEAM = 'm-team'; const NHD = 'nexushd'; const BluRay = 'bluray'; const WEB = 'web'; const HDTV = 'hdtv'; const res1080p = '1080p'; const res720p = '720p'; const res2160p = '2160p' const colorRecipeLight = { name: '', year: 'Blue', media: 'Navy', resolution: 'Green', codec: 'DimGray', team: 'Red', background: '' } const colorRecipeDark = { name: '', year: 'DeepSkyBlue', media: 'DodgerBlue', resolution: 'Green', codec: 'Gray', team: 'Red', background: '' } const siteInfoMap = { [TTG]: { // 主页 hostName: 'totheglory.im', pages: [ 'browse.php' ], searchPage: /(?:&|\?)search_field=/i, // 过滤时是否移除条目。如果设置为true,被过滤的entry不会出现在页面中,否则只是不被高亮 removeFiltered: true, // 字段配色 colors: colorRecipeDark, watchType: { // 720p, 1080p, 2160p resolutions: [res1080p, res2160p], // 监测的media类型:BluRay, WEB, media: [BluRay, WEB, HDTV], // 白名单模式,设置为true时,既不在黑名单也不在白名单中的会被过滤 teamsWhiteListMode: true } }, [PTERCLUB]: { hostName: 'pterclub.com', pages: [ 'torrents.php', 'officialgroup.php' ], searchPage: /(?:&|\?)search=/i, removeFiltered: true, colors: colorRecipeDark, watchType: { resolutions: [res720p, res1080p, res2160p], media: [BluRay, WEB, HDTV], teamsWhiteListMode: true } }, [PUTAO]: { hostName: 'pt.sjtu.edu.cn', pages: [ 'torrents.php' ], searchPage: /(?:&|\?)search=/i, removeFiltered: true, colors: colorRecipeLight, watchType: { resolutions: [res1080p, res2160p], media: [BluRay, WEB, HDTV], teamsWhiteListMode: true } }, [MTEAM]: { hostName: 'm-team.cc', pages: [ 'browse', 'browse/movie' ], searchPage: /(?:&|\?)keyword=/i, waitForElement: 'div[class="flex flex-nowrap"]', removeFiltered: true, colors: colorRecipeDark, watchType: { resolutions: [res1080p, res2160p], media: [BluRay, WEB, HDTV], teamsWhiteListMode: true } }, [NHD]: { hostName: 'nexushd.org', pages: [ 'torrents.php' ], searchPage: /(?:&|\?)search=/i, removeFiltered: false, colors: colorRecipeLight, watchType: { resolutions: [res720p, res1080p, res2160p], media: [BluRay, WEB, HDTV], teamsWhiteListMode: true } } } const retryInterval = 500 //////////////////////////////////////////////////////////////////////////////////////////////// // Functions function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } function decodeTorrentTags(torrent_name) { let start = '' let end = '' // 移除末尾无效内容(方便识别压制组) let matchEnd = torrent_name.match(/\.(mkv|mp4|avi|ts|wmv|mpg)$/, 'i') if (matchEnd) { end = matchEnd[0] torrent_name = torrent_name.substring(0, torrent_name.length - end.length) } // 移除开头无效内容并记录移除部分的长度(用于最终恢复真实字符索引) let matchStart = torrent_name.match(/^(「.*」|\[.*\]) */) if (matchStart) { start = matchStart[0] torrent_name = torrent_name.substring(start.length) } let tags = { name: [-1, 0], year: [-1, 0], media: [-1, 0], resolution: [-1, 0], codec: [-1, 0], team: [-1, 0] }; let match_media = torrent_name.match(/\b(((UHD )?Blu-?Ray)|UHD|(BD|(HD)?DVD|WEB|HDTV)Rip|(HD)?DVD|HDTV|WEB(-?DL)?)\b/i); if (match_media) { tags.media = [match_media.index, match_media[0].length]; } let match_team = torrent_name.match(/\b(D-Z0N3|[^\s-@]*(@[^\s-]+)?)$/); if (match_team) { tags.team = [match_team.index, match_team[0].length]; } let match_codec = torrent_name.match(/\b(x26\d|h26\d|h\.?26\d|avc|hevc|xvid|divx|mpeg-\d|vc-1)\b/i); if (match_codec) { tags.codec = [match_codec.index, match_codec[0].length]; } let match_resolution = torrent_name.match(/\b((480|720|1080|2160)[ip]|4k(?! ?remaster| ?restoration| ?restore))\b/i); if (match_resolution) { tags.resolution = [match_resolution.index, match_resolution[0].length]; } // 注意符合年份regex的可能有多个,因为电影标题中可能有年份,所以要选择最后一个 let match_year = torrent_name.match(/\b\d{4}\b/g); if (match_year) { let year = match_year[match_year.length - 1]; tags.year = [torrent_name.lastIndexOf(year), year.length]; } let name_length = torrent_name.length; for (var tag in tags) { if (tags[tag][0] < name_length && tags[tag][0] > 0) { name_length = tags[tag][0]; } } tags.name = [0, name_length - 1]; // 所有index加上头部长度 for (const key in tags) { tags[key] = [tags[key][0] + start.length, tags[key][1]] } return tags } function whetherRemove(tags, title, siteName) { let site = siteInfoMap[siteName] || {} if (!site.watchType) { return false } let mediaToWatch = site.watchType.media let resolutionsToWatch = site.watchType.resolutions let teamsWhiteListMode = site.watchType.teamsWhiteListMode let team = ''; if (tags.team[0] >= 0) { team = title.substring(tags.team[0], tags.team[0] + tags.team[1]).toLowerCase(); } let resolution = ''; if (tags.resolution[0] >= 0) { resolution = title.substring(tags.resolution[0], tags.resolution[0] + tags.resolution[1]).toLowerCase(); } let media = ''; if (tags.media[0] >= 0) { media = title.substring(tags.media[0], tags.media[0] + tags.media[1]).toLowerCase(); } // 压制组过滤 var remove = whiteList.includes(team) ? false : blackList.includes(team) ? true : teamsWhiteListMode; // 分辨率过滤 if (!remove && resolutionsToWatch) { let res_ok = false; if (resolutionsToWatch.includes(resolution)) { res_ok = true; } else if (resolution.match(/4k/i) && resolutionsToWatch.includes('2160p')) { res_ok = true; } remove = !res_ok; } // 媒介过滤 if (!remove && mediaToWatch) { let media_ok = false; if (mediaToWatch.includes(media)) { media_ok = true; } else if (media.match(/(UHD BluRay)|BluRay|UHD|Blu-ray|BDRip/i) && mediaToWatch.includes('bluray')) { media_ok = true; } else if (media.match(/WEB-DL|WEBRip|WEB/i) && mediaToWatch.includes('web')) { media_ok = true; } else if (media.match(/DVDRip|HDDVD|DVD/i) && mediaToWatch.includes('dvd')) { media_ok = true; } remove = !media_ok; } if (remove) { console.log(`remove ${title}`); } else { console.log(`keep ${title}`); } return remove; } // tags are the slices of the title stored in a dictionary, {'media': [4, 1], 'team': [8, 1]} // renderFieldFunction renders a field, <color=red>text</color> // renderTitleFunction renders a title function renderTitle(originalText, tags, renderFieldFunction, renderTitleFunction, siteName) { let site = siteInfoMap[siteName] || {} let newText = ''; let colors = site.colors || {} // sort the tags by starting index of the tag let sorted_keys = Object.keys(tags).map(k => ([k, tags[k][0]])).sort((a, b) => (a[1] - b[1])); let j = 0; for (let i = 0; i < sorted_keys.length; i++) { let key = sorted_keys[i][0]; let idx_field = tags[key][0]; let len_field = tags[key][1]; let color_field = colors[key]; if (len_field > 0) { let text_field = originalText.substring(idx_field, idx_field + len_field); if (j < idx_field) { newText += originalText.substring(j, idx_field); } if (color_field) { newText += renderFieldFunction(text_field, color_field); } else { newText += text_field; } j = idx_field + len_field; } } // possible extension if (j < originalText.length) { newText += originalText.substring(j); } let renderedTitle = renderTitleFunction(newText); return renderedTitle; } function runWhenReady(readySelector, callback) { var tryNow = function() { var elem = document.querySelector(readySelector) if (elem) { callback(elem) } else { console.log(`Page not ready yet, retrying in ${retryInterval/1000} seconds`) setTimeout(tryNow, retryInterval) } } tryNow() } function update(siteName, isSearchPage) { if (!siteName) { return } const site = siteInfoMap[siteName] if (siteName === PTERCLUB || siteName === PUTAO || siteName === NHD) { let tbody = $('tbody').closest('table.torrents'); tbody.map(function() { let titles = $('a') .closest('table.torrentname') .find('a'); $.each(titles, function(_, obj) { if ($(obj).attr("title")) { let title = $(obj).attr("title").trim(); let tags = decodeTorrentTags(title); let remove = whetherRemove(tags, title, siteName); if (remove) { if (site.removeFiltered && !isSearchPage) { let row = obj.closest('table.torrentname').closest('tr'); row.remove(); } } else { let newText = renderTitle(title, tags, (text, color) => { return `<span style="color: ${color};">${text}</span>`; }, nt => { return nt; }, siteName ); $(obj).find('b').html(newText); if (site.colors && site.colors.background) { $(obj).find('b').css('background-color', site.colors.background); } } } }); }); } else if (siteName === TTG) { let tbody = $('tbody').closest('#torrent_table'); tbody.map(function() { let titles = $('a[class!="treport"]') .closest('div.name_left') .closest('tr.hover_hr') .find('a[class!="treport"][href^="/t/"] b'); $.each(titles, function(_, obj) { if ($(obj).prop('innerHTML')) { let title = $(obj).prop('innerText').split(/\r?\n/)[0].trim(); let tags = decodeTorrentTags(title); let remove = whetherRemove(tags, title, siteName); if (remove) { if (site.removeFiltered && !isSearchPage) { let row = obj.closest('tr.hover_hr'); row.remove(); } } else { let originalText = $(obj).prop('outerHTML'); let newText = renderTitle(title, tags, (text, color) => { return `<span style="color: ${color};">${text}</span>`; }, nt => { let regex = RegExp('(<b>.*)(' + escapeRegExp(title) + ')(.*<br>)', ''); return originalText.replace(regex, '$1' + nt + '$3'); }, siteName ); $(obj).html(newText); if (site.colors && site.colors.background) { $(obj).css('background-color', site.colors.background); } } } }); }) } else if (siteName === MTEAM) { let tbody = $('tbody').closest('#root').find('tbody') tbody.map(function() { let titles = $('strong') .closest('div[class="flex flex-nowrap"]') .find('strong') $.each(titles, function(_, obj) { if ($(obj).prop('textContent')) { let title = $(obj).prop('textContent').trim(); let tags = decodeTorrentTags(title); let remove = whetherRemove(tags, title, siteName); if (remove) { if (site.removeFiltered && !isSearchPage) { let row = obj.closest('tr') row.remove() } } else { let newText = renderTitle(title, tags, (text, color) => { return `<span style="color: ${color};">${text}</span>`; }, nt => { return nt }, siteName ); $(obj).html(`<strong>${newText}</strong>`); if (site.colors && site.colors.background) { $(obj).find('strong').css('background-color', site.colors.background); } } } }); }); } } (() => { 'use strict'; const siteName = Object.keys(siteInfoMap).find(sn => { let st = siteInfoMap[sn] return window.location.href.match(escapeRegExp(st.hostName)) }) let page = '' let site = {} if (siteName) { site = siteInfoMap[siteName] page = site.pages.find(pg => { let url = `${site.hostName}/${pg}` return window.location.href.match(escapeRegExp(url)) }) } if (!siteName || !page) { return } const isSearchPage = !!window.location.href.match(site.searchPage) console.log(`running in site ${siteName} and page ${page}.`) if (isSearchPage) { console.log(`running in search page`) } if (site.waitForElement) { // eslint-disable-next-line no-unused-vars runWhenReady(site.waitForElement, _ => { update(siteName, isSearchPage) }) } else { update(siteName, isSearchPage) } })();