UserScript Compatibility Library

A library to ensure compatibility between different userscript managers

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greatest.deepsurf.us/scripts/519877/1507987/UserScript%20Compatibility%20Library.js

// ==UserScript==
// @name         UserScript Compatibility Library
// @name:en      UserScript Compatibility Library
// @name:zh-CN   UserScript 兼容库
// @name:ru      Библиотека совместимости для пользовательских скриптов
// @name:vi      Thư viện tương thích cho userscript
// @namespace    https://greatest.deepsurf.us/vi/users/1195312-renji-yuusei
// @version      2024.12.23.1
// @description  A library to ensure compatibility between different userscript managers
// @description:en A library to ensure compatibility between different userscript managers
// @description:zh-CN 确保不同用户脚本管理器之间兼容性的库
// @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
// @description:ru  Библиотека для обеспечения совместимости между различными менеджерами пользовательских скриптов
// @author       Yuusei
// @license      GPL-3.0-only
// @grant        unsafeWindow
// @grant        GM_info
// @grant        GM.info
// @grant        GM_getValue
// @grant        GM.getValue
// @grant        GM_setValue
// @grant        GM.setValue
// @grant        GM_deleteValue
// @grant        GM.deleteValue
// @grant        GM_listValues
// @grant        GM.listValues
// @grant        GM_xmlhttpRequest
// @grant        GM.xmlHttpRequest
// @grant        GM_download
// @grant        GM.download
// @grant        GM_notification
// @grant        GM.notification
// @grant        GM_addStyle
// @grant        GM.addStyle
// @grant        GM_registerMenuCommand
// @grant        GM.registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM.unregisterMenuCommand
// @grant        GM_setClipboard
// @grant        GM.setClipboard
// @grant        GM_getResourceText
// @grant        GM.getResourceText
// @grant        GM_getResourceURL
// @grant        GM.getResourceURL
// @grant        GM_openInTab
// @grant        GM.openInTab
// @grant        GM_addElement
// @grant        GM.addElement
// @grant        GM_addValueChangeListener
// @grant        GM.addValueChangeListener
// @grant        GM_removeValueChangeListener
// @grant        GM.removeValueChangeListener
// @grant        GM_log
// @grant        GM.log
// @grant        GM_getTab
// @grant        GM.getTab
// @grant        GM_saveTab
// @grant        GM.saveTab
// @grant        GM_getTabs
// @grant        GM.getTabs
// @grant        GM_cookie
// @grant        GM.cookie
// @grant        GM_webRequest
// @grant        GM.webRequest
// @grant        GM_fetch
// @grant        GM.fetch
// @grant        window.close
// @grant        window.focus
// @grant        window.onurlchange
// @grant        GM_addValueChangeListener
// @grant        GM_removeValueChangeListener
// @grant        GM_getResourceURL
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @grant        GM_openInTab
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_setClipboard
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @grant        GM_download
// @grant        GM_cookie.get
// @grant        GM_cookie.set
// @grant        GM_cookie.delete
// @grant        GM_webRequest.listen
// @grant        GM_webRequest.onBeforeRequest
// @grant        GM_addElement.byTag
// @grant        GM_addElement.byId
// @grant        GM_addElement.byClass
// @grant        GM_addElement.byXPath
// @grant        GM_addElement.bySelector
// @grant        GM_removeElement
// @grant        GM_removeElements
// @grant        GM_getElement
// @grant        GM_getElements
// @grant        GM_addScript
// @grant        GM_removeScript
// @grant        GM_addLink
// @grant        GM_removeLink
// @grant        GM_addMeta
// @grant        GM_removeMeta
// @grant        GM_addIframe
// @grant        GM_removeIframe
// @grant        GM_addImage
// @grant        GM_removeImage
// @grant        GM_addVideo
// @grant        GM_removeVideo
// @grant        GM_addAudio
// @grant        GM_removeAudio
// @grant        GM_addCanvas
// @grant        GM_removeCanvas
// @grant        GM_addSvg
// @grant        GM_removeSvg
// @grant        GM_addObject
// @grant        GM_removeObject
// @grant        GM_addEmbed
// @grant        GM_removeEmbed
// @grant        GM_addApplet
// @grant        GM_removeApplet
// @run-at       document-start
// @license      GPL-3.0-only
// @grant        GM_addValueChangeListener.remove
// @grant        GM_getResourceURL.blob
// @grant        GM_notification.close
// @grant        GM_openInTab.focus
// @grant        GM_setClipboard.format
// @grant        GM_xmlhttpRequest.abort
// @grant        GM_download.progress
// @grant        GM_cookie.list
// @grant        GM_cookie.deleteAll
// @grant        GM_webRequest.filter
// @grant        GM_addElement.create
// @grant        GM_removeElement.all
// @grant        GM_getElement.all
// @grant        GM_addScript.remote
// @grant        GM_addLink.stylesheet
// @grant        GM_addMeta.viewport
// @grant        GM_addIframe.sandbox
// @grant        GM_addImage.lazy
// @grant        GM_addVideo.controls
// @grant        GM_addAudio.autoplay
// @grant        GM_addCanvas.context
// @grant        GM_addSvg.namespace
// @grant        GM_addObject.data
// @grant        GM_addEmbed.type
// @grant        GM_addApplet.code
// ==/UserScript==
(function () {
	'use strict';
	const utils = {
		isFunction: function (fn) {
			return typeof fn === 'function';
		},
		isUndefined: function (value) {
			return typeof value === 'undefined';
		},
		isObject: function (value) {
			return value !== null && typeof value === 'object';
		},
		sleep: function (ms) {
			return new Promise(resolve => setTimeout(resolve, ms));
		},
		retry: async function (fn, attempts = 3, delay = 1000) {
			let lastError;
			for (let i = 0; i < attempts; i++) {
				try {
					return await fn();
				} catch (error) {
					lastError = error;
					if (i === attempts - 1) break;
					await this.sleep(delay * Math.pow(2, i));
				}
			}
			throw lastError;
		},
		debounce: function (fn, wait) {
			let timeout;
			return function (...args) {
				clearTimeout(timeout);
				timeout = setTimeout(() => fn.apply(this, args), wait);
			};
		},
		throttle: function (fn, limit) {
			let timeout;
			let inThrottle;
			return function (...args) {
				if (!inThrottle) {
					fn.apply(this, args);
					inThrottle = true;
					clearTimeout(timeout);
					timeout = setTimeout(() => (inThrottle = false), limit);
				}
			};
		},
		// Thêm các tiện ích mới
		isArray: function (arr) {
			return Array.isArray(arr);
		},
		isString: function (str) {
			return typeof str === 'string';
		},
		isNumber: function (num) {
			return typeof num === 'number' && !isNaN(num);
		},
		isBoolean: function (bool) {
			return typeof bool === 'boolean';
		},
		isNull: function (value) {
			return value === null;
		},
		isEmpty: function (value) {
			if (this.isArray(value)) return value.length === 0;
			if (this.isObject(value)) return Object.keys(value).length === 0;
			if (this.isString(value)) return value.trim().length === 0;
			return false;
		},
	};
	const GMCompat = {
		info: (function () {
			if (!utils.isUndefined(GM_info)) return GM_info;
			if (!utils.isUndefined(GM) && GM.info) return GM.info;
			return {};
		})(),
		storageCache: new Map(),
		cacheTimestamps: new Map(),
		cacheExpiry: 3600000, // 1 hour
		getValue: async function (key, defaultValue) {
			try {
				if (this.storageCache.has(key)) {
					const timestamp = this.cacheTimestamps.get(key);
					if (Date.now() - timestamp < this.cacheExpiry) {
						return this.storageCache.get(key);
					}
				}
				let value;
				if (!utils.isUndefined(GM_getValue)) {
					value = GM_getValue(key, defaultValue);
				} else if (!utils.isUndefined(GM) && GM.getValue) {
					value = await GM.getValue(key, defaultValue);
				} else {
					value = defaultValue;
				}
				this.storageCache.set(key, value);
				this.cacheTimestamps.set(key, Date.now());
				return value;
			} catch (error) {
				console.error('getValue error:', error);
				return defaultValue;
			}
		},
		setValue: async function (key, value) {
			try {
				this.storageCache.set(key, value);
				this.cacheTimestamps.set(key, Date.now());
				if (!utils.isUndefined(GM_setValue)) {
					return GM_setValue(key, value);
				}
				if (!utils.isUndefined(GM) && GM.setValue) {
					return await GM.setValue(key, value);
				}
			} catch (error) {
				this.storageCache.delete(key);
				this.cacheTimestamps.delete(key);
				throw new Error('Failed to set value: ' + error.message);
			}
		},
		deleteValue: async function (key) {
			try {
				this.storageCache.delete(key);
				this.cacheTimestamps.delete(key);
				if (!utils.isUndefined(GM_deleteValue)) {
					return GM_deleteValue(key);
				}
				if (!utils.isUndefined(GM) && GM.deleteValue) {
					return await GM.deleteValue(key);
				}
			} catch (error) {
				throw new Error('Failed to delete value: ' + error.message);
			}
		},
		requestQueue: [],
		processingRequest: false,
		maxRetries: 3,
		retryDelay: 1000,
		xmlHttpRequest: async function (details) {
			const makeRequest = () => {
				return new Promise((resolve, reject) => {
					try {
						const callbacks = {
							onload: resolve,
							onerror: reject,
							ontimeout: reject,
							onprogress: details.onprogress,
							onreadystatechange: details.onreadystatechange,
						};
						const finalDetails = {
							timeout: 30000,
							...details,
							...callbacks,
						};
						if (!utils.isUndefined(GM_xmlhttpRequest)) {
							GM_xmlhttpRequest(finalDetails);
						} else if (!utils.isUndefined(GM) && GM.xmlHttpRequest) {
							GM.xmlHttpRequest(finalDetails);
						} else if (!utils.isUndefined(GM_fetch)) {
							GM_fetch(finalDetails.url, finalDetails);
						} else if (!utils.isUndefined(GM) && GM.fetch) {
							GM.fetch(finalDetails.url, finalDetails);
						} else {
							reject(new Error('XMLHttpRequest API not available'));
						}
					} catch (error) {
						reject(error);
					}
				});
			};
			return utils.retry(makeRequest, this.maxRetries, this.retryDelay);
		},
		download: async function (details) {
			try {
				const downloadWithProgress = {
					...details,
					onprogress: details.onprogress,
					onerror: details.onerror,
					onload: details.onload,
				};
				if (!utils.isUndefined(GM_download)) {
					return new Promise((resolve, reject) => {
						GM_download({
							...downloadWithProgress,
							onload: resolve,
							onerror: reject,
						});
					});
				}
				if (!utils.isUndefined(GM) && GM.download) {
					return await GM.download(downloadWithProgress);
				}
				throw new Error('Download API not available');
			} catch (error) {
				throw new Error('Download failed: ' + error.message);
			}
		},
		notification: function (details) {
			return new Promise((resolve, reject) => {
				try {
					const defaultOptions = {
						timeout: 5000,
						highlight: false,
						silent: false,
						requireInteraction: false,
						priority: 0,
					};
					const callbacks = {
						onclick: utils.debounce((...args) => {
							if (details.onclick) details.onclick(...args);
							resolve('clicked');
						}, 300),
						ondone: (...args) => {
							if (details.ondone) details.ondone(...args);
							resolve('closed');
						},
						onerror: (...args) => {
							if (details.onerror) details.onerror(...args);
							reject('error');
						},
					};
					const finalDetails = { ...defaultOptions, ...details, ...callbacks };
					if (!utils.isUndefined(GM_notification)) {
						GM_notification(finalDetails);
					} else if (!utils.isUndefined(GM) && GM.notification) {
						GM.notification(finalDetails);
					} else {
						if ('Notification' in window) {
							Notification.requestPermission().then(permission => {
								if (permission === 'granted') {
									const notification = new Notification(finalDetails.title, {
										body: finalDetails.text,
										silent: finalDetails.silent,
										icon: finalDetails.image,
										tag: finalDetails.tag,
										requireInteraction: finalDetails.requireInteraction,
										badge: finalDetails.badge,
										vibrate: finalDetails.vibrate,
									});
									notification.onclick = callbacks.onclick;
									notification.onerror = callbacks.onerror;
									if (finalDetails.timeout > 0) {
										setTimeout(() => {
											notification.close();
											callbacks.ondone();
										}, finalDetails.timeout);
									}
								} else {
									reject(new Error('Notification permission denied'));
								}
							});
						} else {
							reject(new Error('Notification API not available'));
						}
					}
				} catch (error) {
					reject(error);
				}
			});
		},
		addStyle: function (css) {
			try {
				const testStyle = document.createElement('style');
				testStyle.textContent = css;
				if (testStyle.sheet === null) {
					throw new Error('Invalid CSS');
				}
				if (!utils.isUndefined(GM_addStyle)) {
					return GM_addStyle(css);
				}
				if (!utils.isUndefined(GM) && GM.addStyle) {
					return GM.addStyle(css);
				}
				const style = document.createElement('style');
				style.textContent = css;
				style.type = 'text/css';
				document.head.appendChild(style);
				return style;
			} catch (error) {
				throw new Error('Failed to add style: ' + error.message);
			}
		},
		registerMenuCommand: function (name, fn, accessKey) {
			try {
				if (!utils.isFunction(fn)) {
					throw new Error('Command callback must be a function');
				}
				if (!utils.isUndefined(GM_registerMenuCommand)) {
					return GM_registerMenuCommand(name, fn, accessKey);
				}
				if (!utils.isUndefined(GM) && GM.registerMenuCommand) {
					return GM.registerMenuCommand(name, fn, accessKey);
				}
			} catch (error) {
				throw new Error('Failed to register menu command: ' + error.message);
			}
		},
		setClipboard: function (text, info) {
			try {
				if (!utils.isUndefined(GM_setClipboard)) {
					return GM_setClipboard(text, info);
				}
				if (!utils.isUndefined(GM) && GM.setClipboard) {
					return GM.setClipboard(text, info);
				}
				return navigator.clipboard.writeText(text);
			} catch (error) {
				throw new Error('Failed to set clipboard: ' + error.message);
			}
		},
		getResourceText: async function (name) {
			try {
				if (!utils.isUndefined(GM_getResourceText)) {
					return GM_getResourceText(name);
				}
				if (!utils.isUndefined(GM) && GM.getResourceText) {
					return await GM.getResourceText(name);
				}
				throw new Error('Resource API not available');
			} catch (error) {
				throw new Error('Failed to get resource text: ' + error.message);
			}
		},
		getResourceURL: async function (name) {
			try {
				if (!utils.isUndefined(GM_getResourceURL)) {
					return GM_getResourceURL(name);
				}
				if (!utils.isUndefined(GM) && GM.getResourceURL) {
					return await GM.getResourceURL(name);
				}
				throw new Error('Resource URL API not available');
			} catch (error) {
				throw new Error('Failed to get resource URL: ' + error.message);
			}
		},
		openInTab: function (url, options = {}) {
			try {
				const defaultOptions = {
					active: true,
					insert: true,
					setParent: true,
				};
				const finalOptions = { ...defaultOptions, ...options };
				if (!utils.isUndefined(GM_openInTab)) {
					return GM_openInTab(url, finalOptions);
				}
				if (!utils.isUndefined(GM) && GM.openInTab) {
					return GM.openInTab(url, finalOptions);
				}
				return window.open(url, '_blank');
			} catch (error) {
				throw new Error('Failed to open tab: ' + error.message);
			}
		},
		cookie: {
			get: async function (details) {
				try {
					if (!utils.isUndefined(GM_cookie) && GM_cookie.get) {
						return await GM_cookie.get(details);
					}
					if (!utils.isUndefined(GM) && GM.cookie && GM.cookie.get) {
						return await GM.cookie.get(details);
					}
					return document.cookie;
				} catch (error) {
					throw new Error('Failed to get cookie: ' + error.message);
				}
			},
			set: async function (details) {
				try {
					if (!utils.isUndefined(GM_cookie) && GM_cookie.set) {
						return await GM_cookie.set(details);
					}
					if (!utils.isUndefined(GM) && GM.cookie && GM.cookie.set) {
						return await GM.cookie.set(details);
					}
					document.cookie = details;
				} catch (error) {
					throw new Error('Failed to set cookie: ' + error.message);
				}
			},
			delete: async function (details) {
				try {
					if (!utils.isUndefined(GM_cookie) && GM_cookie.delete) {
						return await GM_cookie.delete(details);
					}
					if (!utils.isUndefined(GM) && GM.cookie && GM.cookie.delete) {
						return await GM.cookie.delete(details);
					}
				} catch (error) {
					throw new Error('Failed to delete cookie: ' + error.message);
				}
			},
		},
		webRequest: {
			listen: function (filter, callback) {
				try {
					if (!utils.isUndefined(GM_webRequest) && GM_webRequest.listen) {
						return GM_webRequest.listen(filter, callback);
					}
					if (!utils.isUndefined(GM) && GM.webRequest && GM.webRequest.listen) {
						return GM.webRequest.listen(filter, callback);
					}
				} catch (error) {
					throw new Error('Failed to listen to web request: ' + error.message);
				}
			},
			onBeforeRequest: function (filter, callback) {
				try {
					if (!utils.isUndefined(GM_webRequest) && GM_webRequest.onBeforeRequest) {
						return GM_webRequest.onBeforeRequest(filter, callback);
					}
					if (!utils.isUndefined(GM) && GM.webRequest && GM.webRequest.onBeforeRequest) {
						return GM.webRequest.onBeforeRequest(filter, callback);
					}
				} catch (error) {
					throw new Error('Failed to handle onBeforeRequest: ' + error.message);
				}
			},
		},
		dom: {
			addElement: function (tag, attributes = {}, parent = document.body) {
				try {
					const element = document.createElement(tag);
					Object.entries(attributes).forEach(([key, value]) => {
						element.setAttribute(key, value);
					});
					parent.appendChild(element);
					return element;
				} catch (error) {
					throw new Error('Failed to add element: ' + error.message);
				}
			},
			removeElement: function (element) {
				try {
					if (element && element.parentNode) {
						element.parentNode.removeChild(element);
					}
				} catch (error) {
					throw new Error('Failed to remove element: ' + error.message);
				}
			},
			getElement: function (selector) {
				try {
					return document.querySelector(selector);
				} catch (error) {
					throw new Error('Failed to get element: ' + error.message);
				}
			},
			getElements: function (selector) {
				try {
					return Array.from(document.querySelectorAll(selector));
				} catch (error) {
					throw new Error('Failed to get elements: ' + error.message);
				}
			},
		},
		storage: {
			setObject: async function (key, obj) {
				try {
					const jsonStr = JSON.stringify(obj);
					return await GMCompat.setValue(key, jsonStr);
				} catch (error) {
					utils.logger.error('setObject failed:', error);
					throw error;
				}
			},
			getObject: async function (key, defaultValue = null) {
				try {
					const jsonStr = await GMCompat.getValue(key, null);
					return jsonStr ? JSON.parse(jsonStr) : defaultValue;
				} catch (error) {
					utils.logger.error('getObject failed:', error);
					return defaultValue;
				}
			},
			appendToArray: async function (key, value) {
				try {
					const arr = await this.getObject(key, []);
					arr.push(value);
					await this.setObject(key, arr);
					return arr;
				} catch (error) {
					utils.logger.error('appendToArray failed:', error);
					throw error;
				}
			},
			removeFromArray: async function (key, predicate) {
				try {
					const arr = await this.getObject(key, []);
					const filtered = arr.filter(item => !predicate(item));
					await this.setObject(key, filtered);
					return filtered;
				} catch (error) {
					utils.logger.error('removeFromArray failed:', error);
					throw error;
				}
			},
		},
		request: {
			downloadWithProgress: async function (url, filename, onProgress) {
				try {
					return await GMCompat.download({
						url: url,
						name: filename,
						onprogress: onProgress,
						saveAs: true,
					});
				} catch (error) {
					utils.logger.error('downloadWithProgress failed:', error);
					throw error;
				}
			},
			fetchWithRetry: async function (url, options = {}) {
				const finalOptions = {
					method: 'GET',
					timeout: 10000,
					retry: 3,
					retryDelay: 1000,
					...options,
				};

				return await utils.retry(
					async () => {
						return await GMCompat.xmlHttpRequest({
							url: url,
							...finalOptions,
						});
					},
					finalOptions.retry,
					finalOptions.retryDelay
				);
			},
		},
		ui: {
			createMenuCommand: function (name, callback, options = {}) {
				const defaultOptions = {
					accessKey: null,
					autoClose: true,
				};
				const finalOptions = { ...defaultOptions, ...options };

				return GMCompat.registerMenuCommand(
					name,
					async (...args) => {
						try {
							await callback(...args);
							if (finalOptions.autoClose) {
								window.close();
							}
						} catch (error) {
							utils.logger.error('Menu command failed:', error);
							GMCompat.notification({
								title: 'Lỗi',
								text: `Lỗi thực thi lệnh: ${error.message}`,
								type: 'error',
							});
						}
					},
					finalOptions.accessKey
				);
			},
			toast: function (message, type = 'info', duration = 3000) {
				return GMCompat.notification({
					title: type.charAt(0).toUpperCase() + type.slice(1),
					text: message,
					timeout: duration,
					onclick: () => {},
					silent: true,
					highlight: false,
				});
			},
		},
		clipboard: {
			copyFormatted: async function (text, format = 'text/plain') {
				try {
					await GMCompat.setClipboard(text, format);
					return true;
				} catch (error) {
					utils.logger.error('copyFormatted failed:', error);
					return false;
				}
			},
			copyHTML: async function (html) {
				return await this.copyFormatted(html, 'text/html');
			},
		},
		cookies: {
			getAll: async function (domain) {
				try {
					return await GMCompat.cookie.get({ domain: domain });
				} catch (error) {
					utils.logger.error('getAll cookies failed:', error);
					return [];
				}
			},
			setCookie: async function (name, value, options = {}) {
				try {
					const defaultOptions = {
						path: '/',
						secure: true,
						sameSite: 'Lax',
						expirationDate: Math.floor(Date.now() / 1000) + 86400, // 1 day
					};

					await GMCompat.cookie.set({
						name: name,
						value: value,
						...defaultOptions,
						...options,
					});
				} catch (error) {
					utils.logger.error('setCookie failed:', error);
					throw error;
				}
			},
		},
		valueChangeListener: {
			listeners: new Map(),

			add: function (name, callback) {
				try {
					if (typeof GM_addValueChangeListener !== 'undefined') {
						const listenerId = GM_addValueChangeListener(name, callback);
						this.listeners.set(name, listenerId);
						return listenerId;
					}
					return null;
				} catch (error) {
					utils.logger.error('Failed to add value change listener:', error);
					return null;
				}
			},

			remove: function (name) {
				try {
					const listenerId = this.listeners.get(name);
					if (listenerId && typeof GM_removeValueChangeListener !== 'undefined') {
						GM_removeValueChangeListener(listenerId);
						this.listeners.delete(name);
						return true;
					}
					return false;
				} catch (error) {
					utils.logger.error('Failed to remove value change listener:', error);
					return false;
				}
			},
		},
		resource: {
			getBlob: async function (name) {
				try {
					const url = await GMCompat.getResourceURL(name);
					const response = await fetch(url);
					return await response.blob();
				} catch (error) {
					utils.logger.error('Failed to get resource blob:', error);
					throw error;
				}
			},

			getText: async function (name, defaultValue = '') {
				try {
					return (await GMCompat.getResourceText(name)) || defaultValue;
				} catch (error) {
					utils.logger.error('Failed to get resource text:', error);
					return defaultValue;
				}
			},
		},
		tab: {
			open: function (url, options = {}) {
				const defaultOptions = {
					active: true,
					insert: true,
					setParent: true,
					incognito: false,
				};

				try {
					return GMCompat.openInTab(url, { ...defaultOptions, ...options });
				} catch (error) {
					utils.logger.error('Failed to open tab:', error);
					return null;
				}
			},

			focus: function (tab) {
				try {
					if (tab && tab.focus) {
						tab.focus();
						return true;
					}
					return false;
				} catch (error) {
					utils.logger.error('Failed to focus tab:', error);
					return false;
				}
			},
		},
		notification: {
			create: function (details) {
				const defaultDetails = {
					title: '',
					text: '',
					image: '',
					timeout: 5000,
					onclick: null,
					ondone: null,
					silent: false,
				};

				try {
					return GMCompat.notification({ ...defaultDetails, ...details });
				} catch (error) {
					utils.logger.error('Failed to create notification:', error);
					return null;
				}
			},

			close: function (id) {
				try {
					if (typeof GM_notification !== 'undefined' && GM_notification.close) {
						GM_notification.close(id);
						return true;
					}
					return false;
				} catch (error) {
					utils.logger.error('Failed to close notification:', error);
					return false;
				}
			},
		},
		request: {
			abort: function (requestId) {
				try {
					if (typeof GM_xmlhttpRequest !== 'undefined' && GM_xmlhttpRequest.abort) {
						GM_xmlhttpRequest.abort(requestId);
						return true;
					}
					return false;
				} catch (error) {
					utils.logger.error('Failed to abort request:', error);
					return false;
				}
			},
		},
		cookie: {
			list: async function (details = {}) {
				try {
					if (typeof GM_cookie !== 'undefined' && GM_cookie.list) {
						return await GM_cookie.list(details);
					}
					return [];
				} catch (error) {
					utils.logger.error('Failed to list cookies:', error);
					return [];
				}
			},

			deleteAll: async function (details = {}) {
				try {
					if (typeof GM_cookie !== 'undefined' && GM_cookie.deleteAll) {
						return await GM_cookie.deleteAll(details);
					}
					return false;
				} catch (error) {
					utils.logger.error('Failed to delete all cookies:', error);
					return false;
				}
			},
		},
	};
	const exportGMCompat = function () {
		try {
			const target = !utils.isUndefined(unsafeWindow) ? unsafeWindow : window;
			Object.defineProperty(target, 'GMCompat', {
				value: GMCompat,
				writable: false,
				configurable: false,
				enumerable: true,
			});
			if (window.onurlchange !== undefined) {
				window.addEventListener('urlchange', () => {});
			}
		} catch (error) {
			console.error('Failed to export GMCompat:', error);
		}
	};
	exportGMCompat();
})();