// ==UserScript== // @name KGrabber // @namespace thorou // @version 3.0.1-b63 // @description extracts links from kissanime.ru and similar sites // @author Thorou // @license GPLv3 - http://www.gnu.org/licenses/gpl-3.0.txt // @homepageURL https://github.com/thorio/KGrabber/ // @match http*://kissanime.ru/* // @match http*://kimcartoon.to/* // @match http*://kissasian.sh/* // @match http*://kisstvshow.to/* // @connect rapidvideo.com // @connect googleusercontent.com // @connect googlevideo.com // @connect novelplanet.me // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @run-at document-end // @noframes // @require https://code.jquery.com/jquery-3.3.1.min.js // @downloadURL none // ==/UserScript== // bundled with browserify (function() { function outer(modules, cache, entry) { var previousRequire = typeof require == "function" && require; function newRequire(name, jumped){ if(!cache[name]) { if(!modules[name]) { var currentRequire = typeof require == "function" && require; if (!jumped && currentRequire) return currentRequire(name, true); if (previousRequire) return previousRequire(name, true); var err = new Error('Cannot find module \'' + name + '\''); err.code = 'MODULE_NOT_FOUND'; throw err; } var m = cache[name] = {exports:{}}; modules[name][0].call(m.exports, function(x){ var id = modules[name][1][x]; return newRequire(id ? id : x); },m,m.exports,outer,modules,cache,entry); } return cache[name].exports; } for(var i=0;i { await shared.eachEpisode(status.episodes, tryGetQuality, setProgress); }, availableFunc: (action, status) => { return shared.availableFunc(status, { automatic: action.automatic, linkType: LinkTypes.DIRECT, servers: ["beta2"], }); }, automatic: true, }), ]; async function tryGetQuality(episode) { if (episode.error) { return; } if (!episode.functionalLink.match(/.*=m\d\d/)) { util.log.warn(`invalid beta link "${episode.functionalLink}"`); return; } let rawLink = episode.functionalLink.slice(0, -4); let qualityStrings = { "1080": "=m37", "720": "=m22", "360": "=m18" }; let parsedQualityPrefs = preferences.general.quality_order.replace(/\s/g, "").split(","); for (let i of parsedQualityPrefs) { if (qualityStrings[i]) { if (await util.ajax.head(rawLink + qualityStrings[i]).status == HttpStatusCodes.OK) { episode.processedLink = rawLink + qualityStrings[i]; return; } } } } },{"../config/preferenceManager":8,"../util":56,"./shared":6,"http-status-codes":58,"kgrabber-types":38}],2:[function(require,module,exports){ "use strict"; // src\js\actions\generic.js const shared = require("./shared"), config = require("../config"), { Action } = require("kgrabber-types"); module.exports = [ new Action("reset", { executeFunc: async (status, setProgress) => { await shared.eachEpisode(status.episodes, reset, setProgress); status.linkType = config.sites.current().servers.get(status.serverID).linkType; status.automaticDone = false; }, availableFunc: (action, status) => { for (let episode of status.episodes) { if (episode.error || episode.processedLink) { return true; } } return false; }, }), ]; async function reset(episode) { episode.error = ""; episode.processedLink = ""; } },{"../config":7,"./shared":6,"kgrabber-types":38}],3:[function(require,module,exports){ "use strict"; // src\js\actions\index.js const statusManager = require("../statusManager"); const status = statusManager.get(); let actions = [].concat( require("./generic"), require("./rapidvideo"), require("./beta"), require("./nova") ); exports.all = () => actions; exports.available = () => actions.filter((action) => action.isAvailable(status) ); exports.add = (...action) => { actions.push(...action); }; exports.execute = async (action, setSpinnerText) => { await action.invoke(status, setSpinnerText); if (action.automatic) { status.automaticDone = true; } }; },{"../statusManager":42,"./beta":1,"./generic":2,"./nova":4,"./rapidvideo":5}],4:[function(require,module,exports){ "use strict"; // src\js\actions\nova.js const ajax = require("../util/ajax"), util = require("../util"), preferenceManager = require("../config/preferenceManager"), shared = require("./shared"), { Action, LinkTypes } = require("kgrabber-types"); const preferences = preferenceManager.get(); module.exports = [ new Action("get direct links", { executeFunc: async (status, setProgress) => { await shared.eachEpisode(status.episodes, getDirect, setProgress); status.linkType = LinkTypes.DIRECT; }, availableFunc: (action, status) => { return shared.availableFunc(status, { automatic: action.automatic, linkType: LinkTypes.EMBED, servers: ["nova"], }); }, }), ]; async function getDirect(episode) { if (episode.error) { return; } let response = await ajax.post(`https://www.novelplanet.me/api/source/${episode.functionalLink.match(/\/([^/]*?)$/)[1]}`); if (response.success === false && response.data.includes("encoding")) { episode.error = "video is still being encoded"; util.log.err(`nova: ${episode.error}`, response); return; } let json = JSON.parse(response.response); if (!json.data || json.data.length < 1) { episode.error = "no sources found"; util.log.err(`nova: ${episode.error}`, response); return; } let sources = json.data; let parsedQualityPrefs = preferences.general.quality_order.replace(/\s/g, "").split(","); for (let i of parsedQualityPrefs) { for (let j of sources) { if (j.label == i + "p") { episode.processedLink = j.file; return; } } } episode.error = "preferred qualities not found"; util.log.err(`nova: ${episode.error}`, response); } },{"../config/preferenceManager":8,"../util":56,"../util/ajax":55,"./shared":6,"kgrabber-types":38}],5:[function(require,module,exports){ "use strict"; // src\js\actions\rapidvideo.js const util = require("../util"), { ajax } = util, preferenceManager = require("../config/preferenceManager"), shared = require("./shared"), { Action, LinkTypes } = require("kgrabber-types"), HttpStatusCodes = require("http-status-codes"); const preferences = preferenceManager.get(); module.exports = [ new Action("revert domain", { executeFunc: async (status, _setProgress) => { for (let i in status.episodes) { status.episodes[i].processedLink = status.episodes[i].processedLink.replace("rapidvid.to", "rapidvideo.com"); } }, availableFunc: (action, status) => { return shared.availableFunc(status, { automatic: action.automatic, linkType: LinkTypes.EMBED, servers: ["rapid"], }); }, automatic: true, }), new Action("get direct links", { executeFunc: async (status, setProgress) => { await shared.eachEpisode(status.episodes, _rapidvideo_getDirect, setProgress); status.linkType = LinkTypes.DIRECT; }, availableFunc: (action, status) => { return shared.availableFunc(status, { automatic: action.automatic, linkType: LinkTypes.EMBED, servers: ["rapid"], }); }, }), ]; async function _rapidvideo_getDirect(episode) { if (episode.error) { return; } let response = await ajax.get(episode.functionalLink); if (response.status != HttpStatusCodes.OK) { episode.error = `http status ${response.status}`; return; } let $html = $(response.response); let $sources = $html.find("source"); if ($sources.length == 0) { episode.error = "no sources found"; return; } let sources = {}; util.for($sources, (i, obj) => { sources[obj.dataset.res] = obj.src; }); let parsedQualityPrefs = preferences.general.quality_order.replace(/\s/g, "").split(","); for (let i of parsedQualityPrefs) { if (sources[i]) { episode.processedLink = sources[i]; return; } } episode.error = "preferred qualities not found"; } },{"../config/preferenceManager":8,"../util":56,"./shared":6,"http-status-codes":58,"kgrabber-types":38}],6:[function(require,module,exports){ "use strict"; // src\js\actions\shared.js const util = require("../util"), { preferenceManager } = require("../config"); const preferences = preferenceManager.get(); exports.eachEpisode = (episodes, func, setProgress) => { let promises = []; let progress = 0; for (let episode of episodes) { promises.push( func(episode).catch((e) => { episode.error = "something went wrong; see console for details"; util.log.err(e); }).finally(() => { progress++; setProgress(`${progress}/${promises.length}`); }) ); } setProgress(`0/${promises.length}`); return Promise.all(promises); }; exports.availableFunc = (status, { automatic, linkType, servers }) => { if (!servers.includes(status.serverID)) { return false; } if (linkType != status.linkType) { return false; } if (automatic && status.automaticDone || preferences.compatibility.disable_automatic_actions) { return false; } return true; }; },{"../config":7,"../util":56}],7:[function(require,module,exports){ "use strict"; // src\js\config\index.js module.exports = { preferenceManager: require("./preferenceManager"), sites: require("./sites"), }; },{"./preferenceManager":8,"./sites":9}],8:[function(require,module,exports){ "use strict"; // src\js\config\preferenceManager.js const util = require("../util"); const defaultPreferences = { general: { quality_order: "1080, 720, 480, 360", }, internet_download_manager: { idm_path: "C:\\Program Files (x86)\\Internet Download Manager\\IDMan.exe", download_path: "%~dp0", arguments: "/a", keep_title_in_episode_name: false, }, compatibility: { force_default_grabber: false, enable_experimental_grabbers: false, disable_automatic_actions: false, }, }; let preferences; exports.get = () => { if (preferences === undefined) { preferences = load(defaultPreferences); } return preferences; }; let save = exports.save = (newPreferences) => { util.clear(preferences); util.merge(preferences, newPreferences); GM_setValue("KG-preferences", JSON.stringify(preferences)); }; exports.reset = () => save({}); function load(defaults) { let saved = JSON.parse(GM_getValue("KG-preferences", "{}")); for (let i in saved) { if (defaults[i] === undefined) { delete saved[i]; } else { for (let j in saved[i]) { if (defaults[i][j] === undefined) { delete saved[i][j]; } } } } return util.merge(util.clone(defaults), saved); } function getPreferredServers() { return JSON.parse(GM_getValue("preferredServers", "{}")); } function savePreferredServers(servers) { GM_setValue("preferredServers", JSON.stringify(servers)); } exports.getPreferredServer = (host) => getPreferredServers()[host]; exports.setPreferredServer = (host, server) => { let saved = getPreferredServers(); saved[host] = server; savePreferredServers(saved); }; },{"../util":56}],9:[function(require,module,exports){ "use strict"; // src\js\config\sites\index.js const { Dictionary } = require("kgrabber-types"), page = require("../../ui/page"); const sites = new Dictionary([ require("./kissanime"), require("./kimcartoon"), require("./kissasian"), require("./kisstvshow"), ]); exports.current = () => sites.get(page.location.hostname); exports.add = (...newSites) => { sites.add(...newSites); }; },{"../../ui/page":49,"./kimcartoon":10,"./kissanime":11,"./kissasian":12,"./kisstvshow":13,"kgrabber-types":38}],10:[function(require,module,exports){ "use strict"; // src\js\config\sites\kimcartoon.js const { Server, Site, Dictionary, LinkTypes } = require("kgrabber-types"), uiFix = require("./patches/kimcartoon_UIFix"); let servers = new Dictionary([ new Server("openload", { regex: /"https:\/\/openload.co\/embed\/.*?"/, name: "Openload", linkType: LinkTypes.EMBED, }), new Server("streamango", { regex: /"https:\/\/streamango.com\/embed\/.*?"/, name: "Streamango", linkType: LinkTypes.EMBED, }), new Server("beta", { regex: /"https:\/\/redirector.googlevideo.com\/videoplayback\?.*?"/, name: "Beta", linkType: LinkTypes.DIRECT, }), new Server("rapid", { regex: /"https:\/\/w*?.*?rapidvid.to\/e\/.*?"/, name: "RapidVideo", linkType: LinkTypes.EMBED, }), new Server("fs", { regex: /"https:\/\/video.xx.fbcdn.net\/v\/.*?"/, name: "FS (fbcdn.net)", linkType: LinkTypes.DIRECT, }), new Server("gp", { regex: /"https:\/\/redirector.googlevideo.com\/videoplayback\?.*?"/, name: "GP (googleusercontent.com)", linkType: LinkTypes.DIRECT, }), new Server("fe", { regex: /"https:\/\/www.luxubu.review\/v\/.*?"/, name: "FE (luxubu.review)", linkType: LinkTypes.EMBED, }), ]); module.exports = new Site("kimcartoon.to", { contentPath: "Cartoon", noCaptchaServer: "rapid", buttonColor: "#ecc835", buttonTextColor: "#000", servers, patches: uiFix, }); },{"./patches/kimcartoon_UIFix":14,"kgrabber-types":38}],11:[function(require,module,exports){ "use strict"; // src\js\config\sites\kissanime.js const { Server, Site, Dictionary, LinkTypes } = require("kgrabber-types"); let servers = new Dictionary([ new Server("hydrax", { regex: /"https:\/\/replay.watch\/hydrax.html\??.*?#slug=.*?"/, name: "HydraX (no captcha)", linkType: LinkTypes.EMBED, customStep: "modalBegin", }), new Server("nova", { regex: /"https:\/\/www.novelplanet.me\/v\/.*?"/, name: "Nova", linkType: LinkTypes.EMBED, customStep: "modalBegin", }), new Server("beta2", { regex: /"https:\/\/lh3.googleusercontent.com\/.*?"/, name: "Beta2", linkType: LinkTypes.DIRECT, customStep: "modalBegin", }), new Server("openload", { regex: /"https:\/\/openload.co\/embed\/.*?"/, name: "Openload", linkType: LinkTypes.EMBED, customStep: "modalBegin", }), new Server("mp4upload", { regex: /"https:\/\/www.mp4upload.com\/embed-.*?"/, name: "Mp4Upload", linkType: LinkTypes.EMBED, customStep: "modalBegin", }), new Server("streamango", { regex: /"https:\/\/streamango.com\/embed\/.*?"/, name: "Streamango", linkType: LinkTypes.EMBED, customStep: "modalBegin", }), new Server("beta", { regex: /"https:\/\/redirector.googlevideo.com\/videoplayback\?.*?"/, name: "Beta", linkType: LinkTypes.DIRECT, customStep: "modalBegin", }), ]); module.exports = new Site("kissanime.ru", { contentPath: "Anime", noCaptchaServer: "hydrax", buttonColor: "#548602", buttonTextColor: "#fff", servers, }); },{"kgrabber-types":38}],12:[function(require,module,exports){ "use strict"; // src\js\config\sites\kissasian.js const { Server, Site, Dictionary, LinkTypes } = require("kgrabber-types"), uiFix = require("./patches/kissasian_UIFix"); let servers = new Dictionary([ new Server("openload", { regex: /"https:\/\/openload.co\/embed\/.*?"/, name: "Openload", linkType: LinkTypes.EMBED, }), new Server("beta", { regex: /"https:\/\/redirector.googlevideo.com\/videoplayback\?.*?"/, name: "Beta", linkType: LinkTypes.DIRECT, }), new Server("rapid", { regex: /"https:\/\/w*?.*?rapidvid.to\/e\/.*?"/, name: "RapidVideo", linkType: LinkTypes.EMBED, }), new Server("fe", { regex: /"https:\/\/www.gaobook.review\/v\/.*?"/, name: "FE (gaobook.review)", linkType: LinkTypes.EMBED, }), new Server("mp", { regex: /"https:\/\/www.mp4upload.com\/embed-.*?"/, name: "MP (mp4upload.com)", linkType: LinkTypes.EMBED, }), new Server("fb", { regex: /"https:\/\/video.xx.fbcdn.net\/v\/.*?"/, name: "FB (fbcdn.net)", linkType: LinkTypes.DIRECT, }), new Server("alpha", { regex: /"https:\/\/redirector.googlevideo.com\/videoplayback\?.*?"/, name: "Alpha", linkType: LinkTypes.DIRECT, }), ]); module.exports = new Site("kissasian.sh", { contentPath: "Drama", noCaptchaServer: "rapid", buttonColor: "#F5B54B", buttonTextColor: "#000", servers, patches: uiFix, }); },{"./patches/kissasian_UIFix":15,"kgrabber-types":38}],13:[function(require,module,exports){ "use strict"; // src\js\config\sites\kisstvshow.js const { Server, Site, Dictionary, LinkTypes } = require("kgrabber-types"); let servers = new Dictionary([ new Server("openload", { regex: /"https:\/\/openload.co\/embed\/.*?"/, name: "Openload", linkType: LinkTypes.EMBED, }), new Server("streamango", { regex: /"https:\/\/streamango.com\/embed\/.*?"/, name: "Streamango", linkType: LinkTypes.EMBED, }), new Server("beta", { regex: /"https:\/\/redirector.googlevideo.com\/videoplayback\?.*?"/, name: "Beta", linkType: LinkTypes.DIRECT, }), new Server("rapid", { regex: /"https:\/\/w*?.*?rapidvid.to\/e\/.*?"/, name: "RapidVideo", linkType: LinkTypes.EMBED, }), new Server("fb", { regex: /"https:\/\/video.xx.fbcdn.net\/v\/.*?"/, name: "FB (fbcdn.net)", linkType: LinkTypes.DIRECT, }), new Server("gp", { regex: /"https:\/\/redirector.googlevideo.com\/videoplayback\?.*?"/, name: "GP (googleusercontent.com)", linkType: LinkTypes.DIRECT, }), new Server("fe", { regex: /"https:\/\/www.rubicstreaming.com\/v\/.*?"/, name: "FE (rubicstreaming.com)", linkType: LinkTypes.EMBED, }), ]); module.exports = new Site("kisstvshow.to", { contentPath: "Show", noCaptchaServer: "rapid", buttonColor: "#F5B54B", buttonTextColor: "#000", servers, }); },{"kgrabber-types":38}],14:[function(require,module,exports){ "use strict"; // src\js\config\sites\patches\kimcartoon_UIFix.js exports.linkDisplay = () => { let $ld = $("#KG-linkdisplay"); $("#KG-linkdisplay-title").css({ "font-size": "20px", "color": $("a.bigChar").css("color"), }); fixTitle($ld); }; exports.preferences = () => { let $pf = $("#KG-preferences"); fixTitle($pf); }; exports.widget = () => { let $opts = $("#KG-opts-widget"); $opts.insertAfter(`#rightside .clear2:eq(2)`); let title = $opts.find(".barTitle").html(); $opts.before(`
${title}
`); $(".icon:eq(1)").css({ "width": "100%", "box-sizing": "border-box" }); $(".KG-preferences-button").css("margin-top", "5px"); $opts.find(".barTitle").remove(); fixTitle($opts); }; function fixTitle(element) { $(".KG-dialog-title").css("font-size", "18px"); element.find(".arrow-general").remove(); element.find(".barTitle").removeClass("barTitle") .css({ "height": "20px", "padding": "5px", }); } },{}],15:[function(require,module,exports){ "use strict"; // src\js\config\sites\patches\kissasian_UIFix.js exports.widget = () => { $(".KG-preferences-button").css("filter", "invert(0.7)"); }; },{}],16:[function(require,module,exports){ "use strict"; // generated file, provides contents of src\css module.exports = ` /* src\css\captchaModal.less */ .KG-captchaModal-container { background-color: #0005; position: fixed; top: 0px; left: 0px; width: 100vw; height: 100vh; z-index: 9000; overflow: hidden scroll; } .KG-captchaModal { width: 1000px; margin: auto; margin-top: 195px; margin-bottom: 50px; } .KG-captchaModal-description-header { width: 100%; font-size: 1.7em; text-align: center; } .KG-captchaModal-description-header span { color: #d5f406; margin-right: 20px; } .KG-captchaModal-description-header span:not(:last-child)::after { content: "-"; margin-left: 20px; color: #fff; } .KG-captchaModal-image-container { display: flex; flex-direction: row; flex-wrap: wrap; justify-content: center; } .KG-captchaModal-image-container img { margin: 30px; width: 160px; cursor: pointer; } .KG-captchaModal-image-container img.active { outline: 5px solid #d5f406; } #KG-captchaModal-status { margin-left: 25px; } /* src\css\colors.less */ /* src\css\general.less */ .KG-button { background-color: #548602; color: #fff; border: none; padding: 5px 12px; font-size: 15px; margin: 3px; float: left; } .KG-button-container { margin-top: 10px; height: 34px; } .KG-dialog-title { width: 80%; float: left; } .KG-dialog-close { float: right; cursor: pointer; font-size: 16px; margin-right: -4px; margin-top: 1px; } .right { float: right; } .left { float: left; } /* src\css\linkDisplay.less */ #KG-linkdisplay-text { word-break: break-all; } .KG-linkdisplay-row { display: flex; flex-direction: row; } .KG-linkdisplay-row a { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } .KG-linkdisplay-episodenumber { min-width: 30px; text-align: right; user-select: none; margin-right: 5px; } #KG-linkdisplay-export { margin-top: 10px; } #KG-linkdisplay-export-text { width: 100%; height: 150px; min-height: 40px; resize: vertical; background-color: #222; color: #fff; border: none; } #KG-linkdisplay-export-dropdown { margin: 6px; float: left; color: #fff; background-color: #222; } /* src\css\loader.less */ #KG-loader-text { width: 100%; text-align: center; margin-top: -40px; margin-bottom: 40px; min-height: 20px; } /* https://projects.lukehaas.me/css-loaders/ */ .loader, .loader:after { border-radius: 50%; width: 10em; height: 10em; } .loader { margin: 0px auto; font-size: 5px; position: relative; text-indent: -9999em; border-top: 1.1em solid rgba(255, 255, 255, 0.2); border-right: 1.1em solid rgba(255, 255, 255, 0.2); border-bottom: 1.1em solid rgba(255, 255, 255, 0.2); border-left: 1.1em solid #ffffff; -webkit-transform: translateZ(0); -ms-transform: translateZ(0); transform: translateZ(0); -webkit-animation: load8 1.1s infinite linear; animation: load8 1.1s infinite linear; } @-webkit-keyframes load8 { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes load8 { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } /* src\css\pageWidgets.less */ .KG-episodelist-header { width: 3%; text-align: center !important; } .KG-episodelist-number { text-align: right; padding-right: 4px; } .KG-episodelist-button { background-color: #548602; color: #fff; border: none; cursor: pointer; } /* src\css\preferences.less */ #KG-preferences-container-outer { overflow: auto; } .KG-preferences-header { font-size: 17px; letter-spacing: 0px; width: 100%; margin: 10px 0 5px 0; } #KG-preferences-container { overflow: auto; } #KG-preferences-container div { box-sizing: border-box; height: 26px; width: 50%; padding: 0 5px; margin: 2px 0; float: left; line-height: 26px; font-size: 14px; } #KG-preferences-container div span { padding-top: 5px; } .KG-preferences-button { width: 18px; height: 18px; margin: 3px; float: right; border: none; background: transparent; opacity: 0.7; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAABBklEQVRIidVWOw6DMAx9dGBttq49GkfgKByBo9AbwA26doOxLK4iOTSkTuLSn/okS2CeX8wjHwoighIhsdCU7bTqWyEN0HI8C7nOWuRFS3fYa0NENRH19IienxmhbtGMiTtchZyGswyS+walwqY0J2HRVqwsCqepAXBRdi5hBnAAMLpnoUVVQnwCcOKYIpySNaIWSbOFOG88nslwVxbllrLt9ui/NsPaeQawTxVrVnIviINzfa5YM4AkrsZXLCq8GASOFehY0BfvIuLDSjOYRXVicY1E1HGMCV7904XWvCAOrm1WmTfvQw7q3XRWdJ3mfPrAkQ59d+xVYS/BfXjoi3V//lcB4AYxBp8XouibewAAAABJRU5ErkJggg=="); background-size: cover; cursor: pointer; } .KG-preferences-button:hover { opacity: 1; } .KG-preferences-input-text { width: 150px; border: 1px solid #666; background: #222; padding: 3px; margin-left: 5px; color: #fff; } .KG-preferences-input-checkbox { height: 22px; } /* src\css\widget.less */ .KG-widget-episode { width: 40px; border: 1px solid #666; color: #fff; background-color: #222; padding: 3px; } #KG-widget-server { width: 100%; font-size: 14.5px; color: #fff; background-color: #222; } `; },{}],17:[function(require,module,exports){ "use strict"; // src\js\exporters\aria2c.js const { LinkTypes, Exporter } = require("kgrabber-types"); module.exports = new Exporter({ name: "aria2c file", extension: "txt", requireSamePage: false, linkTypes: [LinkTypes.DIRECT], }, runExport); function runExport(status) { let listing = $(".listing a").get().reverse(); let str = ""; for (let episode of status.episodes) { if (!episode.error) { str += `${episode.functionalLink}\n out=${listing[episode.episodeNumber-1].innerText}.mp4\n`; } } return str; } },{"kgrabber-types":38}],18:[function(require,module,exports){ "use strict"; // src\js\exporters\csv.js const { LinkTypes, Exporter } = require("kgrabber-types"), page = require("../ui/page"); module.exports = new Exporter({ name: "csv", extension: "csv", requireSamePage: true, linkTypes: [LinkTypes.DIRECT, LinkTypes.EMBED], }, runExport); function runExport(status) { let listing = page.episodeList(); let str = "episode, name, url\n"; for (let episode of status.episodes) { if (!episode.error) { str += `${episode.episodeNumber}, ${listing[episode.episodeNumber-1].innerText}, ${episode.functionalLink}\n`; } } return str; } },{"../ui/page":49,"kgrabber-types":38}],19:[function(require,module,exports){ "use strict"; // src\js\exporters\html.js const { LinkTypes, Exporter } = require("kgrabber-types"), page = require("../ui/page"); module.exports = new Exporter({ name: "html list", extension: "html", requireSamePage: true, linkTypes: [LinkTypes.DIRECT], }, runExport); function runExport(status) { let listing = page.episodeList(); let str = "\n \n"; for (let episode of status.episodes) { if (!episode.error) { str += `${listing[episode.episodeNumber-1].innerText}
\n`; } } str += "\n\n"; return str; } },{"../ui/page":49,"kgrabber-types":38}],20:[function(require,module,exports){ "use strict"; // src\js\exporters\idmbat.js const { LinkTypes, Exporter } = require("kgrabber-types"), util = require("../util"), preferenceManager = require("../config/preferenceManager"); const preferences = preferenceManager.get(); module.exports = new Exporter({ name: "IDM bat file", extension: "bat", requireSamePage: true, linkTypes: [LinkTypes.DIRECT], }, runExport); function runExport(status) { let listing = $(".listing a").get().reverse(); let title = util.makeBatSafe(status.title); let str = getHeader(title); for (let episode of status.episodes) { if (!episode.error) { let epTitle = util.makeBatSafe(listing[episode.episodeNumber - 1].innerText); if (!preferences.internet_download_manager.keep_title_in_episode_name && epTitle.slice(0, title.length) === title) { epTitle = epTitle.slice(title.length + 1); } str += `"%idm%" /n /p "%dir%\\%title%" /f "${epTitle}.mp4" /d "${episode.functionalLink}" %args%\n`; } } return str; } function getHeader(title) { return `::download and double click me! @echo off set title=${title} set idm=${preferences.internet_download_manager.idm_path} set args=${preferences.internet_download_manager.arguments} set dir=${preferences.internet_download_manager.download_path} if not exist "%idm%" echo IDM not found && echo check your IDM path in preferences && pause && goto eof mkdir "%title%" > nul start "" "%idm%" ping localhost -n 2 > nul\n\n`; } },{"../config/preferenceManager":8,"../util":56,"kgrabber-types":38}],21:[function(require,module,exports){ "use strict"; // src\js\exporters\index.js const exporters = [ require("./list"), require("./m3u8"), require("./json"), require("./html"), require("./csv"), require("./aria2c"), require("./idmbat"), ]; exports.all = () => exporters; exports.available = (linkType, samePage) => exporters.filter((exporter) => filter(exporter, linkType, samePage) ); exports.sorted = (linkType, samePage) => exporters.map((exporter) => { return { available: filter(exporter, linkType, samePage), exporter }; }) .sort((a, b) => b.available - a.available); exports.add = (...newExporters) => { exporters.push(...newExporters); }; function filter(exporter, linkType, samePage) { if (!exporter.linkTypes.includes(linkType)) { return false; } if (exporter.requireSamePage && !samePage) { return false; } return true; } },{"./aria2c":17,"./csv":18,"./html":19,"./idmbat":20,"./json":22,"./list":23,"./m3u8":24}],22:[function(require,module,exports){ "use strict"; // src\js\exporters\json.js const { LinkTypes, Exporter } = require("kgrabber-types"), page = require("../ui/page"); module.exports = new Exporter({ name: "json", extension: "json", requireSamePage: true, linkTypes: [LinkTypes.DIRECT, LinkTypes.EMBED], }, runExport); function runExport(status) { let listing = page.episodeList(); let json = { version: "2.0", scriptVersion: GM_info.script.version, episodes: [], url: status.url, title: status.title, serverID: status.serverID, linkType: status.linkType, }; for (let episode of status.episodes) { json.episodes.push({ grabbedLink: episode.grabbedLink, processedLink: episode.processedLink, error: episode.error, episodeNumber: episode.episodeNumber, name: listing[episode.episodeNumber - 1].innerText, }); } return JSON.stringify(json); } },{"../ui/page":49,"kgrabber-types":38}],23:[function(require,module,exports){ "use strict"; // src\js\exporters\list.js const { LinkTypes, Exporter } = require("kgrabber-types"); module.exports = new Exporter({ name: "list", extension: "txt", requireSamePage: false, linkTypes: [LinkTypes.DIRECT, LinkTypes.EMBED], }, runExport); function runExport(status) { let str = ""; for (let episode of status.episodes) { if (!episode.error) { str += episode.functionalLink + "\n"; } } return str; } },{"kgrabber-types":38}],24:[function(require,module,exports){ "use strict"; // src\js\exporters\m3u8.js const { LinkTypes, Exporter } = require("kgrabber-types"), page = require("../ui/page"); module.exports = new Exporter({ name: "m3u8 playlist", extension: "m3u8", requireSamePage: true, linkTypes: [LinkTypes.DIRECT], }, runExport); function runExport(status) { let listing = page.episodeList(); let str = "#EXTM3U\n"; for (let episode of status.episodes) { if (!episode.error) { str += `#EXTINF:0,${listing[episode.episodeNumber-1].innerText}\n${episode.functionalLink}\n`; } } return str; } },{"../ui/page":49,"kgrabber-types":38}],25:[function(require,module,exports){ "use strict"; // generated file, provides contents of src\html // src\html\captchaModal.html exports[`captchaModal`] = ` `; // src\html\linkDisplay.html exports[`linkDisplay`] = ` `; // src\html\preferences.html exports[`preferences`] = ` `; // src\html\widget.html exports[`widget`] = `
KGrabber
 

from to

`; },{}],26:[function(require,module,exports){ "use strict"; // src\js\main.js const config = require("./config"), { log } = require("./util"), steps = require("./steps"), ui = require("./ui"), statusManager = require("./statusManager"), page = require("./ui/page"), pluginLoader = require("./pluginLoader"); pluginLoader.load(); const status = statusManager.get(), site = config.sites.current(); if (site) { if (site.onContentPath(page.location.pathname) && !page.noTitle()) { ui.injectAll(); site.applyPatch(); } if (status.func) { steps.execute(status.func, status, site); } } else { log.err(`'${page.location.hostname}' is not supported`); } },{"./config":7,"./pluginLoader":39,"./statusManager":42,"./steps":45,"./ui":47,"./ui/page":49,"./util":56}],27:[function(require,module,exports){ // src\js\node_modules\kgrabber-plugin\PluginContext.js module.exports = class PluginContext { constructor({ addActionsFunc, addSitesFunc, addExportersFunc, addStepsFunc, ui, preferences, statusManager }) { this._addActionsFunc = addActionsFunc; this._addSitesFunc = addSitesFunc; this._addExportersFunc = addExportersFunc; this._addStepsFunc = addStepsFunc; this.ui = ui; this.preferences = preferences; this.statusManager = statusManager; Object.freeze(this); } addActions(...actions) { this._addActionsFunc(...actions); } addSites(...sites) { this._addSitesFunc(...sites); } addExporters(...exporters) { this._addExportersFunc(...exporters); } addSteps(steps) { this._addStepsFunc(steps); } }; },{}],28:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\Action.js module.exports = class Action { constructor(name, { availableFunc, executeFunc, automatic = false }) { this.name = name; this.automatic = automatic; this._executeFunc = executeFunc; this._availableFunc = availableFunc; Object.freeze(this); } isAvailable(status) { return this._availableFunc(this, status); } invoke(status, setProgress) { return this._executeFunc(status, setProgress); } }; },{}],29:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\Captcha.js module.exports = class Captcha { constructor(texts, images) { this.texts = texts; this.images = images; Object.seal(this); } }; },{}],30:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\Dictionary.js module.exports = class Dictionary { constructor(objects = []) { this._data = {}; this.add(...objects); Object.freeze(this); } add(...objects) { for (let object of objects) { if (this._data[object.identifier]) { throw new Error(`Duplicate key '${object.identifier}'`); } this._data[object.identifier] = object; } } remove(key) { delete this._data[key]; } get(key) { return this._data[key]; } *[Symbol.iterator]() { for (let i in this._data) { yield this._data[i]; } } }; },{}],31:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\Episode.js module.exports = class Episode { constructor(episodeNumber, kissLink) { this.kissLink = kissLink; this.grabbedLink = ""; this.processedLink = ""; this.error = ""; this.episodeNumber = episodeNumber; Object.seal(this); } get functionalLink() { return this.processedLink || this.grabbedLink; } get displayLink() { return this.error ? `error: ${this.error}` : this.processedLink || this.grabbedLink; } }; },{}],32:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\Exporter.js module.exports = class Exporter { constructor({ name, extension, requireSamePage, linkTypes }, func) { this.name = name; this.extension = extension; this.requireSamePage = requireSamePage; this.linkTypes = linkTypes; this._export = func; Object.freeze(this); } export (status) { return this._export(status); } }; },{}],33:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\LinkTypes.js module.exports = Object.freeze({ EMBED: "embed", DIRECT: "direct", }); },{}],34:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\Logger.js module.exports = class Logger { constructor(name, { color = "#000", backgroundColor = "#fff" } = {}) { this.name = name; this.css = `color: ${color}; background-color: ${backgroundColor}; padding: 0 5px; border-radius: 3px;`; } info(...obj) { console.info(`%c${this.name}`, this.css, ...obj); } log(...obj) { console.log(`%c${this.name}`, this.css, ...obj); } warn(...obj) { console.warn(`%c${this.name}`, this.css, ...obj); } err(...obj) { console.error(`%c${this.name}`, this.css, ...obj); } debug(...obj) { console.debug(`%c${this.name}`, this.css, ...obj); } }; },{}],35:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\Server.js module.exports = class Server { constructor(identifier, { regex, name, linkType, customStep, experimentalStep }) { this.identifier = identifier; this.regex = regex; this.name = name; this.linkType = linkType; this.customStep = customStep; this.experimentalStep = experimentalStep; Object.freeze(this); } getEffectiveStep(defaultStep, allowExperimental, allowCustom) { return allowExperimental && this.experimentalStep || allowCustom && this.customStep || defaultStep; } findLink(html) { let result = html.match(this.regex); if (result) { return result[0].split('"')[1]; } else { return undefined; } } }; },{}],36:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\Site.js module.exports = class Site { constructor(hostname, { contentPath, noCaptchaServer, buttonColor, buttonTextColor, servers, patches = {} }) { this.hostname = hostname; this.contentPath = new RegExp(`^/${contentPath}/`); this.noCaptchaServer = noCaptchaServer; this.buttonColor = buttonColor; this.buttonTextColor = buttonTextColor; this.servers = servers; this.patches = patches; Object.freeze(this); } get identifier() { return this.hostname; } onContentPath(pathname) { return this.contentPath.test(pathname); } applyPatch(uiElement = "page") { if (this.patches[uiElement]) this.patches[uiElement](); } }; },{}],37:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\Status.js const Episode = require("kgrabber-types/Episode"); module.exports = class Status { constructor() { this._reset(); Object.seal(this); } _reset() { this.episodes = []; this.current = 0; this.automaticDone = false; this.func = ""; this.url = ""; this.title = ""; this.serverID = ""; this.linkType = ""; } initialize({ url, title, serverID, linkType }) { this._reset(); this.url = url; this.title = title; this.serverID = serverID; this.linkType = linkType; } clear() { this._reset(); } serialize() { return JSON.stringify(this); } static deserialize(json) { let obj = JSON.parse(json); for (let i in obj.episodes) { obj.episodes[i] = Object.assign(new Episode(), obj.episodes[i]); } return Object.assign(new this, obj); } }; },{"kgrabber-types/Episode":31}],38:[function(require,module,exports){ // src\js\node_modules\kgrabber-types\index.js module.exports = { Action: require("./Action"), Captcha: require("./Captcha"), Dictionary: require("./Dictionary"), Episode: require("./Episode"), Exporter: require("./Exporter"), LinkTypes: require("./LinkTypes"), Logger: require("./Logger"), Server: require("./Server"), Site: require("./Site"), Status: require("./Status"), }; },{"./Action":28,"./Captcha":29,"./Dictionary":30,"./Episode":31,"./Exporter":32,"./LinkTypes":33,"./Logger":34,"./Server":35,"./Site":36,"./Status":37}],39:[function(require,module,exports){ "use strict"; // src\js\pluginLoader.js const util = require("./util"), PluginContext = require("kgrabber-plugin/PluginContext"), { preferenceManager, sites } = require("./config"), statusManager = require("./statusManager"), actions = require("./actions"), exporters = require("./exporters"), steps = require("./steps"), version = require("./pluginVersion"), semverSatisfies = require("semver/functions/satisfies"), ui = require("./ui/pluginExposed"); const $ = unsafeWindow.$, applicationName = "KGrabberPlugin", allowedPluginsKey = "allowedPlugins", preferences = preferenceManager.get(); let allowedPlugins; exports.load = () => { if (!$) { util.log.err(`jquery not present on the page, can't load plugins`); return; } loadPlugins(); }; function loadPlugins() { getAllowedPlugins(); let foundPlugins = discoverPlugins(); if (foundPlugins.length > 0) { let context = new PluginContext({ addActionsFunc: actions.add, addSitesFunc: sites.add, addExportersFunc: exporters.add, addStepsFunc: steps.add, ui, preferences, statusManager, }); for (let plugin of foundPlugins) { let expectedVersion = `^${plugin.version}`; if (!plugin.version || !semverSatisfies(version, expectedVersion)) { util.log.err(`plugin "${plugin.pluginID}" could not be loaded due to version mismatch: expected "${expectedVersion}", got "${version}"`); continue; } if (!allowedPlugins.includes(plugin.pluginID)) { if (confirm(`allow plugin '${plugin.pluginID}'?`)) { allowPlugin(plugin.pluginID); } else { continue; } } loadPlugin(plugin.pluginID, context); } } } function discoverPlugins() { let foundPlugins = []; $(document).on(`${applicationName}/DiscoverResponse`, (e, { pluginID, version }) => { foundPlugins.push({ pluginID, version }); }); $(document).trigger(`${applicationName}/DiscoverRequest`); return foundPlugins; } function loadPlugin(pluginID, context) { util.log.debug(`loading plugin '${pluginID}'`); $(document).trigger(`${applicationName}/LoadPlugin-${pluginID}`, context); } function getAllowedPlugins() { try { allowedPlugins = JSON.parse(GM_getValue(allowedPluginsKey)); } catch (e) { allowedPlugins = []; saveAllowedPlugins(); } } function saveAllowedPlugins() { GM_setValue(allowedPluginsKey, JSON.stringify(allowedPlugins)); } function allowPlugin(pluginID) { allowedPlugins.push(pluginID); saveAllowedPlugins(); } },{"./actions":3,"./config":7,"./exporters":21,"./pluginVersion":40,"./statusManager":42,"./steps":45,"./ui/pluginExposed":51,"./util":56,"kgrabber-plugin/PluginContext":27,"semver/functions/satisfies":71}],40:[function(require,module,exports){ "use strict"; // version of ./src/js/node_modules/kgrabber-plugin module.exports = "1.0.1"; },{}],41:[function(require,module,exports){ "use strict"; // src\js\start.js const config = require("./config"), util = require("./util"), steps = require("./steps"), page = require("./ui/page"), statusManager = require("./statusManager"), { Episode } = require("kgrabber-types"); const preferences = config.preferenceManager.get(), status = statusManager.get(), defaultStep = "defaultBegin"; module.exports = (start, end, serverID) => { let site = config.sites.current(); let server = site.servers.get(serverID); statusManager.initialize({ title: page.title(), serverID, linkType: server.linkType, }); status.episodes = getEpisodes(start, end); status.func = server.getEffectiveStep(defaultStep, preferences.compatibility.enable_experimental_grabbers, !preferences.compatibility.force_default_grabber); statusManager.save(); steps.execute(status.func, status, site); $("html, body").animate({ scrollTop: 0 }, "slow"); }; function getEpisodes(start, end) { let episodes = []; util.for(page.episodeList(), (i, obj) => { episodes.push(new Episode(i + 1, obj.href)); }, { min: start, max: end, }); return episodes; } },{"./config":7,"./statusManager":42,"./steps":45,"./ui/page":49,"./util":56,"kgrabber-types":38}],42:[function(require,module,exports){ "use strict"; // src\js\statusManager.js const log = require("./util").log, page = require("./ui/page"), { Status } = require("kgrabber-types"); const propName = "KG-status"; let status; exports.get = () => { if (status === undefined) { status = load(); } return status; }; exports.save = () => { sessionStorage[propName] = JSON.stringify(status); }; exports.clear = () => { status.clear(); sessionStorage.removeItem(propName); }; exports.initialize = ({ title, serverID, linkType } = {}) => { return status.initialize({ url: page.href, title, serverID, linkType, }); }; function load() { let json = sessionStorage[propName]; if (json) { try { return Status.deserialize(json); } catch (error) { log.err("unable to parse JSON", { error, json }); } } return new Status(); } },{"./ui/page":49,"./util":56,"kgrabber-types":38}],43:[function(require,module,exports){ "use strict"; // src\js\steps\captchaModal.js const util = require("../util"), statusManager = require("../statusManager"), config = require("../config"), linkDisplay = require("../ui/linkDisplay"), captchaModal = require("../ui/captchaModal"), { Captcha } = require("kgrabber-types"); exports.modalBegin = async (status) => { linkDisplay.show(); linkDisplay.showSpinner(); let progress = 0; let func = async ( episode) => { let html = await doCaptcha(`${episode.kissLink}&s=${status.serverID}`); getLink(html, episode, status.serverID); progress++; setStatusText(`${progress}/${promises.length}`); }; let promises = []; util.for(status.episodes, (i, episode) => { promises.push(func(episode)); }); setStatusText(`0/${promises.length}`); await Promise.all(promises); status.func = "defaultFinished"; statusManager.save(); linkDisplay.load(); }; function setStatusText(str) { linkDisplay.setSpinnerText(str); captchaModal.setStatusText(str); } async function doCaptcha(url) { while (true) { let html = (await util.ajax.get(url)).response; let $form = $(html).find("form#formVerify1"); if ($form.length == 0) { return html; } let texts = []; $form.find("span:lt(2)").each((_i, obj) => texts.push(obj.innerText.replace(/[ \n]*(\w)/, "$1"))); let images = []; $form.find("img").each((i, obj) => images.push(obj.src)); let answerCap = (await captchaModal.queue(new Captcha(texts, images))).join(",") + ","; let response = (await util.ajax.post("/Special/AreYouHuman2", util.urlEncode({ reUrl: url, answerCap }), { "content-type": "application/x-www-form-urlencoded" })).response; if (isCaptchaFail(response)) { continue; } return response; } } function getLink(html, episode, serverID) { let link = config.sites.current().servers.get(serverID).findLink(html); if (link) { episode.grabbedLink = link; } else { episode.error = "server not available"; } } function isCaptchaFail(html) { let lowerCase = html.toLowerCase(); return lowerCase.includes("wrong answer") || lowerCase.includes("areyouhuman2"); } },{"../config":7,"../statusManager":42,"../ui/captchaModal":46,"../ui/linkDisplay":48,"../util":56,"kgrabber-types":38}],44:[function(require,module,exports){ "use strict"; // src\js\steps\default.js const statusManager = require("../statusManager"), config = require("../config"), linkDisplay = require("../ui/linkDisplay"), page = require("../ui/page"); exports.defaultBegin = (status) => { status.func = "defaultGetLink"; statusManager.save(); page.href = status.episodes[status.current].kissLink + `&s=${status.serverID}`; }; exports.defaultGetLink = (status) => { if (!config.sites.current().onContentPath(page.location.pathname)) { return; } let episode = status.episodes[status.current]; let link = config.sites.current().servers.get(status.serverID).findLink(document.body.innerHTML); if (link) { episode.grabbedLink = link; } else { episode.error = "selected server may not be available"; } status.current++; if (status.current >= status.episodes.length) { status.func = "defaultFinished"; page.href = status.url; } else { page.href = status.episodes[status.current].kissLink + `&s=${status.serverID}`; } statusManager.save(); }; exports.defaultFinished = (status, site) => { if (site.onContentPath(page.location.pathname)) { linkDisplay.load(); } }; },{"../config":7,"../statusManager":42,"../ui/linkDisplay":48,"../ui/page":49}],45:[function(require,module,exports){ "use strict"; // src\js\steps\index.js const { log } = require("../util"); const steps = Object.assign({}, require("./default"), require("./captchaModal") ); exports.execute = (stepName, status, site) => { if (steps[stepName]) { steps[stepName](status, site); } else { log.err(`tried executing invalid step '${stepName}'`, { steps }); } }; exports.add = (newSteps) => { Object.assign(steps, newSteps); }; },{"../util":56,"./captchaModal":43,"./default":44}],46:[function(require,module,exports){ "use strict"; // src\js\ui\captchaModal.js const util = require("../util"), html = require("../html"), page = require("./page"), { sites } = require("../config"); let queue = []; let injected = false; function inject() { $("body").append(html.captchaModal); sites.current().applyPatch("captchaModal"); injected = true; if (!$(".KG-captchaModal").length) util.log.err(new Error("captchaModal not injected")); } exports.queue = (captcha) => { return new Promise((resolve) => { queue.push({ captcha, resolve }); if (queue.length == 1) { show(); queueNext(); } }); }; function queueNext() { let current = queue[0]; clear(); load(current.captcha, current.resolve); } function queuePop() { queue.splice(0, 1); if (queue.length == 0) { hide(); } else { queueNext(); } } exports.setStatusText = (text) => { $("#KG-captchaModal-status").text(text); }; function show() { if (!injected) inject(); $(".KG-captchaModal-container").fadeIn("fast"); page.scroll(false); } function hide() { $(".KG-captchaModal-container").fadeOut("fast"); page.scroll(true); } function clear() { $(".KG-captchaModal-description-header").empty(); $(".KG-captchaModal-image-container").empty(); } function load(captcha, resolve) { for (let text of captcha.texts) { $("") .text(text) .appendTo(".KG-captchaModal-description-header"); } for (let i in captcha.images) { $("") .attr({ "src": captcha.images[i], "data-index": i, }) .click( (e) => { toggleImage(e.target, captcha, resolve); }) .appendTo(".KG-captchaModal-image-container"); } applyColors(); } async function toggleImage(image, captcha, resolve) { $(image).toggleClass("active"); let $activeImages = $(".KG-captchaModal-image-container img.active"); if ($activeImages.length >= 2) { let activeIndices = []; $activeImages.each( (i, obj) => void activeIndices.push(Number(obj.dataset.index)) ); resolve(activeIndices); queuePop(); } } function applyColors() { } },{"../config":7,"../html":25,"../util":56,"./page":49}],47:[function(require,module,exports){ "use strict"; // src\js\ui\index.js const widget = require("./widget"), pageWidgets = require("./pageWidgets"), css = require("../css"); function injectCss() { $(document.head).append(``); } exports.injectAll = () => { injectCss(); widget.show(); pageWidgets.injectEpisodeListWidgets(); }; },{"../css":16,"./pageWidgets":50,"./widget":54}],48:[function(require,module,exports){ "use strict"; // src\js\ui\linkDisplay.js const shared = require("./shared"), exporters = require("../exporters"), util = require("../util"), html = require("../html"), actions = require("../actions"), statusManager = require("../statusManager"), page = require("./page"), { sites } = require("../config"); const status = statusManager.get(); let injected = false; function inject() { $("#leftside").prepend(html.linkDisplay); setHandlers(); sites.current().applyPatch("linkDisplay"); injected = true; if (!$("#KG-linkdisplay").length) util.log.err(new Error("linkDisplay not injected")); } let load = exports.load = () => { show(true); setTitle(`Extracted Links | ${status.title}`); loadLinks(status.episodes); loadExporters(exporters.sorted(status.linkType, status.url == page.href)); loadActions(actions.available()); shared.applyColors(); }; let show = exports.show = (instant) => { if (!injected) inject(); instant ? $("#KG-linkdisplay").show() : $("#KG-linkdisplay").slideDown(); }; let hide = exports.hide = () => $("#KG-linkdisplay").slideUp(); function setTitle(text) { $("#KG-linkdisplay .KG-dialog-title").text(text); } function loadLinks(episodes) { let html = ""; let padLength = Math.max(2, page.episodeCount().toString().length); util.for(episodes, (i, obj) => { let num = obj.episodeNumber.toString().padStart(padLength, "0"); let number = `
E${num}:
`; let link = `${obj.displayLink}`; html += `
${number} ${link}
`; }); $("#KG-linkdisplay-text").html(`
${html}
`); } function loadActions(actions) { $("#KG-action-container .KG-button").remove(); for (let i in actions) { if (actions[i].automatic) { util.defer(() => { executeAction(actions[i]); }); } else { $(``) .click(() => { executeAction(actions[i]); }) .appendTo("#KG-action-container"); } } } function loadExporters(arr) { let $exporters = $("#KG-linkdisplay-export-dropdown"); $exporters.empty() .off("change") .change((e) => { runExporter(arr[e.target.value].exporter); $(e.target).val(""); }) .append($("