// ==UserScript==
// @name 漫画网站画质AI修复
// @version 0.6.0
// @namespace http://tampermonkey.net/
// @description 修复漫画网站上的低清漫画。需要下载并安装后台程序。仅支持windows。网站暂时只支持漫画柜。
// @author call_duck
// @homepage https://github.com/pboymt/userscript-typescript-template#readme
// @license https://opensource.org/licenses/MIT
// @match https://www.manhuagui.com/comic/**
// @match https://manhuabika.com/pchapter/**
// @run-at document-end
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM.getValue
// @grant GM.setValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// ==/UserScript==
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ 607:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const gm_config_1 = __importDefault(__webpack_require__(494));
const readMode_1 = __webpack_require__(186);
const readMode_2 = __importDefault(__webpack_require__(186));
(function () {
"use strict";
const urlRegs = [
/\/comic\/[0-9]+\/[0-9]+\.html/,
/\/pchapter\//
];
let urlFlag = false;
for (let i = 0; i < urlRegs.length; i++) {
const reg = urlRegs[i];
if (location.href.match(reg)) {
urlFlag = true;
}
}
if (!urlFlag) {
return;
}
if (location.hostname.indexOf('manhuagui') !== -1) {
GM.addStyle(`#smh-msg-box { z-index: 2147483647 !important }`);
}
if (location.hostname.indexOf('manhuabika') !== -1) {
GM.addStyle(`#toast-user { z-index: 2147483647 !important }`);
}
(0, readMode_2.default)();
let imgDomIndex = 0;
let queueLength = 0;
let stop = true;
let isShowError = false;
const maxQueueLength = 4;
const baseUrl = 'http://localhost:31485';
const gmc = new gm_config_1.default({
id: "MyConfig",
title: "设置AI缩放",
frameStyle: "z-index:100003;position: fixed;height:400px!important",
fields: {
scale: {
label: "缩放倍数",
title: "默认“4”",
type: "select",
options: ["2", "3", "4"],
default: "4",
},
maxQueueLength: {
label: "最大同时处理图片数量",
type: "int",
min: 1,
default: 3,
},
isAutoEnterReadMode: {
label: '是否自动进入阅读模式',
type: 'checkbox',
default: true
},
model: {
label: "模型",
title: "realesrgan-x4plus-anime拥有比默认模型realesr-animevideov3更好的质量以及更慢的速度。推荐高端显卡 用户使用。",
type: "select",
options: ["realesr-animevideov3(速度快,默认)", "realesrgan-x4plus-anime(质量好,更慢)"],
default: "realesr-animevideov3(速度快,默认)",
},
stateBarStyle: {
label: "状态栏样式",
title: "设置状态栏显示的信息",
type: "select",
options: ["完整", '精简', '不显示'],
default: "完整",
},
enableCache: {
label: "是否启用缓存",
title: "将生成的高清图片缓存,减少再次请求高清图片时的响应时间。启用后将占用一定的C盘空间",
type: "checkbox",
default: true,
},
cleanCacheSpan: {
label: "自动清理缓存时间间隔(单位为天。设置为0则不自动清理)",
title: "单位为天",
type: "int",
min: 0,
default: 1,
},
cleanCache: {
label: "清除缓存",
type: "button",
click: function () {
GM_xmlhttpRequest({
method: "GET",
url: baseUrl + "/clean_cache",
onload: function (r) {
var _a;
console.log(r.response);
const frame = document.getElementById('MyConfig');
const label = (_a = frame.contentDocument) === null || _a === void 0 ? void 0 : _a.getElementById('sizeLabel');
label.innerHTML = "0MB";
}
});
}
}
},
events: {
save: function () {
gmc.close();
GM_xmlhttpRequest({
method: 'POST',
url: baseUrl + '/setting',
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
data: JSON.stringify({ 'enable_cache': gmc.get('enableCache'), 'clean_cache_span': gmc.get('cleanCacheSpan') }),
onload: function (r) {
console.log(r.response);
},
onerror() {
showErrorStateBar();
}
});
},
},
});
GM_addStyle("#MyConfig {width:500px!important;height:320px!important;}");
function openConfigPanel() {
if (document.getElementById('MyConfig')) {
return;
}
gmc.open();
setTimeout(() => {
console.log(document.getElementById('MyConfig'));
}, 1);
GM_xmlhttpRequest({
method: 'GET',
url: baseUrl + "/get_settings",
onload: function (r) {
var _a, _b, _c, _d;
try {
const info = JSON.parse(r.response);
const settings = info.settings;
if (settings.enable_cache) {
gmc.set("enableCache", settings.enable_cache.toLowerCase() === "true" ? true : false);
}
else {
gmc.set("enableCache", true);
}
gmc.set("cleanCacheSpan", settings.clean_cache_span ? parseInt(settings.clean_cache_span) : 1);
const configFrame = document.getElementById('MyConfig');
if (!configFrame) {
return;
}
const sizeLabel = document.createElement('span');
sizeLabel.innerHTML = ((info.cache_size / 1000) / 1000).toFixed(2) + "MB";
sizeLabel.id = "sizeLabel";
const span = document.createElement('div');
span.innerHTML = '当前缓存大小:';
(_b = (_a = configFrame.contentDocument) === null || _a === void 0 ? void 0 : _a.getElementById('MyConfig_cleanCache_var')) === null || _b === void 0 ? void 0 : _b.appendChild(span);
(_d = (_c = configFrame.contentDocument) === null || _c === void 0 ? void 0 : _c.getElementById('MyConfig_cleanCache_var')) === null || _d === void 0 ? void 0 : _d.appendChild(sizeLabel);
}
catch (error) {
console.log(error);
}
},
onerror: function () {
stateBar.innerHTML = `后端程序未运行,请先启动后端程序 <a class="__callLink" href="mangaAIRepairerBackend:a" style="color:blue">尝试调起应用 <a/>`;
stateBar.style.display = 'block';
}
});
}
function waitImg(img, fun) {
setTimeout(() => {
if (img && img.complete) {
fun(img);
}
else {
waitImg(img, fun);
}
}, 100);
}
function refreshStateBar() {
const stateBarStyle = gmc.fields.stateBarStyle.value;
if (stateBarStyle === '不显示') {
return;
}
if (queueLength === 0 && stop === false) {
stateBar.style.display = "none";
}
else {
stateBar.style.display = "block";
}
let model = gmc.fields.model.value;
model = model.replace(/(\(.+\))/g, '');
if (stateBarStyle === '精简') {
stateBar.innerHTML = `正在处理 ${queueLength} 张图片`;
}
else {
stateBar.innerHTML = `正在处理 ${queueLength} 张图片<br><div style="font-size:10px;line-height:12px">当前模型:<br>${model}</div>`;
}
}
function showErrorStateBar() {
if (isShowError) {
return;
}
isShowError = true;
stateBar.style.display = "block";
stateBar.innerHTML =
`
<span class="msg">本机后台AI画质修复程序未运行,请检查后台程序状态。 </span>
<a href="https://greasyfork.org/zh-CN/scripts/483769-%E6%BC%AB%E7%94%BB%E7%BD%91%E7%AB%99%E7%94%BB%E8%B4%A8ai%E4%BF%AE%E5%A4%8D" style="color:blue" target="_blank">说明文档 <a/>
<a class="__callLink" href="mangaAIRepairerBackend:a" style="color:blue">尝试调起应用 <a/>
`;
stateBar.querySelector('.__callLink').addEventListener("click", function () {
stateBar.querySelector(".msg").innerHTML =
"如果应用未启动则需安装应用。如果更新后无法调起后端应用请重新执行安装脚本。详情见文档。 ";
});
}
function handleImg(img) {
if (img.dataset.handled) {
return;
}
if (queueLength >= gmc.get('maxQueueLength')) {
return;
}
queueLength++;
refreshStateBar();
img.dataset.handled = "true";
let host = window.location.origin + "/";
let model = gmc.fields.model.value;
if (model.indexOf('realesrgan-x4plus-anime') !== -1) {
model = 'realesrgan-x4plus-anime';
}
else {
model = 'realesr-animevideov3';
}
GM_xmlhttpRequest({
method: "GET",
url: img.src,
headers: { referer: host },
responseType: "blob",
onload: function (r) {
var blob = r.response;
let oFileReader = new FileReader();
oFileReader.onloadend = function () {
let base64 = oFileReader.result;
let scale = gmc.fields.scale.value;
const urlObj = new URL(img.src);
const url = urlObj.hostname + urlObj.pathname;
GM_xmlhttpRequest({
method: "POST",
url: "http://localhost:31485/handle_img",
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
data: JSON.stringify({ data: base64, level: scale, scale, model, url }),
onload: function (r) {
const token = r.response;
queueLength--;
refreshStateBar();
const distImgLink = `http://127.0.0.1:31485/get_img?token=${token}`;
(0, readMode_1.replaceReadModeImg)(distImgLink, img.src);
img.src = distImgLink;
},
onerror: function () {
stop = true;
queueLength--;
showErrorStateBar();
delete img.dataset.handled;
},
});
};
oFileReader.readAsDataURL(blob);
},
});
}
function checkAlive() {
return new Promise((res, rej) => {
try {
GM_xmlhttpRequest({
method: "GET",
url: "http://localhost:31485",
timeout: 1000,
onload: function (r) {
res();
},
onerror: function (e) {
rej();
},
});
}
catch (error) {
console.log(error);
}
});
}
var stateBar = document.createElement("div");
stateBar.style.position = "fixed";
stateBar.style.border = "1px solid #333";
stateBar.style.padding = "8px";
stateBar.style.backgroundColor = "#fff";
stateBar.style.fontSize = "14px";
stateBar.style.display = "none";
stateBar.style.left = "20px";
stateBar.style.bottom = "70px";
stateBar.style.zIndex = "100001";
document.body.appendChild(stateBar);
const toolBar = document.createElement("div");
toolBar.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear-fill" viewBox="0 0 16 16">
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
</svg>
`;
toolBar.style.padding = "20px";
toolBar.style.cursor = "pointer";
toolBar.style.opacity = "0.5";
toolBar.style.display = "none";
toolBar.style.marginLeft = "30px";
toolBar.style.marginTop = "100px";
const hover = document.createElement("div");
hover.style.width = "200px";
hover.style.height = "300px";
hover.style.position = "fixed";
hover.style.left = "0";
hover.style.bottom = "0";
hover.addEventListener("mousemove", function () {
toolBar.style.display = "block";
});
hover.addEventListener("mouseleave", function () {
toolBar.style.display = "none";
});
document.body.appendChild(hover);
hover.appendChild(toolBar);
toolBar.addEventListener("click", () => {
openConfigPanel();
});
gmc.onInit = () => {
checkAlive()
.then(() => {
stop = false;
})
.catch(() => {
showErrorStateBar();
});
setInterval(() => {
const allImg = Array.from(document.getElementsByTagName("img"));
allImg.forEach((img, i) => {
if (img.offsetWidth >= 400 && !img.src.match(/.*\.gif/) && !img.dataset.skip) {
var pEle = img.parentElement;
if (pEle.tagName === "A" && pEle.href.indexOf("/ad/") !== -1) {
return;
}
if (!img.dataset.width) {
img.dataset.width = `${img.offsetWidth}px`;
}
if (!img.style.width) {
img.style.width = img.offsetWidth + 'px';
}
if (img.dataset.index === undefined) {
img.dataset.index = `${imgDomIndex}`;
imgDomIndex++;
(0, readMode_1.addImgToReadMode)(img);
}
if (!stop && !img.dataset.handled) {
waitImg(img, handleImg);
}
}
});
}, 300);
setInterval(() => {
checkAlive()
.then(() => {
if (stop) {
stateBar.style.display = 'none';
}
stop = false;
isShowError = false;
})
.catch(() => {
stop = true;
showErrorStateBar();
});
}, 1000);
GM_registerMenuCommand('AI缩放选项', function () {
openConfigPanel();
});
if (gmc.get('isAutoEnterReadMode')) {
setTimeout(() => {
(0, readMode_1.enterReadMode)();
}, 500);
}
};
})();
/***/ }),
/***/ 186:
/***/ ((__unused_webpack_module, exports) => {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.replaceReadModeImg = exports.addImgToReadMode = exports.enterReadMode = exports.imgList = void 0;
function scrollDown() {
document.documentElement.scrollTop = document.documentElement.scrollTop + 1000;
document.body.scrollTop = document.body.scrollTop + 1000;
}
exports.imgList = [];
let readModeTimer = 0;
let oScrollPosition = 0;
const root = document.createElement('div');
const rootId = '__manga_read_mode';
const scrollViewId = '__scrollview';
const singleViewId = '__singleView';
root.id = rootId;
root.style.display = 'none';
root.style.width = '100%';
root.style.height = '100%';
root.style.backgroundColor = 'rgb(255,255,255)';
root.style.position = 'fixed';
root.style.left = '0';
root.style.top = '0';
root.style.zIndex = '100000';
root.style.overflow = 'auto';
const scrollView = document.createElement('div');
scrollView.style.overflow = 'auto';
scrollView.style.width = '100%';
scrollView.style.display = 'flex';
scrollView.style.flexDirection = 'column';
scrollView.style.alignItems = 'center';
scrollView.style.overflowX = 'hidden';
scrollView.id = ('__scrollview');
const scrollViewWrap = document.createElement('div');
scrollViewWrap.id = '__scrollviewWrap';
scrollViewWrap.style.overflow = 'auto';
scrollViewWrap.appendChild(scrollView);
function scrollViewToolbarFactory() {
const scrollViewToolbar = document.createElement('div');
scrollViewToolbar.style.display = 'flex';
scrollViewToolbar.style.justifyContent = 'center';
scrollViewToolbar.style.alignItems = 'center';
scrollViewToolbar.style.width = "100%";
scrollViewToolbar.style.height = "100px";
scrollViewToolbar.innerHTML = `
<div class="__toPrev" style="font-size:20px;cursor:pointer" >上一话</div>
<div style="width:50px"> </div>
<div class="__toNext" style="font-size:20px;cursor:pointer" >下一话</div>
`;
scrollViewToolbar.querySelector('.__toPrev').addEventListener('click', () => {
if (window.location.hostname.indexOf('manhuagui') !== -1) {
SMH.prevC();
}
else if (window.location.hostname.indexOf('manhuabika') !== -1) {
chapterJump('prev');
}
});
scrollViewToolbar.querySelector('.__toNext').addEventListener('click', () => {
if (window.location.hostname.indexOf('manhuagui') !== -1) {
SMH.nextC();
}
else if (window.location.hostname.indexOf('manhuabika') !== -1) {
chapterJump('next');
}
});
return scrollViewToolbar;
}
scrollViewWrap.appendChild(scrollViewToolbarFactory());
scrollViewWrap.insertBefore(scrollViewToolbarFactory(), scrollViewWrap.firstChild);
root.appendChild(scrollViewWrap);
const singleView = document.createElement('div');
singleView.id = singleViewId;
singleView.style.width = '100%';
singleView.style.height = '100%';
singleView.style.display = 'flex';
singleView.style.justifyContent = 'center';
const singleViewImg1 = document.createElement('img');
const singleViewImg1Id = '__singleViewImg1';
singleViewImg1.style.height = window.innerHeight + 'px';
singleViewImg1.style.userSelect = 'none';
singleViewImg1.dataset.skip = 'true';
singleViewImg1.id = singleViewImg1Id;
singleView.appendChild(singleViewImg1);
let readModeCurrentIndex = 0;
function jump(step) {
console.log(exports.imgList);
const currentImgObj = exports.imgList.find(o => o.index === readModeCurrentIndex + step);
if (currentImgObj) {
singleViewImg1.src = currentImgObj.src ? currentImgObj.src : currentImgObj.oSrc;
readModeCurrentIndex = currentImgObj.index;
}
}
singleView.addEventListener('click', function (event) {
var mouseX = event.clientX;
var screenWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
if (mouseX < screenWidth / 2) {
console.log("鼠标位于屏幕左侧");
jump(-1);
}
else {
console.log("鼠标位于屏幕右侧");
jump(1);
}
});
root.appendChild(singleView);
const quitBtn = document.createElement('div');
quitBtn.innerHTML = '退出阅读模式';
quitBtn.style.position = 'fixed';
quitBtn.style.right = '20px';
quitBtn.style.bottom = '20px';
quitBtn.style.cursor = 'pointer';
quitBtn.addEventListener('click', quitReadMode);
root.appendChild(quitBtn);
function enterReadMode(mode = 0) {
oScrollPosition = document.documentElement.scrollTop || document.body.scrollTop;
root.style.display = 'block';
readModeTimer = setInterval(function () {
scrollDown();
}, 100);
enterReadModeBtn.style.visibility = 'hidden';
document.documentElement.style.overflow = 'hidden';
switch (mode) {
case 1:
scrollView.style.display = 'none';
singleView.style.display = 'flex';
root.className = '__single';
break;
default:
scrollView.style.display = 'flex';
singleView.style.display = 'none';
root.className = '__scroll';
}
}
exports.enterReadMode = enterReadMode;
function quitReadMode() {
root.className = '';
document.documentElement.style.overflow = 'auto';
root.style.display = 'none';
clearInterval(readModeTimer);
enterReadModeBtn.style.visibility = 'visible';
document.documentElement.scrollTop = oScrollPosition;
document.body.scrollTop = oScrollPosition;
}
const enterReadModeBtn = document.createElement('div');
enterReadModeBtn.style.backgroundColor = 'white';
enterReadModeBtn.style.position = 'fixed';
enterReadModeBtn.style.padding = '5px 10px';
enterReadModeBtn.style.right = '10px';
enterReadModeBtn.style.bottom = '175px';
enterReadModeBtn.style.border = '1px solid #000';
enterReadModeBtn.style.cursor = 'pointer';
enterReadModeBtn.innerHTML = '进入阅读模式';
enterReadModeBtn.addEventListener('click', () => { enterReadMode(0); });
function initReadMode() {
document.body.appendChild(enterReadModeBtn);
document.body.appendChild(root);
}
exports["default"] = initReadMode;
function addImgToReadMode(img) {
const imgObj = { oSrc: img.src, src: ``, index: parseInt(img.dataset.index) };
exports.imgList.push(imgObj);
if (!singleViewImg1.src) {
singleViewImg1.src = imgObj.oSrc;
}
const scrollView = document.getElementById(scrollViewId);
const imgContainer = document.createElement('div');
imgContainer.style.position = 'relative';
imgContainer.style.width = img.dataset.width;
imgContainer.style.maxWidth = '100%';
imgContainer.className = 'img-container';
imgContainer.dataset.index = `${imgObj.index}`;
const copyImg = img.cloneNode();
copyImg.style.width = '100%';
copyImg.dataset.skip = 'true';
imgContainer.appendChild(copyImg);
const pageTag = document.createElement('div');
pageTag.innerHTML = `第 ${imgObj.index + 1} 页`;
pageTag.style.position = 'absolute';
pageTag.style.right = '-60px';
pageTag.style.bottom = '0px';
imgContainer.appendChild(pageTag);
scrollView.appendChild(imgContainer);
const elements = Array.from(scrollView.getElementsByClassName('img-container'));
elements.sort((a, b) => {
const indexA = parseInt(a.dataset.index);
const indexB = parseInt(b.dataset.index);
if (indexA < indexB) {
return -1;
}
else if (indexA > indexB) {
return 1;
}
else {
return 0;
}
});
elements.forEach(element => {
scrollView.appendChild(element);
});
}
exports.addImgToReadMode = addImgToReadMode;
function replaceReadModeImg(url, oldUrl) {
const readModeImgs = Array.from(root.getElementsByTagName('img'));
readModeImgs.forEach(function (rImg) {
if (rImg.src === oldUrl) {
rImg.src = url;
}
});
const imgObj = exports.imgList.find(o => {
o.oSrc === oldUrl;
});
if (imgObj) {
imgObj.oSrc = url;
}
}
exports.replaceReadModeImg = replaceReadModeImg;
/***/ }),
/***/ 494:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
let GM_config = (function (GM) {
// This is the initializer function
function GM_configInit(config, args) {
// Initialize instance variables
if (typeof config.fields == "undefined") {
config.fields = {};
config.onInit = config.onInit || function () {};
config.onOpen = config.onOpen || function () {};
config.onSave = config.onSave || function () {};
config.onClose = config.onClose || function () {};
config.onReset = config.onReset || function () {};
config.isOpen = false;
config.title = "User Script Settings";
config.css = {
basic:
[
"#GM_config * { font-family: arial,tahoma,myriad pro,sans-serif; }",
"#GM_config { background: #FFF; }",
"#GM_config input[type='radio'] { margin-right: 8px; }",
"#GM_config .indent40 { margin-left: 40%; }",
"#GM_config .field_label { font-size: 12px; font-weight: bold; margin-right: 6px; }",
"#GM_config .radio_label { font-size: 12px; }",
"#GM_config .block { display: block; }",
"#GM_config .saveclose_buttons { margin: 16px 10px 10px; padding: 2px 12px; }",
"#GM_config .reset, #GM_config .reset a," +
" #GM_config_buttons_holder { color: #000; text-align: right; }",
"#GM_config .config_header { font-size: 20pt; margin: 0; }",
"#GM_config .config_desc, #GM_config .section_desc, #GM_config .reset { font-size: 9pt; }",
"#GM_config .center { text-align: center; }",
"#GM_config .section_header_holder { margin-top: 8px; }",
"#GM_config .config_var { margin: 0 0 4px; }",
"#GM_config .section_header { background: #414141; border: 1px solid #000; color: #FFF;",
" font-size: 13pt; margin: 0; }",
"#GM_config .section_desc { background: #EFEFEF; border: 1px solid #CCC; color: #575757;" +
" font-size: 9pt; margin: 0 0 6px; }",
].join("\n") + "\n",
basicPrefix: "GM_config",
stylish: "",
};
}
config.frameStyle = [
"bottom: auto; border: 1px solid #000; display: none; height: 75%;",
"left: 0; margin: 0; max-height: 95%; max-width: 95%; opacity: 0;",
"overflow: auto; padding: 0; position: fixed; right: auto; top: 0;",
"width: 75%; z-index: 9999;",
].join(" ");
var settings = null;
if (
args.length == 1 &&
typeof args[0].id == "string" &&
typeof args[0].appendChild != "function"
)
settings = args[0];
else {
// Provide backwards-compatibility with argument style intialization
settings = {};
// loop through GM_config.init() arguments
for (let i = 0, l = args.length, arg; i < l; ++i) {
arg = args[i];
// An element to use as the config window
if (typeof arg.appendChild == "function") {
settings.frame = arg;
continue;
}
switch (typeof arg) {
case "object":
for (let j in arg) {
// could be a callback functions or settings object
if (typeof arg[j] != "function") {
// we are in the settings object
settings.fields = arg; // store settings object
break; // leave the loop
} // otherwise it must be a callback function
if (!settings.events) settings.events = {};
settings.events[j] = arg[j];
}
break;
case "function": // passing a bare function is set to open callback
settings.events = { onOpen: arg };
break;
case "string": // could be custom CSS or the title string
if (/\w+\s*\{\s*\w+\s*:\s*\w+[\s|\S]*\}/.test(arg))
settings.css = arg;
else settings.title = arg;
break;
}
}
}
/* Initialize everything using the new settings object */
// Set the id
if (settings.id) config.id = settings.id;
else if (typeof config.id == "undefined") config.id = "GM_config";
// Set the title
if (settings.title) config.title = settings.title;
// Set the custom css
if (settings.css) config.css.stylish = settings.css;
// Set the frame
if (settings.frame) config.frame = settings.frame;
// Set the style attribute of the frame
if (typeof settings.frameStyle === "string")
config.frameStyle = settings.frameStyle;
// Set the event callbacks
if (settings.events) {
let events = settings.events;
for (let e in events) {
config["on" + e.charAt(0).toUpperCase() + e.slice(1)] = events[e];
}
}
// If the id has changed we must modify the default style
if (config.id != config.css.basicPrefix) {
config.css.basic = config.css.basic.replace(
new RegExp("#" + config.css.basicPrefix, "gm"),
"#" + config.id
);
config.css.basicPrefix = config.id;
}
// Create the fields
config.isInit = false;
if (settings.fields) {
config.read(null, (stored) => {
// read the stored settings
let fields = settings.fields,
customTypes = settings.types || {},
configId = config.id;
for (let id in fields) {
let field = fields[id],
fieldExists = false;
if (config.fields[id]) {
fieldExists = true;
}
// for each field definition create a field object
if (field) {
if (config.isOpen && fieldExists) {
config.fields[id].remove();
}
config.fields[id] = new GM_configField(
field,
stored[id],
id,
customTypes[field.type],
configId
);
// Add field to open frame
if (config.isOpen) {
config.fields[id].wrapper = config.fields[id].toNode();
config.frameSection.appendChild(config.fields[id].wrapper);
}
} else if (!field && fieldExists) {
// Remove field from open frame
if (config.isOpen) {
config.fields[id].remove();
}
delete config.fields[id];
}
}
config.isInit = true;
config.onInit.call(config);
});
} else {
config.isInit = true;
config.onInit.call(config);
}
}
let construct = function () {
// Parsing of input provided via frontends
GM_configInit(this, arguments);
};
construct.prototype = {
// Support re-initalization
init: function () {
GM_configInit(this, arguments);
},
// call GM_config.open() from your script to open the menu
open: function () {
// don't open before init is finished
if (!this.isInit) {
setTimeout(() => this.open(), 0);
return;
}
// Die if the menu is already open on this page
// You can have multiple instances but you can't open the same instance twice
let match = document.getElementById(this.id);
if (match && (match.tagName == "IFRAME" || match.childNodes.length > 0))
return;
// Sometimes "this" gets overwritten so create an alias
let config = this;
// Function to build the mighty config window :)
function buildConfigWin(body, head) {
let create = config.create,
fields = config.fields,
configId = config.id,
bodyWrapper = create("div", { id: configId + "_wrapper" });
// Append the style which is our default style plus the user style
head.appendChild(
create("style", {
type: "text/css",
textContent: config.css.basic + config.css.stylish,
})
);
// Add header and title
bodyWrapper.appendChild(
create(
"div",
{
id: configId + "_header",
className: "config_header block center",
},
config.title
)
);
// Append elements
let section = bodyWrapper,
secNum = 0; // Section count
// loop through fields
for (let id in fields) {
let field = fields[id],
settings = field.settings;
if (settings.section) {
// the start of a new section
section = bodyWrapper.appendChild(
create("div", {
className: "section_header_holder",
id: configId + "_section_" + secNum,
})
);
if (!Array.isArray(settings.section))
settings.section = [settings.section];
if (settings.section[0])
section.appendChild(
create(
"div",
{
className: "section_header center",
id: configId + "_section_header_" + secNum,
},
settings.section[0]
)
);
if (settings.section[1])
section.appendChild(
create(
"p",
{
className: "section_desc center",
id: configId + "_section_desc_" + secNum,
},
settings.section[1]
)
);
++secNum;
}
if (secNum === 0) {
section = bodyWrapper.appendChild(
create("div", {
className: "section_header_holder",
id: configId + "_section_" + secNum++,
})
);
}
// Create field elements and append to current section
section.appendChild((field.wrapper = field.toNode()));
}
config.frameSection = section;
// Add save and close buttons
bodyWrapper.appendChild(
create(
"div",
{ id: configId + "_buttons_holder" },
create("button", {
id: configId + "_saveBtn",
textContent: "Save",
title: "Save settings",
className: "saveclose_buttons",
onclick: function () {
config.save();
},
}),
create("button", {
id: configId + "_closeBtn",
textContent: "Close",
title: "Close window",
className: "saveclose_buttons",
onclick: function () {
config.close();
},
}),
create(
"div",
{ className: "reset_holder block" },
// Reset link
create("a", {
id: configId + "_resetLink",
textContent: "Reset to defaults",
href: "#",
title: "Reset fields to default values",
className: "reset",
onclick: function (e) {
e.preventDefault();
config.reset();
},
})
)
)
);
body.appendChild(bodyWrapper); // Paint everything to window at once
config.center(); // Show and center iframe
window.addEventListener("resize", config.center, false); // Center frame on resize
// Call the open() callback function
config.onOpen(
config.frame.contentDocument || config.frame.ownerDocument,
config.frame.contentWindow || window,
config.frame
);
// Close frame on window close
window.addEventListener(
"beforeunload",
function () {
config.close();
},
false
);
// Now that everything is loaded, make it visible
config.frame.style.display = "block";
config.isOpen = true;
}
// Either use the element passed to init() or create an iframe
if (this.frame) {
this.frame.id = this.id; // Allows for prefixing styles with the config id
if (this.frameStyle)
this.frame.setAttribute("style", this.frameStyle);
buildConfigWin(
this.frame,
this.frame.ownerDocument.getElementsByTagName("head")[0]
);
} else {
// Create frame
this.frame = this.create("iframe", { id: this.id });
if (this.frameStyle)
this.frame.setAttribute("style", this.frameStyle);
document.body.appendChild(this.frame);
// In WebKit src can't be set until it is added to the page
this.frame.src = "";
// we wait for the iframe to load before we can modify it
let that = this;
this.frame.addEventListener(
"load",
function (e) {
let frame = config.frame;
if (!frame.contentDocument) {
that.log(
"GM_config failed to initialize default settings dialog node!"
);
} else {
let body =
frame.contentDocument.getElementsByTagName("body")[0];
body.id = config.id; // Allows for prefixing styles with the config id
buildConfigWin(
body,
frame.contentDocument.getElementsByTagName("head")[0]
);
}
},
false
);
}
},
save: function () {
this.write(null, null, (vals) => this.onSave(vals));
},
close: function () {
// If frame is an iframe then remove it
if (this.frame && this.frame.contentDocument) {
this.remove(this.frame);
this.frame = null;
} else if (this.frame) {
// else wipe its content
this.frame.innerHTML = "";
this.frame.style.display = "none";
}
// Null out all the fields so we don't leak memory
let fields = this.fields;
for (let id in fields) {
let field = fields[id];
field.wrapper = null;
field.node = null;
}
this.onClose(); // Call the close() callback function
this.isOpen = false;
},
set: function (name, val) {
this.fields[name].value = val;
if (this.fields[name].node) {
this.fields[name].reload();
}
},
get: function (name, getLive) {
/* Migration warning */
if (!this.isInit) {
this.log(
"GM_config: get called before init, see https://github.com/sizzlemctwizzle/GM_config/issues/113"
);
}
let field = this.fields[name],
fieldVal = null;
if (getLive && field.node) {
fieldVal = field.toValue();
}
return fieldVal != null ? fieldVal : field.value;
},
write: function (store, obj, cb) {
let forgotten = null,
values = null;
if (!obj) {
let fields = this.fields;
values = {};
forgotten = {};
for (let id in fields) {
let field = fields[id];
let value = field.toValue();
if (field.save) {
if (value != null) {
values[id] = value;
field.value = value;
} else values[id] = field.value;
} else forgotten[id] = value != null ? value : field.value;
}
}
(async () => {
try {
let val = this.stringify(obj || values);
await this.setValue(store || this.id, val);
} catch (e) {
this.log("GM_config failed to save settings!");
}
cb(forgotten);
})();
},
read: function (store, cb) {
(async () => {
let val = await this.getValue(store || this.id, "{}");
try {
let rval = this.parser(val);
cb(rval);
} catch (e) {
this.log("GM_config failed to read saved settings!");
cb({});
}
})();
},
reset: function () {
let fields = this.fields;
// Reset all the fields
for (let id in fields) {
fields[id].reset();
}
this.onReset(); // Call the reset() callback function
},
create: function () {
let A = null,
B = null;
switch (arguments.length) {
case 1:
A = document.createTextNode(arguments[0]);
break;
default:
A = document.createElement(arguments[0]);
B = arguments[1];
for (let b in B) {
if (b.indexOf("on") == 0)
A.addEventListener(b.substring(2), B[b], false);
else if (
",style,accesskey,id,name,src,href,which,for".indexOf(
"," + b.toLowerCase()
) != -1
)
A.setAttribute(b, B[b]);
else A[b] = B[b];
}
if (typeof arguments[2] == "string") A.innerHTML = arguments[2];
else
for (let i = 2, len = arguments.length; i < len; ++i)
A.appendChild(arguments[i]);
}
return A;
},
center: function () {
let node = this.frame;
if (!node) return;
let style = node.style,
beforeOpacity = style.opacity;
if (style.display == "none") style.opacity = "0";
style.display = "";
style.top =
Math.floor(window.innerHeight / 2 - node.offsetHeight / 2) + "px";
style.left =
Math.floor(window.innerWidth / 2 - node.offsetWidth / 2) + "px";
style.opacity = "1";
},
remove: function (el) {
if (el && el.parentNode) el.parentNode.removeChild(el);
},
};
construct.prototype.name = "GM_config";
construct.prototype.constructor = construct;
let isGM4 =
typeof GM.getValue !== "undefined" && typeof GM.setValue !== "undefined";
let isGM =
isGM4 ||
(typeof GM_getValue !== "undefined" &&
typeof GM_getValue("a", "b") !== "undefined");
construct.prototype.isGM = isGM;
if (!isGM4) {
let promisify =
(old) =>
(...args) => {
return new Promise((resolve, reject) => {
try {
resolve(old.apply(this, args));
} catch (e) {
reject(e);
}
});
};
let getValue = isGM
? GM_getValue
: (name, def) => {
let s = localStorage.getItem(name);
return s !== null ? s : def;
};
let setValue = isGM
? GM_setValue
: (name, value) => localStorage.setItem(name, value);
let log = typeof GM_log !== "undefined" ? GM_log : console.log;
GM.getValue = promisify(getValue);
GM.setValue = promisify(setValue);
GM.log = promisify(log);
}
construct.prototype.stringify = JSON.stringify;
construct.prototype.parser = JSON.parse;
construct.prototype.getValue = GM.getValue;
construct.prototype.setValue = GM.setValue;
construct.prototype.log = GM.log || console.log;
// Passthrough frontends for new and old usage
let config = function () {
return new (config.bind.apply(
construct,
[null].concat(Array.from(arguments))
))();
};
config.prototype.constructor = config;
// Support old method of initalizing
config.init = function () {
GM_config = config.apply(this, arguments);
GM_config.init = function () {
GM_configInit(this, arguments);
};
};
// Reusable functions and properties
// Usable via GM_config.*
config.create = construct.prototype.create;
config.isGM = construct.prototype.isGM;
config.setValue = construct.prototype.setValue;
config.getValue = construct.prototype.getValue;
config.stringify = construct.prototype.stringify;
config.parser = construct.prototype.parser;
config.log = construct.prototype.log;
config.remove = construct.prototype.remove;
config.read = construct.prototype.read.bind(config);
config.write = construct.prototype.write.bind(config);
return config;
})(typeof GM === "object" ? GM : Object.create(null));
let GM_configStruct = (/* unused pure expression or super */ null && (GM_config));
function GM_configField(settings, stored, id, customType, configId) {
// Store the field's settings
this.settings = settings;
this.id = id;
this.configId = configId;
this.node = null;
this.wrapper = null;
this.save = typeof settings.save == "undefined" ? true : settings.save;
// Buttons are static and don't have a stored value
if (settings.type == "button") this.save = false;
// if a default value wasn't passed through init() then
// if the type is custom use its default value
// else use default value for type
// else use the default value passed through init()
this["default"] =
typeof settings["default"] == "undefined"
? customType
? customType["default"]
: this.defaultValue(settings.type, settings.options)
: settings["default"];
// Store the field's value
this.value = typeof stored == "undefined" ? this["default"] : stored;
// Setup methods for a custom type
if (customType) {
this.toNode = customType.toNode;
this.toValue = customType.toValue;
this.reset = customType.reset;
}
}
GM_configField.prototype = {
create: GM_config.create,
defaultValue: function (type, options) {
let value;
if (type.indexOf("unsigned ") == 0) type = type.substring(9);
switch (type) {
case "radio":
case "select":
value = options[0];
break;
case "checkbox":
value = false;
break;
case "int":
case "integer":
case "float":
case "number":
value = 0;
break;
default:
value = "";
}
return value;
},
toNode: function () {
let field = this.settings,
value = this.value,
options = field.options,
type = field.type,
id = this.id,
configId = this.configId,
labelPos = field.labelPos,
create = this.create;
function addLabel(pos, labelEl, parentNode, beforeEl) {
if (!beforeEl) beforeEl = parentNode.firstChild;
switch (pos) {
case "right":
case "below":
if (pos == "below") parentNode.appendChild(create("br", {}));
parentNode.appendChild(labelEl);
break;
default:
if (pos == "above")
parentNode.insertBefore(create("br", {}), beforeEl);
parentNode.insertBefore(labelEl, beforeEl);
}
}
let retNode = create("div", {
className: "config_var",
id: configId + "_" + id + "_var",
title: field.title || "",
}),
firstProp;
// Retrieve the first prop
for (let i in field) {
firstProp = i;
break;
}
let label =
field.label && type != "button"
? create(
"label",
{
id: configId + "_" + id + "_field_label",
for: configId + "_field_" + id,
className: "field_label",
},
field.label
)
: null;
let wrap = null;
switch (type) {
case "textarea":
retNode.appendChild(
(this.node = create("textarea", {
innerHTML: value,
id: configId + "_field_" + id,
className: "block",
cols: field.cols ? field.cols : 20,
rows: field.rows ? field.rows : 2,
}))
);
break;
case "radio":
wrap = create("div", {
id: configId + "_field_" + id,
});
this.node = wrap;
for (let i = 0, len = options.length; i < len; ++i) {
let radLabel = create(
"label",
{
className: "radio_label",
},
options[i]
);
let rad = wrap.appendChild(
create("input", {
value: options[i],
type: "radio",
name: id,
checked: options[i] == value,
})
);
let radLabelPos =
labelPos && (labelPos == "left" || labelPos == "right")
? labelPos
: firstProp == "options"
? "left"
: "right";
addLabel(radLabelPos, radLabel, wrap, rad);
}
retNode.appendChild(wrap);
break;
case "select":
wrap = create("select", {
id: configId + "_field_" + id,
});
this.node = wrap;
for (let i = 0, len = options.length; i < len; ++i) {
let option = options[i];
wrap.appendChild(
create(
"option",
{
value: option,
selected: option == value,
},
option
)
);
}
retNode.appendChild(wrap);
break;
default: // fields using input elements
let props = {
id: configId + "_field_" + id,
type: type,
value: type == "button" ? field.label : value,
};
switch (type) {
case "checkbox":
props.checked = value;
break;
case "button":
props.size = field.size ? field.size : 25;
if (field.script) field.click = field.script;
if (field.click) props.onclick = field.click;
break;
case "hidden":
break;
default:
// type = text, int, or float
props.type = "text";
props.size = field.size ? field.size : 25;
}
retNode.appendChild((this.node = create("input", props)));
}
if (label) {
// If the label is passed first, insert it before the field
// else insert it after
if (!labelPos)
labelPos = firstProp == "label" || type == "radio" ? "left" : "right";
addLabel(labelPos, label, retNode);
}
return retNode;
},
toValue: function () {
let node = this.node,
field = this.settings,
type = field.type,
unsigned = false,
rval = null;
if (!node) return rval;
if (type.indexOf("unsigned ") == 0) {
type = type.substring(9);
unsigned = true;
}
switch (type) {
case "checkbox":
rval = node.checked;
break;
case "select":
rval = node[node.selectedIndex].value;
break;
case "radio":
let radios = node.getElementsByTagName("input");
for (let i = 0, len = radios.length; i < len; ++i) {
if (radios[i].checked) rval = radios[i].value;
}
break;
case "button":
break;
case "int":
case "integer":
case "float":
case "number":
let num = Number(node.value);
let warn =
'Field labeled "' +
field.label +
'" expects a' +
(unsigned ? " positive " : "n ") +
"integer value";
if (
isNaN(num) ||
(type.substr(0, 3) == "int" && Math.ceil(num) != Math.floor(num)) ||
(unsigned && num < 0)
) {
alert(warn + ".");
return null;
}
if (!this._checkNumberRange(num, warn)) return null;
rval = num;
break;
default:
rval = node.value;
break;
}
return rval; // value read successfully
},
reset: function () {
let node = this.node,
field = this.settings,
type = field.type;
if (!node) return;
switch (type) {
case "checkbox":
node.checked = this["default"];
break;
case "select":
for (let i = 0, len = node.options.length; i < len; ++i) {
if (node.options[i].textContent == this["default"])
node.selectedIndex = i;
}
break;
case "radio":
let radios = node.getElementsByTagName("input");
for (let i = 0, len = radios.length; i < len; ++i) {
if (radios[i].value == this["default"]) radios[i].checked = true;
}
break;
case "button":
break;
default:
node.value = this["default"];
break;
}
},
remove: function () {
GM_config.remove(this.wrapper);
this.wrapper = null;
this.node = null;
},
reload: function () {
let wrapper = this.wrapper;
if (wrapper) {
let fieldParent = wrapper.parentNode;
let newWrapper = this.toNode();
fieldParent.insertBefore(newWrapper, wrapper);
GM_config.remove(this.wrapper);
this.wrapper = newWrapper;
}
},
_checkNumberRange: function (num, warn) {
let field = this.settings;
if (typeof field.min == "number" && num < field.min) {
alert(warn + " greater than or equal to " + field.min + ".");
return null;
}
if (typeof field.max == "number" && num > field.max) {
alert(warn + " less than or equal to " + field.max + ".");
return null;
}
return true;
},
};
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (GM_config);
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module is referenced by other modules so it can't be inlined
/******/ var __webpack_exports__ = __webpack_require__(607);
/******/
/******/ })()
;