标签 Tampermonkey 下的文章

各位早中下午好,不知道各位看视频爱不爱看弹幕,反正我是离不开了,恨不得去电影院都能开着弹幕看。就好这一口氛围感。从去年开始 B 站就回到以前的大航海时代,资源满天飞,但还得和版权方躲猫猫,导致要么是 N 个 UP 上传各种神命名的番剧,要么就是来不及看被补档了,导致弹幕分布在各个角落,为了自己看的爽一点,搞了个弹幕合并器脚本。完成得差不多了,分享出来。

预览

搜索界面

通过关键词或 BV 号快速查找目标视频。
搜索界面

管理界面

选择分 P 并设置时间偏移。
管理界面

弹幕效果

多源弹幕同屏显示,增强互动感。
弹幕效果

支持时间跳转

适配时间戳弹幕,支持点击跳转。
支持时间跳转

安装说明

  1. 确保浏览器已安装 Tampermonkey 插件。
  2. 前往 Greasy Fork 点击安装(推荐)。
  3. 或者下载并手动安装本仓库Github中的 Bilibili_Danmaku_Merger.js 脚本。
  4. 刷新 B 站视频页面即可在播放器下方或侧边看到“合并弹幕”按钮。

周五了,利用摸鱼时间为 2Libra 打造了新的油猴脚本 - 2Libra Plus。

✨ 主要功能

1. 通知中心增强

  • 未读消息高亮:自动检测通知列表中的未读条目,并在左侧添加醒目的橙色竖线标记,帮助你快速定位未处理的通知。
  • 自动已读(可选):支持进入通知页后自动将当前页消息标记为已读,减少重复点击操作(默认开启,可在设置中关闭)。

screencapture

2. 主题列表增强

  • 回复时间颜色渐变:根据你上次在首页查看时间,将最新回复显示得更醒目,较久之前的回复颜色更浅,帮助你一眼区分「最近更新」和「很久没动」的帖子。为了避免频繁刷新带来的视觉抖动,「上次查看时间」在 5 分钟内不会更新;最新回复会使用 --color-primary 颜色展示,更加醒目。

screencapture

3. 个性化设置

  • 提供可视化的设置面板,可随时开启或关闭特定功能,按需定制你的使用体验。

!. 还会有更多功能...

⬇️ 安装方法

相关链接

建议反馈

大家如有需要的功能,尽管提,觉得有用我会考虑加进去。

分享下 Gemini 搓的一个 Github 文件夹下载器,妈妈再也不用担心我为了一个文件夹下载整个仓库啦

精修样式,完美融入原生页面:

脚本源码:

// ==UserScript==
// @name                GitHub Folder Downloader
// @name:zh-CN          GitHub 文件夹下载器
// @version             0.7.0.33
// @author              叁月柒
// @match               *://github.com/*
// @grant               none
// @run-at              document-idle
// ==/UserScript==

(function () {
    'use strict';

    const isFolder = () => {
        const path = window.location.pathname.split('/').filter(Boolean);
        return path.length >= 2 && (path.length === 2 || path[2] === 'tree');
    };

    const injectToMenu = () => {
        const portalRoot = document.querySelector('#__primerPortalRoot__');
        if (!portalRoot) return;

        const menu = portalRoot.querySelector('ul[role="menu"]');
        if (!menu || menu.querySelector('.gh-download-integrated')) return;

        const menuText = menu.innerText;
        // 确保是操作菜单
        if (!menuText.includes('Copy path') && !menuText.includes('Delete directory')) return;

        // 1. 分割线
        const dividerHtml = `<li role="none" class="ActionList-sectionDivider gh-download-integrated"></li>`;

        // 2. 标题
        const headerHtml = `
            <li class="ActionList-sectionHeader gh-download-integrated" style="padding: 8px 16px 4px 16px;">
                <span class="ActionList-sectionHeader-label" style="color: #9198a1; font-size: 12px; font-weight: 500; display: block; line-height: 1.5;">
                    Download folder
                </span>
            </li>`;

        // 3. 子选项
        const createItem = (text, url) => `
            <li role="none" class="ActionList-item gh-download-integrated">
                <a role="menuitem" class="ActionList-content ActionList-content--visual16" target="_blank" rel="noopener noreferrer" href="${url}" style="text-decoration: none; padding-left: 16px;">
                    <span class="ActionList-item-label" style="padding-left: 24px; font-weight: 400; font-size: 14px; color: var(--fgColor-default, #adbac7);">
                        ${text}
                    </span>
                </a>
            </li>`;

        const downloadDirUrl = `https://download-directory.github.io?url=${window.location.href}`;
        const downGitUrl = `https://downgit.github.io/#/home?url=${window.location.href}`;

        const fragment = dividerHtml +
                         headerHtml +
                         createItem('by Download-Directory', downloadDirUrl) +
                         createItem('by DownGit', downGitUrl);

        menu.insertAdjacentHTML('beforeend', fragment);
    };

    const observer = new MutationObserver((mutations) => {
        if (!isFolder()) return;
        for (const mutation of mutations) {
            if (mutation.addedNodes.length > 0) {
                injectToMenu();
            }
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });
})();

📌 转载信息
原作者:
MarSeventh
转载时间:
2026/1/24 16:04:58

大家逛论坛的时候,不知道是不是会访问特定的几个链接?比如某个节点或热门页面。
而这些一般不会放进书签或导航页面。

我平时的习惯是在网站的导航栏去找,有的甚至打开后,标签页一直打开着,没事去刷新一下。导致标签页开的越来越多。

为了解决这个痛点,为自己做了一个油猴脚本,可以为每个网站设置不同的分组,每个分组可以添加不同的导航链接。

我管它叫 Shortcuts,快捷导航。

主要特点

  • 按站点智能分组:不同网站/网址自动显示对应的导航组,支持正则匹配。
  • 多种项目类型
    • 固定链接https://example.com/
    • 相对链接/, /node/something(自动基于当前域名跳转)
    • 搜索变量:支持 {hostname} 获取当前网站域名,{selected||query} 获取选中文字或 URL 参数,快速实现“站内搜索”。
    • JS 脚本:支持执行简单的 JavaScript 代码片段。
  • 双重显示模式
    • 悬浮模式:鼠标移至边缘展开,移出隐藏,不占空间。
    • 侧边栏模式:固定在屏幕一侧,适合宽屏常驻显示。
  • 外观定制:支持深色/浅色模式,可自定义图标。

放几个示例展示一下。

screenshot

screenshot

screenshot

screenshot

screenshot

目前还是 BETA 阶段,感兴趣可以体验一下,给些反馈。

安装地址: https://greasyfork.org/zh-CN/scripts/558485-utags-shortcuts | https://scriptcat.org/zh-CN/script-show-page/4910

项目地址: https://github.com/utags/userscripts/tree/main/utags-shortcuts

顺便撒点金币,好久没人发“金币池”了。

2libra 没有自带的图片上传功能,昨天看到 一个帖子 后突然来了灵感,
把之前做的 v2ex 脚本中的图片上传功能抽离出来,做了一个通用的图片上传脚本。

主要功能

  • 支持在所有网站使用。(需要配置 match 规则)
  • 对 2libra, v2ex, nodeseek, deepflood, greasyfork.org 做了适配,显示“插入图片”按钮。其他网站可以自己添加规则。
  • 保存图片上传记录,可以重复利用上传过的图片。
  • 目前支持上传到 Imgur,后续会支持更多图床。
  • 可以批量上传,支持粘贴、拖拽、文件选择收集图片。

2025-10-22-21-23-13
2025-10-22-21-09-33

🔗 安装链接

项目地址: https://github.com/utags/userscripts

目前 是第一个也是唯一一个 2libra 的油猴脚本

日常问很多生活问题和一些小代码问题一般很喜欢用 Google Ai Studio,里面的回复也很不错,冬天的时候上了一个可以打开雪花特效的按钮,雪花特效挺好看的,但是每次都需要手动打开,刷新一下又没有了,让 AI 写了个脚本可以自动打开,每次刷新会自动打开,挺不错的

// ==UserScript==
// @name         Google AI Studio - Auto Snow
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  自动开启 Google AI Studio 的 Let it snow 特效
// @author       You
// @match        https://aistudio.google.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 定义检测器
    const clickSnowButton = () => {
        // 在页面中查找所有可能的按钮或菜单项
        // 因为 Google 的类名经常变,我们直接找包含 "Let it snow" 文字的元素
        // 或者查找特定的图标/按钮位置(这里使用最通用的文本匹配法)
        const allElements = document.querySelectorAll('button, div[role="button"], span, li');

        for (let el of allElements) {
            // 忽略大小写,查找包含 Let it snow 的元素
            if (el.textContent && el.textContent.toLowerCase().includes('let it snow')) {
                // 找到后点击
                console.log('Found Snow button, clicking...', el);
                el.click();

                // 只有点击成功后才清除定时器,防止元素还没加载出来
                return true;
            }
        }
        return false;
    };

    // 使用定时器循环检查,因为 AI Studio 是动态加载的,按钮可能不会一开始就出现
    const checkInterval = setInterval(() => {
        const success = clickSnowButton();
        if (success) {
            // 如果成功点击了,就停止检查,避免重复点击(导致又关掉了)
            clearInterval(checkInterval);
        }
    }, 1000); // 每秒检查一次

    // 设置一个超时,比如 30 秒后还没找到就停止,省点资源
    setTimeout(() => {
        clearInterval(checkInterval);
    }, 30000);

})();

📌 转载信息
转载时间:
2026/1/20 10:46:02

2libra 没有自带的图片上传功能,昨天看到 一个帖子 后突然来了灵感,
把之前做的 v2ex 脚本中的图片上传功能抽离出来,做了一个通用的图片上传脚本。

主要功能

  • 支持在所有网站使用。(需要配置 match 规则)
  • 对 2libra, v2ex, nodeseek, deepflood, greasyfork.org 做了适配,显示“插入图片”按钮。其他网站可以自己添加规则。
  • 保存图片上传记录,可以重复利用上传过的图片。
  • 目前支持上传到 Imgur,后续会支持更多图床。
  • 可以批量上传,支持粘贴、拖拽、文件选择收集图片。

2025-10-22-21-23-13
2025-10-22-21-09-33

🔗 安装链接

项目地址: https://github.com/utags/userscripts

目前 是第一个也是唯一一个 2libra 的油猴脚本

关于 Agentic 搜索,七步以外,Grok 4 Fast 准,七步以内,Grok 4 Fast 又快又准。搜索准确性这块,目前我觉得除了 GPT thinking 以外,没人能跟 Grok Fast 系列抗衡,速度和 NSFW 支持就更是独一档了。

但自从 Grok 4.1 进入 Beta 之后,免费账户网页调用 Grok 4 Fast 的入口就被取消了。Grok 非 Fast 系列的搜索体验简直可以说是稀烂,比不上 Fast 一根,xAI 你真该死啊,于是我写了:

// ==UserScript==
// @name         Grok 4 Fast Unlock
// @namespace    http://tampermonkey.net/
// @version      3.3
// @description  使 Grok 免费账号使用 Grok 4 Fast
// @author       MUTED64
// @match        https://grok.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==
(function() {
    'use strict';
    const TARGET_MODEL = 'grok-4-mini-thinking-tahoe';
    const TARGET_MODE = 'MODEL_MODE_GROK_4_MINI_THINKING';
    const DISPLAY_NAME = 'Grok 4 Fast';

    let isMiniThinkingEnabled = false;
    let lastOfficialModelName = '';

    const originalFetch = window.fetch;
    window.fetch = async function(...args) {
        let [url, options] = args;
        const urlStr = (typeof url === 'string') ? url : (url?.url || '');
        if (isMiniThinkingEnabled && urlStr && (urlStr.includes('/conversations/new') || (urlStr.includes('/conversations/') && urlStr.includes('/messages')))) {
            if (options && options.body) {
                try {
                    const bodyData = JSON.parse(options.body);
                    if (bodyData.modelName) {
                        bodyData.modelName = TARGET_MODEL;
                        bodyData.modelMode = TARGET_MODE;
                        options.body = JSON.stringify(bodyData);
                        console.log(`[Grok Enhanced] 拦截: 已使用 `);
                    }
                } catch (e) {}
            }
        }
        return originalFetch.apply(this, [url, options]);
    };

    function ensureMenuItem(menu) {
        if (menu.querySelector('#mini-thinking-option')) return;
        const template = menu.querySelector('[role="menuitem"]');
        if (!template) return;
        const newItem = template.cloneNode(true);
        newItem.id = 'mini-thinking-option';

        const title = newItem.querySelector('.font-semibold');
        if (title) title.textContent = DISPLAY_NAME;
        const desc = newItem.querySelector('.text-xs.text-secondary');
        if (desc) desc.textContent = 'Grok 4 Fast';
        menu.prepend(newItem);
    }

    function syncUI() {
        const menu = document.querySelector('[role="menu"]');
        const trigger = document.querySelector('#model-select-trigger') || document.querySelector('button[aria-haspopup="menu"]:has(span)');
        const buttonTextSpan = trigger ? trigger.querySelector('.font-semibold') : null;
        if (menu) {
            ensureMenuItem(menu);
            const items = menu.querySelectorAll('[role="menuitem"]');

            items.forEach(item => {
                const check = item.querySelector('.lucide-check');
                if (!check) return;
                const itemName = item.innerText.split('\n')[0];

                if (item.id !== 'mini-thinking-option') {
                    const isCheckedByReact = check.classList.contains('opacity-100') || window.getComputedStyle(check).opacity === '1';
                    if (isCheckedByReact && !isMiniThinkingEnabled) {
                        lastOfficialModelName = itemName;
                    }
                    if (isCheckedByReact && isMiniThinkingEnabled) {

                        lastOfficialModelName = itemName;
                    }
                }

                if (isMiniThinkingEnabled) {
                    if (item.id === 'mini-thinking-option') {
                        check.style.opacity = '1';
                        check.classList.add('opacity-100');
                        check.classList.remove('opacity-0');
                    } else {
                        check.style.opacity = '0';
                        check.classList.add('opacity-0');
                        check.classList.remove('opacity-100');
                    }
                } else {
                    if (item.id === 'mini-thinking-option') {
                        check.style.opacity = '0';
                        check.classList.add('opacity-0');
                        check.classList.remove('opacity-100');
                    }
                }
            });
        }
        if (buttonTextSpan) {
            const desiredText = isMiniThinkingEnabled ? DISPLAY_NAME : lastOfficialModelName;
            if (desiredText && buttonTextSpan.textContent !== desiredText) {
                buttonTextSpan.textContent = desiredText;
            }
        }
    }

    document.addEventListener('click', (e) => {
        const menuItem = e.target.closest('[role="menuitem"]');
        if (menuItem) {
            const itemName = menuItem.innerText.split('\n')[0];
            if (menuItem.id === 'mini-thinking-option') {
                isMiniThinkingEnabled = true;
                setTimeout(() => {
                    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
                }, 30);
            } else {
                isMiniThinkingEnabled = false;
                lastOfficialModelName = itemName;
            }
            syncUI();
        }
    }, true);

    const observer = new MutationObserver(syncUI);
    observer.observe(document, { childList: true, subtree: true });
    console.log('[Grok Enhanced] 脚本加载成功。');
})();

偷偷 Enjoy 吧~


📌 转载信息
原作者:
MUTED64
转载时间:
2026/1/19 17:37:21

日常问很多生活问题和一些小代码问题一般很喜欢用 Google Ai Studio,里面的回复也很不错,冬天的时候上了一个可以打开雪花特效的按钮,雪花特效挺好看的,但是每次都需要手动打开,刷新一下又没有了,让 AI 写了个脚本可以自动打开,每次刷新会自动打开,挺不错的

// ==UserScript==
// @name         Google AI Studio - Auto Snow
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  自动开启 Google AI Studio 的 Let it snow 特效
// @author       You
// @match        https://aistudio.google.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 定义检测器
    const clickSnowButton = () => {
        // 在页面中查找所有可能的按钮或菜单项
        // 因为 Google 的类名经常变,我们直接找包含 "Let it snow" 文字的元素
        // 或者查找特定的图标/按钮位置(这里使用最通用的文本匹配法)
        const allElements = document.querySelectorAll('button, div[role="button"], span, li');

        for (let el of allElements) {
            // 忽略大小写,查找包含 Let it snow 的元素
            if (el.textContent && el.textContent.toLowerCase().includes('let it snow')) {
                // 找到后点击
                console.log('Found Snow button, clicking...', el);
                el.click();

                // 只有点击成功后才清除定时器,防止元素还没加载出来
                return true;
            }
        }
        return false;
    };

    // 使用定时器循环检查,因为 AI Studio 是动态加载的,按钮可能不会一开始就出现
    const checkInterval = setInterval(() => {
        const success = clickSnowButton();
        if (success) {
            // 如果成功点击了,就停止检查,避免重复点击(导致又关掉了)
            clearInterval(checkInterval);
        }
    }, 1000); // 每秒检查一次

    // 设置一个超时,比如 30 秒后还没找到就停止,省点资源
    setTimeout(() => {
        clearInterval(checkInterval);
    }, 30000);

})();

📌 转载信息
转载时间:
2026/1/19 17:37:04

各位佬友好!

最近在用 Boss 直聘招人,每天要刷几百个推荐简历,眼睛都看花了。

想筛选特定技能或经历的候选人,官方的筛选功能又不太够用,

找了一大圈全网的脚本基本都是求职者的。

于是自己撸了一个油猴脚本,分享给有同样需求的佬友们。

主要功能

  • 关键词高亮:设置关键词后,简历中出现的关键词会被黄色高亮标记出来

  • 筛选模式:开启后只显示匹配的简历,不匹配的直接隐藏掉

  • OR/AND 匹配:支持 "任意匹配" 和 "全部匹配" 两种模式

  • 多岗位配置:招多个岗位的佬友有福了,可以保存多套配置,一键切换

  • 面板可拖拽:控制面板支持拖拽移动,还能吸附到屏幕边缘自动收起

使用场景举例

比如招运营,想找有小红书经验的,设置关键词小红书,开启筛选模式,瞬间过滤掉不相关的简历,效率翻倍。

再比如招全栈,同时要求 ReactNode.js,用 AND 模式设置这两个关键词,只显示两个都有的候选人。

安装方法

  1. 先装个油猴插件 (Tampermonkey)
    https://www.tampermonkey.net/
  2. 新建脚本,把代码粘贴进去保存

CTRL+S 可以快速保存

  1. 打开 Boss 直聘推荐人才页面,右上角会出现控制面板
  2. 记得启用

一些说明

  • 脚本只在推荐人才页面 (https://www.zhipin.com/web/chat/recommend) 生效,不影响其他页面
  • 配置保存在浏览器本地存储,不会上传任何数据
  • 代码开源,佬友们可以自行审阅、魔改

后续计划

  • 这个版本我已经用了一段时间了,目前没发现什么新需求了,有需求可以提,我可以顺手帮忙优化

免责声明

Boss 对自动化、插件是禁止使用的,理论上本脚本可以做到 VIP 的筛选效果,但因为非 VIP 每日开聊次数很少,所以我仍然充值了 VIP

因此我会配合 VIP 的筛选 + 本脚本更精准的找到合适的候选人

代码

不想分享到 github 或者 greasyfork 怕有别的问题,所以用附件方式分享

v5.3.0
recommend_filter_v530.7z


欢迎佬友们试用和反馈,有问题直接帖子下面留言就行。

如果觉得有用,点个赞让更多 HR 佬友看到~



📌 转载信息
原作者:
91kevinshi
转载时间:
2026/1/18 19:11:52

跳转到 GitHub 也要手动点击 继续 按钮,觉得太烦了,Vibe 了这个小脚本

// ==UserScript== // @name         Auto Click Popup Continue // @namespace    http://tampermonkey.net/ // @version      1.0 // @description  当弹窗中包含指定域名时,自动点击弹窗中的"继续"按钮 // @author       Your Name // @match        https://linux.do/* // @grant        none // @run-at       document-start // ==/UserScript==

(function () {
  "use strict";

  // 配置:需要检测的域名列表(支持正则表达式) const TARGET_DOMAINS = ["github.com", "github.com/", "githubusercontent.com"];

  // 标记状态 let hasClicked = false;
  let clickCooldown = false;

  // 弹窗选择器关键词 const popupSelectors = [
    '[class*="modal"]',
    '[class*="popup"]',
    '[class*="dialog"]',
    '[class*="overlay"]',
    '[class*="layer"]',
    '[role="dialog"]',
    '[role="alertdialog"]',
    ".modal",
    ".popup",
    ".dialog",
    ".overlay",
    '[aria-modal="true"]',
    ".el-dialog",
    ".ant-modal",
    ".ivu-modal",
    ".layui-layer",
    ".sweet-alert",
    ".swal-modal",
  ];

  // 检查文本是否包含目标域名 function containsTargetDomain(text) {
    if (!text) return false;

    // 支持正则表达式匹配 for (const domain of TARGET_DOMAINS) {
      try {
        // 如果是正则表达式格式 if (domain.startsWith("/") && domain.endsWith("/")) {
          const regex = new RegExp(domain.slice(1, -1));
          if (regex.test(text)) return true;
        } else {
          // 普通字符串匹配 if (text.includes(domain)) return true;
        }
      } catch (e) {
        // 正则表达式错误时回退到普通匹配 if (text.includes(domain)) return true;
      }
    }

    return false;
  }

  // 在容器中检测是否包含目标域名 function containsDomainInContainer(container) {
    // 检查所有链接 const links = container.querySelectorAll("a[href]");
    for (const link of links) {
      const href = link.getAttribute("href") || "";
      if (containsTargetDomain(href)) {
        console.log("检测到目标域名:", href);
        return true;
      }
    }

    // 检查所有文本节点 const walker = document.createTreeWalker(
      container,
      NodeFilter.SHOW_TEXT,
      null,
      false,
    );

    let node;
    while ((node = walker.nextNode())) {
      const text = node.textContent || "";
      if (containsTargetDomain(text)) {
        console.log("检测到目标域名文本:", text.substring(0, 100));
        return true;
      }
    }

    // 检查特定元素的内容 const elements = container.querySelectorAll(
      "[data-url], [href], [src], .url, .link",
    );
    for (const elem of elements) {
      const content =
        elem.getAttribute("data-url") ||
        elem.getAttribute("href") ||
        elem.getAttribute("src") ||
        elem.textContent ||
        "";

      if (containsTargetDomain(content)) {
        console.log("检测到目标域名元素:", content.substring(0, 100));
        return true;
      }
    }

    return false;
  }

  // 查找"继续"按钮 function findContinueButton(container = document) {
    const buttons = container.querySelectorAll(
      'button, input[type="button"], input[type="submit"], [role="button"], .btn, .ant-btn, .ivu-btn, .el-button',
    );

    for (const button of buttons) {
      const text = button.textContent.trim();
      const ariaLabel = button.getAttribute("aria-label") || "";
      const value = button.value || "";

      if (
        text === "继续" ||
        text.includes("继续") ||
        ariaLabel === "继续" ||
        ariaLabel.includes("继续") ||
        value === "继续" ||
        value.includes("继续")
      ) {
        if (isElementVisible(button) && !button.disabled) {
          return button;
        }
      }
    }

    return null;
  }

  // 检查元素是否可见 function isElementVisible(element) {
    if (!element) return false;

    const style = window.getComputedStyle(element);
    if (style.display === "none") return false;
    if (style.visibility === "hidden") return false;
    if (style.opacity === "0") return false;

    const rect = element.getBoundingClientRect();
    if (rect.width === 0 || rect.height === 0) return false;

    return true;
  }

  // 在弹窗中查找按钮 function findButtonInPopup() {
    const combinedSelector = popupSelectors.join(", ");
    const popups = document.querySelectorAll(combinedSelector);

    for (const popup of popups) {
      if (isElementVisible(popup)) {
        // 先检测弹窗中是否包含目标域名 if (containsDomainInContainer(popup)) {
          // 在弹窗内查找"继续"按钮 const button = findContinueButton(popup);
          if (button) {
            return button;
          }
        }
      }
    }

    // 检查页面中是否有直接包含目标域名的弹窗 const anyPopup = document.querySelector(combinedSelector);
    if (anyPopup && isElementVisible(anyPopup)) {
      if (containsDomainInContainer(anyPopup)) {
        const button = findContinueButton(anyPopup);
        if (button) {
          return button;
        }
      }
    }

    return null;
  }

  // 处理弹窗检测 function handlePopupDetection() {
    if (clickCooldown || hasClicked) return;

    const button = findButtonInPopup();

    if (button) {
      console.log('✓ 检测到包含目标域名的弹窗,自动点击"继续"按钮');
      button.click();
      hasClicked = true;
      clickCooldown = true;

      setTimeout(() => {
        clickCooldown = false;
      }, 1000);

      setTimeout(() => {
        hasClicked = false;
      }, 3000);
    }
  }

  // 创建 MutationObserver function createObserver() {
    const observer = new MutationObserver((mutations) => {
      handlePopupDetection();
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["class", "style", "display"],
    });

    return observer;
  }

  // 定期检查 function startPeriodicCheck() {
    setInterval(() => {
      handlePopupDetection();
    }, 500);
  }

  // 初始化 function init() {
    createObserver();
    startPeriodicCheck();

    // 多次延迟检查
    [100, 500, 1000, 2000, 3000].forEach((delay) => {
      setTimeout(() => {
        handlePopupDetection();
      }, delay);
    });

    console.log("自动点击脚本已加载,监测域名:", TARGET_DOMAINS);
  }

  // 启动 if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }

  window.addEventListener("load", () => {
    setTimeout(() => {
      handlePopupDetection();
    }, 500);
  });
})();

📌 转载信息
原作者:
ageovb
转载时间:
2026/1/18 19:00:22

一个轻量、高效的油猴脚本,致力于提升夸克网盘网页版播放器的观影体验。认为有帮助的话帮我点个赞,助我突破 L3。

脚本地址:夸克网盘视频默认最高画质

git 仓库:GitHub - Ry-Run/quark-automatic-quality


📌 转载信息
原作者:
_run
转载时间:
2026/1/14 10:53:04

// ==UserScript==
// @name         工商银行纪念币预约自动填表
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  自动填充工商银行纪念币预约表单(含地区选择)
// @match        *://*.icbc.com.cn/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';


    const CONFIG = {
        custName: '',           // 客户姓名
        paperNum: '',      // 证件号码
        mobileno: '',          // 手机号码
        exchangeQuanlity: '20',        // 预约数量
        province: '天津市',            // 省份
        city: '天津市',                // 城市
        district: '静海区',            // 区县
        branch: '',   // 网点
        exchangeTime: '2026-01-20'    // 兑换时间
    };
    // ================================================

    function fillInput(placeholder, value) {
        const inputs = document.querySelectorAll('input.el-input__inner');
        for (const input of inputs) {
            if ((input.placeholder || '').includes(placeholder)) {
                input.value = value;
                input.dispatchEvent(new Event('input', { bubbles: true }));
                return true;
            }
        }
        return false;
    }

    async function selectDropdown(placeholder, optionText) {
        const inputs = document.querySelectorAll('input.el-input__inner[readonly]');
        for (const input of inputs) {
            if ((input.placeholder || '').includes(placeholder)) {
                input.click();
                await new Promise(r => setTimeout(r, 1));
                const items = document.querySelectorAll('.el-select-dropdown__item:not(.is-disabled)');
                for (const item of items) {
                    if (item.textContent.includes(optionText)) {
                        item.click();
                        await new Promise(r => setTimeout(r, 1));
                        return true;
                    }
                }
            }
        }
        return false;
    }

    async function fillForm() {
        // 填充文本输入框
        fillInput('客户姓名', CONFIG.custName);
        fillInput('证件号码', CONFIG.paperNum);
        fillInput('手机号码', CONFIG.mobileno);
        fillInput('预约数量', CONFIG.exchangeQuanlity);

        // 填充下拉选择框
        await selectDropdown('省份', CONFIG.province);
        await selectDropdown('城市', CONFIG.city);
        await selectDropdown('区县', CONFIG.district);
        await selectDropdown('网点', CONFIG.branch);
        await selectDropdown('兑换时间', CONFIG.exchangeTime);

        console.log('表单已填充');
    }

    function addButton() {
        const btn = document.createElement('button');
        btn.textContent = '手动填充';
        btn.style.cssText = 'position:fixed;top:10px;right:10px;z-index:9999;padding:10px 20px;background:#409EFF;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:14px;';
        btn.onclick = fillForm;
        document.body.appendChild(btn);
    }

    window.addEventListener('load', () => {
        addButton();
        setTimeout(fillForm, 1500);
    });
})();

📌 转载信息
原作者:
sam5440
转载时间:
2026/1/14 10:47:28

起因是日常使用 deepwiki 和 zread.ai 的时候,每次都要打开游览器网址然后选择相关部分然后修改,非常麻烦,本着程序员能自动就绝不动手的懒人思维,用 claude 写了一个油猴脚本,效果如下,供大家使用。

效果就是左下角的按钮点击新开页面跳转,仅仅在主页上实现这个功能


📌 转载信息
转载时间:
2026/1/14 10:41:40

开发缘由:

参考:
https://linux.do/t/topic/1427584


功能:
自动同步 Gemini 主题与系统主题,防止页面加载时主题突变


源码
// ==UserScript==
// @name         Gemini 主题同步助手
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  自动同步 Gemini 主题与系统主题,防止页面加载时主题突变
// @author       Assistant
// @match        https://gemini.google.com/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 防止在 iframe 中运行
    if (window.self !== window.top) {
        return;
    }

    // ==================== 配置常量 ====================
    const THEME_KEY = 'Bard-Color-Theme';
    const DEBUG = false; // 设为 true 可开启调试日志

    // ==================== 工具函数 ====================

    /**
     * 调试日志输出
     * @param  {...any} args - 日志参数
     */
    function log(...args) {
        if (DEBUG) {
            console.log('[Gemini主题同步]', ...args);
        }
    }

    /**
     * 获取系统主题是否为深色模式
     * 使用 CSS Media Query API 检测系统级别的主题偏好
     * @returns {boolean} true 表示系统为深色模式,false 表示浅色模式
     */
    function getSystemThemeIsDark() {
        try {
            return window.matchMedia('(prefers-color-scheme: dark)').matches;
        } catch (e) {
            // 如果 matchMedia 不可用,默认返回 false(浅色模式)
            log('matchMedia API 不可用,默认使用浅色模式');
            return false;
        }
    }

    /**
     * 解析存储的主题值,判断是否为深色模式
     * Gemini 的主题值通常包含 "Light" 或 "Dark" 字符串
     *
     * @param {string} value - localStorage 中存储的主题值
     * @returns {boolean|null}
     *   - true: 深色模式
     *   - false: 浅色模式
     *   - null: 无法判断(值为空或不包含可识别的主题标识)
     */
    function parseStoredTheme(value) {
        // 空值检查
        if (!value || typeof value !== 'string') {
            return null;
        }

        // 转换为小写进行不区分大小写的匹配
        const lowerValue = value.toLowerCase();

        // 检查是否包含深色模式标识
        if (lowerValue.includes('dark')) {
            return true;
        }

        // 检查是否包含浅色模式标识
        if (lowerValue.includes('light')) {
            return false;
        }

        // 无法识别的值
        return null;
    }

    /**
     * 主函数:同步主题与系统设置
     *
     * 逻辑流程:
     * 1. 检查 localStorage 中是否存在主题设置
     * 2. 如果存在,获取系统当前主题偏好
     * 3. 对比两者是否一致
     * 4. 不一致时删除存储的主题值,让页面使用系统默认主题
     */
    function syncThemeWithSystem() {
        try {
            // ========== 步骤1: 读取存储的主题 ==========
            const storedTheme = localStorage.getItem(THEME_KEY);

            // 如果不存在存储的主题,说明页面会使用默认行为,无需干预
            if (!storedTheme) {
                log('未检测到存储的主题,跳过处理');
                return;
            }

            log('检测到存储的主题:', storedTheme);

            // ========== 步骤2: 获取系统主题 ==========
            const systemIsDark = getSystemThemeIsDark();
            log('系统主题:', systemIsDark ? '深色模式' : '浅色模式');

            // ========== 步骤3: 解析存储的主题 ==========
            const storedIsDark = parseStoredTheme(storedTheme);

            // 如果无法解析存储的主题值,保守处理,不做任何操作
            if (storedIsDark === null) {
                log('无法解析存储的主题值,跳过处理');
                return;
            }

            log('存储的主题:', storedIsDark ? '深色模式' : '浅色模式');

            // ========== 步骤4: 对比并处理 ==========
            if (storedIsDark !== systemIsDark) {
                // 主题不一致,删除存储的值
                localStorage.removeItem(THEME_KEY);
                log('✓ 主题不一致,已删除存储的主题值,页面将使用系统主题');
            } else {
                // 主题一致,无需处理
                log('✓ 主题一致,无需处理');
            }

        } catch (error) {
            // 捕获所有异常,确保脚本不会因错误而影响页面正常加载
            console.error('[Gemini主题同步] 执行出错:', error);
        }
    }

    // ==================== 立即执行 ====================
    // 在 document-start 阶段执行,确保在页面渲染前完成主题同步
    syncThemeWithSystem();

})();



📌 转载信息
转载时间:
2026/1/12 10:12:40

// ==UserScript== // @name         微信文章图片下载 // @namespace    http://tampermonkey.net/ // @version      1.1 // @description  微信文章图片右键下载 // @author       You // @match        https://mp.weixin.qq.com/s/* // @run-at       document-end // @grant        none // ==/UserScript==

(function() {
    'use strict';

    const css = `
.rich_media_content *{
pointer-events: all !important;
}
`
; function injectStyle() { const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); } injectStyle(); })();

📌 转载信息
转载时间:
2026/1/8 18:01:38

现在面向 GPT 开发越来越方便了.

但是从我混的很多 telegram 电报群里面看,很多人还在提出一些很基础的需求,看来大家并没有大规模地开始面向 GPT 开发.

我把最近一段时间,我自己实现的面向 GPT 开发的实例整理出来,希望对读者有所启发.

也许你改进一下前期数据的准备,也许你改进一下描述需求的方式,也许你限制一下 GPT 工作的范围,就会得到能让你满意的结果了.

现在各个 GPT 在不同的应用场景 (任务) 下还是各有所长,所以我也会记录用到的 GPT 是哪个.

  • 当然,随着时间的流逝,各个 GPT 还会进一步发展,所以我这里记录的 GPT 也只是一个参考.

1. 用 VS Code 阅读 Sing-box 文档 生成配置文件

Prompt (发给 GPT 的要求)

a)

下载这个项目的文档 Introduction - sing-box

b)

生成一个作为客户端使用的配置文件
监听本地 1080 端口 socks 作为 inbound
连接一个下面这样参数的 reality 协议节点作为 outbound
协议 (protocol) = vless
地址 (address) = 74.48.9.95
端口 (port) = 8972
用户 ID (id) = fb0d60cf-1084-412d-ba59-fd5c1166b89d
流控 (flow) = xtls-rprx-vision
传输协议 (network) = tcp
传输层安全 (TLS) = reality
SNI (serverName) = www.paypal.com
指纹 (Fingerprint) = chrome
公钥 (Public key) = Qam0-DVzhHghfZPi4Pfx3iQbmVt0YJBhcb0cyMsFdEc

用到的 GPT

Antigravity

Gemini3
Antigravity 是 Google 家的,所以里面用的就是这个

关键点

当你给 GPT 一个整体任务时,如果 GPT 返回的效果不太好.
你可以把任务切分,或者让 GPT 先把任务切分,也可以叫规划吧.
你可以看看任务切分 / 规划 得对不对.
然后关注每一步小任务是不是正确完成,如果某一步小任务完成得有问题,就不要进入下一步.

具体到这个案例,如果第 1 步获取的文档是错的或者不完整,那就不要开始生成配置文件.

2. ech-wk 给窗口添加滚动条 改善小屏幕上的使用体验

Prompt (发给 GPT 的要求)

把 gui.py 上传为附件,再提出要求

附件中的代码 有一个问题,在屏幕分辨率不高的情况下,窗口显示不全,而且没有滚动条

用到的 GPT

Claude

关键点

在这个案例中,我用自己的编码能力识别出了,gui.py 负责 GUI 界面。所以只需要处理这个文件.

如果你是纯小白,那么可以先让 GPT 帮你分析整个项目中,负责界面的是哪一部分.

GitHub

3. 在 VPS 注册页面 和 美国人信息页面 高亮显示关键字 油猴篡改猴 tampermonkey 脚本

Prompt (发给 GPT 的要求)

a)

有这样一个 HTML 页面
页面中可能包含 “first name”, 也有可能 是在 input 元素的 placeholder 属性中包含 “first name”
我需要查找并高亮这些 “first name”

b)

需要整合为可以在浏览器的 console 执行的 js 文件

c)

把 first name, firstname, full name, fullname, 全名,姓名 归为一类,显示同样的高亮颜色或边框颜色.
对于同一类关键字,边框颜色和文字底色颜色应该一致.

d)

把 first name, firstname, last name, lastname, full name, fullname, 全名,姓名 归为一类,显示同样的高亮颜色或边框颜色.
把 phone number, phone, 电话 归为一类.
把 street address, street, 街道地址,街道 归为一类.
把 city, 城市 归为一类.
把 full state name, state, 州全称,州 归为一类.
把 postcode, zip code, 邮编,归为一类.
以上每一类,都要使用独特鲜明的颜色,不应该与其它任何一类颜色相同.

用到的 GPT

Gemini

关键点

在这个案例中,我自己先对需求进行了分析。这是一个把同样的逻辑 / 功能 应用到好多个 不同的 分类 中的事情.

所以我首先关注怎么在一个分类中实现我要的功能.

然后再将同样的处理批量应用到更多的分类.

GitHub

4. YouTube 视频信息页面 网速换算为 MB/s 油猴篡改猴 tampermonkey 脚本

Prompt (发给 GPT 的要求)

我有一个 html 页面,
其中这个位置的元素 1)
document.querySelector(“#movie_player > div.html5-video-info-panel.ytp-sfn > div > div:nth-child(9) > span > span:nth-child(2)”)
内容是 17085 Kbps 这样的格式,
我需要在这个元素 1) 的后面,增加一个元素 2) , 内容为 元素 1) 的内容换算为 MB/s 的单位.
元素 2) 的数字 应该每 10 秒刷新

用到的 GPT

Gemini

关键点

在这个案例中,我自己先用浏览器 的 F12 开发者工具 找到了 HTML 元素的 path

如果你是纯小白的话,也许需要使用一些基于浏览器插件形式的 GPT.
这样,你可以基于当前浏览器页面,描述你的需求.

GitHub

5. 用 Cloudflare Snippet 实现反代 blogspot

关键点

Gemini 中关于 snippet 的知识不及时.

我问到下面这样的用法,Gemini 说 snippet 不支持,只有 worker 支持。但,其实现在 snippet 是支持的.

export default {
  async fetch(request, env, ctx) {
    try {
      return await handleRequest(request, env, ctx);
    } catch (e) {
      return new Response(e.message || "Internal Error", { status: 500 });
    }
  },
}; 

用到的 GPT

Claude

6. 下载 独树不成林 播客的全部封面 并做成 电报 telegram 贴纸 sticker

Prompt (发给 GPT 的要求)

实现一个基于 HTML JS 的工具
页面包含以下几个部分

  1. 文本框 可输入 podcast 的 RSS 地址
  2. 文本框 可从 1) 获取 RSS 内容,也可以手工输入 RSS 内容
  3. 文本框 分析 2) 的内容,列出所有封面图片的地址。可手工编辑 添加或删除
  4. 显示 3) 中的地址对应的图片
    举例:
    https://feed.xyzfm.space/y9qnpfdrctnx 是一个 podcast 的 RSS 地址,会被填写到 1)
  5. 可得到 RSS 数据,xml 格式。会被填写到 2)
  6. 中有 <itunes:image href=“https://image.xyzcdn.net/Fgd_z5yexkQF_GB0LF4Xncqqf8CU.png”/> 这样的元素,应该将 https://image.xyzcdn.net/Fgd_z5yexkQF_GB0LF4Xncqqf8CU.png 填写到 3)
  7. 显示 3) 中的地址对应的图片

用到的 GPT

Gemini

关键点

把整个转换过程 规划为几个步骤

每个步骤有可检查的结果,而且可以人工手动修改。再接着进行下一步.

GitHub

7. 去掉 cfnew 的视觉特效 的操作整合到 Github Action 里

Prompt (发给 GPT 的要求)

有这样一个 github 项目
项目里有一个文本文件 file1
我需要用 Github Action 对文件做如下操作:

  1. 查找所有的 animation: 替换为 //animation:
  2. 查找所有的 function createMatrixRain () {, 在下一行添加一行 return;
    这个 Github Action 不要自动触发,只能手动触发

用到的 GPT

Claude

GitHub

8. 去掉 cfnew 的视觉特效 保留业务逻辑

Prompt (发给 GPT 的要求)

分析上传的文件,这是一个用于 cloudflare worker 环境的 js 脚本

请分析出显示 HTTP 页面中的视觉特效部分,位于代码的什么位置.

用到的 GPT

Claude

GitHub

9. 当检测到关键字时 私信发送对应的贴纸 tg-keyword-react-bot

Prompt (发给 GPT 的要求)

用到的 GPT

Claude

生成的程序有 BUG, 获取消息的纯文本

message_text = event.message.message

正确的做法是提取消息的 markdown 文本

from telethon.extensions import markdown
message_text = markdown.unparse(event.message.message, event.message.entities)

关键点

用具体的示例告诉 GPT 应该达到怎样的效果

GitHub

10. 极简 GitHub Porxy 支持 GitHub 脚本的无限嵌套调用

Prompt (发给 GPT 的要求)

基于 cloudflare 的 woker, 开发 一个专门 反向代理 github 的工具

  1. 本代理 接收的 path 部分 应该是一个 http:// 或者 https://
  2. 如果 path 部分 不是 http:// 或者 https:// 开头
    那么加上 http:// 或者 https://
  3. 判断 本代理 接收的 链接 是否 github
    判断方法为:
    链接 的域名部分 应该是 git 开头的主域名

    github.com
    raw.githubusercontent.com
    api.github.com
    gist.github.com
    codeload.github.com
    avatars.githubusercontent.com
    assets-cdn.github.com
    这些域名的 主域名 都是 git 开头的
  4. 在获取需要反向代理的内容后
    检查 path 是否以 .sh 结尾,来判断 是否 脚本文件
  5. 对于 .sh 结尾的脚本文件
    对文本内容进行查找替换
    将 github 的链接前面都加上 本代理的域名,
    这样可以解决脚本嵌套使用的场景
    判断 是否 github 链接的方法 参考 第 3 步

用到的 GPT

Gemini

灵感点

如果你不需要一个大项目的完整的功能,你可以向 GPT 描述你用得着的那一小部分功能,这样能用很少的代码量完成,而且还方便你自己 自定义修改.

GitHub

11. 在网络受限的 VPS 上 运行一个脚本 向外访问网络时暂停 使用者进行替代操作

Prompt (发给 GPT 的要求)

a)

做一个 fake-curl-wget.sh 脚本.

  1. fake-curl-wget.sh 包含一个 curl () 的壳子,和一个 wget () 的壳子
  2. 使用者在终端先 source fake-curl-wget.sh 再执行其它脚本
  3. 这样,后面执行的脚本会调用到 fake-curl-wget.sh 中的 curl () 壳子 和 wget () 壳子
  4. 每次调用 curl 或 wget 时,打印一个调用序号。这个序号每次调用时,自增 1
    为了避免管道命令导致的序号问题,使用临时文件保存序号.
  5. 对于所有 curl 和 wget 调用,这个序号是统一.
    先调用 curl 时,序号为 1.
    接着调用 wget 时,序号为 2.
  6. 输出 pwd 当前目录
  7. 输出 完整的 curl 命令和全部参数
  8. 输出 完整的 wget 命令和全部参数
  9. 这个 curl 壳 或 wget 壳,并不去真正访问网络
  10. 根据调用序号,执行预设的命令。如,
    cp file1 /path/to/file

    cat file2
    用来替代 curl -LO 或 curl -Lo 的保存文件的命令
    或 curl -L 的输出到 stdout 的命令
  11. 这些预设命令是会被人工编辑而增加的。用 case 实现 10) 的逻辑.
  12. 这个 curl 壳 或 wget 壳,永远返回成功.

b)

  1. 当根据序号 进行 case 逻辑 发现没有匹配的预设命令时,脚本暂停。等待使用者输入.
  2. 根据日志打印的 curl 或 wget 命令及参数。使用者判断 当前序号的操作是要保存文件,还是要输出信息到 stdout.
    2a) 如果当前操作是保存文件,则 使用者自己上传文件到指定位置。输入 空。脚本继续执行.
    2b) 如果当前操作是输出 信息到 stdout, 则 使用者输入 替代指令。脚本执行替代指令.
    如,使用者输入 echo “something” 或 cat /path/to/file

关键点

现在各个 GPT 能上下文窗口很大了.

可以把脚本本身和报错信息 一次性全部发过去.

如果文本框限制了字符数,可以把脚本保存为文件上传.

GitHub

12. 开发电报关键词提醒机器人 telegram keyword monitor bot

Prompt (发给 GPT 的要求)

基于 Telethon 框架,生成一个 telegram 监听关键字推送结果 bot
bot 只接受来自指定 id 的 user 或 group 的控制命令,
bot 监听到关键字后,发送通知信息给指定 id 的 user 或 group 或 channel,
关注的 关键字列表 支持正则表达式
排除的 关键字列表 支持正则表达式
关注的 关键字列表 和 排除的 关键字列表 都更新到配置文件中保存
配置文件 yaml 格式,内容如下:

# 账户信息
account: 
  # 监听信息的user
  api_id: '1400003'
  api_hash: 'd11xxxxx112a7e059e831'
  user_phone: '+86190000010'

  # 发送消息的bot
  bot_token: '1000007:AAHNh8axxxxxxxxxxxxxxxxHA'
  bot_username: 'keyxxxxxrt_bot'

# LOG
logger:
  path: null # e.g. /root/absolute-path/   default null: {_current_path}/logs/
  level: INFO # FATAL,ERROR,WARN,INFO,DEBUG,NOTSET

# 代理
proxy:
  type: SOCKS5 # e.g. SOCKS4, SOCKS5, HTTP
  address: null  # e.g. 127.0.0.1
  port: null # e.g. 1088

# 非公共服务
# bot只接收来自以下ID的命令 可以设置为user或group的ID
command_id_list: 
  - 123456789
  - 987654321
# bot的通知信息发送到以下ID 可以设置为user或group或channel的ID
result_id_list: 
  - 123456789
  - 987654321

# 不处理来自机器人的消息
# 比如,有些群里有自动回复机器人,回复的都是重复的消息;或者一些广告机器人加群之后开始刷屏
ignore_bot_msg: true

# 临时禁止一些数据源, 而不需要user从群组或频道中退出
source_filter: false # 开关, 默认 false
source_filter_ignore_list:
  - 123456789
  - 987654321

# 关注的 关键字列表 支持正则表达式
keyword_list:
  - /keyword1|keyword2|keyword3/ig
  - /keyword4|keyword5|keyword6/ig

# 排除的 关键字列表 支持正则表达式
keyword_exclude_list:
  - /exkeyword1|exkeyword2|exkeyword3/ig
  - /exkeyword4|exkeyword5|exkeyword6/ig

用到的 GPT

Claude

关键点

配置文件是从另一个方面描述程序有些什么功能.

GitHub


📌 转载信息
原作者:
crazypeace
转载时间:
2026/1/4 17:19:51

大家好!最近在安卓 Edge 浏览器上用 Tampermonkey 开发了一个简单实用的油猴脚本,专门针对视频播放的痛点:长按视频区域实现倍速调节(类似夸克浏览器的体验)。脚本支持长按左侧从 1× 开始、右侧从 2× 开始,上滑加速、下滑减速,松手恢复原速。还加了小提示和轻振动反馈。

这个脚本基于我跟 AI 助手多次迭代优化,已经基本稳定可用,能兼容大部分 H5 视频网站(B站、YouTube、腾讯视频等)。但还有两个小问题没彻底解决:全屏模式下速度提示消失与 HTML5视频播放器增强脚本(h5player)冲突导致倍速被覆盖。求大佬们帮忙修复或优化!如果能完美兼容 h5player 和全屏提示永可见,就太完美了。

脚本功能亮点

  • 长按触发:左侧从 1×、右侧从 2×,300ms 内触发,轻振动提醒。
  • 滑动调节:上滑 +0.25×/步,下滑 -0.25×/步,范围 0.25~16×。
  • 提示显示:右上角小半透明胶囊,800ms 淡出。
  • 防误触:不会弹出系统复制/分享菜单。
  • 兼容性:保留原生进度条,全屏/非全屏都可用(除提示问题外)。
  • 适用场景:追剧、学习视频倍速神器,远超 Edge 原生播放器。

安装与使用

  1. 在安卓 Edge 安装 Tampermonkey(扩展商店搜索)。
  2. 新建脚本,复制下面代码保存启用。
  3. 打开视频页面,长按视频区域测试。
  4. 可与 h5player 同时用,但有冲突时建议临时关闭 h5player。

完整脚本代码(版本 2.5,作者:Taocrypt)

// ==UserScript== // @name         手机长按倍速播放增强 // @namespace    http://tampermonkey.net/ // @version      2.5 // @description  安卓专用:长按左侧从1×、右侧从2×开始,上滑加速、下滑减速,松手恢复。 // @author       Taocrypt // @match        *://*/* // @grant        none // @run-at       document-start // @noframes // ==/UserScript==

(function () {
    'use strict';

    let currentVideo = null;
    let isLongPressing = false;
    let originalSpeed = 1.0;
    let baseSpeed = 1.0;
    let currentSpeed = 1.0;
    let startY = 0;
    const STEP = 0.25;
    const MIN_SPEED = 0.25;
    const MAX_SPEED = 16.0;
    const LONG_PRESS_TIME = 300;
    const SLIDE_THRESHOLD = 25;

    let originalSetter = null;
    let hijacked = false;

    // 小半透明右上角提示(全屏永可见) function showSpeedTip(speed) {
        let tip = document.getElementById('my-longpress-tip');
        const root = document.documentElement || document.body;

        if (!tip || !root.contains(tip)) {
            if (tip) tip.remove();
            tip = document.createElement('div');
            tip.id = 'my-longpress-tip';
            tip.style.cssText = `
position: fixed !important;
top: 20px !important; right: 20px !important;
background: rgba(0,0,0,0.6) !important;
color: #fff !important;
padding: 6px 12px !important;
border-radius: 16px !important;
font-size: 16px !important;
font-weight: bold !important;
z-index: 2147483647 !important; /* 最高z-index */
pointer-events: none !important;
transition: opacity 0.4s !important;
backdrop-filter: blur(4px) !important;
`
; root.appendChild(tip); } tip.textContent = speed.toFixed(2) + '×'; tip.style.opacity = '0.9'; clearTimeout(tip.hideTimer); tip.hideTimer = setTimeout(() => tip.style.opacity = '0', 800); } // 劫持 playbackRate(最高优先级) function enableHijack() { if (hijacked) return; originalSetter = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate')?.set; if (!originalSetter) return; Object.defineProperty(HTMLMediaElement.prototype, 'playbackRate', { set: function(value) { if (isLongPressing && this === currentVideo) { currentSpeed = value; showSpeedTip(value); } return originalSetter.call(this, value); }, configurable: true }); hijacked = true; } // 恢复原生(脚本卸载或出错时保险) function disableHijack() { if (!hijacked || !originalSetter) return; Object.defineProperty(HTMLMediaElement.prototype, 'playbackRate', { set: originalSetter, configurable: true }); hijacked = false; } function attach(video) { if (video._myLongPressAttached) return; video._myLongPressAttached = true; let pressTimer = null; video.addEventListener('touchstart', e => { if (e.touches.length !== 1 || isLongPressing) return; e.stopImmediatePropagation(); const touch = e.touches[0]; const rect = video.getBoundingClientRect(); if (rect.width < 30 || rect.height < 30) return; const x = touch.clientX - rect.left; const width = rect.width; startY = touch.clientY; if (pressTimer) clearTimeout(pressTimer); pressTimer = setTimeout(() => { isLongPressing = true; currentVideo = video; originalSpeed = video.playbackRate || 1.0; baseSpeed = (x > width * 0.5) ? 2.0 : 1.0; currentSpeed = baseSpeed; enableHijack(); // 开启最高优先级劫持 video.playbackRate = currentSpeed; // 触发显示 showSpeedTip(currentSpeed); if (navigator.vibrate) navigator.vibrate([20, 30, 20]); // 轻柔振动 }, LONG_PRESS_TIME); }, { passive: false }); video.addEventListener('touchmove', e => { if (!isLongPressing || !currentVideo) return; const touch = e.touches[0]; const deltaY = startY - touch.clientY; const steps = Math.floor(Math.abs(deltaY) / SLIDE_THRESHOLD) * (deltaY > 0 ? 1 : -1); if (steps !== 0) { currentSpeed = Math.max(MIN_SPEED, Math.min(MAX_SPEED, currentSpeed + steps * STEP)); video.playbackRate = currentSpeed; // 通过劫持setter自动显示提示 startY = touch.clientY; } e.stopImmediatePropagation(); }, { passive: true }); video.addEventListener('touchend', () => { if (pressTimer) clearTimeout(pressTimer); if (isLongPressing && currentVideo) { currentVideo.playbackRate = originalSpeed; showSpeedTip(originalSpeed); if (navigator.vibrate) navigator.vibrate(20); isLongPressing = false; currentVideo = null; } }); video.addEventListener('contextmenu', e => { e.preventDefault(); e.stopPropagation(); }); } function scan() { document.querySelectorAll('video').forEach(attach); } scan(); setTimeout(scan, 500); setTimeout(scan, 1500); setTimeout(scan, 4000); setTimeout(scan, 10000); new MutationObserver(scan).observe(document, { childList: true, subtree: true }); // 全屏变化时强制重建提示 ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'msfullscreenchange'].forEach(evt => { document.addEventListener(evt, () => { setTimeout(() => showSpeedTip(currentSpeed || originalSpeed || 1.0), 200); }); }); })();

求助:两个小问题修复

  1. 全屏模式下速度提示消失:进入视频全屏后,右上角提示会隐藏或被覆盖。希望能让提示在全屏时永可见(或许用更高优先级 DOM 或 Canvas 绘制?)。
  2. 与 h5player 脚本冲突:同时开启 h5player 时,长按倍速会被 h5player 的速度监控覆盖/恢复。求方法彻底兼容,让长按倍速优先生效。

📌 转载信息
转载时间:
2026/1/2 23:48:05

gemini 最近复制有点问题:复制漏公式

弄个了 markdown 复制脚本:Gemini to Markdown Copier (Fix Empty Lines)

之前的 linux.do 的复制脚本也顺便分享一下:Linux.do Downloader (New Version)

内容:

  1. 支持预览
  2. 支持轮数选择
  3. 常见格式支持
  4. 空行优化

优化了几个版本了,还有问题的话让 gemini 再改一下就行了

效果:


📌 转载信息
原作者:
BigShark667
转载时间:
2026/1/2 12:30:09