为轻小说文库++提供图床支持
Version vom
Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/451075/1091835/imager.js
/* eslint-disable no-multi-spaces */
/* eslint-disable no-implicit-globals */
/* eslint-disable userscripts/no-invalid-headers */
/* eslint-disable userscripts/no-invalid-grant */
// ==UserScript==
// @name imager
// @displayname 图床
// @namespace Wenku8++
// @version 0.1.7
// @description 为轻小说文库++提供图床支持
// @author PY-DNG
// @license GPL-v3
// @regurl https?://www\.wenku8\.net/.*
// @require https://greasyfork.org/scripts/449412-basic-functions/code/Basic%20Functions.js?version=1085783
// @require https://greasyfork.org/scripts/449583-configmanager/code/ConfigManager.js?version=1085836
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function __MAIN__() {
const DATA_IMAGERS = {
default: 'SDAIDEV',
/* Imager Model
_IMAGER_KEY_: {
available: true,
name: '_IMAGER_DISPLAY_NAME_',
tip: '_IMAGER_DISPLAY_TIP_',
upload: {
request: {
url: '_UPLOAD_URL_',
data: {
'_FORM_NAME_FOR_FILE_': '$file$'
}
},
response: {
checksuccess: (json)=>{return json._SUCCESS_KEY_ === '_SUCCESS_VALUE_';},
geturl: (json)=>{return json._PATH_._SUCCESS_URL_KEY_;},
getname: (json)=>{return json._PATH_ ? json._PATH_._FILENAME_ : null;},
getsize: (json)=>{return json._PATH_._SIZE_},
getpage: (json)=>{return json._PATH_ ? json._PATH_._PAGE_ : null;},
gethash: (json)=>{return json._PATH_ ? json._PATH_._HASH_ : null;},
getdelete: (json)=>{return json._PATH_ ? json._PATH_._DELETE_ : null;}
}
},
isImager: true
},
*/
PANDAIMG: {
available: true,
name: '熊猫图床',
tip: '2022-01-16测试可用</br>单张图片最大5MB',
upload: {
request: {
url: 'https://api.pandaimg.com/upload',
data: {
'file': '$file$',
'classifications': '',
'day': '0'
},
headers: {
'usersOrigin': '5edd88d4dfe5d288518c0454d3ccdd2a'
}
},
response: {
checksuccess: (json)=>{return json.code === '200';},
geturl: (json)=>{return json.data.url;},
getname: (json)=>{return json.data.name;}
}
},
isImager: true
},
SDAIDEV: {
available: true,
name: '流浪图床',
tip: '2022-01-09测试可用</br>单张图片最大5MB',
upload: {
request: {
url: 'https://p.sda1.dev/api/v1/upload_external_noform',
urlargs: {
'filename': '$filename$',
'ts': '$time$',
'rand': '$random$'
}
},
response: {
checksuccess: (json)=>{return json.success;},
geturl: (json)=>{return json.data.url;},
getdelete: (json)=>{return json.data ? json.data.delete_url : null;},
getsize: (json)=>{return json.data ? json.data.size : null;}
}
},
isImager: true
},
JITUDISK: {
available: true,
name: '极兔兔床',
tip: '2022-02-02测试可用',
upload: {
request: {
url: 'https://pic.jitudisk.com/api/upload',
data: {
'image': '$file$'
}
},
response: {
checksuccess: (json)=>{return json.code === 200;},
geturl: (json)=>{return json.data.url;},
getname: (json)=>{return json.data.name;}
}
},
isImager: true
},
SMMS: {
available: true,
name: 'SM.MS',
tip: '注意:此图床跨域访问较不稳定,且有用户反映其被国内部分服务商屏蔽,请谨慎使用此图床',
warning: '注意:此图床跨域访问较不稳定,且有用户反映其被国内部分服务商屏蔽,请谨慎使用此图床</br>如出现上传错误/图片加载慢/无法加载图片等情况,请更换其他图床',
upload: {
request: {
url: 'https://sm.ms/api/v2/upload?inajax=1',
data: {
'smfile': '$file$'
}
},
response: {
checksuccess: (json)=>{return json.success === true || /^https?:\/\//.test(json.images);},
geturl: (json)=>{return json.data ? json.data.url : json.images;},
getname: (json)=>{return json.data ? json.data.filename : null;},
getpage: (json)=>{return json.data ? json.data.page : null;},
gethash: (json)=>{return json.data ? json.data.hash : null;},
getdelete: (json)=>{return json.data ? json.data.delete : null;}
}
},
isImager: true
},
CATBOX: {
available: true,
name: 'CatBox',
tip: '注意:此图床访问较不稳定,请谨慎使用此图床',
warning: '注意:此图床访问较不稳定,请谨慎使用此图床</br>如出现上传错误/图片加载慢/无法加载图片等情况,请更换其他图床',
upload: {
request: {
url: 'https://catbox.moe/user/api.php',
responseType: 'text',
data: {
'fileToUpload': '$file$',
'reqtype': 'fileupload'
}
},
response: {
checksuccess: (text)=>{return true;},
geturl: (text)=>{return text;}
}
},
isImager: true
}
};
const CONST = {
Text: {
InputImage: '选择/粘贴/拖拽 上传图片',
UploadError: '上传错误!',
NoNameFromSever: '空(服务器没有返回文件名)',
},
Config_Ruleset: {
'version-key': 'config-version',
'ignores': ["LOCAL-CDN"],
'defaultValues': {
imager: 'SDAIDEV'
}
}
};
const CM = new ConfigManager(CONST.Config_Ruleset);
const CONFIG = CM.Config;
const settings = require('settings');
const SettingPanel = require('SettingPanel');
SettingPanel.registerElement('image', {
createElement: function() {
const SO = this;
const data = SO.hasOwnProperty('data') ? SO.data : {};
const file = $CrE('input');
file.type = 'file';
file.addEventListener('change', fileGot);
const div = $CrE('div');
div.innerHTML = data.hasOwnProperty('innerText') ? data.innerHTML : '';
div.innerHTML = data.hasOwnProperty('innerHTML') ? data.innerHTML : CONST.Text.InputImage;
div.style.color = data.hasOwnProperty('textColor') ? data.textColor : 'grey';
div.style.width = div.style.height = '100%';
div.style.border = div.style.padding = div.style.margin = '0';
data.hasOwnProperty('innerText') && (div.innerText = data.innerText);
data.hasOwnProperty('innerHTML') && (div.innerHTML = data.innerHTML);
div.addEventListener('click', file.click.bind(file));
div.addEventListener('paste', fileGot);
div.addEventListener('dragenter', destroyEvent);
div.addEventListener('dragover', destroyEvent);
div.addEventListener('drop', fileGot);
return div;
function fileGot(e) {
SO.file = fileEvent(e);
if (!SO.file) {return false;}
this.name = SO.file.name;
uploadImage({
file: SO.file,
type: CONFIG.imager,
onload: function(e) {
copyProps(e, SO, Object.keys(e));
div.dispatchEvent(new Event('change'));
},
});
}
},
setValue: function(arg) {
const SO = this;
if (arg instanceof File) {
this.file = arg;
this.name = arg.name;
delete this.url;
uploadImage({
file: SO.file,
type: CONFIG.imager,
onload: function(e) {
copyProps(e, SO, Object.keys(e));
},
});
}
if (typeof arg === 'string') {
this.url = arg;
delete this.name;
delete this.file;
}
},
getValue: function() {return this.url},
});
function fileEvent(e) {
const input = e.dataTransfer || e.clipboardData || window.clipboardData || e.target;
if (!input.files || input.files.length === 0) {return false;};
for (const file of input.files) {
const splited = file.name.split('.');
const ext = splited[splited.length-1].toLowerCase();
const extOkay = ['jpg', 'jpeg', 'png', 'webp'].includes(ext);
const mimeOkay = ['image/bmp', 'image/gif', 'image/vnd.microsoft.icon', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/tiff', 'image/webp'].includes(file.type)
if (extOkay || mimeOkay) {
return file;
}
}
return null;
}
// Upload image to KIENG images
// details: {file: File, onload: Function({url, name, json}), onerror: Function, type: 'sm.ms/jd/sg/tt/...'}
function uploadImage(details) {
const file = details.file;
const onload = details.onload ? details.onload : function() {};
const onerror = details.onerror ? details.onerror : uploadError;
const type = details.imager ? details.imager : CONFIG.imager;
if (!DATA_IMAGERS.hasOwnProperty(type) || !DATA_IMAGERS[type].available) {
onerror();
return false;
}
const imager = DATA_IMAGERS[type];
const upload = imager.upload;
const request = upload.request;
const response = upload.response;
// Construct request url
let url = request.url;
if (request.urlargs) {
const args = request.urlargs;
const makearg = (key, value) => ('{K}={V}'.replace('{K}', key).replace('{V}', value));
const replacers = {
'$filename$': () => (encodeURIComponent(file.name)),
'$random$': () => (Math.random().toString()),
'$time$': () => ((new Date()).getTime().toString())
};
for (let [key, value] of Object.entries(args)) {
url += url.includes('?') ? '&' : '?';
for (const [str, replacer] of Object.entries(replacers)) {
while (value !== null && value.includes(str)) {
const val = replacer(key);
value = (val !== null) ? value.replace(str, val) : null;
}
}
(value !== null) && (url += makearg(key, value));
}
}
// Construst request body
let data;
if (request.data) {
data = new FormData();
const replacers = {
'$file$': (key) => ((data.append(key, file), null)),
'$random$': () => (Math.random().toString()),
'$time$': () => ((new Date()).getTime().toString())
};
for (let [key, value] of Object.entries(request.data)) {
for (const [str, replacer] of Object.entries(replacers)) {
while (value !== null && value.includes(str)) {
const val = replacer(key);
value = (val !== null) ? value.replace(str, val) : null;
}
}
(value !== null) && data.append(key, value);
}
} else {
data = file;
}
// headers
const headers = request.headers || {};
GM_xmlhttpRequest({
method: 'POST',
url: url,
timeout: 15 * 1000,
data: data,
headers: headers,
responseType: request.responseType ? request.responseType : 'json',
onerror: onerror,
ontimeout: onerror,
onabort: onerror,
onload: (e) => {
const json = e.response;
const success = e.status === 200 && response.checksuccess(json);
if (success) {
const url = response.geturl(json);
const name = response.getname ? (response.getname(json) ? response.getname(json) : CONST.Text.NoNameFromSever) : CONST.Text.NoNameFromSever
onload({
url: url,
name: name,
json: json
});
} else {
onerror(json);
return;
}
}
});
function uploadError(json) {
alertify.error(CONST.Text.UploadError);
DoLog(LogLevel.Error, [CONST.Text.UploadError, json]);
}
}
})();