imager

为轻小说文库++提供图床支持

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/451075/1092146/imager.js

  1. /* eslint-disable no-multi-spaces */
  2. /* eslint-disable no-implicit-globals */
  3. /* eslint-disable userscripts/no-invalid-headers */
  4. /* eslint-disable userscripts/no-invalid-grant */
  5.  
  6. // ==UserScript==
  7. // @name imager
  8. // @displayname 图床
  9. // @namespace Wenku8++
  10. // @version 0.2
  11. // @description 为轻小说文库++提供图床支持
  12. // @author PY-DNG
  13. // @license GPL-v3
  14. // @regurl https?://www\.wenku8\.net/.*
  15. // @require https://gf.qytechs.cn/scripts/449412-basic-functions/code/Basic%20Functions.js?version=1085783
  16. // @require https://gf.qytechs.cn/scripts/449583-configmanager/code/ConfigManager.js?version=1085836
  17. // @grant GM_getValue
  18. // @grant GM_setValue
  19. // @grant GM_listValues
  20. // @grant GM_deleteValue
  21. // @grant GM_xmlhttpRequest
  22. // ==/UserScript==
  23.  
  24. (function __MAIN__() {
  25. const DATA_IMAGERS = {
  26. default: 'SDAIDEV',
  27. /* Imager Model
  28. _IMAGER_KEY_: {
  29. available: true,
  30. name: '_IMAGER_DISPLAY_NAME_',
  31. tip: '_IMAGER_DISPLAY_TIP_',
  32. upload: {
  33. request: {
  34. url: '_UPLOAD_URL_',
  35. data: {
  36. '_FORM_NAME_FOR_FILE_': '$file$'
  37. }
  38. },
  39. response: {
  40. checksuccess: (json)=>{return json._SUCCESS_KEY_ === '_SUCCESS_VALUE_';},
  41. geturl: (json)=>{return json._PATH_._SUCCESS_URL_KEY_;},
  42. getname: (json)=>{return json._PATH_ ? json._PATH_._FILENAME_ : null;},
  43. getsize: (json)=>{return json._PATH_._SIZE_},
  44. getpage: (json)=>{return json._PATH_ ? json._PATH_._PAGE_ : null;},
  45. gethash: (json)=>{return json._PATH_ ? json._PATH_._HASH_ : null;},
  46. getdelete: (json)=>{return json._PATH_ ? json._PATH_._DELETE_ : null;}
  47. }
  48. },
  49. isImager: true
  50. },
  51. */
  52. PANDAIMG: {
  53. available: true,
  54. name: '熊猫图床',
  55. tip: '2022-01-16测试可用</br>单张图片最大5MB',
  56. upload: {
  57. request: {
  58. url: 'https://api.pandaimg.com/upload',
  59. data: {
  60. 'file': '$file$',
  61. 'classifications': '',
  62. 'day': '0'
  63. },
  64. headers: {
  65. 'usersOrigin': '5edd88d4dfe5d288518c0454d3ccdd2a'
  66. }
  67. },
  68. response: {
  69. checksuccess: (json)=>{return json.code === '200';},
  70. geturl: (json)=>{return json.data.url;},
  71. getname: (json)=>{return json.data.name;}
  72. }
  73. },
  74. isImager: true
  75. },
  76. SDAIDEV: {
  77. available: true,
  78. name: '流浪图床',
  79. tip: '2022-01-09测试可用</br>单张图片最大5MB',
  80. upload: {
  81. request: {
  82. url: 'https://p.sda1.dev/api/v1/upload_external_noform',
  83. urlargs: {
  84. 'filename': '$filename$',
  85. 'ts': '$time$',
  86. 'rand': '$random$'
  87. }
  88. },
  89. response: {
  90. checksuccess: (json)=>{return json.success;},
  91. geturl: (json)=>{return json.data.url;},
  92. getdelete: (json)=>{return json.data ? json.data.delete_url : null;},
  93. getsize: (json)=>{return json.data ? json.data.size : null;}
  94. }
  95. },
  96. isImager: true
  97. },
  98. JITUDISK: {
  99. available: true,
  100. name: '极兔兔床',
  101. tip: '2022-02-02测试可用',
  102. upload: {
  103. request: {
  104. url: 'https://pic.jitudisk.com/api/upload',
  105. data: {
  106. 'image': '$file$'
  107. }
  108. },
  109. response: {
  110. checksuccess: (json)=>{return json.code === 200;},
  111. geturl: (json)=>{return json.data.url;},
  112. getname: (json)=>{return json.data.name;}
  113. }
  114. },
  115. isImager: true
  116. },
  117. SMMS: {
  118. available: true,
  119. name: 'SM.MS',
  120. tip: '注意:此图床跨域访问较不稳定,且有用户反映其被国内部分服务商屏蔽,请谨慎使用此图床',
  121. warning: '注意:此图床跨域访问较不稳定,且有用户反映其被国内部分服务商屏蔽,请谨慎使用此图床</br>如出现上传错误/图片加载慢/无法加载图片等情况,请更换其他图床',
  122. upload: {
  123. request: {
  124. url: 'https://sm.ms/api/v2/upload?inajax=1',
  125. data: {
  126. 'smfile': '$file$'
  127. }
  128. },
  129. response: {
  130. checksuccess: (json)=>{return json.success === true || /^https?:\/\//.test(json.images);},
  131. geturl: (json)=>{return json.data ? json.data.url : json.images;},
  132. getname: (json)=>{return json.data ? json.data.filename : null;},
  133. getpage: (json)=>{return json.data ? json.data.page : null;},
  134. gethash: (json)=>{return json.data ? json.data.hash : null;},
  135. getdelete: (json)=>{return json.data ? json.data.delete : null;}
  136. }
  137. },
  138. isImager: true
  139. },
  140. CATBOX: {
  141. available: true,
  142. name: 'CatBox',
  143. tip: '注意:此图床访问较不稳定,请谨慎使用此图床',
  144. warning: '注意:此图床访问较不稳定,请谨慎使用此图床</br>如出现上传错误/图片加载慢/无法加载图片等情况,请更换其他图床',
  145. upload: {
  146. request: {
  147. url: 'https://catbox.moe/user/api.php',
  148. responseType: 'text',
  149. data: {
  150. 'fileToUpload': '$file$',
  151. 'reqtype': 'fileupload'
  152. }
  153. },
  154. response: {
  155. checksuccess: (text)=>{return true;},
  156. geturl: (text)=>{return text;}
  157. }
  158. },
  159. isImager: true
  160. }
  161. };
  162. const CONST = {
  163. Text: {
  164. CurImage: '当前图片:',
  165. InputImage: '选择/粘贴/拖拽 上传图片',
  166. InvalidFile: '您选择的文件不是可识别的图片:(</br>请选择.jpg/.jpeg/.png格式的图片',
  167. UploadError: '上传错误!',
  168. NoNameFromSever: '空(服务器没有返回文件名)',
  169. },
  170. Config_Ruleset: {
  171. 'version-key': 'config-version',
  172. 'ignores': ["LOCAL-CDN"],
  173. 'defaultValues': {
  174. imager: 'SDAIDEV'
  175. }
  176. }
  177. };
  178.  
  179. const CM = new ConfigManager(CONST.Config_Ruleset);
  180. const CONFIG = CM.Config;
  181. const alertify = require('alertify');
  182. const settings = require('settings');
  183. const SettingPanel = require('SettingPanel');
  184. SettingPanel.registerElement('image', {
  185. createElement: function() {
  186. const SO = this;
  187. const data = SO.hasOwnProperty('data') ? SO.data : {};
  188.  
  189. // <input type="file">
  190. const file = $CrE('input');
  191. file.type = 'file';
  192. file.addEventListener('change', fileGot);
  193.  
  194. // Displayer div
  195. const div = $CrE('div');
  196. div.innerText = CONST.Text.CurImage + SO.url + '\n' + CONST.Text.InputImage;
  197. div.style.color = data.hasOwnProperty('textColor') ? data.textColor : 'grey';
  198. div.style.width = div.style.height = '100%';
  199. div.style.border = div.style.padding = div.style.margin = '0';
  200. data.hasOwnProperty('innerText') && (div.innerText = data.innerText);
  201. data.hasOwnProperty('innerHTML') && (div.innerHTML = data.innerHTML);
  202. div.addEventListener('click', file.click.bind(file));
  203. div.addEventListener('paste', fileGot);
  204. div.addEventListener('dragenter', destroyEvent);
  205. div.addEventListener('dragover', destroyEvent);
  206. div.addEventListener('drop', fileGot);
  207. return div;
  208.  
  209. function fileGot(e) {
  210. const file = fileEvent(e);
  211. if (!file) {
  212. alertify.error(CONST.Text.InvalidFile);
  213. return false;
  214. }
  215. uploadImage({
  216. file: file,
  217. type: CONFIG.imager,
  218. onload: function(e) {
  219. copyProps(e, SO, Object.keys(e));
  220. div.innerText = CONST.Text.CurImage + SO.url + '\n' + CONST.Text.InputImage;
  221. div.dispatchEvent(new Event('change'));
  222. },
  223. });
  224. }
  225. },
  226. setValue: function(url) {
  227. this.url = url;
  228. this.element.innerText = CONST.Text.CurImage + url + '\n' + CONST.Text.InputImage;
  229. },
  230. getValue: function() {return this.url;},
  231. });
  232.  
  233. function fileEvent(e) {
  234. destroyEvent(e);
  235. const input = e.dataTransfer || e.clipboardData || window.clipboardData || e.target;
  236. if (!input.files || input.files.length === 0) {return false;};
  237.  
  238. for (const file of input.files) {
  239. const splited = file.name.split('.');
  240. const ext = splited[splited.length-1].toLowerCase();
  241. const extOkay = ['jpg', 'jpeg', 'png', 'webp'].includes(ext);
  242. const mimeOkay = ['image/bmp', 'image/gif', 'image/vnd.microsoft.icon', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/tiff', 'image/webp'].includes(file.type)
  243. if (extOkay || mimeOkay) {
  244. return file;
  245. }
  246. }
  247.  
  248. return null;
  249. }
  250.  
  251. // Upload image to KIENG images
  252. // details: {file: File, onload: Function({url, name, json}), onerror: Function, type: 'sm.ms/jd/sg/tt/...'}
  253. function uploadImage(details) {
  254. const file = details.file;
  255. const onload = details.onload ? details.onload : function() {};
  256. const onerror = details.onerror ? details.onerror : uploadError;
  257. const type = details.imager ? details.imager : CONFIG.imager;
  258. if (!DATA_IMAGERS.hasOwnProperty(type) || !DATA_IMAGERS[type].available) {
  259. onerror();
  260. return false;
  261. }
  262. const imager = DATA_IMAGERS[type];
  263. const upload = imager.upload;
  264. const request = upload.request;
  265. const response = upload.response;
  266.  
  267. // Construct request url
  268. let url = request.url;
  269. if (request.urlargs) {
  270. const args = request.urlargs;
  271. const makearg = (key, value) => ('{K}={V}'.replace('{K}', key).replace('{V}', value));
  272. const replacers = {
  273. '$filename$': () => (encodeURIComponent(file.name)),
  274. '$random$': () => (Math.random().toString()),
  275. '$time$': () => ((new Date()).getTime().toString())
  276. };
  277. for (let [key, value] of Object.entries(args)) {
  278. url += url.includes('?') ? '&' : '?';
  279. for (const [str, replacer] of Object.entries(replacers)) {
  280. while (value !== null && value.includes(str)) {
  281. const val = replacer(key);
  282. value = (val !== null) ? value.replace(str, val) : null;
  283. }
  284. }
  285. (value !== null) && (url += makearg(key, value));
  286. }
  287. }
  288.  
  289. // Construst request body
  290. let data;
  291. if (request.data) {
  292. data = new FormData();
  293. const replacers = {
  294. '$file$': (key) => ((data.append(key, file), null)),
  295. '$random$': () => (Math.random().toString()),
  296. '$time$': () => ((new Date()).getTime().toString())
  297. };
  298.  
  299. for (let [key, value] of Object.entries(request.data)) {
  300. for (const [str, replacer] of Object.entries(replacers)) {
  301. while (value !== null && value.includes(str)) {
  302. const val = replacer(key);
  303. value = (val !== null) ? value.replace(str, val) : null;
  304. }
  305. }
  306. (value !== null) && data.append(key, value);
  307. }
  308. } else {
  309. data = file;
  310. }
  311.  
  312. // headers
  313. const headers = request.headers || {};
  314.  
  315. GM_xmlhttpRequest({
  316. method: 'POST',
  317. url: url,
  318. timeout: 15 * 1000,
  319. data: data,
  320. headers: headers,
  321. responseType: request.responseType ? request.responseType : 'json',
  322. onerror: onerror,
  323. ontimeout: onerror,
  324. onabort: onerror,
  325. onload: (e) => {
  326. const json = e.response;
  327. const success = e.status === 200 && response.checksuccess(json);
  328. if (success) {
  329. const url = response.geturl(json);
  330. const name = response.getname ? (response.getname(json) ? response.getname(json) : CONST.Text.NoNameFromSever) : CONST.Text.NoNameFromSever
  331. onload({
  332. url: url,
  333. name: name,
  334. json: json
  335. });
  336. } else {
  337. onerror(json);
  338. return;
  339. }
  340. }
  341. });
  342.  
  343. function uploadError(json) {
  344. alertify.error(CONST.Text.UploadError);
  345. DoLog(LogLevel.Error, [CONST.Text.UploadError, json]);
  346. }
  347. }
  348. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址