您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Format upload info
/* eslint-disable object-property-newline */ // ==UserScript== // @name Post Formatter // @description Format upload info // @version 1.3.2.15 // @author Anonymous inspired by Secant(TYT@NexusHD) // @match *.nexushd.org/* // @match pterclub.com/* // @match pt.sjtu.edu.cn/* // @match kp.m-team.cc/* // @match totheglory.im/* // @match greatposterwall.com/* // @match uhdbits.org/* // @grant GM_xmlhttpRequest // @require https://cdn.staticfile.org/jquery/2.1.4/jquery.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== //= ======================================================================================================== // constants and configurations const $ = window.jQuery const NHD = 'nexushd'; const PUTAO = 'pt.sjtu'; const MTEAM = 'm-team'; const TTG = 'totheglory'; const GPW = 'greatposterwall'; const UHD = 'uhdbits' const PTERCLUB = 'pterclub'; const IMGPILE = 'imgpile'; const PTPIMG = 'ptpimg'; const KSHARE = 'kshare.club'; const PIXHOST = 'pixhost'; const IMGBOX = 'imgbox'; const IMG4K = 'img4k'; const ILIKESHOTS = 'yes.ilikeshots.club' // 特殊组名备注 const weirdTeams = ['de[42]', 'D-Z0N3', 'WEB-DL'] const NEXUSPHP = 'nexusphp'; const GAZELLE = 'gazelle'; const MTORRENT = 'mTorrent' const allTagBoxes = ['box', 'hide', 'spoiler', 'expand'] // 匿名发布开关 const ANONYMOUS = true // medianinfo 键长(方便格式化) const mediainfoKeyLength = 31 const languageMap = {chinese_simplified: 'chs|zh', chinese_traditional: 'cht', mandarin: 'mandarin', cantonese: 'canto|cant|can', japanese: 'jp|jpn|jap|ja', korean: 'kor|ko', english: 'en|eng', french: 'fre|fra|fr', german: 'ger|deu|de', italian: 'ita|it', polish: 'pol|pl', romanian: 'rum|ron|ro', russian: 'ru|rus', spanish: 'spa|es', thai: 'tai', turkish: 'tur|tr', vietnamese: 'vi|vie', hindi: 'hin|hi', greek: 'gre|ell|el', swedish: 'swe|sv', azerbaijani: 'aze|az', bulgarian: 'bul|bg', danish: 'dan|da', estonian: 'est|et', finnish: 'fin|fi', hebrew: 'heb|he', croatian: 'hrv|hr', hungarian: 'hun|hu', icelandic: 'ice|isl|is', latvian: 'lav|lv', lithuanian: 'lit|lt', dutch: 'dut|nld|nl', norwegian: 'nor|no', portuguese: 'por|pt', slovenian: 'slv|sl', slovak: 'slo|slk|sk', latin: 'lat|la', ukrainian: 'ukr|uk', persian: 'per|fas|fa', arabic: 'ara|ar', brazilian_port: 'bra', czech: 'cze|ces|cs', idonesian: 'ido', serbian: 'srp|sr' } const invalidImageAnchor = 'NA' const weirdTeamsStr = weirdTeams.map(team => `(?:${escapeRegExp(team)})`).join('|') // 用于提取截图对比的多个压制组 const regexTeam = RegExp('\\b(?:(?:' + weirdTeamsStr + '|\\w[\\w-.#@~!$&* ]+)) ?(?:(?:\\([\\w-.#@~!$&* ]+\\)|<[\\w-.#@~!$&* ]+>|\\[[\\w-.#@~!$&* ]+\\]) ?(?:[\\w-.#@~!$&* ]+)?)?', 'i') // const regexTeamsSplitter = /\||,|\/|(?<!D)-(?=Z0N3)|(?<=D)-(?!Z0N3)|(?<!WEB)-(?=DL)|(?<=WEB)-(?!DL)|(?<!WEB|D)-(?!DL|Z0N3)| v\.?s\.? |>\s*v\.?s\.?\s*</i const allTeamSplitters = [',', '|', '/', ' vs ', ' vs. ', ' v.s ', ' v.s. ', '> vs <', ' - '] const [regexTeamsSplitter] = getTeamSplitterRegex(weirdTeams, allTeamSplitters, 'i') // 用于提取单个压制组 const regexTeamExtraction = RegExp('\\b(?:' + weirdTeamsStr + '|(?:[^\\s-@.]+(?:@[^\\s-@.]+)?))$', 'i') // max comparison teams in a comparison, must be larger than 1 const maxTeamsInComparison = 8 const maxNonWordsInTitled = 20 const minScreenshotsNonComparison = 3 // https://url1 const regexUrl = /https?:[A-Za-z0-9\-._~!$&'()*+;=:@/?]+/i // https://1.png const regexImage = RegExp( regexUrl.source + '?\\.(?:png|jpg)', 'ig') // [img]https://1.png[/img] or [img=https://1.png] const regexImageBbcode = RegExp( '(?:\\[img\\]' + regexImage.source + '\\[\\/img\\]|\\[img=' + regexImage.source + '\\])', 'ig') // [url=https://url1][img]https://1.png[/img][/url] const regexImageThumbBbcode = RegExp( '\\[url=' + regexUrl.source + '\\]\\s*' + regexImageBbcode.source + '\\s*\\[\\/url\\]', 'ig') // [box=team1 | team2 | team3][url=https://url1][img]https://1.png[/img][/url] [url=https://url2][img]https://2.png[/img][/url] [url=https://url3][img]https://3.png[/img][/url][/box] const regexComparisonThumbBbcodeBoxed = RegExp( '\\[(box|hide|expand|spoiler|quote)\\s*=\\s*\\w*?\\s*(' + regexTeam.source + '(?:\\s*(' + regexTeamsSplitter.source + ')\\s*)' + regexTeam.source + '(?:\\s*\\3\\s*' + regexTeam.source + `){0,${maxTeamsInComparison-2}}` + ')\\s*\\]((?:\\s*' + regexImageThumbBbcode.source + '\\s*)+)\\[\\/\\1\\]', 'mig') // team1 | team2 | team3 // [url=https://url1][img]https://1.png[/img][/url] [url=https://url2][img]https://2.png[/img][/url] [url=https://url3][img]https://3.png[/img][/url] const regexComparisonThumbBbcodeTitled = RegExp( '\\b(' + regexTeam.source + '(?:\\s*(' + regexTeamsSplitter.source + ')\\s*)' + regexTeam.source + '(?:\\s*\\2\\s*' + regexTeam.source + `){0,${maxTeamsInComparison-2}}` + `)[\\W]{0,${maxNonWordsInTitled}}\\r?\\n+\\s*((?:\\s*` + regexImageThumbBbcode.source + '\\s*)+)', 'mig') // [box=team1 | team2 | team3][img]https://1.png[/img] [img]https://2.png[/img] [img]https://3.png[/img][/box] const regexComparisonImageBbcodeBoxed = RegExp( '\\[(comparison|box|hide|expand|spoiler|quote)\\s*=\\s*\\w*?\\s*(' + regexTeam.source + '(?:\\s*(' + regexTeamsSplitter.source + ')\\s*)' + regexTeam.source + '(?:\\s*\\3\\s*' + regexTeam.source + `){0,${maxTeamsInComparison-2}}` + ')\\s*\\]((?:\\s*' + regexImageBbcode.source + '\\s*)+)\\[\\/\\1\\]', 'mig') // team1 | team2 | team3 // [img]https://1.png[/img] [img]https://2.png[/img] [img]https://3.png[/img] const regexComparisonImageBbcodeTitled = RegExp( '\\b(' + regexTeam.source + '(?:\\s*(' + regexTeamsSplitter.source + ')\\s*)' + regexTeam.source + '(?:\\s*\\2\\s*' + regexTeam.source + `){0,${maxTeamsInComparison-2}}` + `)[\\W]{0,${maxNonWordsInTitled}}\\r?\\n+\\s*((?:\\s*` + regexImageBbcode.source + '\\s*)+)', 'mig') // [comparison=team1, team2, team3]https://1.png https://2.png https://3.png[/comparison] const regexComparisonImageBoxed = RegExp( '\\[(comparison|box|hide|expand|spoiler|quote)=\\s*(' + regexTeam.source + '(?:\\s*(' + regexTeamsSplitter.source +')\\s*' + regexTeam.source + `){1,${maxTeamsInComparison-1}})\\]` + '(\\s*(?:' + regexImage.source + '(?:\\s+|\\s*,)\\s*)+' + regexImage.source + ')\\s*\\[\\/\\1\\]', 'mig') // [img]https://1.png[/img] [img]https://2.png[/img] [img]https://3.png[/img] const regexNonComparison = RegExp( '(' + regexImageBbcode.source + `\\s*){${minScreenshotsNonComparison},}`, 'mig') // 对比图相关正则表达式信息,由于可能不止一个会被匹配到,注意排序 const regexInfo = [ // [box=team1 | team2 | team3][url=https://url1][img]https://1.png[/img][/url] [url=https://url2][img]https://2.png[/img][/url] [url=https://url3][img]https://3.png[/img][/url][/box] { regex: regexComparisonThumbBbcodeBoxed, groupForTeams: 2, groupForTeamSplitter: 3, groupForUrls: 4, containerStyle: 'boxed', urlType: 'thumbBbcode' }, // team1 | team2 | team3 // [url=https://url1][img]https://1.png[/img][/url] [url=https://url2][img]https://2.png[/img][/url] [url=https://url3][img]https://3.png[/img][/url] { regex: regexComparisonThumbBbcodeTitled, groupForTeams: 1, groupForTeamSplitter: 2, groupForUrls: 3, containerStyle: 'titled', urlType: 'thumbBbcode' }, // [box=team1 | team2 | team3][img]https://1.png[/img] [img]https://2.png[/img] [img]https://3.png[/img][/box] { regex: regexComparisonImageBbcodeBoxed, groupForTeams: 2, groupForTeamSplitter: 3, groupForUrls: 4, containerStyle: 'boxed', urlType: 'imageBbcode' }, // team1 | team2 | team3 // [img]https://1.png[/img] [img]https://2.png[/img] [img]https://3.png[/img] { regex: regexComparisonImageBbcodeTitled, groupForTeams: 1, groupForTeamSplitter: 2, groupForUrls: 3, containerStyle: 'titled', urlType: 'imageBbcode' }, // [comparison=team1, team2, team3]https://1.png https://2.png https://3.png[/comparison] { regex: regexComparisonImageBoxed, groupForTeams: 2, groupForTeamSplitter: 3, groupForUrls: 4, containerStyle: 'boxed', urlType: 'image' }, // [img]https://1.png[/img] [img]https://2.png[/img] [img]https://3.png[/img] { regex: regexNonComparison, groupForTeams: -1, groupForTeamSplitter: -1, groupForUrls: 0, containerStyle: 'none', urlType: 'imageBbcode' } ] const siteInfoMap = { // bracket makes the value of the string 'nexushd' the true key or instead the string 'NHD' will be used as key [NHD]: { // 主页 hostName: 'nexushd.org', // 匹配页面 pages: { upload: 'upload.php', edit: 'edit.php', subtitles: 'subtitles.php' }, // 架构 construct: NEXUSPHP, // box 类标签,具备隐藏功能 targetBoxTag: 'box', // 是否支持 [box=...]的形式 boxSupportDescr: true, // [quote=A] displays as 'title': -A---, 'writer': -A wrote---, 'none': ------ quoteStyle: 'title', // 是否需要在 box 标签右括号末端加上换行 boxNeedBreakLine: false, // 不支持的标签 unsupportedTags: ['align', 'pre', 'email'], inputFile: $('input[type="file"][name="file"]'), nameBoxUpload: $('#name'), nameBoxEdit: $("input[type='text'][name='name']"), anonymousControl: $("input[name='uplver'][type='checkbox']")[0], descrBox: $('#descr'), smallDescBox: $("input[name='small_descr']"), imdbLinkBox: $("input[name='url'][type='text']"), doubanLinkBox: $("input[name='douban_url']"), categorySel: $('#browsecat'), sourceSel: $("select[name='source_sel']"), standardSel: $("select[name='standard_sel']"), processingSel: $("select[name='processing_sel']"), codecSel: $("select[name='codec_sel']"), pullMovieScore: false, translatedChineseNameInTitle: false, doubanIdInsteadofLink: false, screenshotsStyle: 'conventional', categoryInfo: { default: 0, movie: 101, tvSeries: 102, tvShow: 103, documentary: 104, animation: 105 }, sourceInfo: { default: 0, bluray: 1, hddvd: 2, dvd: 3, hdtv: 4, webdl: 7, webrip: 9 }, standardInfo: { default: 0, res1080p: 1, res1080i: 2, res720p: 3, res2160p: 6, sd: 4 }, processingInfo: { default: 0, raw: 1, encode: 2 }, codecInfo: { default: 0, h264: 1, h265: 2, vc1: 3, xvid: 4, mpeg2: 5, flac: 10, ape: 11 }, inputFileSubtitle: $('input[type="file"][name="file"]'), titleBoxSubtitle: $('input[type="text"][name="title"]'), languageSelSubtitle: $('select[name="sel_lang"]'), anonymousCheckSubtitle: $("input[name='uplver'][type='checkbox']")[0], subtitleInfo: { default: 0, english: 6, chinese_simplified: 25, chinese_traditional: 28, japanese: 15, french: 9, german: 10, italian: 14, korean: 16, spanish: 26, other: 18 } }, [PTERCLUB]: { hostName: 'pterclub.com', pages: { upload: 'upload.php', edit: 'edit.php', subtitles: 'subtitles.php' }, construct: NEXUSPHP, targetBoxTag: 'hide', boxSupportDescr: true, quoteStyle: 'title', boxNeedBreakLine: false, unsupportedTags: ['align', 'pre', 'email'], inputFile: $('input[type="file"][name="file"]'), nameBoxUpload: $('#name'), nameBoxEdit: $("input[type='text'][name='name']"), anonymousControl: $("input[name='uplver'][type='checkbox']")[0], descrBox: $('#descr'), smallDescBox: $("input[name='small_descr']"), imdbLinkBox: $("input[name='url'][type='text']"), doubanLinkBox: $("input[name='douban']"), categorySel: $('#browsecat'), sourceSel: $("select[name='source_sel']"), areaSel: $("select[name='team_sel']"), chsubCheck: $('#zhongzi')[0], englishSubCheck: $('#ensub')[0], chdubCheck: $('#guoyu')[0], cantodubCheck: $('#yueyu')[0], pullMovieScore: true, translatedChineseNameInTitle: false, doubanIdInsteadofLink: false, // 对比图风格,conventional 是指缩略图超链接方式 screenshotsStyle: 'conventional', categoryInfo: { default: 0, movie: 401, tvSeries: 404, tvShow: 405, documentary: 402, animation: 403 }, sourceInfo: { default: 0, bluray: 2, remux: 3, encode: 6, hdtv: 4, webdl: 5, dvd: 7 }, areaInfo: { default: 0, cnMl: 1, hk: 2, tw: 3, euAme: 4, kor: 5, jap: 6, ind: 7, other: 8 }, inputFileSubtitle: $('input[type="file"][name="file"]'), titleBoxSubtitle: $('input[type="text"][name="title"]'), languageSelSubtitle: $('select[name="sel_lang"]'), anonymousCheckSubtitle: $("input[name='uplver'][type='checkbox']")[0], subtitleInfo: { default: 0, english: 6, chinese_simplified: 25, chinese_traditional: 28, japanese: 15, french: 9, german: 10, italian: 14, korean: 16, spanish: 26, other: 18 } }, [PUTAO]: { hostName: 'pt.sjtu.edu.cn', pages: { upload: 'upload.php', edit: 'edit.php', subtitles: 'subtitles.php' }, construct: NEXUSPHP, targetBoxTag: '', boxSupportDescr: true, quoteStyle: 'none', boxNeedBreakLine: false, unsupportedTags: ['align', 'center', 'pre', 'email'], inputFile: $('input[type="file"][name="file"]'), nameBoxUpload: $('#name'), nameBoxEdit: $("input[type='text'][name='name']"), anonymousControl: $("input[name='uplver'][type='checkbox']")[0], descrBox: $('#descr'), smallDescBox: $("input[name='small_descr']"), imdbLinkBox: $("input[name='url'][type='text']"), doubanLinkBox: $("input[name='douban_url']"), categorySel: $('#browsecat'), standardSel: $("select[name='standard_sel']"), codecSel: $("select[name='codec_sel']"), pullMovieScore: false, translatedChineseNameInTitle: true, doubanIdInsteadofLink: false, screenshotsStyle: 'conventional', categoryInfo: { default: 0, documentary: 406, animation: 431, movieCn: 401, movieEuAme: 402, movieAsia: 403, tvSeriesHkTw: 407, tvSeriesAsia: 408, tvSeriesCnMl: 409, tvSeriesEuAme: 410, catTvShowCnMl: 411, tvShowHkTw: 412, tvShowEuAme: 413, tvshowJapKor: 414 }, standardInfo: { default: 0, res1080p: 1, res1080i: 2, res720p: 3, res2160p: 6, sd: 4 }, codecInfo: { default: 0, h264: 1, vc1: 2, xvid: 3, mpeg2: 4, flac: 5, ape: 6, h265: 10 }, inputFileSubtitle: $('input[type="file"][name="file"]'), titleBoxSubtitle: $('input[type="text"][name="title"]'), languageSelSubtitle: $('select[name="sel_lang"]'), anonymousCheckSubtitle: $("input[name='uplver'][type='checkbox']")[0], subtitleInfo: { default: 0, english: 6, chinese_simplified: 25, chinese_traditional: 28, japanese: 15, french: 9, german: 10, italian: 14, korean: 16, spanish: 26, other: 18 } }, [MTEAM]: { hostName: 'm-team.cc', pages: { upload: 'upload', edit: 'upload', subtitles: 'subtitles' }, construct: MTORRENT, targetBoxTag: 'expand', boxSupportDescr: false, quoteStyle: 'none', boxNeedBreakLine: false, unsupportedTags: ['align', 'pre', 'email'], inputFile: $('#torrent-input'), nameBoxUpload: $('#name'), nameBoxEdit: $('#name'), anonymousControl: $("#anonymous")[0], descrBox: $('#descr'), smallDescBox: $('#smallDescr'), imdbLinkBox: $('#imdb'), doubanLinkBox: $('#douban'), categorySel: $('#category'), sourceSel: $('#source'), standardSel: $("#standard"), videoCodecSel: $("#videoCodec"), audioCodecSel: $('#audioCodec'), mediumSel: $('#medium'), teamSel: $("#team"), areaSel: $("#processing"), chsubCheck: $("input[type='checkbox'][value='sub']")[0], chdubCheck: $("input[type='checkbox'][value='dub']")[0], mediainfoBox: $('#mediainfo'), pullMovieScore: true, translatedChineseNameInTitle: false, doubanIdInsteadofLink: false, screenshotsStyle: 'conventional', categoryInfo: { default: 0, movieSd: 401, movieHd: 419, movieRemux: 439, tvSeriesHd: 402, documentary: 404, animation: 405 }, sourceInfo: { bluray: 1, dvd: 3, hdtv: 4, tv: 5, other: 6, cd: 7 }, standardInfo: { default: 0, res1080p: 1, res1080i: 2, res720p: 3, res2160p: 6, sd: 5 }, videoCodecInfo: { default: 0, h264: 1, vc1: 2, h265: 16, xvid: 3, mpeg2: 4, mpeg4: 15, av1: 19 }, audioCodecInfo: { default: 0, flac: 1, ape: 2, dts: 3, mp3: 4, ogg: 5, aac: 6, other: 7, ac3: 8 }, mediumInfo: { default: 0, bluray: 2, hddvd: 2, remux: 3, minibd: 4, hdtv: 5, dvdr: 6, encode: 7, cd: 8, track: 9, webdl: 10 }, areaInfo: { default: 0, cnMl: 1, euAme: 2, hkTw: 3, jap: 4, kor: 5, other: 6 }, inputFileSubtitle: $('input[type="file"][name="file[]"]'), titleBoxSubtitle: $('input[type="text"][name="title[]"]'), languageSelSubtitle: $('select[name="sel_lang[]"]'), anonymousCheckSubtitle: $("input[name='uplver'][type='checkbox']")[0], subtitleInfo: { default: 0, english: 6, chinese_simplified: 25, chinese_traditional: 28, japanese: 15, korean: 16, other: 18 } }, [TTG]: { hostName: 'totheglory.im', pages: { upload: 'upload.php', edit: 'edit.php', subtitles: 'dox.php' }, construct: NEXUSPHP, targetBoxTag: '', boxSupportDescr: false, quoteStyle: 'writer', boxNeedBreakLine: false, unsupportedTags: ['align', 'center', 'email'], inputFile: $('input[type="file"][name="file"]'), nameBoxUpload: $("input[type='text'][name='name']"), nameBoxEdit: $("input[type='text'][name='name']"), descrBox: $('textarea[name="descr"]'), smallDescBox: $("input[type='text'][name='subtitle']"), subtitleBox: $("input[type='text'][name='highlight']"), imdbLinkBox: $("input[name='imdb_c'][type='text']"), doubanLinkBox: $("input[name='douban_id'][type='text']"), categorySel: $('select[name="type"]'), anonymousControl: $('select[name="anonymity"]'), pullMovieScore: true, translatedChineseNameInTitle: false, doubanIdInsteadofLink: true, screenshotsStyle: 'conventional', categoryInfo: { default: 0, movie720P: 52, movie1080ip: 53, movie2160p: 108, documentary720p: 62, documentary1080ip: 63, tvSeriesEuAme: 87, tvSeriesJap: 88, tvSeriesKor: 99, tvSeriesCn: 90, tvShowJap: 101, tvShowKor: 103, tvShow: 60 } }, [GPW]: { hostName: 'greatposterwall.com', pages: { upload: 'upload.php', edit: 'torrents.php?action=edit', subtitles: 'subtitles.php' }, construct: GAZELLE, targetBoxTag: 'hide', boxSupportDescr: true, quoteStyle: 'writer', boxNeedBreakLine: true, unsupportedTags: ['align', 'pre', 'email'], inputFile: $('#file'), mediainfoBox: $('textarea[name="mediainfo[]"]'), descrBox: $('#release_desc'), sourceSel: $('select[id="source"]'), codecSel: $('select[id="codec"]'), standardSel: $('select[id="resolution"]'), processingSel: $('select[id="processing"]'), containerSel: $('select[id="container"]'), videoInfo: { bit10: $('input[type="checkbox"][id="10_bit"]')[0], hdr10: $('input[type="checkbox"][id="hdr10"]')[0], hdr10plus: $('input[type="checkbox"][id="hdr10plus"]')[0], dovi: $('input[type="checkbox"][id="dolby_vision"]')[0] }, audioInfo: { dtsX: $('input[type="checkbox"][id="dts_x"]')[0], atmos: $('input[type="checkbox"][id="dolby_atmos"]')[0], chineseDub: $('input[type="checkbox"][id="chinese_dubbed"]')[0] }, movieEditionContainer: $('#movie_edition_information_container'), showMovieEditionCheck: $('input[type="checkbox"][id="movie_edition_information"]')[0], movieEditionSelected: $('input[id="remaster_title_hide"]'), movieEditionInfo: { criterionCollection: 'the_criterion_collection', mastersOfCinema: 'masters_of_cinema', withCommentary: 'with_commentary', directorsCut: 'director_s_cut', theatrical: 'theatrical_cut', uncut: 'uncut', unrated: 'unrated', extended: 'extended_edition', remaster4k: '4k_remaster', remaster: 'remaster', dualAudio: 'dual_audio', restoration4k: '4k_restoration', twoInOne: '2_in_1' }, mixedSubCheck: $('input[type="radio"][id="mixed_subtitles"]')[0], noSubCheck: $('input[type="radio"][id="no_subtitles"]')[0], otherSubtitlesDiv: $('div[id="other_subtitles"]'), subtitleInfo: { chinese_simplified: $('input[type="checkbox"][id="chinese_simplified"]')[0], chinese_traditional: $('input[type="checkbox"][id="chinese_traditional"]')[0], english: $('input[type="checkbox"][id="english"]')[0], japanese: $('input[type="checkbox"][id="japanese"]')[0], korean: $('input[type="checkbox"][id="korean"]')[0], french: $('input[type="checkbox"][id="french"]')[0], german: $('input[type="checkbox"][id="german"]')[0], italian: $('input[type="checkbox"][id="italian"]')[0], polish: $('input[type="checkbox"][id="polish"]')[0], romanian: $('input[type="checkbox"][id="romanian"]')[0], russian: $('input[type="checkbox"][id="russian"]')[0], spanish: $('input[type="checkbox"][id="spanish"]')[0], thai: $('input[type="checkbox"][id="thai"]')[0], turkish: $('input[type="checkbox"][id="turkish"]')[0], vietnamese: $('input[type="checkbox"][id="vietnamese"]')[0], hindi: $('input[type="checkbox"][id="hindi"]')[0], greek: $('input[type="checkbox"][id="greek"]')[0], swedish: $('input[type="checkbox"][id="swedish"]')[0], azerbaijani: $('input[type="checkbox"][id="azerbaijani"]')[0], bulgarian: $('input[type="checkbox"][id="bulgarian"]')[0], danish: $('input[type="checkbox"][id="danish"]')[0], estonian: $('input[type="checkbox"][id="estonian"]')[0], finnish: $('input[type="checkbox"][id="finnish"]')[0], hebrew: $('input[type="checkbox"][id="hebrew"]')[0], croatian: $('input[type="checkbox"][id="croatian"]')[0], hungarian: $('input[type="checkbox"][id="hungarian"]')[0], icelandic: $('input[type="checkbox"][id="icelandic"]')[0], latvian: $('input[type="checkbox"][id="latvian"]')[0], lithuanian: $('input[type="checkbox"][id="lithuanian"]')[0], dutch: $('input[type="checkbox"][id="dutch"]')[0], norwegian: $('input[type="checkbox"][id="norwegian"]')[0], portuguese: $('input[type="checkbox"][id="portuguese"]')[0], slovenian: $('input[type="checkbox"][id="slovenian"]')[0], slovak: $('input[type="checkbox"][id="slovak"]')[0], latin: $('input[type="checkbox"][id="latin"]')[0], ukrainian: $('input[type="checkbox"][id="ukrainian"]')[0], persian: $('input[type="checkbox"][id="persian"]')[0], arabic: $('input[type="checkbox"][id="arabic"]')[0], brazilian_port: $('input[type="checkbox"][id="brazilian_port"]')[0], czech: $('input[type="checkbox"][id="czech"]')[0], idonesian: $('input[type="checkbox"][id="idonesian"]')[0], serbian: $('input[type="checkbox"][id="serbian"]')[0] }, pullMovieScore: true, translatedChineseNameInTitle: false, minScreenshots: 3, maxScreenshots: 10, supportedImageHosts: [KSHARE, PIXHOST, PTPIMG, PTERCLUB, ILIKESHOTS, IMGBOX], screenshotsStyle: 'comparison', sourceInfo: { default: '---', bluray: 'Blu-ray', web: 'WEB', hdtv: 'HDTV', dvd: 'DVD' }, codecInfo: { default: '---', h264: 'H.264', h265: 'H.265', xvid: 'XviD', divx: 'DivX', x264: 'x264', x265: 'x265' }, standardInfo: { default: '---', res1080i: '1080i', res1080p: '1080p', res2160p: '2160p', res720p: '720p', sd: '480p' }, processingInfo: { default: '---', encode: 'Encode', remux: 'Remux' }, containerInfo: { default: '---', mkv: 'MKV', mp4: 'MP4', avi: 'AVI' }, inputFileSubtitle: $('#file') }, [UHD]: { hostName: 'uhdbits.org', pages: { upload: 'upload.php', edit: 'torrents.php?action=edit', subtitles: 'subtitle.php' }, construct: GAZELLE, targetBoxTag: 'spoiler', boxSupportDescr: true, quoteStyle: 'writer', boxNeedBreakLine: true, unsupportedTags: ['align', 'pre', 'email'], inputFile: $('#file'), mediainfoBox: $('textarea[name="mediainfo"]'), descrBox: $('#release_desc'), sourceSel: $('select[id="media"]'), codecSel: $('select[id="codec"]'), standardSel: $('select[id="format"]'), teamBox: $('input[type="text"][id="team"]'), categorySel: $('select[id="categories"]'), anonymousControl: $('input[type="checkbox"][id="anonymous"]')[0], hdrSel: $('select[id="hdr"]'), seasonSel: $('select[id="season"]'), movieEditionSelected: $('input[type="text"][id="Version"]'), movieEditionInfo: { criterionCollection: 'Criterion', twoInOne: '2in1', threeInOne: '3in1', bit10: '10-bit', remaster4k: '4K Remaster', restoration4k: '4K Restoration', bAndWVersion: 'B & W Version', directorsCut: 'Director\'s Cut', extras: 'Extras', theatrical: 'Theatrical', extended: 'Extended', hybrid: 'Hybrid', imax: 'IMAX', remaster: 'Remastered', uncut: 'Uncut', tvCut: 'TV Cut', unrated: 'Unrated' }, pullMovieScore: true, translatedChineseNameInTitle: false, screenshotsStyle: 'conventional', sourceInfo: { default: '---', bluray: 'Blu-ray', remux: 'Remux', encode: 'Encode', webdl: 'WEB-DL', webrip: 'WEBRip', hdrip: 'HDRip', hdtv: 'HDTV', others: 'Others', hdAudio: 'HD Audio' }, codecInfo: { default: '---', h264: 'H.264', h265: 'HEVC', vc1: 'VC-1', mpeg2: 'MPEG-2', av1: 'AV1', x264: 'x264', x265: 'x265', x266: 'x266' }, standardInfo: { default: '---', mhd: 'mHD', res1080i: '1080i', res1080p: '1080p', res2160p: '2160p', res720p: '720p', others: 'Others' }, hdrInfo: { default: 'No', hdr10: 'HDR10', hdr10plus: 'HDR10+', dovi: 'DoVi' }, categoryInfo: { movie: '0', music: '1', tvSeries: '2' }, seansonInfo: { default: '---', s01: '1' }, inputFileSubtitle: $('input[type="file"][name="sub"]'), titleBoxSubtitle: $('input[type="text"][name="releasename"]'), languageSelSubtitle: $('select[name="language"]'), subtitleInfo: { default: '', english: 'English', vietnamese: 'Vietnamese', danish: 'Danish', norwegian: 'Norwegian', finnish: 'Finnish', spanish: 'Spanish', french: 'French' } } } const imageHostInfoMap = { [PIXHOST]: { images2Thumbs: { pattern: /https:\/\/img(\d+)\.pixhost\.to\/images\/([A-Za-z0-9\-._~!$&'()*+;=:@/?]+)\.png/gi, replacement: '[url=https://pixhost.to/show/$2.png][img]https://t$1.pixhost.to/thumbs/$2.png[/img][/url]' }, thumbs2Images: { pattern: /\[url=https:\/\/pixhost\.to\/show\/([A-Za-z0-9\-._~!$&'()*+,;=:@/?]+.png)\]\s*\[img\]https:\/\/t([A-Za-z0-9\-._~!$&'()*+,;=:@/?]+)\.pixhost[A-Za-z0-9\-._~!$&'()*+,;=:@/?]+?\[\/img\]\s*\[\/url\]/gi, replacement: 'https://img$2.pixhost.to/images/$1' } }, [IMGBOX]: { images2Thumbs: { pattern: /https:\/\/images(\d+)\.imgbox\.com\/(\w+\/\w+)\/(\w+)_o\.png/gi, replacement: '[url=https://imgbox.com/$3][img]https://thumbs$1.imgbox.com/$2/$3_t.png[/img][/url]' }, thumbs2Images: { pattern: /\[url=[A-Za-z0-9\-._~!$&'()*+,;=:@/?]+\]\s*\[img\]https:\/\/thumbs([A-Za-z0-9\-._~!$&'()*+,;=:@/?]+)_t\.png\[\/img\]\s*\[\/url\]/gi, replacement: 'https://images$1_o.png' } }, [IMG4K]: { images2Thumbs: null, thumbs2Images: { pattern: /\[url=[A-Za-z0-9\-._~!$&'()*+,;=:@/?]+\]\s*\[img\]([A-Za-z0-9\-._~!$&'()*+,;=:@/?]+)\.(?:md|th)\.png\[\/img\]\s*\[\/url\]/gi, replacement: '$1.png' } }, [ILIKESHOTS]: { images2Thumbs: null, thumbs2Images: null }, [PTERCLUB]: { images2Thumbs: null, thumbs2Images: { pattern: /\[url=[A-Za-z0-9\-._~!$&'()*+,;=:@/?]+\]\s*\[img\]([A-Za-z0-9\-._~!$&'()*+,;=:@/?]+)\.th\.png\[\/img\]\s*\[\/url\]/gi, replacement: '$1.png' } }, [IMGPILE]: { images2Thumbs: null, thumbs2Images: { pattern: /\[url=https:\/\/imgpile\.com\/i\/([A-Za-z0-9\-._~!$&'()*+,;=:@/?]+)\]\s*\[img\][A-Za-z0-9\-._~!$&'()*+,;=:@/?]+\.png\[\/img\]\s*\[\/url\]/gi, replacement: 'https://imgpile.com/images/$1.png' } }, [PTPIMG]: { images2Thumbs: null, thumbs2Images: null }, [KSHARE]: { images2Thumbs: null, thumbs2Images: null }, [TTG]: { images2Thumbs: { pattern: /([A-Za-z0-9\-._~!$&'()*+,;=:@/?]+)\.png/gi, replacement: '[url=$1.png][img]$1_thumb.png[/img][/url]' }, thumbs2Images: { pattern: /\[url=([A-Za-z0-9\-._~!$&'()*+,;=:@/?]+)\.png\]\s*\[img\][A-Za-z0-9\-._~!$&'()*+,;=:@/?]+\[\/img\]\s*\[\/url\]/gi, replacement: '$1.png' } } } //= ======================================================================================================== // functions function escapeRegExp (string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } // 生成teamSplitter的正则表达组合以escape特定组名,如teams = ['D-Z0N3'], splitters = ['-'], 返回'(?<=D)-(?!Z0N3)|(?<!D)-(?=Z0N3)|(?<!D)-(?!Z0N3)' function getTeamSplitterRegex(teams, splitters, flags='') { let patterns = [] for (let splitter of splitters) { let leftPatterns = [] let rightPatterns = [] for (let team of teams) { let i = 0 // eslint-disable-next-line no-constant-condition while (true) { let lastIndex = team.indexOf(splitter, i) if (lastIndex >= 0) { let [left, right] = [escapeRegExp(team.substring(0, lastIndex)), escapeRegExp(team.substring(lastIndex + splitter.length))] if (left && right) { patterns.push(`(?<=${left})${escapeRegExp(splitter)}(?!${right})`) patterns.push(`(?<!${left})${escapeRegExp(splitter)}(?=${right})`) leftPatterns.push(left) rightPatterns.push(right) } else if (left) { patterns.push(`(?<!${left})${escapeRegExp(splitter)}`) leftPatterns.push(left) } else if (right) { patterns.push(`${escapeRegExp(splitter)}(?!${right})`) rightPatterns.push(right) } i = lastIndex + 1 } else { break } } } if (leftPatterns.length || rightPatterns.length) { patterns.push(`(?<!${leftPatterns.join('|')})${escapeRegExp(splitter)}(?!${rightPatterns.join('|')})`) } else { patterns.push(escapeRegExp(splitter)) } } let regex = RegExp(patterns.join('|'), flags) return [regex, patterns] } // requires numbers of left and right tags match, other wise some of the contents may be removed // keepNonQuoted 选择是否保留两个0级别 quote 之间的内容,如'是这些文字[quote]不是这些文字[/quote]是这些文字[quote]不是这些文字[/quote]是这些文字' function processTags (inputText, tag, processLeft, processRight, keepNonQuoted=true) { let regexTagsLeft = new RegExp('\\[((' + tag + ')((?:=([^\\]]+))?))\\]', 'g') let regexTagsRight = new RegExp('\\[\\/(' + tag + ')\\]', 'g') let outputText = '' let remainedText = '' let indexOutput = 0 let indexRemained = 0 let currentLevel = 0 // eslint-disable-next-line no-constant-condition while(true) { regexTagsLeft.lastIndex = indexOutput regexTagsRight.lastIndex = indexOutput let matchLeft = regexTagsLeft.exec(inputText) let matchRight = regexTagsRight.exec(inputText) let match = null let left = true if (matchLeft && matchRight) { if (matchLeft.index < matchRight.index) { match = matchLeft left = true } else { match = matchRight left = false } } else { left = matchLeft match = matchLeft ? matchLeft : matchRight ? matchRight : null } if (match) { if (currentLevel === 0) { if (left) { // 左括号,0级,根据 keepNonQuoted 确定是否保留上一次匹配末尾到本次匹配之间的内容 indexOutput = keepNonQuoted ? indexOutput : match.index } else { // 右括号,0级,无法匹配,扔掉前面的内容,直接从本次匹配末尾开始 indexOutput = match.index + match[0].length } } if (indexOutput < match.index) { outputText += inputText.substring(indexOutput, match.index) indexOutput = match.index } else { // indexOutput === match.index || indexOutput === match.index + match[0].length remainedText += inputText.substring(indexRemained, match.index) indexRemained = match.index } if (indexOutput < match.index + match[0].length) { outputText += left ? processLeft(match[0]) : processRight(match[0]) } else { remainedText += left ? processLeft(match[0]) : processRight(match[0]) } indexOutput = match.index + match[0].length indexRemained = indexOutput left ? currentLevel++ : currentLevel >=1 ? currentLevel-- : 0 } else { if (currentLevel === 0) { if (keepNonQuoted) { outputText += inputText.substring(indexOutput) } else { remainedText += inputText.substring(indexRemained) } } else { outputText += inputText.substring(indexOutput) } break } } return [outputText, remainedText] } function nestExplode (inputText, targetBoxTag) { let outputText, c const pat1 = '\\[' + targetBoxTag + '((?:=[^\\]]+)?\\](?:(?!\\[\\/' + targetBoxTag + '\\])[\\s\\S])*\\[' + targetBoxTag + '(?:=[^\\]]+)?\\])' const pat2 = '(\\[\\/' + targetBoxTag + '\\](?:(?!\\[' + targetBoxTag + '(?:=[^\\]]+)?\\])[\\s\\S])*)\\[\\/' + targetBoxTag + '\\]' const regex1 = RegExp(pat1, 'g') const regex2 = RegExp(pat2, 'g') do { outputText = inputText.replace(regex1, '[quote$1').replace(regex2, '$1[/quote]') c = (inputText !== outputText) inputText = outputText } while (c) return outputText } function compactContent (inputText, targetBoxTag) { let outputText, c const pat1 = '(\\[\\/?(?:' + targetBoxTag + ')(?:=[^\\]]+)?\\])\\s+(\\S)' const pat2 = '(\\S)\\s+(\\[\\/?(?:' + targetBoxTag + ')(?:=[^\\]]+)?\\])' const pat3 = '(\\[' + targetBoxTag + '(?:=[^\\]]+)?\\](?:(?!\\[\\/)[\\s\\S])*\\[(?:font|b|i|u|color|size)(?:=[^\\]]+)?\\])\\r?\\n+([^\\r\\n])' const regex1 = RegExp(pat1, 'g') const regex2 = RegExp(pat2, 'g') const regex3 = RegExp(pat3, 'g') do { outputText = inputText.replace(regex1, '$1$2').replace(regex2, '$1$2').replace(regex3, '$1$2') c = (inputText !== outputText) inputText = outputText } while (c) return outputText } function formatTorrentName (torrentName) { if (!torrentName) { return '' } else { return ( torrentName .replace(/^\s?(\[.*?\]\s?)+/gi, '') .replace(/((\s*\(\d+\)\s*)?\.(mkv|mp4|avi|ts|wmv|mpg|torrent))+$/, '') .replace(/\bh\.(26[456])\b/gi, 'H/$1') .replace(/(\b[a-zA-Z]*\d{1,2})\.(\d{1,2}\b)/g, function (_, p1, p2) { return p1 + '/' + p2 }) .replace(/\((\d{4})\)/g, '$1') .replace(/\bWEB(?!-DL)\b/gi, 'WEB-DL') .replace(/\bweb-?rip\b/gi, 'WEBRip') .replace(/\bblu-?ray\b/gi, 'BluRay') .replace(/\bdvd(rip)?\b/gi, function (_, p1) { return 'DVD' + (p1 ? 'Rip' : '') }) .replace(/\b((?:480|720|1080|2160)[PI])\b/g, function (_, p1) { return p1.toLowerCase() }) .replace(/\bx\.?(26[456])\b/gi, 'x$1') // 点号前面是数字(一至两位),后面是单个数字的情况不替换(DDP5.1) // 点号后面是空格(The Talented Mr. Ripley 1999 1080p BluRay DD+5.1 x264-HiDt)或者点号的时候不替换(The.Talented.Mr..Ripley.1999.1080p.BluRay.DD+5.1.x264-HiDt) .replace(/((?<!\d{1,2})\.(?!( |\.)))|(\.(?!(\d| |\.)\b))/g, ' ') .replace(/\//g, '.') .trim() ) } } // eslint-disable-next-line no-unused-vars function getThumbSize(numTeams, siteName) { if (numTeams <= 0) { console.error(`[getThumbSize] Invalid team number ${numTeams}`) } return numTeams === 1 ? 350 : numTeams === 2 ? 300 : numTeams === 3 ? 250 : numTeams === 4 ? 187 : numTeams === 5 ? 150 : 150 } function getInvalidImageAnchor(numTeams, siteName) { if (numTeams <= 0) { console.error(`[getInvalidImageAnchor] Invalid team number ${numTeams}`) } let thumbPixels = getThumbSize(numTeams, siteName) let pixelsPerChar = 4 let numCharHalf = Math.ceil((thumbPixels / pixelsPerChar - invalidImageAnchor.length) / 2) numCharHalf = Math.max(numCharHalf, 0) let charsHalf = Array(numCharHalf).fill(' ').join('') return charsHalf + invalidImageAnchor + charsHalf } // decode [url=...][img]...[/img][/url] -> https://1.png async function thumbBbcode2Image (thumbBbcodes, numTeams, siteName) { const site = siteInfoMap[siteName] const size = getThumbSize(numTeams, siteName) const supportPixhost = site.supportedImageHosts ? site.supportedImageHosts.includes(PIXHOST) : true let images = Array(thumbBbcodes.length).fill('') let indicesForPixhost = [] for (const [i, thumb] of thumbBbcodes.entries()) { const imageHostName = Object.keys(imageHostInfoMap).find(ih => thumb.match(RegExp(escapeRegExp(ih), 'i'))) || '' const imageHost = imageHostInfoMap[imageHostName] if (!imageHost) { continue } let pattern = imageHost.thumbs2Images ? imageHost.thumbs2Images.pattern : '' let replacement = imageHost.thumbs2Images ? imageHost.thumbs2Images.replacement : '' if (pattern) { const match = thumb.match(RegExp(pattern.source, 'i')) if (match) { images[i] = match[0].replace(pattern, replacement) const supportCurrentImageHost = site.supportedImageHosts ? site.supportedImageHosts.includes(imageHostName) : true // 确保转换图床的都是有效url if (!supportCurrentImageHost) { indicesForPixhost.push(i) } } } } // 条件supportPixhost 确保了递归调用时supportCurrentImageHost===true,indicesForPixhost必为空(否则可能导致无限循环) if (indicesForPixhost.length && supportPixhost) { const imagesForPixhost = images.filter((_, index) => indicesForPixhost.includes(index)) const thumbBbcodesFromPixhost = await sendImagesToPixhost(imagesForPixhost, size) const imagesFromPixhost = await thumbBbcode2Image(thumbBbcodesFromPixhost, numTeams, siteName) for (let i = 0; i < indicesForPixhost.length; i++) { images[indicesForPixhost[i]] = imagesFromPixhost[i] } } return images } // https://1.png -> https://1.png, change imagehost if necessary // 同时[img]https://1.png[/img] -> https://1.png async function image2image (images, numTeams, siteName) { // const imageUrlsJoined = imageUrls.map(image => image.trim()).join(' ') const site = siteInfoMap[siteName] const supportPixhost = site.supportedImageHosts ? site.supportedImageHosts.includes(PIXHOST) : true const size = getThumbSize(numTeams, siteName) let indicesForPixhost = [] for (const [i, image] of images.entries()) { const imageHostName = Object.keys(imageHostInfoMap).find(ih => image.match(RegExp(escapeRegExp(ih), 'i'))) || '' const match = image.match(RegExp(regexImage.source, 'i')) if (match) { images[i] = match[0] const supportCurrentImageHost = site.supportedImageHosts ? site.supportedImageHosts.includes(imageHostName) : true if (!supportCurrentImageHost) { indicesForPixhost.push(i) } } else { images[i] = '' } } if (indicesForPixhost.length && supportPixhost) { const imagesForPixhost = images.filter((_, index) => indicesForPixhost.includes(index)) const thumbBbcodesFromPixhost = await sendImagesToPixhost(imagesForPixhost, size) const imagesFromPixhost = await thumbBbcode2Image(thumbBbcodesFromPixhost, numTeams, siteName) for (let i = 0; i < indicesForPixhost.length; i++) { images[indicesForPixhost[i]] = imagesFromPixhost[i] } } return images } // https://1.png -> [url=...][img]...[/img][/url] async function image2ThumbBbcode (images, numTeams, siteName) { const site = siteInfoMap[siteName] const size = getThumbSize(numTeams, siteName) const supportPixhost = site.supportedImageHosts ? site.supportedImageHosts.includes(PIXHOST) : true let thumbBbcodes = Array(images.length).fill('') let indicesForPixhost = [] // const imageUrlsJoined = imageUrls.map(image => image.trim()).join(' ') for (const [i, image] of images.entries()) { const imageHostName = Object.keys(imageHostInfoMap).find(ih => image.match(RegExp(escapeRegExp(ih), 'i'))) || '' const imageHost = imageHostInfoMap[imageHostName] let pattern = '' let replacement = '' if (imageHost) { pattern = imageHost.images2Thumbs ? imageHost.images2Thumbs.pattern: '' replacement = imageHost.images2Thumbs ? imageHost.images2Thumbs.replacement: '' } if (pattern) { const match = image.match(RegExp(pattern.source, 'i')) if (match) { thumbBbcodes[i] = match[0].replace(pattern, replacement) const supportCurrentImageHost = site.supportedImageHosts ? site.supportedImageHosts.includes(imageHostName) : true // 不支持当前图床,发送至Pixhost if (!supportCurrentImageHost) { indicesForPixhost.push(i) } } } else { // 不可从图片链接解析缩略图的图床(如PTPIMG),发送至Pixhost const match = image.match(RegExp(regexImage.source, 'i')) if (match) { images[i] = match[0] indicesForPixhost.push(i) } } } if (indicesForPixhost.length && supportPixhost) { const imagesForPixhost = images.filter((_, index) => indicesForPixhost.includes(index)) const thumbBbcodesFromPixhost = await sendImagesToPixhost(imagesForPixhost, size) for (let i = 0; i < indicesForPixhost.length; i++) { thumbBbcodes[indicesForPixhost[i]] = thumbBbcodesFromPixhost[i] } } return thumbBbcodes } function mediainfo2String(mediainfo) { let mediainfoStr = '' if (!mediainfo) { return mediainfoStr } Object.entries(mediainfo).forEach(([sectorKey, sector]) => { mediainfoStr += `${sectorKey}\n` Object.entries(sector).forEach(([fieldKey, fieldValue]) => { // at least keep 1 empty space let emptyLength = Math.max(mediainfoKeyLength - fieldKey.length, 1) mediainfoStr += `${fieldKey}${' '.repeat(emptyLength)}: ${fieldValue}\n` }) mediainfoStr += '\n' }) return mediainfoStr.trim() } function string2Mediainfo (mediainfoStr) { let mi = {} if (!mediainfoStr) { return mi } let currentSectorKey = '' // \r is for clipboard content operation mediainfoStr.split(/\r?\n/g).forEach(sector => { if (sector && sector.trim()) { let [fieldKey, fieldValue] = sector.split(/ +: +/) if (fieldKey) { fieldKey = fieldKey.trim() if (fieldValue) { fieldValue = fieldValue.trim() if (currentSectorKey) { mi[currentSectorKey][fieldKey] = fieldValue } else { // invalid mediainfo format mi = {} return } } else { currentSectorKey = fieldKey mi[currentSectorKey] = {} } } } }) return mi } // 发送到PixHost,返回的是带链接的缩略图。返回的Array长度与输入一致,如果有生成失败的缩略图,返回的Array对应元素的值为 "" async function sendImagesToPixhost (images, size) { console.log(`[sendImagesToPixhost] sending ${images.length} images to PIXHOST`) const hostname = 'https://pixhost.to/remote/' const data = encodeURI(`imgs=${images.join('\n')}&content_type=0&max_th_size=${size}`) const headers = { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36' } return new Promise((resolve, reject) => { // eslint-disable-next-line no-undef GM_xmlhttpRequest({ method: 'POST', url: hostname, headers, data, onload: response => { if (response.status !== 200) { reject(response.status) } else { const upload_results_array = response.responseText.match(/upload_results = ({.*});/) if (upload_results_array && upload_results_array.length) { const upload_results = JSON.parse(upload_results_array[1]) const resultList = upload_results.images const notices = upload_results.notices if (notices) { notices.forEach(notice => console.warn(`[sendImagesToPixhost] notice from PIXHOST: ${notice}`)) } const outputImageNames = resultList.map(item => item.name) let thumbBbcodes = [] let failedImages = [] // 由于原链接图片失效等原因,输出的链接数量可能小于输入的数量,需要对齐并找出失效图片的位置 for (const [i, image] of images.entries()) { // 输入url的最后一个'/'之后的内容作lowercase,得到outputImageName let inputImageName = image.replace(/.*?([^/]+)$/, '$1').toLowerCase() if (inputImageName === outputImageNames[i - failedImages.length]) { let result = resultList[i - failedImages.length] thumbBbcodes.push(`[url=${result.show_url}][img]${result.th_url}[/img][/url]`) } else { thumbBbcodes.push('') failedImages.push(i + 1) } } if (failedImages.length) { console.warn(`[sendImagesToPixhost] Failed images when sending to PIXHOST (indexed from 1): [${failedImages.join(', ')}]`) } resolve(thumbBbcodes) } else { console.log(response) reject(new Error('[sendImagesToPixhost] Failed to upload')) } } } }) }) } // 提取全部对比图信息 function collectComparisons (text) { const comparisons = [] let lastIndex = 0 // eslint-disable-next-line no-constant-condition while (true) { let closestMatchIndex = text.length const result = { starts: 0, ends: 0, teams: [], urls: [], containerStyle: '', urlType: '', text: '' } for (let item of regexInfo) { const regex = item.regex regex.lastIndex = lastIndex const match = regex.exec(text) if (match && match.index < closestMatchIndex) { closestMatchIndex = match.index result.containerStyle = item.containerStyle result.urlType = item.urlType if (item.groupForTeams >= 0 && item.groupForTeamSplitter >= 0) { let teamSplitter = match[item.groupForTeamSplitter] result.teams = match[item.groupForTeams] .split(teamSplitter) .map(ele => ele.trim()) } else { result.teams = [] } if (item.groupForUrls >= 0) { const urls = match[item.groupForUrls] if (item.urlType === 'thumbBbcode') { result.urls = urls.match(regexImageThumbBbcode) } else if (item.urlType === 'imageBbcode') { result.urls = urls.match(regexImageBbcode) } else if (item.urlType === 'image') { result.urls = urls.match(regexImage) } } else { result.urls = [] } result.starts = match.index result.ends = match.index + match[0].length result.text = match[0] } } if (result.ends === 0) { return comparisons } else { lastIndex = result.ends comparisons.push(result) } } } // 从简介中提取信息并格式化截图 async function decomposeDescription (siteName, textToConsume, mediainfoStr, torrentTitle) { let mediainfo = {} let description = '' const site = siteInfoMap[siteName] // 优先从简介中获取mediainfo,避免mediainfo框内容陈旧导致冲突 const tagForMediainfo = site.targetBoxTag || 'quote' const regexMIStr = site.boxSupportDescr ? '\\[(' + tagForMediainfo + '|quote)\\s*=\\s*mediainfo\\]\\s*(General\\s+Unique ID[^\\0]+?)\\[\\/\\1\\]' : '\\[(' + tagForMediainfo + '|quote)\\]\\s*(General\\s+Unique ID[^\\0]+?)\\[\\/\\1\\]' // 如果存在多个mediainfo,一般是因为简介中包含了Source MediaInfo,多为remux let regexMi = RegExp(regexMIStr, 'gim') let mediainfoArray = [] let results = [] // eslint-disable-next-line no-cond-assign while (mediainfoArray = regexMi.exec(textToConsume)) { let miStr = mediainfoArray[2] .replace(/^\s*\[\w+(\s*=[^\]]+)?\]/g, '') .replace(/\s*\[\/\w+\]\s*$/g, '') let mi = string2Mediainfo(miStr) let completeName = mi.General ? mi.General['Complete name'] || mi.General['Movie name'] || '' : '' results.push({ 'mediainfo': mi, 'index': mediainfoArray.index, 'length': mediainfoArray[0].length, 'completeName': completeName }) } if (results.length) { console.log(`[decomposeDescription] got ${results.length} mediainfo from description`) let encodeResult = { 'mediainfo': {}, 'index': 0, 'length': 0, 'completeName': '' } if (results.length === 1) { // 匹配到单个mediainfo encodeResult = results[0] } else { // 如果匹配到多个mediainfo,一般是因为其中有Source mediainfo,多为remux results.forEach(result => { if (!result.completeName.match(/\bremux\b/i)) { encodeResult = result return } }) } mediainfo = encodeResult.mediainfo // if the site has a place to fill out the mediainfo, remove it in the description box if (site.mediainfoBox && encodeResult.length) { textToConsume = textToConsume.substring(0, encodeResult.index) + textToConsume.substring(encodeResult.index + encodeResult.length) } } else { // 若简介中无mediainfo信息,读取mediainfoStr mediainfo = string2Mediainfo(mediainfoStr) } if (mediainfo && mediainfo.General) { torrentTitle = mediainfo.General['Complete name'] || mediainfo.General['Movie name'] if (torrentTitle) { torrentTitle = torrentTitle.replace(/.*?([^\\/]+)$/, '$1') torrentTitle = formatTorrentName(torrentTitle) } } const comparisons = collectComparisons(textToConsume) // 倒序,以保证在替换textToConsume中的内容时,comparison中的starts和ends的有效性 .sort((a, b) => b.starts - a.starts) console.log(`[decomposeDescription] got ${comparisons.length} comparisons from description. handling them in screenshot style '${site.screenshotsStyle}'`) if (site.screenshotsStyle === 'conventional') { for (let { starts, ends, teams, urls, containerStyle, urlType } of comparisons) { let screenshotsConsumed = '' if (containerStyle !== 'none') { console.log(`[decomposeDescription] comparison: container type '${containerStyle}', url type '${urlType}', teams [${teams.join(', ')}]`) if (urlType === 'image') { urls = await image2ThumbBbcode(urls, teams.length, siteName) } else if (urlType === 'imageBbcode') { urls = await image2image(urls, teams.length, siteName) urls = await image2ThumbBbcode(urls, teams.length, siteName) } else if (urlType !== 'thumbBbcode') { console.error(`[decomposeDescription] invalid url type '${urlType}'`) urls = [] } if (urls.length > 0 && teams.length > 0) { screenshotsConsumed = `[b]${teams.join(' | ')}[/b]` urls.forEach((url, i) => { url = url || getInvalidImageAnchor(teams.length, siteName) screenshotsConsumed += (i % teams.length === 0 ? '\n' + url : ' ' + url) }) screenshotsConsumed = site.unsupportedTags.includes('center') ? `${screenshotsConsumed}\n` : `[center]${screenshotsConsumed}[/center]\n` } } else { console.log(`[decomposeDescription] screenshots of ${urls.length} urls`) urls = await image2image(urls, 2, siteName) urls = urls.map(url => `[img]${url}[/img]`) if (urls.length > 0) { screenshotsConsumed = urls.join('\n') } } textToConsume = textToConsume.substring(0, starts) + screenshotsConsumed + textToConsume.substring(ends) } } else if (site.screenshotsStyle === 'comparison') { // 3张以上截图 let screenshotsEncode = '' let comparisonsProcessed = [] for (let { starts, ends, teams, urls, containerStyle, urlType } of comparisons) { let screenshotsConsumed = '' if (containerStyle !== 'none') { console.log(`[decomposeDescription] comparison: container type '${containerStyle}', url type '${urlType}', teams [${teams.join(', ')}]`) if (urlType === 'thumbBbcode') { urls = await thumbBbcode2Image(urls, teams.length, siteName) } else if (urlType === 'imageBbcode' || urlType === 'image') { urls = await image2image(urls, teams.length, siteName) } else { console.error(`[decomposeDescription] invalid url type '${urlType}'`) urls = [] } // comparison style情况下,不仅需要移除无效链接,还要把同一组比较的链接一并删除,否则展示结果是不对齐的 if (urls.length > 0 && teams.length > 0) { let urlsFiltered = [] let groupsFailed = [] for (let i = 0; i < urls.length; i += teams.length) { let currentGroupOk = true for (let j = 0; j < teams.length; j++) { if (!urls[i + j]) { currentGroupOk = false break } } if (currentGroupOk) { for (let j = 0; j < teams.length; j++) { urlsFiltered.push(urls[i + j]) } } else { groupsFailed.push(Math.floor(i / teams.length) + 1) } } if (groupsFailed.length) { console.warn(`[decomposeDescription] Failed groups (indexed from 1): [${groupsFailed.join(', ')}] for comparison among [${teams.join(', ')}]`) } screenshotsConsumed = `[comparison=${teams.join(', ')}]${urlsFiltered.join(' ')}[/comparison]` comparisonsProcessed.push({teams: teams, urls: urlsFiltered}) } } else if (urlType === 'imageBbcode') { console.log(`[decomposeDescription] screenshots of ${urls.length} urls`) urls = await image2image(urls, 2, siteName) urls = urls.map(url => `[img]${url}[/img]`) if (urls.length > 0) { screenshotsConsumed = urls.join('\n') screenshotsEncode = screenshotsConsumed } } textToConsume = textToConsume.substring(0, starts) + screenshotsConsumed + textToConsume.substring(ends) } if (!screenshotsEncode && comparisonsProcessed.length) { const teamArray = torrentTitle.match(regexTeamExtraction) // 如果之前没有获取到teamEncode,直接用Encode赋值,避免后续'includes'判断错误(string.includes('') === true) let teamEncode = teamArray ? teamArray[0] : 'Encode' console.log(`[decomposeDescription] extracting screenshots from comparisons with '${teamEncode}' as the encoding team`) let currentScreenshots = 0 for (const item of comparisonsProcessed) { let teams = item.teams let urls = item.urls if (!teams.find(team => team.toLowerCase() === teamEncode.toLowerCase() || team.toLowerCase() === 'encode')) { // 截图对比描述中可能会多一些内容,如 Source vs TayTO<Shout Factory> vs CRiSC<MGM> teamEncode = teams.find(team => team.toLowerCase().includes(teamEncode.toLowerCase()) || team.toLowerCase().includes('encode')) } if (teamEncode && urls.length / teams.length >= site.minScreenshots) { for (let i = 0; i < urls.length; i++) { let image = urls[i] const teamCurrent = teams[i % teams.length] if (currentScreenshots < site.maxScreenshots && (teamCurrent.toLowerCase() === 'encode' || teamCurrent.toLowerCase() === teamEncode.toLowerCase())) { if (image.match(regexImageBbcode)) { screenshotsEncode += image } else { screenshotsEncode += `[img]${image}[/img]` } currentScreenshots += 1 } } if (currentScreenshots >= site.minScreenshots){ break } } } if (screenshotsEncode) { textToConsume += `\n${screenshotsEncode}` } } } // [xxx write ...] -> [b]xxx[/b] [...] if (site.quoteStyle === 'writer') { [description] = processTags( textToConsume, 'quote', matchLeft => matchLeft.replace(/\[quote(?:=([^\]]+))\]/g, '[b]$1[/b]\n[quote]'), matchRight => matchRight, true) } else { description = textToConsume } return [description, mediainfo, torrentTitle] } // 处理简介文本 function processDescription (siteName, description) { const site = siteInfoMap[siteName] const targetBoxTag = site.targetBoxTag const boxSupportDescr = site.boxSupportDescr const boxNeedBreakLine = site.boxNeedBreakLine const allTagBoxesStr = allTagBoxes.join('|') const otherTagBoxesStr = allTagBoxes.filter(tag => tag !== site.targetBoxTag).join('|') const unsupportedTagsStr = site.unsupportedTags.join('|') // 对于不支持box标签的站,统一替换为'quote'标签 const replaceTag = targetBoxTag || 'quote' if (targetBoxTag) { description = nestExplode(description, targetBoxTag) description = compactContent(description, targetBoxTag) } description = description // 处理 mediainfo 容器标签,切换为 [box=mediainfo] 的形式,以便于后续统一匹配 mediainfo .replace(RegExp('\\[(' + allTagBoxesStr + '|quote|code)(?:\\s*=\\s*mediainfo)?\\]\\s*(General\\s+Unique ID[^\\0]+?)\\[\\/\\1\\]', 'gim'), boxSupportDescr ? `[${replaceTag}=mediainfo]$2[/${replaceTag}]` : `[${replaceTag}]$2[/${replaceTag}]`) // NHD mediainfo style .replace(/\[mediainfo\](\s*General\s+Unique ID[^\0]+?)\[\/mediainfo\]/gim, boxSupportDescr ? `[${replaceTag}=mediainfo]$1[/${replaceTag}]` : `[${replaceTag}]$1[/${replaceTag}]`) // 处理除了 mediainfo 以外的容器类标签 // 注意 allTagBoxesStr(由多个'|'组成)不需要 escape // 注意 GPW虽然 boxSupportDescr===true,但显示效果有区别,所以最后也会处理为`[b]$1[/b]\n[${replaceTag}]`形式, // 但这一操作会留到后续才执行,因为现在需要保留这个格式方便识别 .replace(RegExp('\\[(?:' + otherTagBoxesStr + ')(=([^\\]]+))\\]', 'g'), boxSupportDescr ? `[${replaceTag}$1]` : `[b]$2[/b]\n[${replaceTag}]`) .replace(RegExp('\\[(?:' + otherTagBoxesStr + ')\\]', 'g'), `[${replaceTag}]`) .replace(RegExp('\\[\\/(?:' + otherTagBoxesStr + ')\\]', 'g'), `[/${replaceTag}]`) .replace(RegExp('\\[\\/(?:' + replaceTag + ')\\](?!\\r?\\n)', 'g'), boxNeedBreakLine ? `[/${replaceTag}]\n` : `[/${replaceTag}]`) // 不支持的标签 .replace(RegExp('\\[\\/?(' + unsupportedTagsStr + ')(=[^\\]]+)?\\]', 'g'), '\n') .replace(/(\[\/?)(\w+)((?:=(?:[^\r\n\t\f\v [\]])+)?\])/g, (_, p1, p2, p3) => p1 + p2.toLowerCase() + p3) .replace(/(?:(?:\[\/(url|flash|flv))|^)(?:(?!\[(url|flash|flv))[\s\S])*(?:(?:\[(url|flash|flv))|$)/g, matches => matches.replace(/\[align(=\w*)?\]/g, '\n')) // 去除头尾空白 .replace(/^\s*([\s\S]*\S)\s*$/g, '$1') // 至多两个换行 .replace(/(\r?\n){3,}/g, '\n\n') // for pterclub .replace(/\[(\/?img)\d+\]/g, '[$1]') if (siteName === GPW) { description = description .replace(/\[\/?(size|color|font|b|i|u|pre)(=[^\]]+)?\]/g, '') .replace(/\[\/?center\]/g, '\n') } return description } (() => { 'use strict' //= ======================================================================================================== // Main 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 = Object.keys(site.pages).find(pg => { let url = `${site.hostName}/${site.pages[pg]}` return window.location.href.match(escapeRegExp(url)) }) } if (!siteName || !page) { return } console.log(`[main] running in site ${siteName} and page ${page}`) if (page === 'upload' || page === 'edit') { //= ======================================================================================================== // 上传和编辑种子页面 const nameBox = page === 'upload' ? site.nameBoxUpload : site.nameBoxEdit let btnBingo = $('<input>') if (site.construct === NEXUSPHP) { btnBingo.attr({ type: 'button', name: 'bingo', value: 'BINGO', style: 'font-size: 11px; font-weight: bold; color: blue; margin-right: 3px' }) const tableBingo = $('<table>').attr({ cellspacing: '1', cellpadding: '2', border: '0', style: 'margin-top:3px' }).append( $('<tbody>').append( $('<tr>').attr({ id: 'multi_function' }).append( $('<td>').attr({ class: 'embedded' }).append(btnBingo) ) ) ) if (siteName === MTEAM || siteName === NHD || siteName === PTERCLUB || siteName === PUTAO) { $('#compose input[name="quote"]').closest('table').after(tableBingo) } else if (siteName === TTG) { $('#upload input[name="quote"]').closest('table').after(tableBingo) } } else if (site.construct === GAZELLE) { if (siteName === GPW) { btnBingo.attr({ type: 'button', name: 'bingo', value: 'BINGO', style: 'font-weight: bold; color: white;', class: 'BBCodeToolbar-button' }) const bbcodeToolbar = $('div.BBCodeToolbar').closest('#description-container').find('div.BBCodeToolbar') bbcodeToolbar.append(btnBingo) // 如果没有"hide"按钮,添加一个 if (bbcodeToolbar.find('button[data-cmd="hide"]').length === 0) { const btnHide = $('<input>').attr({ type: 'button', class: 'BBCodeToolbar-button', 'data-cmd': 'hide', value: '{H}', style: 'font-weight: bold; color: white;' }) bbcodeToolbar.find('button[data-cmd="code"]').after(btnHide) } } else if (siteName === UHD) { btnBingo.attr({ type: 'button', name: 'bingo', value: 'BINGO', style: 'font-weight: bold; color: white;', class: 'wysibb-toolbar-btn' }) const divBingo = $('<div>').attr({ class: 'wysibb-toolbar-container' }).append(btnBingo) const bbcodeToolbar = $('div.wysibb-toolbar').closest('#textarea_wrap_0').find('div.wysibb-toolbar') bbcodeToolbar.append(divBingo) } } else if (site.construct === MTORRENT) { if (siteName === MTEAM) { btnBingo = $('<button>', { type: 'button', class: 'toolbar-item', value: 'BINGO', title: 'BINGO', 'aria-label': 'BINGO', style: 'font-weight: bold; color: white;' }) } $('button.toolbar-item').find('i.markdown').parent().after(btnBingo) } // function definition btnBingo.on('click', async () => { const oriTextBingo = btnBingo.val() const torrentInfo = {} try { btnBingo.val('Handling') //= ======================================================================================================== // processing description let textToConsume = '' if (site.construct === NEXUSPHP) { const oldText = site.descrBox.val() let readClipboard = false if (siteName === NHD || siteName === PTERCLUB || siteName === PUTAO || siteName === MTEAM) { readClipboard = !oldText } else if (siteName === TTG) { readClipboard = !oldText ? true : oldText.length < 125 } textToConsume = readClipboard ? await navigator.clipboard.readText() : oldText } else if (site.construct === GAZELLE) { const oldText = site.descrBox.val() let readClipboard = !oldText if (readClipboard) { btnBingo.focus() } textToConsume = readClipboard ? await navigator.clipboard.readText() : oldText } else if (site.construct === MTORRENT) { console.log() } textToConsume = processDescription(siteName, textToConsume) // 为了在未选择种子文件的情况下也能获取torrentTitle,将torrentTitle中信息的识别放到mediainfo之后 // 优先读取nameBox torrentInfo.torrentTitle = nameBox ? nameBox.val() : '' // 再读取inpuFile if (!torrentInfo.torrentTitle) { let inputFile = (site.inputFile.val() || '').replace(/.*?([^\\/]+)$/, '$1') torrentInfo.torrentTitle = formatTorrentName(inputFile) } //= ======================================================================================================== let mediainfoStr = site.mediainfoBox ? site.mediainfoBox.val() : '' // decompose description (and generate comparison screenshots) ;[textToConsume, torrentInfo.mediainfo, torrentInfo.torrentTitle] = await decomposeDescription(siteName, textToConsume, mediainfoStr, torrentInfo.torrentTitle) // dtsX: false, atmos: false, commentary: false, language: 'chinese' torrentInfo.audioInfo = [] torrentInfo.videoInfo = { bit10: false, hdr10: false, hdr10plus: false, dovi: false, container: '' } // from languageMap torrentInfo.subtitleInfo = [] // info from mediainfo Object.entries(torrentInfo.mediainfo).forEach(([infoKey, infoValue]) => { if (infoKey.match(/^text( #\d+)?/i)) { // subtitle let subtitle = { language: '', commentary: false } const title = infoValue.Title || '' const languageRaw = infoValue.Language || '' subtitle.commentary = title.match(/commentary/i) ? true : false if (languageRaw.match(/chinese|mandarin/i) || title.match(/chinese|mandarin/i)) { if (languageRaw.match(/cht|traditional|繁體|繁体/i) || title.match(/cht|traditional|繁體|繁体/i)) { subtitle.language = 'chinese_traditional' } else { subtitle.language = 'chinese_simplified' } } else { Object.keys(languageMap).forEach(lang => { if (subtitle.language) { return } else { // cases like Spanish (Latin America) will match latin let language = languageRaw.replace(/\(.+\)/, '').trim() if (language.match(RegExp(escapeRegExp(lang), 'i')) || language.match(RegExp(escapeRegExp(lang.replace(/_/ig, ' ')), 'i'))) { subtitle.language = lang return } } }) } torrentInfo.subtitleInfo.push(subtitle) } else if (infoKey.match(/^audio( #\d+)?/i)) { // audio let audio = { language: '', commentary: false, atmos: false, dtsX: false } const title = infoValue.Title || '' const languageRaw = infoValue.Language || '' audio.commentary = title.match(/commentary/i) ? true : false if (languageRaw.match(/cantonese|广东|粤|廣東/i) || title.match(/cantonese|广东|粤|廣東/i)) { audio.language = 'cantonese' } else if (languageRaw.match(/chinese|mandarin|国语|普通话|国配/i) || title.match(/chinese|mandarin|国语|普通话|国配/i)) { audio.language = 'mandarin' } else { Object.keys(languageMap).forEach(lang => { if (audio.language) { return } else { // cases like Spanish (Latin America) will match latin let language = languageRaw.replace(/\(.+\)/, '').trim() if (language.match(RegExp(escapeRegExp(lang), 'i')) || language.match(RegExp(escapeRegExp(lang.replace(/_/ig, ' ')), 'i'))) { audio.language = lang return } } }) } const commecialName = infoValue['Commercial name'] || '' if (commecialName.match(/Dolby Atmos/i)) { audio.atmos = true } else if (commecialName.match(/DTS-HD Master Audio/i)) { audio.dtsX = true } torrentInfo.audioInfo.push(audio) } else if (infoKey.match(/^video/i)) { // video const hdrFormat = infoValue['HDR format'] || infoValue['Commercial name'] || '' const bitDepth = infoValue['Bit depth'] || '' if (hdrFormat) { if (hdrFormat.match(/HDR10\+/i)) { torrentInfo.videoInfo.hdr10plus = true } else if (hdrFormat.match(/HDR10/i)) { torrentInfo.videoInfo.hdr10 = true } if (hdrFormat.match(/Dolby Vision/i)) { torrentInfo.videoInfo.dovi = true } } else if (bitDepth.match(/10 bits/i)) { torrentInfo.videoInfo.bit10 = true } } else if (infoKey.match(/^general$/i)) { // general if (infoValue.Format === 'Matroska') { torrentInfo.videoInfo.container = 'MKV' } else if (infoValue.Format === 'MPEG-4') { torrentInfo.videoInfo.container = 'MP4' } else if (infoValue.Format === 'AVI') { torrentInfo.videoInfo.container = 'AVI' } else { if (infoValue.Format) { torrentInfo.videoInfo.container = infoValue.Format.trim() } else { torrentInfo.videoInfo.container = '' console.error('[main] invalid mediainfo (no video format)') } } // 如果 torrentInfo.torrentTitle 尚未被赋值,直接使用mediainfo 中的值 torrentInfo.torrentTitle = torrentInfo.torrentTitle || formatTorrentName(infoValue['Complete name']) || formatTorrentName(infoValue['Movie name']) } }) console.log(`[main] video: { 10 bits: ${torrentInfo.videoInfo.bit10}, HDR10: ${torrentInfo.videoInfo.hdr10}, HDR10+: ${torrentInfo.videoInfo.hdr10plus}, Dolby Vision: ${torrentInfo.videoInfo.dovi}, container: ${torrentInfo.videoInfo.container} }`) torrentInfo.audioInfo.forEach(audio => { console.log(`[main] audio: { language: ${audio.language}, commentary: ${audio.commentary}, Atmos: ${audio.atmos}, DtsX: ${audio.dtsX} }`) }) torrentInfo.subtitleInfo.forEach(subtitle => { console.log(`[main] subtitle: { language: ${subtitle.language}, commentary: ${subtitle.commentary} }`) }) //= ======================================================================================================== // info from title torrentInfo.editionInfo = {} torrentInfo.sourceInfo = {} torrentInfo.standardInfo = {} torrentInfo.processingInfo = {} torrentInfo.codecInfo = {} if (torrentInfo.torrentTitle) { // edition torrentInfo.editionInfo.criterionCollection = !!torrentInfo.torrentTitle.match(/\b(cc|criterion)\b/i) torrentInfo.editionInfo.mastersOfCinema = !!torrentInfo.torrentTitle.match(/\bmoc\b/i) torrentInfo.editionInfo.directorsCut = !!torrentInfo.torrentTitle.match(/\b(dc|director('?s)? cut)\b/i) torrentInfo.editionInfo.unrated = !!torrentInfo.torrentTitle.match(/\bunrated\b/i) torrentInfo.editionInfo.uncut = !!torrentInfo.torrentTitle.match(/\buncut\b/i) torrentInfo.editionInfo.theatrical = !!torrentInfo.torrentTitle.match(/\btheatrical\b/i) torrentInfo.editionInfo.extended = !!torrentInfo.torrentTitle.match(/\bextended\b/i) torrentInfo.editionInfo.remaster4k = !!torrentInfo.torrentTitle.match(/\b4k remaster\b/i) torrentInfo.editionInfo.remaster = !torrentInfo.editionInfo.remaster4k && !!torrentInfo.torrentTitle.match(/\bremaster\b/i) torrentInfo.editionInfo.restoration4k = !!torrentInfo.torrentTitle.match(/\b4k restoration\b/i) torrentInfo.editionInfo.twoInOne = !!torrentInfo.torrentTitle.match(/\b2in1\b/i) torrentInfo.editionInfo.threeInOne = !!torrentInfo.torrentTitle.match(/\b3in1\b/i) torrentInfo.editionInfo.hybrid = !!torrentInfo.torrentTitle.match(/\bhybrid\b/i) torrentInfo.editionInfo.imax = !!torrentInfo.torrentTitle.match(/\bimax\b/i) torrentInfo.editionInfo.tvCut = !!torrentInfo.torrentTitle.match(/\btv ?cut\b/i) // processing torrentInfo.processingInfo.raw = !!torrentInfo.torrentTitle.match(/\b(remux|web-?dl|(bd|dvd)?iso)\b/i) torrentInfo.processingInfo.encode = !torrentInfo.processingInfo.raw torrentInfo.processingInfo.remux = !!torrentInfo.torrentTitle.match(/\bremux\b/i) // source torrentInfo.sourceInfo.remux = torrentInfo.processingInfo.remux torrentInfo.sourceInfo.encode = torrentInfo.processingInfo.encode torrentInfo.sourceInfo.bluray = !!torrentInfo.torrentTitle.match(/\b(blu-?ray|bdrip|uhd)\b/i) torrentInfo.sourceInfo.hdtv = !!torrentInfo.torrentTitle.match(/\bhdtv(rip)?\b/i) torrentInfo.sourceInfo.hdrip = !!torrentInfo.torrentTitle.match(/\bhdrip\b/i) torrentInfo.sourceInfo.webdl = !!torrentInfo.torrentTitle.match(/\bweb-?dl\b/i) torrentInfo.sourceInfo.webrip = !!torrentInfo.torrentTitle.match(/\bwebrip\b/i) torrentInfo.sourceInfo.web = torrentInfo.sourceInfo.webdl || torrentInfo.sourceInfo.webrip torrentInfo.sourceInfo.dvd = !!torrentInfo.torrentTitle.match(/\bdvd(rip)?/i) torrentInfo.sourceInfo.hddvd = !!torrentInfo.torrentTitle.match(/\bhddvd\b/i) // resolution torrentInfo.standardInfo.res1080p = !!torrentInfo.torrentTitle.match(/\b1080p\b/i) torrentInfo.standardInfo.res1080i = !!torrentInfo.torrentTitle.match(/\b1080i\b/i) torrentInfo.standardInfo.res720p = !!torrentInfo.torrentTitle.match(/\b720p\b/i) torrentInfo.standardInfo.res2160p = !!torrentInfo.torrentTitle.match(/\b(2160p|4k(?!= ?remaster| ?restoration))\b/i) torrentInfo.standardInfo.sd = !!torrentInfo.torrentTitle.match(/\b480p\b/i) || torrentInfo.sourceInfo.dvd torrentInfo.standardInfo.mhd = !!torrentInfo.torrentTitle.match(/\bmhd\b/i) // codec torrentInfo.codecInfo.h264 = !!torrentInfo.torrentTitle.match(/\b(avc|h\.?264)\b/i) torrentInfo.codecInfo.x264 = !!torrentInfo.torrentTitle.match(/\bx264\b/i) torrentInfo.codecInfo.h265 = !!torrentInfo.torrentTitle.match(/\b(hevc|h\.?265)\b/i) torrentInfo.codecInfo.x265 = !!torrentInfo.torrentTitle.match(/\bx265\b/i) torrentInfo.codecInfo.x266 = !!torrentInfo.torrentTitle.match(/\bx266\b/i) torrentInfo.codecInfo.vc1 = !!torrentInfo.torrentTitle.match(/\bvc-1\b/i) torrentInfo.codecInfo.av1 = !!torrentInfo.torrentTitle.match(/\bav1\b/i) torrentInfo.codecInfo.mpeg2 = !!torrentInfo.torrentTitle.match(/\bmpeg-2\b/i) torrentInfo.codecInfo.xvid = !!torrentInfo.torrentTitle.match(/\bxvid\b/i) torrentInfo.codecInfo.divx = !!torrentInfo.torrentTitle.match(/\bdivx\b/i) torrentInfo.codecInfo.flac = !!torrentInfo.torrentTitle.match(/\bflac\b/i) torrentInfo.codecInfo.ape = !!torrentInfo.torrentTitle.match(/\bape\b/i) // team const teamArray = torrentInfo.torrentTitle.match(regexTeamExtraction) torrentInfo.team = teamArray ? teamArray[0] : '' } if (torrentInfo.audioInfo) { // edition from audioInfo (for GPW) torrentInfo.editionInfo.withCommentary = torrentInfo.audioInfo.some(audio => audio.commentary) // 计算非评论音轨的语言种类,用于判定“双音轨”标签 let dubs = torrentInfo.audioInfo .map(audio => audio.commentary ? '' : audio.language) .filter((x, i, a) => x && a.indexOf(x) == i ) torrentInfo.editionInfo.dualAudio = dubs.length === 2 } if (torrentInfo.videoInfo) { // edition from videoInfo (for UHD) torrentInfo.editionInfo.bit10 = torrentInfo.videoInfo.bit10 } //= ======================================================================================================== // info from douban / imdb const categoryMovie = 'Movie'; const categoryTvSeries = 'TV Series'; const categoryAnimation = 'Animation' const categoryDocumentary = 'Documentary'; const categoryTvShow = 'TV Show' if (site.construct === NEXUSPHP) { torrentInfo.movieInfo = { areaInfo: {} } // area const areaArray = textToConsume.match(/(?:产\s*地|国\s*家)\s+(.+)$/m) const area = areaArray ? areaArray[1].split(/\s*\/\s*/)[0].trim() : '' if (area.match(/中国大陆/)) { torrentInfo.movieInfo.areaInfo.cnMl = true } else if (area.match(/香港/)) { torrentInfo.movieInfo.areaInfo.hk = true } else if (area.match(/台湾/)) { torrentInfo.movieInfo.areaInfo.tw = true } else if (area.match(/美国|加拿大|英国|法国|德国|希腊|匈牙利|爱尔兰|意大利|阿尔巴尼亚|安道尔|奥地利|白俄罗斯|比利时|波斯尼亚|黑塞哥维那|保加利亚|克罗地亚|塞浦路斯|捷克|丹麦|爱沙尼亚|法罗群岛|冰岛|芬兰|拉脱维亚|列支敦士登|立陶宛|卢森堡|马其顿|马耳他|摩尔多瓦|摩纳哥|荷兰|挪威|波兰|葡萄牙|罗马尼亚|俄罗斯|圣马力诺|塞黑|斯洛伐克|斯洛文尼亚|西班牙|瑞典|瑞士|乌克兰|梵蒂冈/)) { torrentInfo.movieInfo.areaInfo.euAme = true } else if (area.match(/印度|韩国|日本|新加坡|泰国|印度尼西亚|菲律宾|越南|土耳其|老挝|柬埔寨|缅甸|马来西亚|文莱|东帝汶|尼泊尔|不丹|孟加拉国|巴基斯坦|斯里兰卡|马尔代夫|阿富汗|伊拉克|伊朗|叙利亚|约旦|黎巴嫩|以色列|巴勒斯坦|沙特阿拉伯|阿曼|也门|格鲁吉亚|亚美尼亚|塞浦路斯|哈萨克斯坦|吉尔吉斯斯坦|塔吉克斯坦|乌兹别克斯坦|土库曼斯坦|蒙古|朝鲜/)) { torrentInfo.movieInfo.areaInfo.asia = true if (area.match(area.match(/韩国/))) { torrentInfo.movieInfo.areaInfo.kor = true } else if (area.match(/日本/)) { torrentInfo.movieInfo.areaInfo.jap = true } else if (area.match(/印度/)) { torrentInfo.movieInfo.areaInfo.ind = true } } // title const originalTitleArray = textToConsume.match(/片\s*名\s+(.+)$/m) if (originalTitleArray) { torrentInfo.movieInfo.originalTitle = originalTitleArray[1].trim() } const translatedTitlesArray = textToConsume.match(/译\s*名\s+(.+)$/m) if (translatedTitlesArray) { torrentInfo.movieInfo.translatedTitles = translatedTitlesArray[1].trim().split(/\s*\/\s*/).filter(title => title) } // festival const festivalArray = textToConsume.match(/(\d{4})-\d{2}-\d{2}\((\S+电影节)\)/) torrentInfo.movieInfo.festival = festivalArray ? (festivalArray[1] + festivalArray[2]).trim() : '' // category const genresArray = textToConsume.match(/类\s*别\s+(.+)$/m) torrentInfo.movieInfo.genres = genresArray ? genresArray[1].trim().split(/\s*\/\s*/).filter(genre => genre).join(' / ') : '' torrentInfo.movieInfo.category = torrentInfo.movieInfo.genres.match('纪录') ? categoryDocumentary : torrentInfo.movieInfo.genres.match('动画') ? categoryAnimation : textToConsume.match(/集\s*数\s+\d+/) ? categoryTvSeries : torrentInfo.movieInfo.genres.match('秀') ? categoryTvShow : categoryMovie // douban and imdb score in small_desc const doubanScoreArray = textToConsume.match(/豆\s*瓣\s*评\s*分\s+(\d(?:\.\d))\/10\sfrom\s((?:\d+,)*\d+)\susers/) if (doubanScoreArray) { torrentInfo.movieInfo.doubanScore = doubanScoreArray[1] torrentInfo.movieInfo.doubanScoreRatingNumber = doubanScoreArray[2] } const imdbScoreArray = textToConsume.match(/IMDb\s*评\s*分\s+(\d(?:\.\d))\/10\sfrom\s((?:\d+,)*\d+)\susers/i) if (imdbScoreArray) { torrentInfo.movieInfo.imdbScore = imdbScoreArray[1] torrentInfo.movieInfo.imdbRatingNumber = imdbScoreArray[2] } // director const directorArray = textToConsume.match(/导\s*演\s+(.+)$/m) torrentInfo.movieInfo.director = directorArray ? directorArray[1].split(/\s*\/\s*/)[0] : '' // douban link const doubanLinkArray = textToConsume.match(/豆\s*瓣\s*链\s*接.+(https?:\/\/movie\.douban\.com\/subject\/(\d+)\/?)/) torrentInfo.movieInfo.doubanLink = doubanLinkArray ? doubanLinkArray[1] : '' torrentInfo.movieInfo.doubanId = doubanLinkArray ? doubanLinkArray[2] : '' // imdb link const imdbLinkArray = textToConsume.match(/IMDb\s*链\s*接.+(https?:\/\/www\.imdb\.com\/title\/(tt\d+)\/?)/i) torrentInfo.movieInfo.imdbLink = imdbLinkArray ? imdbLinkArray[1] : '' torrentInfo.movieInfo.imdbId = imdbLinkArray ? imdbLinkArray[2] : '' } //= ======================================================================================================== // fill the page // common controls // 用于记录种子在站点的匹配信息 torrentInfo.infoInSite = { 'site': siteName } // namebox if (nameBox && torrentInfo.torrentTitle) { torrentInfo.infoInSite.torrentTitle = torrentInfo.torrentTitle if (site.translatedChineseNameInTitle) { if (torrentInfo.movieInfo.areaInfo.cnMl) { torrentInfo.infoInSite.torrentTitle = torrentInfo.torrentTitle.match(RegExp(escapeRegExp(torrentInfo.movieInfo.originalTitle), 'i')) ? torrentInfo.torrentTitle : `[${torrentInfo.movieInfo.originalTitle}] ${torrentInfo.torrentTitle}` } else if (torrentInfo.movieInfo.translatedTitles.length) { torrentInfo.infoInSite.torrentTitle = torrentInfo.torrentTitle.match(RegExp(escapeRegExp(torrentInfo.movieInfo.translatedTitles[0]), 'i')) ? torrentInfo.torrentTitle : `[${torrentInfo.movieInfo.translatedTitles[0]}] ${torrentInfo.torrentTitle}` } } else { torrentInfo.infoInSite.torrentTitle = torrentInfo.torrentTitle } nameBox.val(torrentInfo.infoInSite.torrentTitle) } // small description if (torrentInfo.movieInfo && (torrentInfo.movieInfo.doubanLink || torrentInfo.movieInfo.imdbLink)) { // container for small_desc(副标题) fields const smallDescrArray = [] if (torrentInfo.movieInfo.originalTitle && torrentInfo.movieInfo.translatedTitles) { let titles = [] let torrentTitle = torrentInfo.infoInSite.torrentTitle || torrentInfo.torrentTitle if (!torrentTitle.match(RegExp(escapeRegExp(torrentInfo.movieInfo.originalTitle), 'i'))) { titles.push(torrentInfo.movieInfo.originalTitle) } torrentInfo.movieInfo.translatedTitles.forEach(title => { if (!torrentTitle.match(RegExp(escapeRegExp(title), 'i'))) { titles.push(title) } }) smallDescrArray.push(titles.join(' / ')) } if (torrentInfo.movieInfo.festival) { smallDescrArray.push(torrentInfo.movieInfo.festival) } if (torrentInfo.movieInfo.genres) { smallDescrArray.push(torrentInfo.movieInfo.genres) } if (!site.pullMovieScore) { if (torrentInfo.movieInfo.doubanScore) { smallDescrArray.push(`豆瓣 ${torrentInfo.movieInfo.doubanScore} (${torrentInfo.movieInfo.doubanScoreRatingNumber})`) } if (torrentInfo.movieInfo.imdbScore) { smallDescrArray.push(`IMDb ${torrentInfo.movieInfo.imdbScore} (${torrentInfo.movieInfo.imdbRatingNumber})`) } } if (torrentInfo.movieInfo.director) { smallDescrArray.push(torrentInfo.movieInfo.director) } // complete small_descr if (site.smallDescBox) { torrentInfo.infoInSite.smallDescr = smallDescrArray.join(' | ') site.smallDescBox.val(torrentInfo.infoInSite.smallDescr) } // TTG的edit页面,smallDescr的内容需要附加到nameBox中 if (siteName === TTG && page === 'edit') { nameBox.val(nameBox.val() + ` [${smallDescrArray.join(' | ')}]`) } } // douban link if (site.doubanLinkBox && torrentInfo.movieInfo && torrentInfo.movieInfo.doubanLink) { if (!site.doubanIdInsteadofLink) { site.doubanLinkBox.val(torrentInfo.movieInfo.doubanLink) } else { site.doubanLinkBox.val(torrentInfo.movieInfo.doubanId) } } // imdb link if (site.imdbLinkBox && torrentInfo.movieInfo && torrentInfo.movieInfo.imdbLink) { if (!site.doubanIdInsteadofLink) { site.imdbLinkBox.val(torrentInfo.movieInfo.imdbLink) } else { site.imdbLinkBox.val(torrentInfo.movieInfo.imdbId) } } // source if (site.sourceSel && torrentInfo.sourceInfo && Object.values(torrentInfo.sourceInfo).some(option => option)) { torrentInfo.infoInSite.source = site.sourceInfo.default || 0 if (siteName === PTERCLUB) { torrentInfo.infoInSite.source = torrentInfo.sourceInfo.remux ? site.sourceInfo.remux// remux : torrentInfo.sourceInfo.encode ? site.sourceInfo.encode// encode : torrentInfo.sourceInfo.hdtv ? site.sourceInfo.hdtv// hdtv : torrentInfo.sourceInfo.webdl ? site.sourceInfo.webdl// web-dl : torrentInfo.sourceInfo.dvd || torrentInfo.sourceInfo.hddvd ? site.sourceInfo.dvd : torrentInfo.infoInSite.source// other } else if (siteName === NHD) { torrentInfo.infoInSite.source = torrentInfo.sourceInfo.bluray ? site.sourceInfo.bluray : torrentInfo.sourceInfo.hddvd ? site.sourceInfo.hddvd : torrentInfo.sourceInfo.dvd ? site.sourceInfo.dvd : torrentInfo.sourceInfo.webdl ? site.sourceInfo.webdl : torrentInfo.sourceInfo.webrip ? site.sourceInfo.webrip : torrentInfo.infoInSite.source } else if (siteName === GPW) { torrentInfo.infoInSite.source = torrentInfo.sourceInfo.bluray ? site.sourceInfo.bluray : torrentInfo.sourceInfo.hddvd ? site.sourceInfo.hddvd : torrentInfo.sourceInfo.dvd ? site.sourceInfo.dvd : torrentInfo.sourceInfo.web ? site.sourceInfo.web : torrentInfo.infoInSite.source } else if (siteName === UHD) { torrentInfo.infoInSite.source = torrentInfo.sourceInfo.encode ? site.sourceInfo.encode : torrentInfo.sourceInfo.remux ? site.sourceInfo.remux : torrentInfo.sourceInfo.webdl ? site.sourceInfo.webdl : torrentInfo.sourceInfo.webrip ? site.sourceInfo.webrip : torrentInfo.sourceInfo.hdrip ? site.sourceInfo.hdrip : torrentInfo.sourceInfo.hdtv ? site.sourceInfo.hdtv : torrentInfo.sourceInfo.bluray ? site.sourceInfo.bluray : torrentInfo.infoInSite.source } site.sourceSel.val(torrentInfo.infoInSite.source) } // standard if (site.standardSel && torrentInfo.standardInfo && Object.values(torrentInfo.standardInfo).some(option => option)) { torrentInfo.infoInSite.standard = torrentInfo.standardInfo.res1080p ? site.standardInfo.res1080p : torrentInfo.standardInfo.res1080i ? site.standardInfo.res1080i : torrentInfo.standardInfo.res720p ? site.standardInfo.res720p : torrentInfo.standardInfo.res2160p ? site.standardInfo.res2160p : site.standardInfo.default if (torrentInfo.infoInSite.standard === site.standardInfo.default) { if (Object.keys(site.standardInfo).includes('sd') && torrentInfo.standardInfo.sd) { torrentInfo.infoInSite.standard = site.standardInfo.sd } else if (Object.keys(site.standardInfo).includes('mhd') && torrentInfo.standardInfo.mhd) { torrentInfo.infoInSite.standard = site.standardInfo.mhd } } site.standardSel.val(torrentInfo.infoInSite.standard) } // processing if (site.processingSel && torrentInfo.processingInfo && Object.values(torrentInfo.processingInfo).some(option => option)) { torrentInfo.infoInSite.processing = site.processingInfo.default || 0 if (siteName === NHD) { torrentInfo.infoInSite.processing = torrentInfo.processingInfo.raw ? site.processingInfo.raw : torrentInfo.processingInfo.encode ? site.processingInfo.encode : torrentInfo.infoInSite.processing } else if (siteName === GPW) { site.processingSel.closest('tr.hidden').removeClass('hidden') torrentInfo.infoInSite.processing = torrentInfo.processingInfo.remux ? site.processingInfo.remux : torrentInfo.processingInfo.encode ? site.processingInfo.encode : torrentInfo.infoInSite.processing } site.processingSel.val(torrentInfo.infoInSite.processing) } // codec if (site.codecSel && torrentInfo.codecInfo && Object.values(torrentInfo.codecInfo).some(option => option)) { torrentInfo.infoInSite.codec = site.codecInfo.default || 0 if (siteName === NHD || siteName === PUTAO || siteName === MTEAM) { torrentInfo.infoInSite.codec = torrentInfo.codecInfo.x264 || torrentInfo.codecInfo.h264 ? site.codecInfo.h264 : torrentInfo.codecInfo.x265 || torrentInfo.codecInfo.h265 ? site.codecInfo.h265 : torrentInfo.codecInfo.vc1 ? site.codecInfo.vc1 : torrentInfo.codecInfo.mpeg2 ? site.codecInfo.mpeg2 : torrentInfo.codecInfo.xvid ? site.codecInfo.xvid : torrentInfo.codecInfo.flac ? site.codecInfo.flac : torrentInfo.codecInfo.ape ? site.codecInfo.ape : torrentInfo.infoInSite.codec } else if (siteName === GPW) { torrentInfo.infoInSite.codec = torrentInfo.codecInfo.h264 ? site.codecInfo.h264 : torrentInfo.codecInfo.h265 ? site.codecInfo.h265 : torrentInfo.codecInfo.x264 ? site.codecInfo.x264 : torrentInfo.codecInfo.x265 ? site.codecInfo.x265 : torrentInfo.codecInfo.xvid ? site.codecInfo.xvid : torrentInfo.codecInfo.divx ? site.codecInfo.divx : torrentInfo.infoInSite.codec } else if (siteName === UHD) { torrentInfo.infoInSite.codec = torrentInfo.codecInfo.h264 ? site.codecInfo.h264 : torrentInfo.codecInfo.h265 ? site.codecInfo.h265 : torrentInfo.codecInfo.x264 ? site.codecInfo.x264 : torrentInfo.codecInfo.x265 ? site.codecInfo.x265 : torrentInfo.codecInfo.x266 ? site.codecInfo.x266 : torrentInfo.codecInfo.vc1 ? site.codecInfo.vc1 : torrentInfo.codecInfo.mpeg2 ? site.codecInfo.mpeg2 : torrentInfo.codecInfo.av1 ? site.codecInfo.av1 : torrentInfo.infoInSite.codec } site.codecSel.val(torrentInfo.infoInSite.codec) } // team if (torrentInfo.team) { if (site.teamSel) { torrentInfo.infoInSite.team = torrentInfo.team site.teamSel.find('option').each((_, element) => { if (element.text.toLowerCase() === torrentInfo.team.toLowerCase()) { site.teamSel.val(element.value) } }) } else if (site.teamBox) { torrentInfo.infoInSite.team = torrentInfo.team site.teamBox.val(torrentInfo.team) } } // area selection if (site.areaSel && torrentInfo.movieInfo && torrentInfo.movieInfo.areaInfo) { torrentInfo.infoInSite.area = site.areaInfo.default || 0 if (siteName === PTERCLUB) { torrentInfo.infoInSite.area = torrentInfo.movieInfo.areaInfo.cnMl ? site.areaInfo.cnMl : torrentInfo.movieInfo.areaInfo.hk ? site.areaInfo.hk : torrentInfo.movieInfo.areaInfo.tw ? site.areaInfo.tw : torrentInfo.movieInfo.areaInfo.euAme ? site.areaInfo.euAme : torrentInfo.movieInfo.areaInfo.kor ? site.areaInfo.kor : torrentInfo.movieInfo.areaInfo.jap ? site.areaInfo.jap : torrentInfo.movieInfo.areaInfo.ind ? site.areaInfo.ind : site.areaInfo.other } else if (siteName === MTEAM) { torrentInfo.infoInSite.area = torrentInfo.movieInfo.areaInfo.cnMl ? site.areaInfo.cnMl : torrentInfo.movieInfo.areaInfo.euAme ? site.areaInfo.euAme : torrentInfo.movieInfo.areaInfo.hk || torrentInfo.movieInfo.areaInfo.tw ? site.areaInfo.hkTw : torrentInfo.movieInfo.areaInfo.jap ? site.areaInfo.jap : torrentInfo.movieInfo.areaInfo.kor ? site.areaInfo.kor : site.areaInfo.other } site.areaSel.val(torrentInfo.infoInSite.area) } // category selection if (site.categorySel) { torrentInfo.infoInSite.category = site.categoryInfo.default || 0 if ((siteName === NHD || siteName === PTERCLUB) && torrentInfo.movieInfo) { torrentInfo.infoInSite.category = torrentInfo.movieInfo.category === categoryMovie ? site.categoryInfo.movie : torrentInfo.movieInfo.category === categoryTvSeries ? site.categoryInfo.tvSeries : torrentInfo.movieInfo.category === categoryAnimation ? site.categoryInfo.animation : torrentInfo.movieInfo.category === categoryDocumentary ? site.categoryInfo.documentary : torrentInfo.movieInfo.category === categoryTvShow ? site.categoryInfo.tvShow : torrentInfo.infoInSite.category } else if (siteName === PUTAO && torrentInfo.movieInfo && torrentInfo.movieInfo.areaInfo) { if (torrentInfo.movieInfo.category === categoryMovie) { torrentInfo.infoInSite.category = torrentInfo.movieInfo.areaInfo.cnMl || torrentInfo.movieInfo.areaInfo.hk || torrentInfo.movieInfo.areaInfo.tw ? site.categoryInfo.movieCn : torrentInfo.movieInfo.areaInfo.euAme ? site.categoryInfo.movieEuAme : torrentInfo.movieInfo.areaInfo.asia ? site.categoryInfo.movieAsia : torrentInfo.infoInSite.category } else if (torrentInfo.movieInfo.category === categoryDocumentary) { // for clarification torrentInfo.infoInSite.category = site.categoryInfo.documentary } else if (torrentInfo.movieInfo.category === categoryAnimation) { // for clarification torrentInfo.infoInSite.category = site.categoryInfo.animation } else if (torrentInfo.movieInfo.category === categoryTvSeries) { torrentInfo.infoInSite.category = torrentInfo.movieInfo.areaInfo.hk || torrentInfo.movieInfo.areaInfo.tw ? site.categoryInfo.tvSeriesHkTw : torrentInfo.movieInfo.areaInfo.cnMl ? site.categoryInfo.tvSeriesCnMl : torrentInfo.movieInfo.areaInfo.asia ? site.categoryInfo.tvSeriesAsia : torrentInfo.movieInfo.areaInfo.euAme ? site.categoryInfo.tvSeriesEuAme : torrentInfo.infoInSite.category } else if (torrentInfo.movieInfo.category === categoryTvShow) { torrentInfo.infoInSite.category = torrentInfo.movieInfo.areaInfo.cnMl ? site.categoryInfo.tvShowCnMl : torrentInfo.movieInfo.areaInfo.hk || torrentInfo.movieInfo.areaInfo.tw ? site.categoryInfo.tvShowHkTw : torrentInfo.movieInfo.areaInfo.euAme ? site.categoryInfo.tvShowEuAme : torrentInfo.movieInfo.areaInfo.jap || torrentInfo.movieInfo.areaInfo.kor ? site.categoryInfo.tvShowJapKor : torrentInfo.infoInSite.category } } else if (siteName === MTEAM && torrentInfo.sourceInfo) { if (torrentInfo.movieInfo.category === categoryMovie) { torrentInfo.infoInSite.category = torrentInfo.sourceRemux ? site.categoryInfo.movieRemux : torrentInfo.sourceInfo.encode || torrentInfo.sourceInfo.hdtv || torrentInfo.sourceInfo.hddvd || torrentInfo.sourceInfo.web ? site.categoryInfo.movieHd : torrentInfo.infoInSite.category } else if (torrentInfo.movieInfo.category === categoryTvSeries || torrentInfo.movieInfo.category === categoryTvShow) { torrentInfo.infoInSite.category = torrentInfo.sourceInfo.encode || torrentInfo.sourceInfo.hdtv || torrentInfo.sourceInfo.hddvd || torrentInfo.sourceInfo.web ? site.categoryInfo.tvSeriesHd : torrentInfo.infoInSite.category } else if (torrentInfo.movieInfo.category === categoryDocumentary) { torrentInfo.infoInSite.category = site.categoryInfo.documentary } else if (torrentInfo.movieInfo.category === categoryAnimation) { torrentInfo.infoInSite.category = site.categoryInfo.animation } } else if (siteName === TTG && torrentInfo.standardInfo && torrentInfo.movieInfo && torrentInfo.movieInfo.areaInfo) { if (torrentInfo.movieInfo.category === categoryMovie) { torrentInfo.infoInSite.category = torrentInfo.standardInfo.res720p ? site.categoryInfo.movie720p : torrentInfo.standardInfo.res1080i || torrentInfo.standardInfo.res1080p ? site.categoryInfo.movie1080ip : torrentInfo.standardInfo.res2160p ? site.categoryInfo.movie2160p : torrentInfo.infoInSite.category } else if (torrentInfo.movieInfo.category === categoryDocumentary) { torrentInfo.infoInSite.category = torrentInfo.standardInfo.res720p ? site.categoryInfo.documentary720p : torrentInfo.standardInfo.res1080i || torrentInfo.standardInfo.res1080p ? site.categoryInfo.documentary1080ip : torrentInfo.infoInSite.category } else if (torrentInfo.movieInfo.category === categoryAnimation) { torrentInfo.infoInSite.category = site.categoryInfo.animation } else if (torrentInfo.movieInfo.category === categoryTvSeries) { torrentInfo.infoInSite.category = torrentInfo.movieInfo.areaInfo.jap ? site.categoryInfo.tvSeriesJap : torrentInfo.movieInfo.areaInfo.kor ? site.categoryInfo.tvSeriesKor : torrentInfo.euAme ? site.categoryInfo.tvSeriesEuAme : torrentInfo.movieInfo.areaInfo.cnMl || torrentInfo.movieInfo.areaInfo.hk || torrentInfo.movieInfo.areaInfo.tw ? site.categoryInfo.tvSeriesCn : torrentInfo.infoInSite.category } else if (torrentInfo.movieInfo.category === categoryTvShow) { torrentInfo.infoInSite.category = torrentInfo.movieInfo.areaInfo.kor ? site.categoryInfo.tvShowKor : torrentInfo.movieInfo.areaInfo.jap ? site.categoryInfo.tvShowJap : site.categoryInfo.tvShow } } site.categorySel.val(torrentInfo.infoInSite.category) } // site-specific if (site.construct === NEXUSPHP) { let chs_subbed = torrentInfo.subtitleInfo.some(sub => sub.language === 'chinese_simplified' && !sub.commentary) let cht_subbed = torrentInfo.subtitleInfo.some(sub => sub.language === 'chinese_traditional' && !sub.commentary) let eng_subbed = torrentInfo.subtitleInfo.some(sub => sub.language === 'english' && !sub.commentary) let mandarin_dubbed = torrentInfo.audioInfo.some(audio => audio.language === 'mandarin' && !audio.commentary) let canto_dubbed = torrentInfo.audioInfo.some(audio => audio.language === 'cantonese' && !audio.commentary) if (siteName === PTERCLUB) { if (site.chsubCheck) { site.chsubCheck.checked = chs_subbed || cht_subbed } if (site.englishSubCheck) { site.englishSubCheck.checked = eng_subbed } if (site.chdubCheck) { site.chdubCheck.checked = mandarin_dubbed } if (site.cantodubCheck) { site.cantodubCheck.checked = canto_dubbed } } else if (siteName === MTEAM) { if (site.chsubCheck) { site.chsubCheck.checked = chs_subbed || cht_subbed } if (site.chdubCheck) { site.chdubCheck.checked = mandarin_dubbed } } else if (siteName === TTG) { if (chs_subbed && cht_subbed) { site.subtitleBox.val('* 内封简繁字幕') } else if (chs_subbed) { site.subtitleBox.val('* 内封简体字幕') } else if (cht_subbed) { site.subtitleBox.val('* 内封繁体字幕') } } } else if (site.construct === GAZELLE) { if (siteName === GPW) { // movie edition if (torrentInfo.editionInfo) { if (site.movieEditionContainer.css("display") === 'none') { site.showMovieEditionCheck.click() } Object.entries(site.movieEditionInfo).forEach(([tagKey, tag]) => { let selectedTags = site.movieEditionSelected.val().trim().split(/\s*\/\s*/i) let toSelect = torrentInfo.editionInfo[tagKey] let checker = $(`a[onclick*="${tag}"]`)[0] if (toSelect !== selectedTags.includes(tag)) { console.log(`[main] edition ${tag}`) checker.click() } }) } // subtitles const subbed = torrentInfo.subtitleInfo.some(sub => !sub.commentary) site.noSubCheck.checked = !subbed site.mixedSubCheck.checked = subbed if (subbed) { site.otherSubtitlesDiv.removeClass('hidden') Object.keys(languageMap).forEach(lang => { if (site.subtitleInfo[lang]) { site.subtitleInfo[lang].checked = torrentInfo.subtitleInfo.some(sub => sub.language === lang && !sub.commentary ) } }) } // video info if (torrentInfo.videoInfo) { site.videoInfo.bit10.checked = torrentInfo.videoInfo.bit10 site.videoInfo.hdr10.checked = torrentInfo.videoInfo.hdr10 site.videoInfo.hdr10plus.checked = torrentInfo.videoInfo.hdr10plus site.videoInfo.dovi.checked = torrentInfo.videoInfo.dovi } // audio info if (torrentInfo.audioInfo) { site.audioInfo.dtsX.checked = torrentInfo.audioInfo.some(audio => audio.dtsX) site.audioInfo.atmos.checked = torrentInfo.audioInfo.some(audio => audio.atmos) // GPW的国语配音作为特色属性,特指外语片的译制音轨 site.audioInfo.chineseDub.checked = torrentInfo.audioInfo.some(audio => audio.language === 'mandarin' && !audio.commentary) && torrentInfo.audioInfo.some(audio => audio.language !== 'mandarin' && !audio.commentary) } // container info if (Object.values(site.containerInfo).includes(torrentInfo.videoInfo.container)) { site.containerSel.val(torrentInfo.videoInfo.container) } } else if (siteName === UHD) { // movie edition if (torrentInfo.editionInfo) { let tags = [] Object.entries(site.movieEditionInfo).forEach(([tagKey, tag]) => { if (torrentInfo.editionInfo[tagKey]) { tags.push(tag) } }) site.movieEditionSelected.val(tags.join(' / ')) } // hdr info if (torrentInfo.videoInfo) { if (torrentInfo.videoInfo.dovi) { site.hdrSel.val(site.hdrInfo.dovi) } else if (torrentInfo.videoInfo.hdr10plus) { site.hdrSel.val(site.hdrInfo.hdr10plus) } else if (torrentInfo.videoInfo.hdr10) { site.hdrSel.val(site.hdrInfo.hdr10) } } // season info if (site.seasonSel) { // totally unnecessary, only to pass the uploading procedure site.seasonSel.val(site.seansonInfo.s01) } } // repair the mediainfo in case 'Complete name' is missing if (torrentInfo.mediainfo && torrentInfo.mediainfo.General) { if (!torrentInfo.mediainfo.General['Complete name'] && torrentInfo.mediainfo.General['Movie name'] && torrentInfo.videoInfo && torrentInfo.videoInfo.container) { torrentInfo.mediainfo.General['Complete name'] = `${torrentInfo.mediainfo.General['Movie name']}.${torrentInfo.videoInfo.container.toLowerCase()}` } site.mediainfoBox.val(mediainfo2String(torrentInfo.mediainfo)) } } // anonymously uploading if (site.anonymousControl) { if (siteName === NHD || siteName === PTERCLUB || siteName === PUTAO || siteName === MTEAM || siteName === UHD) { site.anonymousControl.checked = ANONYMOUS } else if (siteName === TTG) { site.anonymousControl.val(ANONYMOUS ? 'yes' : 'no') } } site.descrBox.val(textToConsume) } catch (error) { console.error('Error:', error) } finally { btnBingo.val(oriTextBingo) } }) } else if (page === 'subtitles') { //= ======================================================================================================== // 字幕页面 if (!site.inputFileSubtitle) { return } site.inputFileSubtitle.change(() => { if (site.anonymousCheckSubtitle) { site.anonymousCheckSubtitle.checked = ANONYMOUS } const pathSub = site.inputFileSubtitle.val() const fileName = pathSub.replace(/.*?([^\\/]+)$/, '$1') if (fileName) { if (site.titleBoxSubtitle) { site.titleBoxSubtitle.val(fileName) } const abbrLangInSub = pathSub.replace(/.*\.([^.]+)\.[^.]+$/i, '$1') || '' let subtitleInfo = {} Object.entries(languageMap).forEach(([languageInAll, abbrLang]) => { subtitleInfo[languageInAll] = abbrLangInSub.match(RegExp('\\b' + abbrLang + '\\b', 'i')) }) if (site.languageSelSubtitle) { let langSelected = site.subtitleInfo.default if (site.subtitleInfo.other && Object.values(subtitleInfo).filter(lang => lang).length > 1) { // 多语字幕 langSelected = site.subtitleInfo.other } else { Object.entries(subtitleInfo).forEach(([languageInAll, present]) => { if (present) { langSelected = site.subtitleInfo[languageInAll] || site.subtitleInfo.default return } }) } site.languageSelSubtitle.val(langSelected) } else if (siteName === GPW) { Object.entries(subtitleInfo).forEach(([languageInAll, present]) => { if (site.subtitleInfo[languageInAll]) { site.subtitleInfo[languageInAll].checked = present } }) } } }) } })() // //////////////////////////////////////////////////////////////////////////////////////////////// // for unit test // Conditionally export for unit testing if (typeof module !== 'undefined' && module.exports) { module.exports = { collectComparisons, decomposeDescription, processDescription, mediainfo2String, string2Mediainfo, processTags, getTeamSplitterRegex, formatTorrentName, NHD, PTERCLUB, GPW, MTEAM, TTG, PUTAO, UHD, siteInfoMap } }