Catbox Droptarget for Oppaitime

Upload/rehost images to catbox.moe directly from upload page

  1. // ==UserScript==
  2. // @name Catbox Droptarget for Oppaitime
  3. // @namespace https://greasyfork.org/users/390979-parliament
  4. // @version 1.51
  5. // @description Upload/rehost images to catbox.moe directly from upload page
  6. // @author Anakunda
  7. // @iconURL https://catbox.moe/pictures/favicon.ico
  8. // @match https://oppaiti.me/upload.php*
  9. // @match https://oppaiti.me/torrents.php?action=edit*
  10. // @match https://oppaiti.me/requests.php?action=new*
  11. // @match https://oppaiti.me/requests.php?action=edit*
  12. // @match https://oppaiti.me/reports.php?action=report*
  13. // @match https://oppaiti.me/reportsv2.php?*
  14. // @match https://oppaiti.me/artist.php?action=edit*
  15. // @connect catbox.moe
  16. // @grant GM_xmlhttpRequest
  17. // @grant GM_getValue
  18. // @grant GM_setValue
  19. // @grant GM_deleteValue
  20. // @grant GM_log
  21. // ==/UserScript==
  22.  
  23. 'use strict';
  24.  
  25. String.prototype.toASCII = function() {
  26. return this.normalize("NFKD").replace(/[\x00-\x1F\u0080-\uFFFF]/g, '');
  27. }
  28.  
  29. document.head.appendChild(document.createElement('style')).innerHTML = `
  30. .catbox-droptarget {
  31. margin-left: 8px; padding: 5px;
  32. background-color: #fff9cc; border: solid thin black;
  33. align-content: center; vertical-align: 1px;
  34. }
  35.  
  36. .catbox-img {
  37. height: 25px;
  38. vertical-align: middle;
  39. }
  40. `;
  41.  
  42. var userhash = GM_getValue('userhash');
  43. var image;
  44. bindAll();
  45. onReportTypeChange();
  46. if (document.location.pathname.toLowerCase() != '/requests.php') {
  47. var rlsTypeSelect = document.querySelector('select#categories');
  48. if (rlsTypeSelect != null) rlsTypeSelect.addEventListener('change', onRlsTypeChange);
  49. }
  50. var reportTypeSelect = document.querySelector('select#type');
  51. if (reportTypeSelect != null) reportTypeSelect.addEventListener('change', onReportTypeChange);
  52. GM_setValue('userhash', userhash || '');
  53.  
  54. function onReportTypeChange(evt) {
  55. setTimeout(function() {
  56. if (evt instanceof Event) {
  57. image = document.querySelector('input#proofimages') || document.querySelector('input#image');
  58. if (image != null) image.parentNode.append(createDropTarget(image));
  59. }
  60. bindToTextarea('extra');
  61. }, 1000);
  62. }
  63. function onRlsTypeChange(evt) { setTimeout(bindAll, 1000) }
  64.  
  65. function imageDropHandler(evt) {
  66. evt.preventDefault();
  67. uploadFiles(evt.currentTarget, evt.dataTransfer.files);
  68. }
  69.  
  70. function clickHandler(evt) {
  71. evt.preventDefault();
  72. if (!evt.currentTarget.boundElement) throw new Error('boundElement not set');
  73. if (evt.currentTarget.boundElement.nodeName == 'INPUT' && /^https?:\/\//i.test(evt.currentTarget.boundElement.value)
  74. && !evt.currentTarget.boundElement.value.toLowerCase().includes('catbox.moe/')) {
  75. rehostUrl(evt.currentTarget, evt.currentTarget.boundElement.value);
  76. } else {
  77. let currentTarget = evt.currentTarget;
  78. let inputElement = document.createElement("input");
  79. inputElement.type = "file";
  80. inputElement.accept = '.jpg, .jpeg, .jfif, .png, .gif, .webp';
  81. inputElement.multiple = true;
  82. inputElement.onchange = evt => { uploadFiles(currentTarget, inputElement.files) };
  83. inputElement.dispatchEvent(new MouseEvent("click"));
  84. }
  85. }
  86.  
  87. function voidDragHandler(evt) { evt.preventDefault() }
  88.  
  89. function uploadFiles(evtSrc, files) {
  90. if (files.length <= 0) return;
  91. if (!evtSrc.boundElement) throw new Error('boundElement not set');
  92. if (evtSrc.busy) throw new Error('Wait till current upload finishes');
  93. evtSrc.busy = true;
  94. if (evtSrc.hTimer) {
  95. clearTimeout(evtSrc.hTimer);
  96. delete evtSrc.hTimer;
  97. }
  98. evtSrc.style.backgroundColor = 'red';
  99. Promise.all(upload2Catbox(files))
  100. .then(function(results) {
  101. if (results.length > 0) {
  102. switch (evtSrc.boundElement.nodeName) {
  103. case 'INPUT':
  104. evtSrc.boundElement.value = results[0];
  105. break;
  106. case 'TEXTAREA':
  107. evtSrc.boundElement.value += results.join('\n');
  108. break;
  109. }
  110. evtSrc.style.backgroundColor = '#00C000';
  111. evtSrc.hTimer = setTimeout(function() {
  112. evtSrc.style.backgroundColor = null;
  113. delete evtSrc.hTimer;
  114. }, 3000);
  115. } else evtSrc.style.backgroundColor = null;
  116. }).catch(function(e) {
  117. alert(e);
  118. evtSrc.style.backgroundColor = null;
  119. }).then(function() {
  120. evtSrc.busy = false;
  121. });
  122. };
  123.  
  124. function rehostUrl(evtSrc, url) {
  125. if (!/^https?:\/\//i.test(url)) return;
  126. if (!evtSrc.boundElement) throw new Error('boundElement not set');
  127. if (evtSrc.busy) throw new Error('Wait till current upload finishes');
  128. evtSrc.busy = true;
  129. if (evtSrc.hTimer) {
  130. clearTimeout(evtSrc.hTimer);
  131. delete evtSrc.hTimer;
  132. }
  133. evtSrc.style.backgroundColor = 'red';
  134. rehost2Catbox(evtSrc.boundElement.value).then(function(result) {
  135. evtSrc.boundElement.value = result;
  136. evtSrc.style.backgroundColor = '#00C000';
  137. evtSrc.hTimer = setTimeout(function() {
  138. delete evtSrc.hTimer;
  139. evtSrc.style.backgroundColor = null;
  140. }, 3000);
  141. }).catch(function(e) {
  142. alert(e);
  143. evtSrc.style.backgroundColor = null;
  144. }).then(function() {
  145. evtSrc.busy = false;
  146. });
  147. }
  148.  
  149. function bindAll() {
  150. if ((image = document.getElementById('image')) != null) {
  151. image.parentNode.append(createDropTarget(image));
  152. } else if ((image = document.querySelector('input[name="image"]')) != null) {
  153. image.parentNode.insertBefore(createDropTarget(image), image.parentNode.querySelector(':scope > br'));
  154. }
  155. ['album_desc', 'release_desc', 'desc', 'body', 'description', 'screenshots'].forEach(bindToTextarea);
  156. }
  157.  
  158. function bindToTextarea(id) {
  159. var desc = document.querySelector('textarea#' + id);
  160. if (desc != null) {
  161. var btn = desc.parentNode.parentNode.querySelector('div > input[class^="button_preview"]');
  162. if (btn != null) {
  163. btn.parentNode.append(createDropTarget(desc));
  164. } else if ((btn = desc.parentNode.parentNode.querySelector(':scope > td.label')) != null) {
  165. var div = document.createElement('div');
  166. div.style.marginTop = '60px';
  167. div.append(createDropTarget(desc));
  168. btn.append(div);
  169. } else if ((btn = desc.parentNode.querySelector('div#Bbcode_Toolbar > div[style]:last-of-type')) != null) {
  170. btn.parentNode.insertBefore(createDropTarget(desc), btn);
  171. }
  172. return btn != null;
  173. } else if ((desc = document.querySelector('textarea[name="' + id + '"]')) != null
  174. && (btn = desc.parentNode.querySelector(':scope > div > input[value="Submit"]')) != null) {
  175. btn.parentNode.append(createDropTarget(desc));
  176. return true;
  177. }
  178. return false;
  179. }
  180.  
  181. function createDropTarget(boundElement) {
  182. if (!(boundElement instanceof HTMLElement)) throw new Error('invalid boundElement');
  183. var dropTarget = document.createElement('span');
  184. dropTarget.boundElement = boundElement;
  185. dropTarget.className = 'catbox-droptarget';
  186. dropTarget.ondragover = voidDragHandler;
  187. dropTarget.ondrop = imageDropHandler; // upload
  188. dropTarget.onclick = clickHandler; // rehost
  189. var img = document.createElement('img');
  190. img.src = 'data:image/png;base64,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';
  191. img.onerror = function() { this.src = 'https://catbox.moe/pictures/logo.png' };
  192. img.className = 'catbox-img';
  193. dropTarget.append(img);
  194. return dropTarget;
  195. }
  196.  
  197. function upload2Catbox(files) {
  198. if (!(files instanceof FileList)) return Promise.reject('Bad parameter (files)');
  199. return Array.from(files)
  200. .sort((file1, file2) => file1.name.localeCompare(file2.name))
  201. .map(file => new Promise(function(resolve, reject) {
  202. var fr = new Promise(function(resolve) {
  203. var reader = new FileReader();
  204. reader.onload = function() { resolve(reader.result) }
  205. reader.readAsBinaryString/*readAsArrayBuffer(file)*/(file);
  206. });
  207. fr.then(function(result) {
  208. const boundary = '----WebKitFormBoundaryTID_GM';
  209. var data = '--' + boundary + '\r\n';
  210. data += 'Content-Disposition: form-data; name="reqtype"\r\n\r\n';
  211. data += 'fileupload\r\n';
  212. if (userhash) {
  213. data += '--' + boundary + '\r\n';
  214. data += 'Content-Disposition: form-data; name="userhash"\r\n\r\n';
  215. data += userhash + '\r\n';
  216. }
  217. data += '--' + boundary + '\r\n';
  218. data += 'Content-Disposition: form-data; name="fileToUpload"; filename="' + file.name.toASCII() + '"\r\n';
  219. data += 'Content-Type: ' + file.type + '\r\n\r\n';
  220. data += result + '\r\n';
  221. data += '--' + boundary + '--\r\n';
  222. GM_xmlhttpRequest({
  223. method: 'POST',
  224. url: 'https://catbox.moe/user/api.php',
  225. responseType: 'text',
  226. headers: {
  227. 'Content-Type': 'multipart/form-data; boundary=' + boundary,
  228. 'Content-Length': data.length,
  229. },
  230. data: data,
  231. binary: true,
  232. onload: function(response) {
  233. if (response.status != 200) reject('Response error ' + response.status + ' (' + response.statusText + ')');
  234. resolve(response.response);
  235. },
  236. onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
  237. ontimeout: function() { reject('Timeout') },
  238. });
  239. });
  240. }));
  241. }
  242.  
  243. function rehost2Catbox(url) {
  244. if (typeof url != 'string' || !url) return Promise.reject('Bad parameter (url)');
  245. return new Promise(function(resolve, reject) {
  246. var data = new URLSearchParams({
  247. reqtype: 'urlupload',
  248. url: url.trim(),
  249. });
  250. if (userhash) data.set('userhash', userhash);
  251. GM_xmlhttpRequest({
  252. method: 'POST',
  253. url: 'https://catbox.moe/user/api.php',
  254. responseType: 'text',
  255. headers: {
  256. 'Content-Type': 'application/x-www-form-urlencoded',
  257. 'Content-Length': data.toString().length,
  258. },
  259. data: data.toString(),
  260. onload: function(response) {
  261. if (response.status != 200) reject('Response error ' + response.status + ' (' + response.statusText + ')');
  262. resolve(response.response);
  263. },
  264. onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
  265. ontimeout: function() { reject('Timeout') },
  266. });
  267. });
  268. }