Greasy Fork is available in English.

Greasyfork 快捷编辑收藏

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

Fra 10.06.2022. Se den seneste versjonen.

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