You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dev-sidecar/tampermonkey.js

722 lines
26 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 篡改猴Tampermonkey| 油猴Greasemonkey浏览器脚本扩展
*
* @version 0.1.8
* @since 2024-09-27 14:24
* @author 王良 / wangliang181230
* @authorHomePage https://wangliang1024.cn
* @remark 当前脚本为仿照的版本,并非篡改猴插件的源码,仅供学习参考。
* @description 篡改猴 (Tampermonkey) 是拥有 超过 1000 万用户 的最流行的浏览器扩展之一。 它适用于 Chrome、Microsoft Edge、Safari、Opera Next 和 Firefox。
* 有些人也会把篡改猴(Tampermonkey)称作油猴(Greasemonkey),尽管后者只是一款仅适用于 Firefox 浏览器的浏览器扩展程序。
* 它允许用户自定义并增强您最喜爱的网页的功能。用户脚本是小型 JavaScript 程序,可用于向网页添加新功能或修改现有功能。使用 篡改猴,您可以轻松在任何网站上创建、管理和运行这些用户脚本。
* 例如,使用 篡改猴,您可以向网页添加一个新按钮,可以快速在社交媒体上分享链接,或自动填写带有个人信息的表格。在数字化时代,这特别有用,因为网页常常被用作访问广泛的服务和应用程序的用户界面。
* 此外,篡改猴 使您轻松找到并安装其他用户创建的用户脚本。这意味着您可以快速轻松地访问为您喜爱的网页定制的广泛库,而无需花费数小时编写自己的代码。
* 无论您是希望为您的站点添加新功能的 Web 开发人员,还是只是希望 改善在线体验的普通用户,篡改猴 都是您的工具箱中的一个很好的工具。
* @homepageUrl https://www.tampermonkey.net
*/
'use strict';
(function () {
const version = "0.1.8";
const PRE = "DS-Tampermonkey:"; // 前缀
const MENU_ID_PRE = PRE + "menu-";
const icon = "data:image/png;base64,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";
const context = {
initialized: false, // 是否已经初始化
defaultPluginOptions: {}, // 默认插件选项
pluginOptions: {}, // 插件选项
styleElement: null, // 插件样式元素
pluginElement: null, // 插件元素
arrowElement: null, // 箭头元素
menusElement: null, // 菜单列表元素
userMenusElement: null, // 用户菜单列表元素
menus: {}, // 菜单集合
menuIndex: 0, // 菜单索引用于生成menuCmdId
lastNotification: null // 最后一次通知
/* 最后一次通知的对象结构如下:
{
obj: null, // 通知对象类型Notification
options: null, // 通知选项
timeout: null // 通知定时器
}
*/
};
// 创建插件API
const api = {};
// 监听页面关闭事件,用于关闭最后一个通知
window.addEventListener("beforeunload", function (event) {
api.closeLastNotification();
});
//region DS自定义的API start
// 获取上下文
api.getContext = () => context;
// 创建插件样式
api.createPluginStyle = (options) => {
options = options || {};
// 创建一个新的<style>元素
const styleElement = document.createElement("style");
styleElement.id = PRE + "plugin-style";
// 设置<style>元素的type属性
styleElement.type = "text/css";
// 设置<style>元素的内容
let cssContent = `
.___ds-tampermonkey___{
position: fixed;
right: 10px;
top: 30%;
z-index: 9999;
width: 36px;
height: 36px;
border-radius: 8px;
user-select: none; /* Standard syntax */
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10+/Edge */
background-color: #DDD;
background-repeat: no-repeat;
background-size: cover;
background-image: url("${icon}");
}
.___ds-tampermonkey-hide___{
display: none;
}
.___ds-tampermonkey-hide-icon___{
width: 0;
}
.___ds-menus___{
display: none;
position: absolute;
right: 36px;
top: 0;
z-index: 10000;
min-width: 200px;
min-height: 35px;
border-radius: 8px;
background-color: #323231;
border: 1px solid #52525E;
overflow: hidden;
}
.___ds-tampermonkey___:hover:not(.___ds-tampermonkey-hide-icon___) .___ds-menus___{
display: block;
}
.___ds-tampermonkey-hide-icon___ .___ds-menus___{
display: none;
}
.___ds-menu___{
height: 35px;
line-height: 35px;
padding: 0 10px;
white-space: nowrap;
color: #FFF;
cursor: pointer;
margin-left: 26px;
}
.___ds-menu___.no-click{
cursor: default;
}
.___ds-menu___:hover{
background-color: #855F16;
}
.___ds-menu0___{
margin-left: 0;
font-size: 16px;
font-weight: bold;
}
.___ds-menu0___ img{
width: 23px;
height: 23px;
vertical-align: middle;
margin: 0 8px 3px 8px;
}
.___ds-arrow___{
width: 0;
height: 0;
position: absolute;
top: 11px;
left: 36px;
cursor: pointer;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-left: 10px solid #665c5c;
display: none;
}
.___ds-tampermonkey___:hover .___ds-arrow___{
display: block;
}
.___ds-tampermonkey-hide-icon___ .___ds-arrow___{
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-right: 10px solid #665c5c;
border-left: 0;
left: 0;
display: block;
}
`;
// 如果有自定义样式,则添加到 CSS 内容中
if (options.style) {
cssContent += options.style;
}
// 添加 CSS 内容到<style>元素中
if (styleElement.styleSheet) {
// 兼容 IE
styleElement.styleSheet.cssText = cssContent;
} else {
styleElement.appendChild(document.createTextNode(cssContent));
}
// 将<style>元素添加到<head>中
document.head.append(styleElement);
// 将<style>元素保存在上下文中
context.styleElement = styleElement;
};
// 创建插件div
api.createPluginDiv = (options) => {
options = {
...{ name: "未知名的脚本" },
...options
}
// 创建插件div
context.pluginElement = document.createElement("div");
context.pluginElement.id = PRE + "plugin";
context.pluginElement.title = "油猴插件" + (options.name ? "" + options.name : "");
context.pluginElement.className = "___ds-tampermonkey___";
// 根据配置,设置显示或隐藏
const pluginHide = api.GM_getValue("ds_hide");
if (pluginHide === true || pluginHide === 1) {
api.hideIcon(); // 只隐藏图标
} else if (pluginHide === 2) {
api.hidePlugin(); // 完全隐藏插件
}
// 绑定键盘事件,用于完全隐藏插件以及显示菜单
window.addEventListener("keyup", function (event) {
if (event.ctrlKey && event.altKey && !event.shiftKey) {
if (event.key === 'h' || event.key === 'H') {
api.hidePlugin();
} else if (event.key === 's' || event.key === 'S') {
api.showPlugin();
}
}
});
// 创建菜单列表div
context.menusElement = document.createElement("div");
context.menusElement.id = PRE + "menus";
context.menusElement.className = "___ds-menus___";
if (options.width > 0) {
context.menusElement.style["min-width"] = options.width + "px";
}
// 将菜单列表div添加到插件div中
context.pluginElement.append(context.menusElement);
// 创建开关菜单
const enabled = api.GM_getValue("ds_enabled", true)
const switchMenuElement = document.createElement("div");
const icon = (options.icon ? `<img alt="icon" src="${options.icon}"/>` : " ");
switchMenuElement.id = PRE + "menu-0";
switchMenuElement.className = "___ds-menu___ ___ds-menu0___";
switchMenuElement.innerHTML = (enabled ? "✅" : "❌") + icon + options.name;
switchMenuElement.title = `点击${enabled ? "关闭" : "开启"}此脚本功能`;
switchMenuElement.onclick = function () {
let enabled = api.GM_getValue("ds_enabled", true)
if (enabled) {
api.hideUserMenus();
enabled = false;
} else {
api.showUserMenus();
enabled = true;
}
switchMenuElement.innerHTML = (enabled ? "✅" : "❌") + icon + options.name;
switchMenuElement.title = `点击${enabled ? "关闭" : "开启"}此脚本功能`;
api.GM_setValue("ds_enabled", enabled)
api.GM_notification({
title: "脚本状态变更通知",
text: `${enabled ? "开启" : "关闭"}${options.name}」 功能\n(点击刷新网页后生效)`,
timeout: 3500,
onclick: () => location.reload()
});
};
// 将开关菜单添加到菜单列表div中
context.menusElement.append(switchMenuElement);
// 创建用户菜单列表div
context.userMenusElement = document.createElement("div");
context.userMenusElement.id = PRE + "user-menus";
context.userMenusElement.className = "___ds-user-menus___";
// 将用户菜单div添加到菜单div中
context.menusElement.append(context.userMenusElement);
// 获取body元素
const body = document.getElementsByTagName("body")[0];
// 将插件div添加到body中
body.prepend(context.pluginElement);
}
// 创建箭头
api.createArrow = (options) => {
// 创建箭头元素
context.arrowElement = document.createElement("div");
context.arrowElement.id = PRE + "arrow";
context.arrowElement.className = "___ds-arrow___";
// 初始化title
api.initArrowTitle();
// 绑定点击事件
context.arrowElement.onclick = () => {
if (__ds_global__.getContext().pluginElement.classList.contains("___ds-tampermonkey-hide-icon___")) {
api.showPlugin();
api.initArrowTitle(false);
} else {
api.hideIcon();
api.initArrowTitle(true);
}
}
// 将箭头元素添加到插件div中
context.pluginElement.append(context.arrowElement);
}
api.initArrowTitle = (isHidden) => {
if (isHidden == null) {
isHidden = context.pluginElement.classList.contains("___ds-tampermonkey-hide-icon___");
}
let title;
if (isHidden) {
title = "点击展示「油猴插件」的操作界面";
} else {
title = "点击隐藏「油猴插件」的操作界面";
}
context.arrowElement.title = title + "\n\n快捷键\nCtrl+Alt+H完全隐藏油猴插件\nCtrl+Alt+S显示油猴插件"
}
// 隐藏插件
api.hidePlugin = () => {
if (context.pluginElement) {
context.pluginElement.classList.add("___ds-tampermonkey-hide___");
}
api.GM_setValue("ds_hide", 2);
}
// 隐藏图标
api.hideIcon = () => {
if (context.pluginElement) {
context.pluginElement.classList.add("___ds-tampermonkey-hide-icon___");
}
api.GM_setValue("ds_hide", 1);
}
// 显示插件
api.showPlugin = () => {
if (context.pluginElement) {
context.pluginElement.classList.remove("___ds-tampermonkey-hide___");
context.pluginElement.classList.remove("___ds-tampermonkey-hide-icon___");
}
api.GM_setValue("ds_hide", 0);
}
// 显示用户菜单列表
api.showUserMenus = () => {
if (context.userMenusElement) {
context.userMenusElement.style.display = "block";
}
}
// 隐藏用户菜单列表
api.hideUserMenus = () => {
if (context.userMenusElement) {
context.userMenusElement.style.display = "none";
}
}
// 初始化篡改猴操作界面
api.DS_init = (options) => {
try {
// 如果已经初始化过,则直接返回
if (context.initialized) return;
// 合并默认参数
options = {
...context.defaultPluginOptions,
...options
};
// 创建样式元素
api.createPluginStyle(options);
// 创建插件div
api.createPluginDiv(options);
// 创建箭头
api.createArrow(options);
// 保存参数
context.pluginOptions = options;
// 初始化完成
context.initialized = true;
console.log(`ds_tampermonkey_${version}: initialization completed篡改猴插件初始化完成篡改猴图标已显示在页面右侧鼠标移到上面可展示功能列表`)
} catch (e) {
console.error(`ds_tampermonkey_${version}: initialization failed篡改猴插件初始化失败:`, e);
}
};
// 关闭上一个通知
api.closeLastNotification = () => {
let lastNotification = context.lastNotification;
if (lastNotification) {
context.lastNotification = null;
lastNotification.timeout && clearTimeout(lastNotification.timeout);
try {
lastNotification.obj && lastNotification.obj.close();
} catch (e) {
console.error(`ds_tampermonkey_${version}: GM_notification: 关闭上一个通知失败:`, e);
}
}
};
//endregion DS自定义的API end
//region 篡改猴标准API由DS自定义实现 start
// 注册菜单
api.GM_registerMenuCommand = (name, callback, options_or_accessKey) => {
const options = typeof options_or_accessKey === "string" ? { accessKey: options_or_accessKey } : (options_or_accessKey || {});
// 生成菜单ID
let menuCmdId;
if (options.id) {
if (typeof options.id !== "string") {
options.id = options.id.toString();
}
menuCmdId = (options.id.indexOf(MENU_ID_PRE) === 0 ? '' : MENU_ID_PRE) + options.id;
// 如果是数字ID为了避免与自增ID索引冲突将数字ID赋值给自增ID索引
if (options.id.match("^\\d+$")) {
const numberId = parseInt(options.id);
if (numberId > context.menuIndex) {
context.menuIndex = numberId;
}
}
} else {
menuCmdId = MENU_ID_PRE + (++context.menuIndex);
}
// 创建菜单元素
const menuElement = document.createElement("div");
menuElement.id = menuCmdId;
menuElement.className = "___ds-menu___";
menuElement.innerHTML = name;
if (options.title) {
menuElement.title = typeof options.title === "function" ? options.title() : options.title;
}
if (callback) {
menuElement.onclick = callback;
} else {
menuElement.className += " no-click"
}
if (options.accessKey) {
// TODO: 快捷键功能待开发篡改猴官方文档https://www.tampermonkey.net/documentation.php#api:GM_registerMenuCommand
}
// 将菜单元素添加到菜单列表div中
context.userMenusElement.append(menuElement);
// 将菜单添加到菜单集合中
context.menus[menuCmdId] = {
name: name,
callback: callback,
options: options,
element: menuElement
};
// 返回菜单ID
return menuCmdId;
};
// 删除菜单
api.GM_unregisterMenuCommand = (menuCmdId) => {
if (menuCmdId == null) {
return;
}
if (typeof menuCmdId !== "string") {
menuCmdId = menuCmdId.toString();
}
if (menuCmdId.indexOf(MENU_ID_PRE) !== 0) {
menuCmdId = MENU_ID_PRE + menuCmdId;
}
const menu = context.menus[menuCmdId];
if (menu) {
menu.element.remove();
delete context.menus[menuCmdId];
} else {
const menuElement = document.getElementById(menuCmdId)
if (menuElement) {
menuElement.remove();
}
}
};
// 打开新标签
api.GM_openInTab = (url, options_or_loadInBackground) => {
// const options = typeof options_or_loadInBackground === "boolean"
// ? { loadInBackground: options_or_loadInBackground }
// : (options_or_loadInBackground || {});
window.open(url)
};
// 获取配置
api.GM_getValue = (key, defaultValue) => {
key = PRE + key;
const valueStr = localStorage.getItem(key);
if (valueStr == null || valueStr === '') {
return defaultValue;
}
try {
return JSON.parse(valueStr).v;
} catch (e) {
}
return valueStr;
};
// 设置配置
api.GM_setValue = (key, value) => {
key = PRE + key;
localStorage.setItem(key, JSON.stringify({ v: value }));
};
// 删除设置
api.GM_deleteValue = (key) => {
key = PRE + key;
localStorage.removeItem(key);
};
// 通知
api.GM_notification = (details_or_text, ondone_or_title, image, onclick) => {
// param1
let options = typeof details_or_text === "string" ? { text: details_or_text } : details_or_text;
if (typeof options !== "object") {
console.error(`ds_tampermonkey_${version}: GM_notification: 无效的参数值details_or_text = ` + details_or_text);
return;
}
// param2
if (typeof ondone_or_title === "string") {
options.title = ondone_or_title;
} else if (typeof ondone_or_title === "function") {
options.ondone = ondone_or_title;
} else if (ondone_or_title != null) {
console.warn(`ds_tampermonkey_${version}: GM_notification: 无效的参数值ondone_or_title = ` + ondone_or_title);
}
// param3
if (typeof image === "string") {
options.image = image;
} else if (image != null) {
console.warn(`ds_tampermonkey_${version}: GM_notification: 无效的参数值image = ` + image);
}
// param4
if (typeof onclick === "function") {
options.onclick = onclick;
} else if (onclick != null) {
console.warn(`ds_tampermonkey_${version}: GM_notification: 无效的参数值onclick = ` + onclick);
}
// 显示通知方法
const showNotification = () => {
// 先关闭上一个通知
api.closeLastNotification();
// 获取标题和文本
let text = options.text;
let title = options.title;
if (title == null) {
title = text;
text = null;
} else {
delete options.title;
}
delete options.text;
// 创建通知属性
const notificationOptions = {
...options,
icon: options.image || options.icon || (context.pluginOptions ? context.pluginOptions.icon : null) || icon
};
if (text) notificationOptions.body = text;
// 创建通知
const notification = new Notification(title, notificationOptions);
// 将通知对象保存到context中
const lastNotification = {
obj: notification,
options: options,
timeout: null
}
context.lastNotification = lastNotification;
// 设置点击通知事件
if (options.onclick) {
notification.onclick = () => options.onclick();
}
// 设置通知关闭事件
if (typeof options.ondone === "function" || typeof options.onclose === "function") {
notification.onclose = () => {
// 执行回调方法
if (typeof options.ondone === "function") {
try {
options.ondone();
} catch (e) {
console.error(`ds_tampermonkey_${version}: GM_notification: ondone回调函数执行失败`, e);
}
}
// 执行关闭方法
if (typeof options.onclose === "function") {
try {
options.onclose();
} catch (e) {
console.error(`ds_tampermonkey_${version}: GM_notification: onclose关闭函数执行失败`, e);
}
}
}
}
// 设置定时关闭
if (options.timeout) {
lastNotification.timeout = setTimeout(() => {
context.lastNotification = null;
notification.close();
}, options.timeout);
}
return notification;
};
// 当不支持Notification API则使用alert显示通知
const showAlert = () => {
let text = options.text;
if (options.title) {
text = options.title + ": " + text;
}
alert(text);
if (options.ondone) options.ondone(); // 回调
};
// 检查浏览器是否支持Notification API
if (!("Notification" in window)) {
showAlert(); // 不支持直接使用alert显示通知
}
// 检查用户是否已授予权限
else if (Notification.permission === "granted") {
// 如果用户已授予权限,我们可以显示通知
showNotification();
}
// 否则,先请求权限
else if (Notification.permission !== "denied") {
Notification.requestPermission(function (permission) {
if (permission === "granted") {
showNotification(); // 用户接受权限,我们可以显示通知
} else {
showAlert(); // 用户驳回了权限直接使用alert显示通知
}
});
}
};
// 将剪贴板的文本设置为指定值
api.GM_setClipboard = async (data, info, callback) => {
// data为空时直接返回
if (data == null) {
return;
}
// 设置info
if (typeof info === "function") {
callback = info;
info = {};
} else if (typeof info === "string") {
info = { type: info };
} else if (typeof info !== "object") {
info = {};
}
const doCallback = async () => {
// 提示设置成功
if (info.notification !== false) {
api.GM_notification({
title: info.notificationTitle,
text: info.notification || "内容复制成功,请使用 Ctrl+V 粘贴内容吧!",
timeout: 2000
});
}
// 设置剪切板成功,执行回调方法
if (typeof callback === "function") {
try {
callback();
} catch (e) {
console.error(`ds_tampermonkey_${version}: GM_setClipboard: 回滚方法执行失败:`, e);
}
}
};
try {
const mimeType = info.mimetype;
if (typeof mimeType === "string" && mimeType.length > 0) {
const dataArr = typeof data === "object" && data.length >= 0 ? data : [data];
const blob = new Blob(dataArr, { type: mimeType });
const clipboardItem = new ClipboardItem({ [mimeType]: blob });
navigator.clipboard.write([clipboardItem])
.then(() => {
doCallback();
})
.catch(e => {
console.error(`ds_tampermonkey_${version}: GM_setClipboard: 写入剪贴板失败:`, e);
});
} else {
// data转换为string类型
if (typeof data === "object") {
data = JSON.stringify(data);
} else if (typeof data === "string") {
// do nothing
} else if (typeof data === "number") {
data = data.toString();
} else {
console.error(`ds_tampermonkey_${version}: GM_setClipboard: 无效的参数值dataType${typeof data}, data`, data);
return;
}
navigator.clipboard.writeText(data)
.then(() => {
doCallback();
})
.catch(e => {
console.error(`ds_tampermonkey_${version}: GM_setClipboard: 写入剪贴板失败:`, e);
});
}
} catch (e) {
console.error(`ds_tampermonkey_${version}: GM_setClipboard: 写入剪贴板失败:`, e);
}
}
//endregion 篡改猴标准API由DS自定义实现 end
// 设置API
window.__ds_global__ = api;
// 模块化支持
if (typeof module !== "undefined") {
module.exports = api;
}
console.log(`ds_tampermonkey_${version}: completed`)
})();