Greasy Fork is available in English.

UserScript Compatibility Library

A library to ensure compatibility between different userscript managers

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greatest.deepsurf.us/scripts/519877/1507987/UserScript%20Compatibility%20Library.js

  1. // ==UserScript==
  2. // @name UserScript Compatibility Library
  3. // @name:en UserScript Compatibility Library
  4. // @name:zh-CN UserScript 兼容库
  5. // @name:ru Библиотека совместимости для пользовательских скриптов
  6. // @name:vi Thư viện tương thích cho userscript
  7. // @namespace https://greatest.deepsurf.us/vi/users/1195312-renji-yuusei
  8. // @version 2024.12.23.1
  9. // @description A library to ensure compatibility between different userscript managers
  10. // @description:en A library to ensure compatibility between different userscript managers
  11. // @description:zh-CN 确保不同用户脚本管理器之间兼容性的库
  12. // @description:vi Thư viện đảm bảo tương thích giữa các trình quản lý userscript khác nhau
  13. // @description:ru Библиотека для обеспечения совместимости между различными менеджерами пользовательских скриптов
  14. // @author Yuusei
  15. // @license GPL-3.0-only
  16. // @grant unsafeWindow
  17. // @grant GM_info
  18. // @grant GM.info
  19. // @grant GM_getValue
  20. // @grant GM.getValue
  21. // @grant GM_setValue
  22. // @grant GM.setValue
  23. // @grant GM_deleteValue
  24. // @grant GM.deleteValue
  25. // @grant GM_listValues
  26. // @grant GM.listValues
  27. // @grant GM_xmlhttpRequest
  28. // @grant GM.xmlHttpRequest
  29. // @grant GM_download
  30. // @grant GM.download
  31. // @grant GM_notification
  32. // @grant GM.notification
  33. // @grant GM_addStyle
  34. // @grant GM.addStyle
  35. // @grant GM_registerMenuCommand
  36. // @grant GM.registerMenuCommand
  37. // @grant GM_unregisterMenuCommand
  38. // @grant GM.unregisterMenuCommand
  39. // @grant GM_setClipboard
  40. // @grant GM.setClipboard
  41. // @grant GM_getResourceText
  42. // @grant GM.getResourceText
  43. // @grant GM_getResourceURL
  44. // @grant GM.getResourceURL
  45. // @grant GM_openInTab
  46. // @grant GM.openInTab
  47. // @grant GM_addElement
  48. // @grant GM.addElement
  49. // @grant GM_addValueChangeListener
  50. // @grant GM.addValueChangeListener
  51. // @grant GM_removeValueChangeListener
  52. // @grant GM.removeValueChangeListener
  53. // @grant GM_log
  54. // @grant GM.log
  55. // @grant GM_getTab
  56. // @grant GM.getTab
  57. // @grant GM_saveTab
  58. // @grant GM.saveTab
  59. // @grant GM_getTabs
  60. // @grant GM.getTabs
  61. // @grant GM_cookie
  62. // @grant GM.cookie
  63. // @grant GM_webRequest
  64. // @grant GM.webRequest
  65. // @grant GM_fetch
  66. // @grant GM.fetch
  67. // @grant window.close
  68. // @grant window.focus
  69. // @grant window.onurlchange
  70. // @grant GM_addValueChangeListener
  71. // @grant GM_removeValueChangeListener
  72. // @grant GM_getResourceURL
  73. // @grant GM_notification
  74. // @grant GM_xmlhttpRequest
  75. // @grant GM_openInTab
  76. // @grant GM_registerMenuCommand
  77. // @grant GM_unregisterMenuCommand
  78. // @grant GM_setClipboard
  79. // @grant GM_getResourceText
  80. // @grant GM_addStyle
  81. // @grant GM_download
  82. // @grant GM_cookie.get
  83. // @grant GM_cookie.set
  84. // @grant GM_cookie.delete
  85. // @grant GM_webRequest.listen
  86. // @grant GM_webRequest.onBeforeRequest
  87. // @grant GM_addElement.byTag
  88. // @grant GM_addElement.byId
  89. // @grant GM_addElement.byClass
  90. // @grant GM_addElement.byXPath
  91. // @grant GM_addElement.bySelector
  92. // @grant GM_removeElement
  93. // @grant GM_removeElements
  94. // @grant GM_getElement
  95. // @grant GM_getElements
  96. // @grant GM_addScript
  97. // @grant GM_removeScript
  98. // @grant GM_addLink
  99. // @grant GM_removeLink
  100. // @grant GM_addMeta
  101. // @grant GM_removeMeta
  102. // @grant GM_addIframe
  103. // @grant GM_removeIframe
  104. // @grant GM_addImage
  105. // @grant GM_removeImage
  106. // @grant GM_addVideo
  107. // @grant GM_removeVideo
  108. // @grant GM_addAudio
  109. // @grant GM_removeAudio
  110. // @grant GM_addCanvas
  111. // @grant GM_removeCanvas
  112. // @grant GM_addSvg
  113. // @grant GM_removeSvg
  114. // @grant GM_addObject
  115. // @grant GM_removeObject
  116. // @grant GM_addEmbed
  117. // @grant GM_removeEmbed
  118. // @grant GM_addApplet
  119. // @grant GM_removeApplet
  120. // @run-at document-start
  121. // @license GPL-3.0-only
  122. // @grant GM_addValueChangeListener.remove
  123. // @grant GM_getResourceURL.blob
  124. // @grant GM_notification.close
  125. // @grant GM_openInTab.focus
  126. // @grant GM_setClipboard.format
  127. // @grant GM_xmlhttpRequest.abort
  128. // @grant GM_download.progress
  129. // @grant GM_cookie.list
  130. // @grant GM_cookie.deleteAll
  131. // @grant GM_webRequest.filter
  132. // @grant GM_addElement.create
  133. // @grant GM_removeElement.all
  134. // @grant GM_getElement.all
  135. // @grant GM_addScript.remote
  136. // @grant GM_addLink.stylesheet
  137. // @grant GM_addMeta.viewport
  138. // @grant GM_addIframe.sandbox
  139. // @grant GM_addImage.lazy
  140. // @grant GM_addVideo.controls
  141. // @grant GM_addAudio.autoplay
  142. // @grant GM_addCanvas.context
  143. // @grant GM_addSvg.namespace
  144. // @grant GM_addObject.data
  145. // @grant GM_addEmbed.type
  146. // @grant GM_addApplet.code
  147. // ==/UserScript==
  148. (function () {
  149. 'use strict';
  150. const utils = {
  151. isFunction: function (fn) {
  152. return typeof fn === 'function';
  153. },
  154. isUndefined: function (value) {
  155. return typeof value === 'undefined';
  156. },
  157. isObject: function (value) {
  158. return value !== null && typeof value === 'object';
  159. },
  160. sleep: function (ms) {
  161. return new Promise(resolve => setTimeout(resolve, ms));
  162. },
  163. retry: async function (fn, attempts = 3, delay = 1000) {
  164. let lastError;
  165. for (let i = 0; i < attempts; i++) {
  166. try {
  167. return await fn();
  168. } catch (error) {
  169. lastError = error;
  170. if (i === attempts - 1) break;
  171. await this.sleep(delay * Math.pow(2, i));
  172. }
  173. }
  174. throw lastError;
  175. },
  176. debounce: function (fn, wait) {
  177. let timeout;
  178. return function (...args) {
  179. clearTimeout(timeout);
  180. timeout = setTimeout(() => fn.apply(this, args), wait);
  181. };
  182. },
  183. throttle: function (fn, limit) {
  184. let timeout;
  185. let inThrottle;
  186. return function (...args) {
  187. if (!inThrottle) {
  188. fn.apply(this, args);
  189. inThrottle = true;
  190. clearTimeout(timeout);
  191. timeout = setTimeout(() => (inThrottle = false), limit);
  192. }
  193. };
  194. },
  195. // Thêm các tiện ích mới
  196. isArray: function (arr) {
  197. return Array.isArray(arr);
  198. },
  199. isString: function (str) {
  200. return typeof str === 'string';
  201. },
  202. isNumber: function (num) {
  203. return typeof num === 'number' && !isNaN(num);
  204. },
  205. isBoolean: function (bool) {
  206. return typeof bool === 'boolean';
  207. },
  208. isNull: function (value) {
  209. return value === null;
  210. },
  211. isEmpty: function (value) {
  212. if (this.isArray(value)) return value.length === 0;
  213. if (this.isObject(value)) return Object.keys(value).length === 0;
  214. if (this.isString(value)) return value.trim().length === 0;
  215. return false;
  216. },
  217. };
  218. const GMCompat = {
  219. info: (function () {
  220. if (!utils.isUndefined(GM_info)) return GM_info;
  221. if (!utils.isUndefined(GM) && GM.info) return GM.info;
  222. return {};
  223. })(),
  224. storageCache: new Map(),
  225. cacheTimestamps: new Map(),
  226. cacheExpiry: 3600000, // 1 hour
  227. getValue: async function (key, defaultValue) {
  228. try {
  229. if (this.storageCache.has(key)) {
  230. const timestamp = this.cacheTimestamps.get(key);
  231. if (Date.now() - timestamp < this.cacheExpiry) {
  232. return this.storageCache.get(key);
  233. }
  234. }
  235. let value;
  236. if (!utils.isUndefined(GM_getValue)) {
  237. value = GM_getValue(key, defaultValue);
  238. } else if (!utils.isUndefined(GM) && GM.getValue) {
  239. value = await GM.getValue(key, defaultValue);
  240. } else {
  241. value = defaultValue;
  242. }
  243. this.storageCache.set(key, value);
  244. this.cacheTimestamps.set(key, Date.now());
  245. return value;
  246. } catch (error) {
  247. console.error('getValue error:', error);
  248. return defaultValue;
  249. }
  250. },
  251. setValue: async function (key, value) {
  252. try {
  253. this.storageCache.set(key, value);
  254. this.cacheTimestamps.set(key, Date.now());
  255. if (!utils.isUndefined(GM_setValue)) {
  256. return GM_setValue(key, value);
  257. }
  258. if (!utils.isUndefined(GM) && GM.setValue) {
  259. return await GM.setValue(key, value);
  260. }
  261. } catch (error) {
  262. this.storageCache.delete(key);
  263. this.cacheTimestamps.delete(key);
  264. throw new Error('Failed to set value: ' + error.message);
  265. }
  266. },
  267. deleteValue: async function (key) {
  268. try {
  269. this.storageCache.delete(key);
  270. this.cacheTimestamps.delete(key);
  271. if (!utils.isUndefined(GM_deleteValue)) {
  272. return GM_deleteValue(key);
  273. }
  274. if (!utils.isUndefined(GM) && GM.deleteValue) {
  275. return await GM.deleteValue(key);
  276. }
  277. } catch (error) {
  278. throw new Error('Failed to delete value: ' + error.message);
  279. }
  280. },
  281. requestQueue: [],
  282. processingRequest: false,
  283. maxRetries: 3,
  284. retryDelay: 1000,
  285. xmlHttpRequest: async function (details) {
  286. const makeRequest = () => {
  287. return new Promise((resolve, reject) => {
  288. try {
  289. const callbacks = {
  290. onload: resolve,
  291. onerror: reject,
  292. ontimeout: reject,
  293. onprogress: details.onprogress,
  294. onreadystatechange: details.onreadystatechange,
  295. };
  296. const finalDetails = {
  297. timeout: 30000,
  298. ...details,
  299. ...callbacks,
  300. };
  301. if (!utils.isUndefined(GM_xmlhttpRequest)) {
  302. GM_xmlhttpRequest(finalDetails);
  303. } else if (!utils.isUndefined(GM) && GM.xmlHttpRequest) {
  304. GM.xmlHttpRequest(finalDetails);
  305. } else if (!utils.isUndefined(GM_fetch)) {
  306. GM_fetch(finalDetails.url, finalDetails);
  307. } else if (!utils.isUndefined(GM) && GM.fetch) {
  308. GM.fetch(finalDetails.url, finalDetails);
  309. } else {
  310. reject(new Error('XMLHttpRequest API not available'));
  311. }
  312. } catch (error) {
  313. reject(error);
  314. }
  315. });
  316. };
  317. return utils.retry(makeRequest, this.maxRetries, this.retryDelay);
  318. },
  319. download: async function (details) {
  320. try {
  321. const downloadWithProgress = {
  322. ...details,
  323. onprogress: details.onprogress,
  324. onerror: details.onerror,
  325. onload: details.onload,
  326. };
  327. if (!utils.isUndefined(GM_download)) {
  328. return new Promise((resolve, reject) => {
  329. GM_download({
  330. ...downloadWithProgress,
  331. onload: resolve,
  332. onerror: reject,
  333. });
  334. });
  335. }
  336. if (!utils.isUndefined(GM) && GM.download) {
  337. return await GM.download(downloadWithProgress);
  338. }
  339. throw new Error('Download API not available');
  340. } catch (error) {
  341. throw new Error('Download failed: ' + error.message);
  342. }
  343. },
  344. notification: function (details) {
  345. return new Promise((resolve, reject) => {
  346. try {
  347. const defaultOptions = {
  348. timeout: 5000,
  349. highlight: false,
  350. silent: false,
  351. requireInteraction: false,
  352. priority: 0,
  353. };
  354. const callbacks = {
  355. onclick: utils.debounce((...args) => {
  356. if (details.onclick) details.onclick(...args);
  357. resolve('clicked');
  358. }, 300),
  359. ondone: (...args) => {
  360. if (details.ondone) details.ondone(...args);
  361. resolve('closed');
  362. },
  363. onerror: (...args) => {
  364. if (details.onerror) details.onerror(...args);
  365. reject('error');
  366. },
  367. };
  368. const finalDetails = { ...defaultOptions, ...details, ...callbacks };
  369. if (!utils.isUndefined(GM_notification)) {
  370. GM_notification(finalDetails);
  371. } else if (!utils.isUndefined(GM) && GM.notification) {
  372. GM.notification(finalDetails);
  373. } else {
  374. if ('Notification' in window) {
  375. Notification.requestPermission().then(permission => {
  376. if (permission === 'granted') {
  377. const notification = new Notification(finalDetails.title, {
  378. body: finalDetails.text,
  379. silent: finalDetails.silent,
  380. icon: finalDetails.image,
  381. tag: finalDetails.tag,
  382. requireInteraction: finalDetails.requireInteraction,
  383. badge: finalDetails.badge,
  384. vibrate: finalDetails.vibrate,
  385. });
  386. notification.onclick = callbacks.onclick;
  387. notification.onerror = callbacks.onerror;
  388. if (finalDetails.timeout > 0) {
  389. setTimeout(() => {
  390. notification.close();
  391. callbacks.ondone();
  392. }, finalDetails.timeout);
  393. }
  394. } else {
  395. reject(new Error('Notification permission denied'));
  396. }
  397. });
  398. } else {
  399. reject(new Error('Notification API not available'));
  400. }
  401. }
  402. } catch (error) {
  403. reject(error);
  404. }
  405. });
  406. },
  407. addStyle: function (css) {
  408. try {
  409. const testStyle = document.createElement('style');
  410. testStyle.textContent = css;
  411. if (testStyle.sheet === null) {
  412. throw new Error('Invalid CSS');
  413. }
  414. if (!utils.isUndefined(GM_addStyle)) {
  415. return GM_addStyle(css);
  416. }
  417. if (!utils.isUndefined(GM) && GM.addStyle) {
  418. return GM.addStyle(css);
  419. }
  420. const style = document.createElement('style');
  421. style.textContent = css;
  422. style.type = 'text/css';
  423. document.head.appendChild(style);
  424. return style;
  425. } catch (error) {
  426. throw new Error('Failed to add style: ' + error.message);
  427. }
  428. },
  429. registerMenuCommand: function (name, fn, accessKey) {
  430. try {
  431. if (!utils.isFunction(fn)) {
  432. throw new Error('Command callback must be a function');
  433. }
  434. if (!utils.isUndefined(GM_registerMenuCommand)) {
  435. return GM_registerMenuCommand(name, fn, accessKey);
  436. }
  437. if (!utils.isUndefined(GM) && GM.registerMenuCommand) {
  438. return GM.registerMenuCommand(name, fn, accessKey);
  439. }
  440. } catch (error) {
  441. throw new Error('Failed to register menu command: ' + error.message);
  442. }
  443. },
  444. setClipboard: function (text, info) {
  445. try {
  446. if (!utils.isUndefined(GM_setClipboard)) {
  447. return GM_setClipboard(text, info);
  448. }
  449. if (!utils.isUndefined(GM) && GM.setClipboard) {
  450. return GM.setClipboard(text, info);
  451. }
  452. return navigator.clipboard.writeText(text);
  453. } catch (error) {
  454. throw new Error('Failed to set clipboard: ' + error.message);
  455. }
  456. },
  457. getResourceText: async function (name) {
  458. try {
  459. if (!utils.isUndefined(GM_getResourceText)) {
  460. return GM_getResourceText(name);
  461. }
  462. if (!utils.isUndefined(GM) && GM.getResourceText) {
  463. return await GM.getResourceText(name);
  464. }
  465. throw new Error('Resource API not available');
  466. } catch (error) {
  467. throw new Error('Failed to get resource text: ' + error.message);
  468. }
  469. },
  470. getResourceURL: async function (name) {
  471. try {
  472. if (!utils.isUndefined(GM_getResourceURL)) {
  473. return GM_getResourceURL(name);
  474. }
  475. if (!utils.isUndefined(GM) && GM.getResourceURL) {
  476. return await GM.getResourceURL(name);
  477. }
  478. throw new Error('Resource URL API not available');
  479. } catch (error) {
  480. throw new Error('Failed to get resource URL: ' + error.message);
  481. }
  482. },
  483. openInTab: function (url, options = {}) {
  484. try {
  485. const defaultOptions = {
  486. active: true,
  487. insert: true,
  488. setParent: true,
  489. };
  490. const finalOptions = { ...defaultOptions, ...options };
  491. if (!utils.isUndefined(GM_openInTab)) {
  492. return GM_openInTab(url, finalOptions);
  493. }
  494. if (!utils.isUndefined(GM) && GM.openInTab) {
  495. return GM.openInTab(url, finalOptions);
  496. }
  497. return window.open(url, '_blank');
  498. } catch (error) {
  499. throw new Error('Failed to open tab: ' + error.message);
  500. }
  501. },
  502. cookie: {
  503. get: async function (details) {
  504. try {
  505. if (!utils.isUndefined(GM_cookie) && GM_cookie.get) {
  506. return await GM_cookie.get(details);
  507. }
  508. if (!utils.isUndefined(GM) && GM.cookie && GM.cookie.get) {
  509. return await GM.cookie.get(details);
  510. }
  511. return document.cookie;
  512. } catch (error) {
  513. throw new Error('Failed to get cookie: ' + error.message);
  514. }
  515. },
  516. set: async function (details) {
  517. try {
  518. if (!utils.isUndefined(GM_cookie) && GM_cookie.set) {
  519. return await GM_cookie.set(details);
  520. }
  521. if (!utils.isUndefined(GM) && GM.cookie && GM.cookie.set) {
  522. return await GM.cookie.set(details);
  523. }
  524. document.cookie = details;
  525. } catch (error) {
  526. throw new Error('Failed to set cookie: ' + error.message);
  527. }
  528. },
  529. delete: async function (details) {
  530. try {
  531. if (!utils.isUndefined(GM_cookie) && GM_cookie.delete) {
  532. return await GM_cookie.delete(details);
  533. }
  534. if (!utils.isUndefined(GM) && GM.cookie && GM.cookie.delete) {
  535. return await GM.cookie.delete(details);
  536. }
  537. } catch (error) {
  538. throw new Error('Failed to delete cookie: ' + error.message);
  539. }
  540. },
  541. },
  542. webRequest: {
  543. listen: function (filter, callback) {
  544. try {
  545. if (!utils.isUndefined(GM_webRequest) && GM_webRequest.listen) {
  546. return GM_webRequest.listen(filter, callback);
  547. }
  548. if (!utils.isUndefined(GM) && GM.webRequest && GM.webRequest.listen) {
  549. return GM.webRequest.listen(filter, callback);
  550. }
  551. } catch (error) {
  552. throw new Error('Failed to listen to web request: ' + error.message);
  553. }
  554. },
  555. onBeforeRequest: function (filter, callback) {
  556. try {
  557. if (!utils.isUndefined(GM_webRequest) && GM_webRequest.onBeforeRequest) {
  558. return GM_webRequest.onBeforeRequest(filter, callback);
  559. }
  560. if (!utils.isUndefined(GM) && GM.webRequest && GM.webRequest.onBeforeRequest) {
  561. return GM.webRequest.onBeforeRequest(filter, callback);
  562. }
  563. } catch (error) {
  564. throw new Error('Failed to handle onBeforeRequest: ' + error.message);
  565. }
  566. },
  567. },
  568. dom: {
  569. addElement: function (tag, attributes = {}, parent = document.body) {
  570. try {
  571. const element = document.createElement(tag);
  572. Object.entries(attributes).forEach(([key, value]) => {
  573. element.setAttribute(key, value);
  574. });
  575. parent.appendChild(element);
  576. return element;
  577. } catch (error) {
  578. throw new Error('Failed to add element: ' + error.message);
  579. }
  580. },
  581. removeElement: function (element) {
  582. try {
  583. if (element && element.parentNode) {
  584. element.parentNode.removeChild(element);
  585. }
  586. } catch (error) {
  587. throw new Error('Failed to remove element: ' + error.message);
  588. }
  589. },
  590. getElement: function (selector) {
  591. try {
  592. return document.querySelector(selector);
  593. } catch (error) {
  594. throw new Error('Failed to get element: ' + error.message);
  595. }
  596. },
  597. getElements: function (selector) {
  598. try {
  599. return Array.from(document.querySelectorAll(selector));
  600. } catch (error) {
  601. throw new Error('Failed to get elements: ' + error.message);
  602. }
  603. },
  604. },
  605. storage: {
  606. setObject: async function (key, obj) {
  607. try {
  608. const jsonStr = JSON.stringify(obj);
  609. return await GMCompat.setValue(key, jsonStr);
  610. } catch (error) {
  611. utils.logger.error('setObject failed:', error);
  612. throw error;
  613. }
  614. },
  615. getObject: async function (key, defaultValue = null) {
  616. try {
  617. const jsonStr = await GMCompat.getValue(key, null);
  618. return jsonStr ? JSON.parse(jsonStr) : defaultValue;
  619. } catch (error) {
  620. utils.logger.error('getObject failed:', error);
  621. return defaultValue;
  622. }
  623. },
  624. appendToArray: async function (key, value) {
  625. try {
  626. const arr = await this.getObject(key, []);
  627. arr.push(value);
  628. await this.setObject(key, arr);
  629. return arr;
  630. } catch (error) {
  631. utils.logger.error('appendToArray failed:', error);
  632. throw error;
  633. }
  634. },
  635. removeFromArray: async function (key, predicate) {
  636. try {
  637. const arr = await this.getObject(key, []);
  638. const filtered = arr.filter(item => !predicate(item));
  639. await this.setObject(key, filtered);
  640. return filtered;
  641. } catch (error) {
  642. utils.logger.error('removeFromArray failed:', error);
  643. throw error;
  644. }
  645. },
  646. },
  647. request: {
  648. downloadWithProgress: async function (url, filename, onProgress) {
  649. try {
  650. return await GMCompat.download({
  651. url: url,
  652. name: filename,
  653. onprogress: onProgress,
  654. saveAs: true,
  655. });
  656. } catch (error) {
  657. utils.logger.error('downloadWithProgress failed:', error);
  658. throw error;
  659. }
  660. },
  661. fetchWithRetry: async function (url, options = {}) {
  662. const finalOptions = {
  663. method: 'GET',
  664. timeout: 10000,
  665. retry: 3,
  666. retryDelay: 1000,
  667. ...options,
  668. };
  669.  
  670. return await utils.retry(
  671. async () => {
  672. return await GMCompat.xmlHttpRequest({
  673. url: url,
  674. ...finalOptions,
  675. });
  676. },
  677. finalOptions.retry,
  678. finalOptions.retryDelay
  679. );
  680. },
  681. },
  682. ui: {
  683. createMenuCommand: function (name, callback, options = {}) {
  684. const defaultOptions = {
  685. accessKey: null,
  686. autoClose: true,
  687. };
  688. const finalOptions = { ...defaultOptions, ...options };
  689.  
  690. return GMCompat.registerMenuCommand(
  691. name,
  692. async (...args) => {
  693. try {
  694. await callback(...args);
  695. if (finalOptions.autoClose) {
  696. window.close();
  697. }
  698. } catch (error) {
  699. utils.logger.error('Menu command failed:', error);
  700. GMCompat.notification({
  701. title: 'Lỗi',
  702. text: `Li thc thi lnh: ${error.message}`,
  703. type: 'error',
  704. });
  705. }
  706. },
  707. finalOptions.accessKey
  708. );
  709. },
  710. toast: function (message, type = 'info', duration = 3000) {
  711. return GMCompat.notification({
  712. title: type.charAt(0).toUpperCase() + type.slice(1),
  713. text: message,
  714. timeout: duration,
  715. onclick: () => {},
  716. silent: true,
  717. highlight: false,
  718. });
  719. },
  720. },
  721. clipboard: {
  722. copyFormatted: async function (text, format = 'text/plain') {
  723. try {
  724. await GMCompat.setClipboard(text, format);
  725. return true;
  726. } catch (error) {
  727. utils.logger.error('copyFormatted failed:', error);
  728. return false;
  729. }
  730. },
  731. copyHTML: async function (html) {
  732. return await this.copyFormatted(html, 'text/html');
  733. },
  734. },
  735. cookies: {
  736. getAll: async function (domain) {
  737. try {
  738. return await GMCompat.cookie.get({ domain: domain });
  739. } catch (error) {
  740. utils.logger.error('getAll cookies failed:', error);
  741. return [];
  742. }
  743. },
  744. setCookie: async function (name, value, options = {}) {
  745. try {
  746. const defaultOptions = {
  747. path: '/',
  748. secure: true,
  749. sameSite: 'Lax',
  750. expirationDate: Math.floor(Date.now() / 1000) + 86400, // 1 day
  751. };
  752.  
  753. await GMCompat.cookie.set({
  754. name: name,
  755. value: value,
  756. ...defaultOptions,
  757. ...options,
  758. });
  759. } catch (error) {
  760. utils.logger.error('setCookie failed:', error);
  761. throw error;
  762. }
  763. },
  764. },
  765. valueChangeListener: {
  766. listeners: new Map(),
  767.  
  768. add: function (name, callback) {
  769. try {
  770. if (typeof GM_addValueChangeListener !== 'undefined') {
  771. const listenerId = GM_addValueChangeListener(name, callback);
  772. this.listeners.set(name, listenerId);
  773. return listenerId;
  774. }
  775. return null;
  776. } catch (error) {
  777. utils.logger.error('Failed to add value change listener:', error);
  778. return null;
  779. }
  780. },
  781.  
  782. remove: function (name) {
  783. try {
  784. const listenerId = this.listeners.get(name);
  785. if (listenerId && typeof GM_removeValueChangeListener !== 'undefined') {
  786. GM_removeValueChangeListener(listenerId);
  787. this.listeners.delete(name);
  788. return true;
  789. }
  790. return false;
  791. } catch (error) {
  792. utils.logger.error('Failed to remove value change listener:', error);
  793. return false;
  794. }
  795. },
  796. },
  797. resource: {
  798. getBlob: async function (name) {
  799. try {
  800. const url = await GMCompat.getResourceURL(name);
  801. const response = await fetch(url);
  802. return await response.blob();
  803. } catch (error) {
  804. utils.logger.error('Failed to get resource blob:', error);
  805. throw error;
  806. }
  807. },
  808.  
  809. getText: async function (name, defaultValue = '') {
  810. try {
  811. return (await GMCompat.getResourceText(name)) || defaultValue;
  812. } catch (error) {
  813. utils.logger.error('Failed to get resource text:', error);
  814. return defaultValue;
  815. }
  816. },
  817. },
  818. tab: {
  819. open: function (url, options = {}) {
  820. const defaultOptions = {
  821. active: true,
  822. insert: true,
  823. setParent: true,
  824. incognito: false,
  825. };
  826.  
  827. try {
  828. return GMCompat.openInTab(url, { ...defaultOptions, ...options });
  829. } catch (error) {
  830. utils.logger.error('Failed to open tab:', error);
  831. return null;
  832. }
  833. },
  834.  
  835. focus: function (tab) {
  836. try {
  837. if (tab && tab.focus) {
  838. tab.focus();
  839. return true;
  840. }
  841. return false;
  842. } catch (error) {
  843. utils.logger.error('Failed to focus tab:', error);
  844. return false;
  845. }
  846. },
  847. },
  848. notification: {
  849. create: function (details) {
  850. const defaultDetails = {
  851. title: '',
  852. text: '',
  853. image: '',
  854. timeout: 5000,
  855. onclick: null,
  856. ondone: null,
  857. silent: false,
  858. };
  859.  
  860. try {
  861. return GMCompat.notification({ ...defaultDetails, ...details });
  862. } catch (error) {
  863. utils.logger.error('Failed to create notification:', error);
  864. return null;
  865. }
  866. },
  867.  
  868. close: function (id) {
  869. try {
  870. if (typeof GM_notification !== 'undefined' && GM_notification.close) {
  871. GM_notification.close(id);
  872. return true;
  873. }
  874. return false;
  875. } catch (error) {
  876. utils.logger.error('Failed to close notification:', error);
  877. return false;
  878. }
  879. },
  880. },
  881. request: {
  882. abort: function (requestId) {
  883. try {
  884. if (typeof GM_xmlhttpRequest !== 'undefined' && GM_xmlhttpRequest.abort) {
  885. GM_xmlhttpRequest.abort(requestId);
  886. return true;
  887. }
  888. return false;
  889. } catch (error) {
  890. utils.logger.error('Failed to abort request:', error);
  891. return false;
  892. }
  893. },
  894. },
  895. cookie: {
  896. list: async function (details = {}) {
  897. try {
  898. if (typeof GM_cookie !== 'undefined' && GM_cookie.list) {
  899. return await GM_cookie.list(details);
  900. }
  901. return [];
  902. } catch (error) {
  903. utils.logger.error('Failed to list cookies:', error);
  904. return [];
  905. }
  906. },
  907.  
  908. deleteAll: async function (details = {}) {
  909. try {
  910. if (typeof GM_cookie !== 'undefined' && GM_cookie.deleteAll) {
  911. return await GM_cookie.deleteAll(details);
  912. }
  913. return false;
  914. } catch (error) {
  915. utils.logger.error('Failed to delete all cookies:', error);
  916. return false;
  917. }
  918. },
  919. },
  920. };
  921. const exportGMCompat = function () {
  922. try {
  923. const target = !utils.isUndefined(unsafeWindow) ? unsafeWindow : window;
  924. Object.defineProperty(target, 'GMCompat', {
  925. value: GMCompat,
  926. writable: false,
  927. configurable: false,
  928. enumerable: true,
  929. });
  930. if (window.onurlchange !== undefined) {
  931. window.addEventListener('urlchange', () => {});
  932. }
  933. } catch (error) {
  934. console.error('Failed to export GMCompat:', error);
  935. }
  936. };
  937. exportGMCompat();
  938. })();