AnimeGo的WebAPI调用插件,能快速添加下载项目,配置筛选规则。!!没有适配mikan的高级订阅模式,请关闭后使用。!!
// ==UserScript== // @name AnimeGoHelper[Mikan快速订阅] // @namespace https://github.com/deqxj00/AnimeGoHelper // @version 0.60 // @description AnimeGo的WebAPI调用插件,能快速添加下载项目,配置筛选规则。!!没有适配mikan的高级订阅模式,请关闭后使用。!! // @author DeQxJ00,Wetor // @match https://mikanani.me/* // @match https://mikanime.tv/* // @icon https://mikanime.tv/favicon.ico // @grant unsafeWindow // @grant GM_getResourceText // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_notification // @require https://cdn.jsdelivr.net/npm/[email protected]/base64.min.js // @require https://cdn.jsdelivr.net/npm/@yaireo/[email protected]/dist/tagify.min.js // @require https://cdn.jsdelivr.net/npm/@yaireo/[email protected]/dist/tagify.polyfills.min.js // @resource tagifycss https://cdn.jsdelivr.net/npm/@yaireo/[email protected]/dist/tagify.css // @run-at document-end // @license MIT // ==/UserScript== (function () { 'use strict'; const samplepath = 'http://youraddress.local/api'; const taglist = ["h264", "mp4", "mkv", "h265", "x264", "720p", "1280x720", "hevc", "简体", "繁体", "简中", "繁中", "特别篇", "典藏版", "简繁日", "简日", "繁日"]; GM_addStyle(GM_getResourceText("tagifycss")); //snackbar src https://www.w3schools.com/howto/howto_js_snackbar.asp GM_addStyle(` /* The snackbar - position it at the bottom and in the middle of the screen */ #snackbar { visibility: hidden; /* Hidden by default. Visible on click */ min-width: 250px; /* Set a default minimum width */ /*margin-left: -125px; Divide value of min-width by 2 */ background-color: #333; /* Black background color */ color: #fff; /* White text color */ text-align: center; /* Centered text */ border-radius: 2px; /* Rounded borders */ padding: 16px; /* Padding */ position: fixed; /* Sit on top of the screen */ z-index: 1; /* Add a z-index if needed */ left: 50%; /* Center the snackbar */ bottom: 30px; /* 30px from the bottom */ transform: translate(-50%, 0); } /* Show the snackbar when clicking on a button (class added with JavaScript) */ #snackbar.show { visibility: visible; /* Show the snackbar */ /* Add animation: Take 0.5 seconds to fade in and out the snackbar. However, delay the fade out process for 2.5 seconds */ -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; animation: fadein 0.5s, fadeout 0.5s 2.5s; } /* Animations to fade the snackbar in and out */ @-webkit-keyframes fadein { from {bottom: 0; opacity: 0;} to {bottom: 30px; opacity: 1;} } @keyframes fadein { from {bottom: 0; opacity: 0;} to {bottom: 30px; opacity: 1;} } @-webkit-keyframes fadeout { from {bottom: 30px; opacity: 1;} to {bottom: 0; opacity: 0;} } @keyframes fadeout { from {bottom: 30px; opacity: 1;} to {bottom: 0; opacity: 0;} } /* The switch - the box around the slider */ .switch { position: relative; display: inline-block; width: 43px; height: 17px; } /* Hide default HTML checkbox */ .switch input { opacity: 0; width: 0; height: 0; } /* The slider */ .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; -webkit-transition: .4s; transition: .4s; } .slider:before { position: absolute; content: ""; height: 14px; width: 13px; left: 2px; bottom: 2px; background-color: white; -webkit-transition: .4s; transition: .4s; } input:checked + .slider { background-color: #2196F3; } input:focus + .slider { box-shadow: 0 0 1px #2196F3; } input:checked + .slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); } /* Rounded sliders */ .slider.round { border-radius: 17px; } .slider.round:before { border-radius: 50%; } /* input api url config */ #inputurldiv,#filterdiv{ opacity: 75; position: fixed; left: 35%; top: 10%; z-index: 100; width: 400px; } #inputurldiv_inner,#filterdiv_inner{ background-color:#fff; -webkit-box-shadow: #666 0px 0px 10px; -moz-box-shadow: #666 0px 0px 10px; box-shadow: #666 0px 0px 10px; } #inputurlbox,#inputurlbox2,#inputurlbox3{ width: 200px; border-radius: 25px; padding-right: 7px; margin-top: 5px; margin-bottom: 5px; } .btn-setting{ color:#fff; float:right; margin:5px; background-color:#61ccd1; } .btn-setting2{ color:#fff; float:right; margin:5px; background-color:#00b8ee; } /* input tag whitelist blacklist */ .tags-look .tagify__dropdown__item{ display: inline-block; vertical-align: middle; border-radius: 3px; padding: .3em .5em; border: 1px solid #CCC; background: #F3F3F3; margin: .2em; font-size: .85em; color: black; transition: 0s; } .tags-look .tagify__dropdown__item--active{ color: black; } .tags-look .tagify__dropdown__item:hover{ background: lightyellow; border-color: gold; } .tags-look .tagify__dropdown__item--hidden { max-width: 0; max-height: initial; padding: .3em 0; margin: .2em 0; white-space: nowrap; text-indent: -20px; border: 0; } `); // 过滤匹配类型详细设定 // ============================================== // 1,2,3 都需要先请求一次/Home/Episode/xxxxxxx的页面 // 0,4 不需要 // ============================================== // 0.全局关键词过滤 // 1.BangumiId+SubGroupId(匹配这部动画BangumiId+这个字幕组SubId的关键词过滤) // 2.BangumiId(匹配这部动画的BangumiId,然后进行关键词过滤) // 3.SubGroupId(匹配这个字幕组SubId,然后进行关键词过滤) // 4.SubGroupName(匹配这个字幕组名称,然后进行关键词过滤) // ============================================= //黑白名单同时开启时 先处理白名单 再处理黑名单 class ListFiliterInner { constructor() { this.is_enable_whitelist = false; this.whitelist = new Array(); this.is_enable_blacklist = false; this.blacklist = new Array(); } } class ListFiliters { constructor() { this.Filiter0 = new Object(); this.Filiter1 = new Object(); this.Filiter2 = new Object(); this.Filiter3 = new Object(); this.Filiter4 = new Object(); } }; var myFiliters = new ListFiliters(); function InitListFiliters() { let tmp = GM_getValue('myFiliters'); if (tmp != null) { myFiliters = JSON.parse(tmp); } else { //sample //let defaultlist = new ListFiliterInner(); //defaultlist.is_enable_blacklist = true; //defaultlist.blacklist.push("720p","1080x720"); //myFiliters.Filiter0.set("0",defaultlist); //GM_setValue('myFiliters',JSON.stringify(myFiliters,replacer)); } //newValue = JSON.parse(str, reviver); } async function digestMessage(message) { const msgUint8 = new TextEncoder().encode(message); const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); return hashHex; } InitListFiliters(); var apipath = samplepath; var token = ''; var tokensha256 = ''; //提醒信息div document.body.insertAdjacentHTML('afterend', '<div id="snackbar">Error</div>'); var x = document.getElementById("snackbar"); //设置按钮 var loginbox = document.getElementsByClassName("w-other-c text-right"); if (loginbox.length > 0) { //已登入 loginbox[0].insertAdjacentHTML('afterbegin', '<a style="color: #47c1c5;" onclick="showSetting()">AnimeGo设置</a><br>'); } else { //未登入 loginbox = document.getElementsByClassName("pull-right"); if (loginbox.length > 0) { loginbox[0].insertAdjacentHTML('afterbegin', '<a style="color: #47c1c5;" onclick="showSetting()">AnimeGo设置</a><br>'); } } //api地址输入框设定 var content0 = '<div id="inputurldiv" style="display:none"><div id="inputurldiv_inner"><div class="popover-header popover-title"><span style="color:#3bc0c3">AnimeGo设置</span><button type="button" class="close" onclick="closeSetting()" data-dismiss="popover-x" aria-hidden="true"><i class="fa fa-times" aria-hidden="true"></i></button></div>' + '<div class="popover-body popover-content">' + '<span>请修改api地址:</span><input id="inputurlbox" type="text" class="form-control input-sm" placeholder="' + samplepath + '" ><br>' + '<span>AccessKey:</span><input id="inputurlbox2" type="password" class="form-control input-sm"><br>' + '<span>PluginName:</span><input id="inputurlbox3" type="text" value="filter/mikan_tool.py" class="form-control input-sm"><br>' + '<span>备份/导入/清除过滤配置<br>指的是浏览器端插件的</span><br><br>' + '<span>上传/获取过滤配置<br>通过AnimeGO的WebAPI同步后端和浏览器插件的过滤配置</span><br><br>' + '<input type="file" accept=".json" id="upload" style="visibility:hidden">' + '</div>' + '<div class="popover-footer"><div style="margin-right: 12px;height:75px">' + '<button type="button" class="btn btn-sm btn-submit btn-setting" onclick="window.confirmurl()">确定</button>' + '<button type="button" class="btn btn-sm btn-submit btn-setting" onclick="window.exportjson()">备份过滤配置</button>' + '<button type="button" class="btn btn-sm btn-submit btn-setting" onclick="window.importjson()">导入过滤配置</button>' + '<button type="button" class="btn btn-sm btn-submit btn-setting" onclick="window.clearjson()">清除过滤配置</button>' + '<button type="button" class="btn btn-sm btn-submit btn-setting2" onclick="window.uploadjson()">上传过滤配置</button>' + '<button type="button" class="btn btn-sm btn-submit btn-setting2" onclick="window.downloadjson()">获取过滤配置</button>' + '<button type="button" class="btn btn-sm btn-submit btn-setting2" onclick="window.testapi()">测试WEB API</button>' + '</div></div></div></div>' document.body.insertAdjacentHTML('afterend', content0); const input = document.querySelector("#upload"); const fr = new FileReader(); fr.onload = async function () { const blob = new Blob([fr.r###lt]) var tmpValue; try { tmpValue = JSON.parse(await blob.text()); } catch (error) { toast('JSON转换错误:' + error); } if (tmpValue !== null && tmpValue !== undefined && tmpValue !== 'undefined' && tmpValue !== '') { myFiliters = tmpValue; await GM_setValue('myFiliters', JSON.stringify(myFiliters)); toast('JSON导入成功'); } } input.addEventListener('change', function () { const files = this.files; if (files.length > 0) { console.log(files[0]); fr.readAsArrayBuffer(files[0]) } }, false); //过滤器设定 var content = 'AnimeName:<input type="text" id="AnimeName" disabled ><br>BangumiId:<input type="text" id="BangumiId" disabled ><br>SubgroupId:<input type="text" id="SubgroupId" disabled ><br>GroupName:<input type="text" id="GroupName" disabled ><br><br>'; content += '请选择一个黑白名单的匹配key<br>后端会根据这个key对应的黑白名单,来决定对应的动画是否下载<br>黑白名单同时开启的情况下先适配白名单再黑名单<br>白名单定义:必须包含其中任意一个词<br>黑名单定义:不能包含其中任意一个词<br><br><select id="myselect"><option value="0">0.全局关键词过滤</option><option value="1" selected="selected">1.BangumiId+SubGroupId</option><option value="2" >2.BangumiId</option><option value="3">3.SubGroupId</option><option value="4">4.SubGroupName</option></select> KEY:<input type="text" id="selectkey" ><br><br>'; content += '<div style="border-style:dotted;border-width:1px;">筛选说明:<br>0.全局规则适用于所有页面的订阅<br>1.规则适用于这个动画中的这个订阅字幕组<br>2.规则适用于这个动画所有的组<br>3.规则适用于这个订阅字幕组<br>1,2,3 后端都会先请求一次/Home/Episode/xxxxxxx的页面,<br> 用来获取BangumiId,SubGroupId<br>(如果不想后端多发一次请求 请不要添加任何一条1,2,3的规则即可)<br>0,4 不需要上面的请求 是根据rss信息重的标题进行过滤<br>举例:https://mikanani.me/Home/Bangumi/228#562 <br>这个228就是BangumiId 562就是SubGroupId</div><br>'; content += '<span style="margin-right:5px;">开启白名单</span><label class="switch" id="switchWhitelist"><span> </span><input type="checkbox"><span class="slider round"></span></label><br>'; content += '<input name="input-custom-dropdown-whitelist" type="text" placeholder="输入白名单过滤关键词后按回车" ><br><br>'; content += '<span style="margin-right:5px;">开启黑名单</span><label class="switch" id="switchBlacklist"><span> </span><input type="checkbox"><span class="slider round"></span></label><br>'; content += '<input name="input-custom-dropdown-balcklist" type="text" placeholder="输入黑名单过滤关键词后按回车" >'; document.body.insertAdjacentHTML('afterend', '<div id="filterdiv" style="display:none"><div id="filterdiv_inner"><div class="popover-header popover-title"><span id="filtertitle" style="color:#3bc0c3">高级过滤设置</span><button type="button" class="close" onclick="closeSettingSubGroup()" data-dismiss="popover-x" aria-hidden="true"><i class="fa fa-times" aria-hidden="true"></i></button></div><div class="popover-body popover-content">' + content + '</div><div class="popover-footer"><div style="margin-right: 12px;height:30px"><button type="button" id="inputurlspan" class="btn btn-sm btn-submit" style="background-color:#61ccd1" onclick="window.confirmfilter()">确定</button></div></div></div></div>'); const switchWhitelist = document.getElementById('switchWhitelist'); const switchBlacklist = document.getElementById('switchBlacklist'); const selectkey = document.getElementById("selectkey"); const myselect = document.getElementById("myselect"); const textAnimeName = document.getElementById("AnimeName"); const textBangumiId = document.getElementById("BangumiId"); const textSubgroupId = document.getElementById("SubgroupId"); const textGroupName = document.getElementById("GroupName"); const inputurldiv = document.getElementById("inputurldiv"); const inputurlbox = document.getElementById("inputurlbox"); const inputurlbox2 = document.getElementById("inputurlbox2"); const inputurlbox3 = document.getElementById("inputurlbox3"); const inputurlspan = document.getElementById("inputurlspan"); const filterdiv = document.getElementById("filterdiv"); //读取存储的地址 var tmppath = GM_getValue('apipath'); var tmp2path = GM_getValue('token'); if (tmp2path !== null && tmp2path !== undefined && tmp2path !== 'undefined' && tmp2path !== '') { inputurlbox2.value = tmp2path; token = tmp2path; //toast("已读取access key:"+tmp2path); digestMessage(tmp2path).then( (digestHex) => { tokensha256 = digestHex; } ); } if (tmppath !== null && tmppath !== undefined && tmppath !== 'undefined' && tmppath !== samplepath) { apipath = tmppath; inputurlbox.value = tmppath; inputurldiv.style.display = 'none'; } else { inputurldiv.style.display = ''; } //debug if (GM_getValue('debug') == true) { //inputurldiv.style.display = 'block'; } unsafeWindow.clearjson = async function () { myFiliters.Filiter0 = new Object(); myFiliters.Filiter1 = new Object(); myFiliters.Filiter2 = new Object(); myFiliters.Filiter3 = new Object(); myFiliters.Filiter4 = new Object(); await GM_deleteValue('myFiliters'); InitListFiliters(); toast('过滤设置已经重置'); } unsafeWindow.importjson = function () { input.click(); } unsafeWindow.exportjson = function () { var json = JSON.stringify(myFiliters); let blob = new Blob([json], {type: 'text;charset=utf-8;'}); let encodedUrl = URL.createObjectURL(blob); let url = document.createElement("a"); url.setAttribute("href", encodedUrl); url.setAttribute("download", "export.json"); document.body.appendChild(url); url.click(); } function urlcheck(link) { var isOk = true; if (!link.includes("http://") && !link.includes("https://")) { toast(link + ',需要包含http://或者https://'); isOk = false; } if (!link.includes("/api")) { toast(link + ',需要包含/api'); isOk = false; } return isOk; } unsafeWindow.uploadjson = function () { const link = inputurlbox.value; const name = inputurlbox3.value; if (!urlcheck(link)) { return } var json = JSON.stringify(myFiliters); var _data = JSON.stringify({"name": name, "data": Base64.encode(json)}); GM_xmlhttpRequest({ method: 'POST', url: apipath + "/plugin/config", data: _data, headers: { 'Access-Key': tokensha256, 'Content-Type': 'application/json' }, onerror: response => { console.log('onerror'); toast('[api地址不正确] error') }, ontimeout: response => { toast('[http]请求超时') }, onloadend: response => { }, onload: response => { if (response.status == 200) { var resp = JSON.parse(response.responseText); if (resp === null || resp === undefined || resp === 'undefined') { toast(name + ',[resp is null or undefined]'); } else { var code = resp.code; if (code === 200 || code === '200') { toast(resp.msg); } else { toast(name + ',[json code error] code:' + resp.code + ',msg:' + resp.msg); } } } else { toast(name + ', [http request error] ' + response.status) } } }); } unsafeWindow.downloadjson = function () { const link = inputurlbox.value; const name = inputurlbox3.value; if (!urlcheck(link)) { return } if (name == '') { toast('PluginName不能为空') return } GM_xmlhttpRequest({ method: 'GET', url: apipath + "/plugin/config?name=" + name, headers: { 'Access-Key': tokensha256, }, onerror: response => { console.log('onerror'); toast('[api地址不正确] error') }, ontimeout: response => { toast('[http]请求超时') }, onloadend: response => { }, onload: response => { if (response.status == 200) { var resp = JSON.parse(response.responseText); if (resp === null || resp === undefined || resp === 'undefined') { toast(name + ',[resp is null or undefined]'); } else { var code = resp.code; if (code === 200 || code === '200') { var tmpValue; try { const jsonstr = Base64.decode(resp.data.data); tmpValue = JSON.parse(jsonstr); myFiliters = tmpValue; toast(resp.msg); GM_setValue('myFiliters', JSON.stringify(myFiliters)); } catch (error) { toast('JSON转换错误:' + error); } } else { toast(name + ',[json code error] code:' + resp.code + ',msg:' + resp.msg); } } } else { toast(name + ', [http request error] ' + response.status) } } }); } unsafeWindow.testapi = function () { var link = inputurlbox.value; if (!urlcheck(link)) { return } link = link.replace("/api", "/ping"); GM_xmlhttpRequest({ method: 'GET', url: link, headers: {'Accept': 'application/rss+xml'}, onerror: response => { toast(link + ',测试错误,error') }, ontimeout: response => { toast(link + ',测试错误,timeout'); }, onloadend: response => { console.log(link + ',loadend'); }, onload: response => { //console.log(response.status); if (response.status == 200) { var resp = response.responseText; toast('测试成功: ' + resp) } else { toast('[http response error] ' + response.status) } } }); } unsafeWindow.showSetting = function () { inputurldiv.style.display = 'block'; } unsafeWindow.closeSetting = function () { inputurldiv.style.display = 'none'; } //myselect.selectedIndex var inputwhitelist = document.querySelector('input[name="input-custom-dropdown-whitelist"]'); var inputbalcklist = document.querySelector('input[name="input-custom-dropdown-balcklist"]'); // init Tagify script on the above inputs var tagifyWhitelist = new Tagify(inputwhitelist, { whitelist: taglist, maxTags: 10, dropdown: { maxItems: 20, classname: "tags-look", enabled: 0, closeOnSelect: true } }); var tagifyBlacklist = new Tagify(inputbalcklist, { whitelist: taglist, maxTags: 10, dropdown: { maxItems: 20, classname: "tags-look", enabled: 0, closeOnSelect: true } }); function myselectonchange() { var myFiliter = null; switchWhitelist.children[1].checked = false; switchBlacklist.children[1].checked = false; tagifyWhitelist.removeAllTags(); tagifyBlacklist.removeAllTags(); if (myselect.selectedIndex == 0) { selectkey.value = '0'; myFiliter = myFiliters.Filiter0[selectkey.value]; } else if (myselect.selectedIndex == 1) { selectkey.value = 'key_' + BangumiId.value + '_' + SubgroupId.value; myFiliter = myFiliters.Filiter1[selectkey.value]; } else if (myselect.selectedIndex == 2) { selectkey.value = BangumiId.value; myFiliter = myFiliters.Filiter2[selectkey.value]; } else if (myselect.selectedIndex == 3) { selectkey.value = SubgroupId.value; myFiliter = myFiliters.Filiter3[selectkey.value]; } else if (myselect.selectedIndex == 4) { selectkey.value = GroupName.value; myFiliter = myFiliters.Filiter4[selectkey.value]; } if (myFiliter !== null && myFiliter !== undefined && myFiliter !== 'undefined') { switchWhitelist.children[1].checked = myFiliter.is_enable_whitelist; switchBlacklist.children[1].checked = myFiliter.is_enable_blacklist; tagifyWhitelist.addTags(myFiliter.whitelist); tagifyBlacklist.addTags(myFiliter.blacklist); } } myselect.addEventListener('change', (event) => { myselectonchange(); }); unsafeWindow.showSettingSubGroup = function (n, animename, groupname, subgroupid, bangumiid) { toast("showSettingSubGroup:animename" + animename + ",groupname:" + groupname + ",subgroupid:" + subgroupid + ",bangumiid:" + bangumiid); textAnimeName.value = animename; textGroupName.value = groupname; textBangumiId.value = bangumiid; textSubgroupId.value = subgroupid; myselectonchange(); filterdiv.style.display = 'block'; } unsafeWindow.closeSettingSubGroup = function () { filterdiv.style.display = 'none'; } unsafeWindow.confirmfilter = function () { if (selectkey.value !== null && selectkey.value !== undefined && selectkey.value !== 'undefined' && selectkey.value !== '') { let defaultlist = new ListFiliterInner(); defaultlist.is_enable_whitelist = switchWhitelist.children[1].checked; defaultlist.is_enable_blacklist = switchBlacklist.children[1].checked; defaultlist.whitelist = [] tagifyWhitelist.getCleanValue().forEach((obj) => { defaultlist.whitelist.push(obj.value) }); defaultlist.blacklist = [] tagifyBlacklist.getCleanValue().forEach((obj) => { defaultlist.blacklist.push(obj.value) }); if (myselect.selectedIndex == 0) { myFiliters.Filiter0[selectkey.value] = defaultlist; } else if (myselect.selectedIndex == 1) { myFiliters.Filiter1[selectkey.value] = defaultlist; } else if (myselect.selectedIndex == 2) { myFiliters.Filiter2[selectkey.value] = defaultlist; } else if (myselect.selectedIndex == 3) { myFiliters.Filiter3[selectkey.value] = defaultlist; } else if (myselect.selectedIndex == 4) { myFiliters.Filiter4[selectkey.value] = defaultlist; } GM_setValue('myFiliters', JSON.stringify(myFiliters)); filterdiv.style.display = 'none'; toast(`确认保存设定:${myselect.selectedIndex},${selectkey.value}`); } else { toast(`数据为空 不能保存`); } } unsafeWindow.confirmurl = function () { var tmp = inputurlbox.value; var tmp2 = inputurlbox2.value; if (tmp2 !== null && tmp2 !== undefined && tmp2 !== 'undefined') { GM_setValue('token', tmp2); toast("已保存access key:" + tmp2); token = tmp2; digestMessage(tmp2).then( (digestHex) => { if (tmp2 !== '') { tokensha256 = digestHex; } } ); } GM_setValue('apipath', tmp); apipath = tmp; inputurldiv.style.display = 'none'; toast("保存url:" + tmp); } unsafeWindow.postApiAllEpisode = function (n, type_name, anime_name, subgroupid, bangumiid) { unsafeWindow.postApiBase(n, type_name, anime_name, subgroupid, bangumiid, false, '', false, null); } unsafeWindow.postApiSingleEpisode = function (n, type_name, anime_name, link, torrent=null) { if (n.classList.contains("running")) { return; } var name = anime_name + "," + type_name; var u = Ladda.create(n); if (type_name != "updateHomeEpisode") { u.start(); n.classList.add('running'); } GM_xmlhttpRequest({ method: 'GET', url: link, headers: {'Accept': 'application/rss+xml'}, onerror: response => { toast(link + ',onerror') }, ontimeout: response => { toast(link + ',ontimeout'); }, onloadend: response => { console.log(link + ',onloadend'); //u.stop(); n.classList.remove('running'); }, onload: response => { //console.log(response.status); if (response.status == 200) { var resp = response.responseText; if (resp === null || resp === undefined || resp === 'undefined') { toast(name + '[response is null or undefined]'); } else { var parser = new DOMParser(); var htmlDoc = parser.parseFromString(resp, "text/html"); var rssels = htmlDoc.getElementsByClassName('mikan-rss'); if (rssels.length > 0 && rssels[0].hasAttribute('href')) { var bangumiId = getParameterByName('bangumiId', rssels[0].href); var subgroupid = getParameterByName('subgroupid', rssels[0].href); n.classList.remove('running'); //u.stop(); if(torrent!=null && torrent!='null'){ unsafeWindow.postNewApiBase(n, type_name, anime_name, torrent, link, true, u); }else{ unsafeWindow.postApiBase(n, type_name, anime_name, subgroupid, bangumiId, true, link, true, u); } } else { toast(name + ', 网页信息缺失 请稍后再试 ' + response.status); n.classList.remove('running'); u.stop(); } } } else { toast(name + ', [http request error] ' + response.status) } } }); } unsafeWindow.postNewApiBase = function (n, type_name, anime_name, torrent_url, mikan_url, is_already_loading, u) { if (n.classList.contains("running")) { return; } console.log(`typr_name: ${type_name}, animename: ${anime_name}, torrent: ${torrent_url}, mikan: ${mikan_url}`) if (!apipath.includes("http://") && !apipath.includes("https://")) { toast('[api地址 需要包含http://或者https:// ]'); } if (!AdvancedSubscriptionEnabled) { if (n.classList.contains('running')) { } else { var _data = JSON.stringify({ "source": "mikan", "data": [{ "torrent": torrent_url, "info": { "name": anime_name, "url": mikan_url } }] }); console.log(_data); n.classList.add('running'); if (!is_already_loading) { u = Ladda.create(n); u.start(); } GM_xmlhttpRequest({ method: 'POST', url: apipath + "/download/manager", data: _data, headers: { 'Access-Key': tokensha256, 'Content-Type': 'application/json' }, onerror: response => { console.log('onerror'); toast('[api地址不正确] error') }, ontimeout: response => { console.log('ontimeout'); }, onloadend: response => { console.log('onloadend'); u.stop(); n.classList.remove('running'); }, onload: response => { //console.log(response.status); if (response.status == 200) { var resp = JSON.parse(response.responseText); if (resp === null || resp === undefined || resp === 'undefined') { toast(anime_name + '[resp is null or undefined]'); } else { var code = resp.code; if (code === 200 || code === '200') { toast(resp.msg); } else { toast(name + '[json code error] code:' + resp.code + ',msg:' + resp.msg); } } } else { toast(name + ', [http request error] ' + response.status) } } }); } } else { toast('高级订阅模式未进行适配'); } }; unsafeWindow.postApiBase = function (n, type_name, anime_name, subgroupid, bangumiid, is_select_ep, ep_link, is_already_loading, u) { if (n.classList.contains("running")) { return; } console.log('type_name:' + type_name + ',animename:' + anime_name + ',subgroupid:' + subgroupid + ',bangumiid:' + bangumiid + ',is_select_ep:' + is_select_ep + ',link:' + ep_link); var name = anime_name + "," + type_name; var rssurl = `https://mikanani.me/RSS/Bangumi?bangumiid=${bangumiid}&subgroupid=${subgroupid}`; if (!apipath.includes("http://") && !apipath.includes("https://")) { toast('[api地址 需要包含http://或者https:// ]'); } if (!AdvancedSubscriptionEnabled) { if (n.classList.contains('running')) { } else { var _data = JSON.stringify({ "source": "mikan", "rss": {"url": rssurl}, "is_select_ep": is_select_ep, "ep_links": [ep_link] }); console.log(_data); n.classList.add('running'); if (!is_already_loading) { u = Ladda.create(n); u.start(); } GM_xmlhttpRequest({ method: 'POST', url: apipath + "/rss", data: _data, headers: { 'Access-Key': tokensha256, 'Content-Type': 'application/json' }, onerror: response => { console.log('onerror'); toast('[api地址不正确] error') }, ontimeout: response => { console.log('ontimeout'); }, onloadend: response => { console.log('onloadend'); u.stop(); n.classList.remove('running'); }, onload: response => { //console.log(response.status); if (response.status == 200) { var resp = JSON.parse(response.responseText); if (resp === null || resp === undefined || resp === 'undefined') { toast(anime_name + '[resp is null or undefined]'); } else { var code = resp.code; if (code === 200 || code === '200') { toast(resp.msg); } else { toast(name + '[json code error] code:' + resp.code + ',msg:' + resp.msg); } } } else { toast(name + ', [http request error] ' + response.status) } } }); } } else { toast('高级订阅模式未进行适配'); } }; function toast(msg) { x.innerText = msg; x.className = "show"; setTimeout(function () { x.className = x.className.replace("show", ""); }, 2000); } function getParameterByName(name, url = window.location.href) { name = name.replace(/[\[\]]/g, '\\$&'); var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), r###lts = regex.exec(url); if (!r###lts) return null; if (!r###lts[2]) return ''; return decodeURIComponent(r###lts[2].replace(/\+/g, ' ')); } function getId(element) { var groupname = ''; var animename = ''; var div = element.getElementsByClassName('btn-primary')[0]; var div2 = element.getElementsByClassName('sk-col tag-res-new'); var div3 = element.getElementsByClassName('sk-col tag-res-name'); var div4 = element.parentElement.parentElement.getElementsByClassName('sk-col res-ul-title-text w-other-nh'); if (div2.length == 1) { div2[0].style.margin = '0 0 0 -41px'; } if (div3.length == 1) { groupname = div3[0].innerText; } var subtitlegroupid = div.getAttribute('data-subtitlegroupid'); var bangumiid = div.getAttribute('data-bangumiid'); if (div4.length == 1) { animename = div4[0].innerText; var els = document.getElementsByTagName('a'); for (var i = 0; i < els.length; i++) { var el = els[i]; if (el.href !== null && el.href !== undefined && el.href !== 'undefined' && el.href.includes('Home/Bangumi/' + bangumiid)) { animename = el.innerText + " , " + animename; //console.log(el.href+","+animename); break; } } } animename = animename.replaceAll("'","#39;"); //btn-primary ladda-button sk-col tag-sub js-subscribe_bangumi div.insertAdjacentHTML('afterend', '<div class="ladda-button sk-col tag-sub" onclick="window.showSettingSubGroup(this,\'' + animename + '\',\'' + groupname + '\',' + subtitlegroupid + ',' + bangumiid + ')" style="background-color: #47c1c5;float:right;margin-right:5px;margin-top:-17px;" data-style="zoom-in">设</div>'); div.insertAdjacentHTML('afterend', '<div class="ladda-button sk-col tag-sub" onclick="window.postApiAllEpisode(this,\'' + animename + '\',\'' + groupname + '\',' + subtitlegroupid + ',' + bangumiid + ')" style="background-color: #5467d8;float:right;margin-right:5px;" data-style="zoom-in">全</div>'); } var bangumidiv = document.querySelector('#sk-body'); var searchdiv = document.querySelector('#sk-container'); var anlistdiv = document.querySelector('#an-list'); const config = {attributes: false, childList: true, subtree: true}; const callback = function (mutationsList, observer) { for (const mutation of mutationsList) { if (mutation.type === 'childList') { //console.log(mutation); if (mutation.target.id === 'an-episode-updates') { updateMyBangumiAnlist(mutation.target); } if (mutation.target.classList.contains('central-container')) { updateMagnetlinkwrap(mutation.target); } if (mutation.target.classList.length == 2) { updateMagnetlinkwrap(mutation.target); if (mutation.target.classList.contains('row') && mutation.target.classList.contains('an-res-row-frame')) { //js-expand_bangumi-subgroup js-subscribed active var ul = mutation.target.querySelector('ul.list-unstyled.res-ul'); if (ul !== null && ul !== undefined && ul !== 'undefined') { var lis = ul.getElementsByTagName('li'); for (var k = 0, length = lis.length; k < length; k++) { getId(lis[k]); } if (lis.length > 0) { break; } } } } } } }; function buildSingleButton(type, anime_name, link, torrent){ return `<div class="ladda-button sk-col tag-sub" onclick="window.postApiSingleEpisode(this, \`${type}\`, \`${anime_name}\`, \`${link}\`, \`${torrent}\`)" style="background-color: #5467d8;color:white;padding:0px 2px 0px 2px;margin:0px 2px 0px 2px;" data-style="zoom-in">单</div>` } function getAnimeTorrentLink(li){ var torrent = null var as = li.getElementsByTagName('a') Array.prototype.forEach.call(as, function (el) { if (el.href !== null && el.href !== undefined && el.href !== 'undefined' && el.href.includes('/Download') && el.href.includes('.torrent')) { torrent = el.href return } }); if(torrent != null){ return torrent } console.log('未找到torrent下载链接') return null } function updateMagnetlinkwrap(mainel) { var els = mainel.getElementsByClassName('magnet-link-wrap'); Array.prototype.forEach.call(els, function (el) { if (el.href !== null && el.href !== undefined && el.href !== 'undefined' && el.href.includes('Home/Episode') && !el.classList.contains('anigoadded')) { var name = el.innerHTML; el.classList.add('anigoadded'); name = name.replaceAll("'","#39;"); var torrent = getAnimeTorrentLink(el.parentNode.parentNode) var button = buildSingleButton('updateMagnetlinkwrap', name, el.href, torrent) el.insertAdjacentHTML('afterend', button); } }); } function updateMyBangumiAnlist(mainel) { var els = mainel.getElementsByClassName('w-other-c rss-episode-name'); Array.prototype.forEach.call(els, function (el) { if (el.href !== null && el.href !== undefined && el.href !== 'undefined' && el.href.includes('Home/Episode') && !el.classList.contains('anigoadded')) { var name = el.previousElementSibling.previousElementSibling.innerText; el.classList.add('anigoadded'); name = name.replaceAll("'","#39;"); var torrent = getAnimeTorrentLink(el.parentNode.parentNode) var button = buildSingleButton('updateMyBangumiAnlist', name, el.href, torrent) el.insertAdjacentHTML('afterend', button); } }); } const observer = new MutationObserver(callback); var path = window.location.pathname; if (path.includes('/Home/Bangumi/') || path.includes('/Home/Search') || path.includes('/Home/Classic')) { var animename = ''; var title = document.getElementsByClassName('bangumi-title'); if (title.length == 1) { animename = title[0].innerText; } var els = document.getElementsByClassName('mikan-rss'); Array.prototype.forEach.call(els, function (el) { if (el.href !== null && el.href !== undefined && el.href !== 'undefined' && el.href.includes('bangumiId') && el.href.includes('subgroupid')) { var bangumiId = getParameterByName('bangumiId', el.href); var subgroupid = getParameterByName('subgroupid', el.href); var groupname = ''; if (el.previousElementSibling !== null) { groupname = el.previousElementSibling.innerHTML; if (groupname == ' ') { groupname = el.previousElementSibling.previousElementSibling.children[0].children[0].innerHTML; } } animename = animename.replace("'","#39;"); el.insertAdjacentHTML('afterend', '<div class="ladda-button sk-col tag-sub" onclick="window.showSettingSubGroup(this,\'' + animename + '\',\'' + groupname + '\',' + subgroupid + ',' + bangumiId + ')" style="background-color: #47c1c5;color:white;padding:0px 2px 0px 2px;margin:0px 2px 0px 2px;" data-style="zoom-in">设</div>'); el.insertAdjacentHTML('afterend', '<div class="ladda-button sk-col tag-sub" onclick="window.postApiAllEpisode(this,\'' + animename + '\',\'' + groupname + '\',' + subgroupid + ',' + bangumiId + ')" style="background-color: #5467d8;color:white;padding:0px 2px 0px 2px;margin:0px 2px 0px 2px;" data-style="zoom-in">全</div>'); } }); //适配 https://mikanani.me/Home/Episode/xxxxxxxxxxxxxxxxxxxxxxx updateMagnetlinkwrap(document); observer.observe(searchdiv, config); } if (path.includes('/Home/MyBangumi') || path === '/Home' || path === '/') { updateMyBangumiAnlist(document); observer.observe(bangumidiv, config); observer.observe(anlistdiv, config); } if (path.includes('/Home/Episode/')) { var title1 = document.getElementsByClassName('episode-title')[0].innerText; var btn = document.getElementsByClassName('btn episode-btn')[0]; var name = title1; var link = window.location.href; //mikan-rss var rssels = document.getElementsByClassName('mikan-rss'); if (rssels.length > 0 && rssels[0].hasAttribute('href')) { var bangumiId = getParameterByName('bangumiId', rssels[0].href); var subgroupid = getParameterByName('subgroupid', rssels[0].href); var magnetspan = document.getElementsByClassName('magnet-link-wrap')[0]; var groupname = ''; if (magnetspan.children.length == 0) { groupname = '[' + magnetspan.innerText + '] '; } else { groupname = '[' + magnetspan.children[0].innerText + '] '; } name = name.replaceAll("'","#39;"); console.log(magnetspan) var torrent = getAnimeTorrentLink(el.parentNode.parentNode) var button = buildSingleButton('updateHomeEpisode', name, link, torrent) btn.insertAdjacentHTML('afterend', button); groupname += document.getElementsByClassName('bangumi-title')[0].children[0].innerText; btn.insertAdjacentHTML('afterend', '<div class="btn episode-btn" onclick="window.postApiAllEpisode(this,\'' + groupname + '\',' + subgroupid + ',' + bangumiId + ')" style="" data-style="zoom-in">添加全集到AnimeGo</div>'); // btn.insertAdjacentHTML('afterend', '<div class="btn episode-btn" onclick="window.postApiSingleEpisode(this,\'updateHomeEpisode\',\'' + name + '\',\'' + link + '\')" style="" data-style="zoom-in">添加单集到AnimeGo</div>'); } } //observer.disconnect(); })();