Greasyfork 快捷编辑收藏

在脚本信息页添加快速打开收藏集编辑页面功能

目前為 2022-02-08 提交的版本,檢視 最新版本

  1. /* eslint-disable no-multi-spaces */
  2.  
  3. // ==UserScript==
  4. // @name Greasyfork 快捷编辑收藏
  5. // @name:zh-CN Greasyfork 快捷编辑收藏
  6. // @name:en Greasyfork script-set-edit button
  7. // @namespace Greasyfork-Favorite
  8. // @version 0.1
  9. // @description 在脚本信息页添加快速打开收藏集编辑页面功能
  10. // @description:zh-CN 在脚本信息页添加快速打开收藏集编辑页面功能
  11. // @description:en Add open script-set-edit-page button in script info page
  12. // @author PY-DNG
  13. // @license GPL-3
  14. // @match http*://greasyfork.org/*
  15. // @icon https://api.iowen.cn/favicon/get.php?url=greasyfork.org
  16. // @grant GM_xmlhttpRequest
  17. // @grant GM_setValue
  18. // @grant GM_getValue
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict';
  23.  
  24. // Arguments: level=LogLevel.Info, logContent, asObject=false
  25. // Needs one call "DoLog();" to get it initialized before using it!
  26. function DoLog() {
  27. // Global log levels set
  28. window.LogLevel = {
  29. None: 0,
  30. Error: 1,
  31. Success: 2,
  32. Warning: 3,
  33. Info: 4,
  34. }
  35. window.LogLevelMap = {};
  36. window.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'}
  37. window.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'}
  38. window.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'}
  39. window.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'}
  40. window.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'}
  41. window.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'}
  42.  
  43. // Current log level
  44. DoLog.logLevel = (unsafeWindow ? unsafeWindow.isPY_DNG : window.isPY_DNG) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  45.  
  46. // Log counter
  47. DoLog.logCount === undefined && (DoLog.logCount = 0);
  48. if (++DoLog.logCount > 512) {
  49. console.clear();
  50. DoLog.logCount = 0;
  51. }
  52.  
  53. // Get args
  54. let level, logContent, asObject;
  55. switch (arguments.length) {
  56. case 1:
  57. level = LogLevel.Info;
  58. logContent = arguments[0];
  59. asObject = false;
  60. break;
  61. case 2:
  62. level = arguments[0];
  63. logContent = arguments[1];
  64. asObject = false;
  65. break;
  66. case 3:
  67. level = arguments[0];
  68. logContent = arguments[1];
  69. asObject = arguments[2];
  70. break;
  71. default:
  72. level = LogLevel.Info;
  73. logContent = 'DoLog initialized.';
  74. asObject = false;
  75. break;
  76. }
  77.  
  78. // Log when log level permits
  79. if (level <= DoLog.logLevel) {
  80. let msg = '%c' + LogLevelMap[level].prefix;
  81. let subst = LogLevelMap[level].color;
  82.  
  83. if (asObject) {
  84. msg += ' %o';
  85. } else {
  86. switch(typeof(logContent)) {
  87. case 'string': msg += ' %s'; break;
  88. case 'number': msg += ' %d'; break;
  89. case 'object': msg += ' %o'; break;
  90. }
  91. }
  92.  
  93. console.log(msg, subst, logContent);
  94. }
  95. }
  96. DoLog();
  97.  
  98. bypassXB();
  99. GM_PolyFill('default');
  100.  
  101. // Inner consts with i18n
  102. const CONST = {
  103. Text: {
  104. 'zh-CN': {
  105. FavEdit: '收藏集:',
  106. Edit: '编辑',
  107. CopySID: '复制脚本ID'
  108. },
  109. 'en': {
  110. FavEdit: 'Add to/Remove from favorite list: ',
  111. Edit: 'Edit',
  112. CopySID: 'Copy-Script-ID'
  113. },
  114. 'default': {
  115. FavEdit: 'Add to/Remove from favorite list: ',
  116. Edit: 'Edit',
  117. CopySID: 'Copy-Script-ID'
  118. },
  119. }
  120. }
  121.  
  122. // Get i18n code
  123. let i18n = navigator.language;
  124. if (!Object.keys(CONST.Text).includes(i18n)) {i18n = 'default';}
  125.  
  126. main()
  127. function main() {
  128. const HOST = getHost();
  129. const API = getAPI();
  130.  
  131. // Common actions
  132. commons();
  133.  
  134. // API-based actions
  135. switch(API[1]) {
  136. case "scripts":
  137. centerScript(API);
  138. break;
  139. default:
  140. DoLog('API is {}'.replace('{}', API));
  141. }
  142. }
  143.  
  144. function centerScript(API) {
  145. switch(API[3]) {
  146. case undefined:
  147. pageScript();
  148. break;
  149. case 'code':
  150. pageCode();
  151. break;
  152. case 'feedback':
  153. pageFeedback();
  154. break;
  155. }
  156. }
  157.  
  158. function commons() {
  159. // Your common actions here...
  160. }
  161.  
  162. function pageScript() {
  163. addFavPanel();
  164. }
  165.  
  166. function pageCode() {
  167. addFavPanel();
  168. }
  169.  
  170. function pageFeedback() {
  171. addFavPanel();
  172. }
  173.  
  174. function addFavPanel() {
  175. if (!getUserpage()) {return false;}
  176. GUI();
  177.  
  178. function GUI() {
  179. // Get elements
  180. const script_after = $('#script-feedback-suggestion+*') || $('#new-script-discussion');
  181. const script_parent = script_after.parentElement;
  182.  
  183. // My elements
  184. const script_favorite = $C('div');
  185. script_favorite.id = 'script-favorite';
  186. script_favorite.style.margin = '0.75em 0';
  187. script_favorite.innerHTML = CONST.Text[i18n].FavEdit;
  188.  
  189. const favorite_groups = $C('select');
  190. favorite_groups.id = 'favorite-groups';
  191.  
  192. const stored_sets = GM_getValue('script-sets', {sets: []}).sets;
  193. for (const set of stored_sets) {
  194. // Make <option>
  195. const option = $C('option');
  196. option.innerText = set.name;
  197. option.value = set.linkedit;
  198. $A(favorite_groups, option);
  199. }
  200.  
  201. getScriptSets(function(sets) {
  202. clearChildnodes(favorite_groups);
  203. for (const set of sets) {
  204. // Make <option>
  205. const option = $C('option');
  206. option.innerText = set.name;
  207. option.value = set.linkedit;
  208. $A(favorite_groups, option);
  209. }
  210.  
  211. // Set edit-button.href
  212. favorite_edit.href = favorite_groups.value;
  213. })
  214. favorite_groups.addEventListener('change', function(e) {
  215. favorite_edit.href = favorite_groups.value;
  216. });
  217.  
  218. const favorite_edit = $C('a');
  219. favorite_edit.id = 'favorite-add';
  220. favorite_edit.innerHTML = CONST.Text[i18n].Edit;
  221. favorite_edit.style.margin = favorite_edit.style.margin = '0px 0.5em';
  222. favorite_edit.target = '_blank';
  223.  
  224. const favorite_copy = $C('a');
  225. favorite_copy.id = 'favorite-copy';
  226. favorite_copy.href = 'javascript: void(0);';
  227. favorite_copy.innerHTML = CONST.Text[i18n].CopySID;
  228. favorite_copy.addEventListener('click', function() {
  229. copyText(getStrSID());
  230. });
  231.  
  232. // Append to document
  233. $A(script_favorite, favorite_groups);
  234. $I(script_parent, script_favorite, script_after);
  235. $A(script_favorite, favorite_edit);
  236. $A(script_favorite, favorite_copy);
  237. }
  238. }
  239.  
  240. function getScriptSets(callback, args=[]) {
  241. const userpage = getUserpage();
  242. getDocument(userpage, function(oDom) {
  243. const user_script_sets = oDom.querySelector('#user-script-sets');
  244. const script_sets = [];
  245.  
  246. for (const li of user_script_sets.querySelectorAll('li')) {
  247. // Get fav info
  248. const name = li.childNodes[0].nodeValue.trimRight();
  249. const link = li.children[0].href;
  250. const linkedit = li.children[1].href;
  251.  
  252. // Append to script_sets
  253. script_sets.push({
  254. name: name,
  255. link: link,
  256. linkedit: linkedit
  257. });
  258. }
  259.  
  260. // Save to GM_storage
  261. GM_setValue('script-sets', {
  262. sets: script_sets,
  263. time: (new Date()).getTime(),
  264. version: '0.1'
  265. });
  266.  
  267. // callback
  268. callback.apply(null, [script_sets].concat(args));
  269. });
  270. }
  271.  
  272. function getUserpage() {
  273. const a = $('#nav-user-info>.user-profile-link>a');
  274. return a ? a.href : null;
  275. }
  276.  
  277. function getStrSID(url=location.href) {
  278. const API = getAPI(url);
  279. const strSID = API[2].match(/\d+/);
  280. return strSID;
  281. }
  282.  
  283. function getSID(url=location.href) {
  284. return Number(getStrSID(url));
  285. }
  286.  
  287. function $(e) {return document.querySelector(e);}
  288. function $C(e) {return document.createElement(e);}
  289. function $A(a,b) {return a.appendChild(b);}
  290. function $I(a,b,c) {return a.insertBefore(b,c);}
  291.  
  292. // Remove all childnodes from an element
  293. function clearChildnodes(element) {
  294. const cns = []
  295. for (const cn of element.childNodes) {
  296. cns.push(cn);
  297. }
  298. for (const cn of cns) {
  299. element.removeChild(cn);
  300. }
  301. }
  302.  
  303. // Just stopPropagation and preventDefault
  304. function destroyEvent(e) {
  305. if (!e) {return false;};
  306. if (!e instanceof Event) {return false;};
  307. e.stopPropagation();
  308. e.preventDefault();
  309. }
  310.  
  311. // Download and parse a url page into a html document(dom).
  312. // when xhr onload: callback.apply([dom, args])
  313. function getDocument(url, callback, args=[]) {
  314. GM_xmlhttpRequest({
  315. method : 'GET',
  316. url : url,
  317. responseType : 'blob',
  318. onloadstart : function() {
  319. DoLog(LogLevel.Info, 'getting document, url=\'' + url + '\'');
  320. },
  321. onload : function(response) {
  322. const htmlblob = response.response;
  323. parseDocument(htmlblob, callback, args);
  324. }
  325. })
  326. }
  327.  
  328. function parseDocument(htmlblob, callback, args=[]) {
  329. const reader = new FileReader();
  330. reader.onload = function(e) {
  331. const htmlText = reader.result;
  332. const dom = new DOMParser().parseFromString(htmlText, 'text/html');
  333. args = [dom].concat(args);
  334. callback.apply(null, args);
  335. //callback(dom, htmlText);
  336. }
  337. reader.readAsText(htmlblob, document.characterSet);
  338. }
  339.  
  340. // GM_XHR HOOK: The number of running GM_XHRs in a time must under maxXHR
  341. // Returns the abort function to stop the request anyway(no matter it's still waiting, or requesting)
  342. // (If the request is invalid, such as url === '', will return false and will NOT make this request)
  343. // If the abort function called on a request that is not running(still waiting or finished), there will be NO onabort event
  344. // Requires: function delItem(){...} & function uniqueIDMaker(){...}
  345. function GMXHRHook(maxXHR=5) {
  346. const GM_XHR = GM_xmlhttpRequest;
  347. const getID = uniqueIDMaker();
  348. let todoList = [], ongoingList = [];
  349. GM_xmlhttpRequest = safeGMxhr;
  350.  
  351. function safeGMxhr() {
  352. // Get an id for this request, arrange a request object for it.
  353. const id = getID();
  354. const request = {id: id, args: arguments, aborter: null};
  355.  
  356. // Deal onload function first
  357. dealEndingEvents(request);
  358.  
  359. /* DO NOT DO THIS! KEEP ITS ORIGINAL PROPERTIES!
  360. // Stop invalid requests
  361. if (!validCheck(request)) {
  362. return false;
  363. }
  364. */
  365.  
  366. // Judge if we could start the request now or later?
  367. todoList.push(request);
  368. checkXHR();
  369. return makeAbortFunc(id);
  370.  
  371. // Decrease activeXHRCount while GM_XHR onload;
  372. function dealEndingEvents(request) {
  373. const e = request.args[0];
  374.  
  375. // onload event
  376. const oriOnload = e.onload;
  377. e.onload = function() {
  378. reqFinish(request.id);
  379. checkXHR();
  380. oriOnload ? oriOnload.apply(null, arguments) : function() {};
  381. }
  382.  
  383. // onerror event
  384. const oriOnerror = e.onerror;
  385. e.onerror = function() {
  386. reqFinish(request.id);
  387. checkXHR();
  388. oriOnerror ? oriOnerror.apply(null, arguments) : function() {};
  389. }
  390.  
  391. // ontimeout event
  392. const oriOntimeout = e.ontimeout;
  393. e.ontimeout = function() {
  394. reqFinish(request.id);
  395. checkXHR();
  396. oriOntimeout ? oriOntimeout.apply(null, arguments) : function() {};
  397. }
  398.  
  399. // onabort event
  400. const oriOnabort = e.onabort;
  401. e.onabort = function() {
  402. reqFinish(request.id);
  403. checkXHR();
  404. oriOnabort ? oriOnabort.apply(null, arguments) : function() {};
  405. }
  406. }
  407.  
  408. // Check if the request is invalid
  409. function validCheck(request) {
  410. const e = request.args[0];
  411.  
  412. if (!e.url) {
  413. return false;
  414. }
  415.  
  416. return true;
  417. }
  418.  
  419. // Call a XHR from todoList and push the request object to ongoingList if called
  420. function checkXHR() {
  421. if (ongoingList.length >= maxXHR) {return false;};
  422. if (todoList.length === 0) {return false;};
  423. const req = todoList.shift();
  424. const reqArgs = req.args;
  425. const aborter = GM_XHR.apply(null, reqArgs);
  426. req.aborter = aborter;
  427. ongoingList.push(req);
  428. return req;
  429. }
  430.  
  431. // Make a function that aborts a certain request
  432. function makeAbortFunc(id) {
  433. return function() {
  434. let i;
  435.  
  436. // Check if the request haven't been called
  437. for (i = 0; i < todoList.length; i++) {
  438. const req = todoList[i];
  439. if (req.id === id) {
  440. // found this request: haven't been called
  441. delItem(todoList, i);
  442. return true;
  443. }
  444. }
  445.  
  446. // Check if the request is running now
  447. for (i = 0; i < ongoingList.length; i++) {
  448. const req = todoList[i];
  449. if (req.id === id) {
  450. // found this request: running now
  451. req.aborter();
  452. reqFinish(id);
  453. checkXHR();
  454. }
  455. }
  456.  
  457. // Oh no, this request is already finished...
  458. return false;
  459. }
  460. }
  461.  
  462. // Remove a certain request from ongoingList
  463. function reqFinish(id) {
  464. let i;
  465. for (i = 0; i < ongoingList.length; i++) {
  466. const req = ongoingList[i];
  467. if (req.id === id) {
  468. ongoingList = delItem(ongoingList, i);
  469. return true;
  470. }
  471. }
  472. return false;
  473. }
  474. }
  475. }
  476.  
  477. // Get a url argument from lacation.href
  478. // also recieve a function to deal the matched string
  479. // returns defaultValue if name not found
  480. // Args: name, dealFunc=(function(a) {return a;}), defaultValue=null
  481. function getUrlArgv(details) {
  482. typeof(details) === 'string' && (details = {name: details});
  483. typeof(details) === 'undefined' && (details = {});
  484. if (!details.name) {return null;};
  485.  
  486. const url = details.url ? details.url : location.href;
  487. const name = details.name ? details.name : '';
  488. const dealFunc = details.dealFunc ? details.dealFunc : ((a)=>{return a;});
  489. const defaultValue = details.defaultValue ? details.defaultValue : null;
  490. const matcher = new RegExp(name + '=([^&]+)');
  491. const result = url.match(matcher);
  492. const argv = result ? dealFunc(result[1]) : defaultValue;
  493.  
  494. return argv;
  495. }
  496.  
  497. // Copy text to clipboard (needs to be called in an user event)
  498. function copyText(text) {
  499. // Create a new textarea for copying
  500. const newInput = document.createElement('textarea');
  501. document.body.appendChild(newInput);
  502. newInput.value = text;
  503. newInput.select();
  504. document.execCommand('copy');
  505. document.body.removeChild(newInput);
  506. }
  507.  
  508. // Append a style text to document(<head>) with a <style> element
  509. function addStyle(css, id) {
  510. const style = document.createElement("style");
  511. id && (style.id = id);
  512. style.textContent = css;
  513. for (const elm of document.querySelectorAll('#'+id)) {
  514. elm.parentElement && elm.parentElement.removeChild(elm);
  515. }
  516. document.head.appendChild(style);
  517. }
  518.  
  519. // File download function
  520. // details looks like the detail of GM_xmlhttpRequest
  521. // onload function will be called after file saved to disk
  522. function downloadFile(details) {
  523. if (!details.url || !details.name) {return false;};
  524.  
  525. // Configure request object
  526. const requestObj = {
  527. url: details.url,
  528. responseType: 'blob',
  529. onload: function(e) {
  530. // Save file
  531. saveFile(URL.createObjectURL(e.response), details.name);
  532.  
  533. // onload callback
  534. details.onload ? details.onload(e) : function() {};
  535. }
  536. }
  537. if (details.onloadstart ) {requestObj.onloadstart = details.onloadstart;};
  538. if (details.onprogress ) {requestObj.onprogress = details.onprogress;};
  539. if (details.onerror ) {requestObj.onerror = details.onerror;};
  540. if (details.onabort ) {requestObj.onabort = details.onabort;};
  541. if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;};
  542. if (details.ontimeout ) {requestObj.ontimeout = details.ontimeout;};
  543.  
  544. // Send request
  545. GM_xmlhttpRequest(requestObj);
  546. }
  547.  
  548. // get '/' splited API array from a url
  549. function getAPI(url=location.href) {
  550. return url.replace(/https?:\/\/(.*?\.){1,2}.*?\//, '').replace(/\?.*/, '').match(/[^\/]+?(?=(\/|$))/g);
  551. }
  552.  
  553. // get host part from a url(includes '^https://', '/$')
  554. function getHost(url=location.href) {
  555. const match = location.href.match(/https?:\/\/[^\/]+\//);
  556. return match ? match[0] : match;
  557. }
  558.  
  559. // Your code here...
  560. // Bypass xbrowser's useless GM_functions
  561. function bypassXB() {
  562. if (typeof(mbrowser) === 'object') {
  563. window.unsafeWindow = window.GM_setClipboard = window.GM_openInTab = window.GM_xmlhttpRequest = window.GM_getValue = window.GM_setValue = window.GM_listValues = window.GM_deleteValue = undefined;
  564. }
  565. }
  566.  
  567. // GM_Polyfill By PY-DNG
  568. // 2021.07.18 - 2021.07.19
  569. // Simply provides the following GM_functions using localStorage, XMLHttpRequest and window.open:
  570. // Returns object GM_POLYFILLED which has the following properties that shows you which GM_functions are actually polyfilled:
  571. // GM_setValue, GM_getValue, GM_deleteValue, GM_listValues, GM_xmlhttpRequest, GM_openInTab, GM_setClipboard, unsafeWindow(object)
  572. // All polyfilled GM_functions are accessable in window object/Global_Scope(only without Tempermonkey Sandboxing environment)
  573. function GM_PolyFill(name='default') {
  574. const GM_POLYFILL_KEY_STORAGE = 'GM_STORAGE_POLYFILL';
  575. let GM_POLYFILL_storage;
  576. const GM_POLYFILLED = {
  577. GM_setValue: true,
  578. GM_getValue: true,
  579. GM_deleteValue: true,
  580. GM_listValues: true,
  581. GM_xmlhttpRequest: true,
  582. GM_openInTab: true,
  583. GM_setClipboard: true,
  584. unsafeWindow: true,
  585. once: false
  586. }
  587.  
  588. // Ignore GM_PolyFill_Once
  589. window.GM_POLYFILLED && window.GM_POLYFILLED.once && (window.unsafeWindow = window.GM_setClipboard = window.GM_openInTab = window.GM_xmlhttpRequest = window.GM_getValue = window.GM_setValue = window.GM_listValues = window.GM_deleteValue = undefined);
  590.  
  591. GM_setValue_polyfill();
  592. GM_getValue_polyfill();
  593. GM_deleteValue_polyfill();
  594. GM_listValues_polyfill();
  595. GM_xmlhttpRequest_polyfill();
  596. GM_openInTab_polyfill();
  597. GM_setClipboard_polyfill();
  598. unsafeWindow_polyfill();
  599.  
  600. function GM_POLYFILL_getStorage() {
  601. let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);
  602. gstorage = gstorage ? JSON.parse(gstorage) : {};
  603. let storage = gstorage[name] ? gstorage[name] : {};
  604. return storage;
  605. }
  606.  
  607. function GM_POLYFILL_saveStorage() {
  608. let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);
  609. gstorage = gstorage ? JSON.parse(gstorage) : {};
  610. gstorage[name] = GM_POLYFILL_storage;
  611. localStorage.setItem(GM_POLYFILL_KEY_STORAGE, JSON.stringify(gstorage));
  612. }
  613.  
  614. // GM_setValue
  615. function GM_setValue_polyfill() {
  616. typeof (GM_setValue) === 'function' ? GM_POLYFILLED.GM_setValue = false: window.GM_setValue = PF_GM_setValue;;
  617.  
  618. function PF_GM_setValue(name, value) {
  619. GM_POLYFILL_storage = GM_POLYFILL_getStorage();
  620. name = String(name);
  621. GM_POLYFILL_storage[name] = value;
  622. GM_POLYFILL_saveStorage();
  623. }
  624. }
  625.  
  626. // GM_getValue
  627. function GM_getValue_polyfill() {
  628. typeof (GM_getValue) === 'function' ? GM_POLYFILLED.GM_getValue = false: window.GM_getValue = PF_GM_getValue;
  629.  
  630. function PF_GM_getValue(name, defaultValue) {
  631. GM_POLYFILL_storage = GM_POLYFILL_getStorage();
  632. name = String(name);
  633. if (GM_POLYFILL_storage.hasOwnProperty(name)) {
  634. return GM_POLYFILL_storage[name];
  635. } else {
  636. return defaultValue;
  637. }
  638. }
  639. }
  640.  
  641. // GM_deleteValue
  642. function GM_deleteValue_polyfill() {
  643. typeof (GM_deleteValue) === 'function' ? GM_POLYFILLED.GM_deleteValue = false: window.GM_deleteValue = PF_GM_deleteValue;
  644.  
  645. function PF_GM_deleteValue(name) {
  646. GM_POLYFILL_storage = GM_POLYFILL_getStorage();
  647. name = String(name);
  648. if (GM_POLYFILL_storage.hasOwnProperty(name)) {
  649. delete GM_POLYFILL_storage[name];
  650. GM_POLYFILL_saveStorage();
  651. }
  652. }
  653. }
  654.  
  655. // GM_listValues
  656. function GM_listValues_polyfill() {
  657. typeof (GM_listValues) === 'function' ? GM_POLYFILLED.GM_listValues = false: window.GM_listValues = PF_GM_listValues;
  658.  
  659. function PF_GM_listValues() {
  660. GM_POLYFILL_storage = GM_POLYFILL_getStorage();
  661. return Object.keys(GM_POLYFILL_storage);
  662. }
  663. }
  664.  
  665. // unsafeWindow
  666. function unsafeWindow_polyfill() {
  667. typeof (unsafeWindow) === 'object' ? GM_POLYFILLED.unsafeWindow = false: window.unsafeWindow = window;
  668. }
  669.  
  670. // GM_xmlhttpRequest
  671. // not supported properties of details: synchronous binary nocache revalidate context fetch
  672. // not supported properties of response(onload arguments[0]): finalUrl
  673. // ---!IMPORTANT!--- DOES NOT SUPPORT CROSS-ORIGIN REQUESTS!!!!! ---!IMPORTANT!---
  674. function GM_xmlhttpRequest_polyfill() {
  675. typeof (GM_xmlhttpRequest) === 'function' ? GM_POLYFILLED.GM_xmlhttpRequest = false: window.GM_xmlhttpRequest = PF_GM_xmlhttpRequest;
  676.  
  677. // details.synchronous is not supported as Tempermonkey
  678. function PF_GM_xmlhttpRequest(details) {
  679. const xhr = new XMLHttpRequest();
  680.  
  681. // open request
  682. const openArgs = [details.method, details.url, true];
  683. if (details.user && details.password) {
  684. openArgs.push(details.user);
  685. openArgs.push(details.password);
  686. }
  687. xhr.open.apply(xhr, openArgs);
  688.  
  689. // set headers
  690. if (details.headers) {
  691. for (const key of Object.keys(details.headers)) {
  692. xhr.setRequestHeader(key, details.headers[key]);
  693. }
  694. }
  695. details.cookie ? xhr.setRequestHeader('cookie', details.cookie) : function () {};
  696. details.anonymous ? xhr.setRequestHeader('cookie', '') : function () {};
  697.  
  698. // properties
  699. xhr.timeout = details.timeout;
  700. xhr.responseType = details.responseType;
  701. details.overrideMimeType ? xhr.overrideMimeType(details.overrideMimeType) : function () {};
  702.  
  703. // events
  704. xhr.onabort = details.onabort;
  705. xhr.onerror = details.onerror;
  706. xhr.onloadstart = details.onloadstart;
  707. xhr.onprogress = details.onprogress;
  708. xhr.onreadystatechange = details.onreadystatechange;
  709. xhr.ontimeout = details.ontimeout;
  710. xhr.onload = function (e) {
  711. const response = {
  712. readyState: xhr.readyState,
  713. status: xhr.status,
  714. statusText: xhr.statusText,
  715. responseHeaders: xhr.getAllResponseHeaders(),
  716. response: xhr.response
  717. };
  718. (details.responseType === '' || details.responseType === 'text') ? (response.responseText = xhr.responseText) : function () {};
  719. (details.responseType === '' || details.responseType === 'document') ? (response.responseXML = xhr.responseXML) : function () {};
  720. details.onload(response);
  721. }
  722.  
  723. // send request
  724. details.data ? xhr.send(details.data) : xhr.send();
  725.  
  726. return {
  727. abort: xhr.abort
  728. };
  729. }
  730. }
  731.  
  732. // NOTE: options(arg2) is NOT SUPPORTED! if provided, then will just be skipped.
  733. function GM_openInTab_polyfill() {
  734. typeof (GM_openInTab) === 'function' ? GM_POLYFILLED.GM_openInTab = false: window.GM_openInTab = PF_GM_openInTab;
  735.  
  736. function PF_GM_openInTab(url) {
  737. window.open(url);
  738. }
  739. }
  740.  
  741. // NOTE: needs to be called in an event handler function, and info(arg2) is NOT SUPPORTED!
  742. function GM_setClipboard_polyfill() {
  743. typeof (GM_setClipboard) === 'function' ? GM_POLYFILLED.GM_setClipboard = false: window.GM_setClipboard = PF_GM_setClipboard;
  744.  
  745. function PF_GM_setClipboard(text) {
  746. // Create a new textarea for copying
  747. const newInput = document.createElement('textarea');
  748. document.body.appendChild(newInput);
  749. newInput.value = text;
  750. newInput.select();
  751. document.execCommand('copy');
  752. document.body.removeChild(newInput);
  753. }
  754. }
  755.  
  756. return GM_POLYFILLED;
  757. }
  758.  
  759. // Makes a function that returns a unique ID number each time
  760. function uniqueIDMaker() {
  761. let id = 0;
  762. return makeID;
  763. function makeID() {
  764. id++;
  765. return id;
  766. }
  767. }
  768.  
  769. // Del a item from an array using its index. Returns the array but can NOT modify the original array directly!!
  770. function delItem(arr, delIndex) {
  771. arr = arr.slice(0, delIndex).concat(arr.slice(delIndex+1));
  772. return arr;
  773. }
  774. })();