标签 用户脚本 下的文章

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

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

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

我管它叫 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

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

// ==UserScript== // @name         GitLab - 一键导出所有项目 // @namespace    http://tampermonkey.net/ // @version      1.0 // @description  在 GitLab 项目页面顶部添加自定义按钮用于一键导出所有项目 // @author       LeonShaw // @match        *://*/dashboard/projects // @grant        none // @run-at       document-idle // ==/UserScript==

(function () {
    'use strict';

    const { protocol, host } = window.location;
    const baseUrl = `${protocol}//${host}`;
    const apiUrl = `${baseUrl}/api/v4/projects`;

    // 获取所有项目(分页) async function fetchAllProjects() {
        const statusEl = document.getElementById('clone-status');
        statusEl.style.display = 'block';
        statusEl.textContent = '正在加载项目...';

        let page = 1;
        let allProjects = [];
        try {
            while (true) {
                const url = `${apiUrl}?per_page=100&page=${page}&order_by=created_at&sort=desc`;
                const res = await fetch(url, {
                    credentials: 'include' // 携带 cookie,确保认证
                });

                if (!res.ok) {
                    throw new Error(`HTTP ${res.status}: ${await res.text()}`);
                }

                const data = await res.json();
                if (!Array.isArray(data) || data.length === 0) break;

                allProjects.push(...data);
                statusEl.textContent = `已加载 ${allProjects.length} 个项目...`;
                page++;
            }

            generateAndDownloadScript(allProjects);
            statusEl.textContent = `完成!共 ${allProjects.length} 个项目。`;
        } catch (err) {
            console.error('获取项目失败:', err);
            statusEl.textContent = `错误: ${err.message || '未知错误'}`;
        } finally {
            setTimeout(() => {
                statusEl.style.display = 'none';
                statusEl.textContent = '';
            }, 30000);
        }
    }

    // 生成并下载 .sh 脚本 function generateAndDownloadScript(projects) {
        const lines = ['#!/bin/bash\n'];

        projects.forEach(proj => {
            if (!proj.ssh_url_to_repo) return;

            const sshUrl = proj.ssh_url_to_repo;

            const localPath = proj.path_with_namespace;
            lines.push(`git clone "${sshUrl}" "./${localPath}"`);
        });

        const scriptContent = lines.join('\n') + '\n';
        const blob = new Blob([scriptContent], { type: 'text/plain;charset=utf-8' });
        const filename = `clone-all-git-repos(${host}).sh`;

        // 创建下载链接 const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = filename;
        link.click();
        URL.revokeObjectURL(link.href);
    }

    function addButton() {
        const breadcrumbWrapper = document.querySelector('#js-vue-page-breadcrumbs-wrapper');
        if (!breadcrumbWrapper) return;

        // 防止重复添加 if (breadcrumbWrapper.querySelector('.custom-gitlab-button')) return;

        // 创建按钮 const button = document.createElement('button');
        button.textContent = '一键导出所有项目';
        button.className = 'custom-gitlab-button gl-button btn btn-default btn-sm gl-mx-2';
        button.style.marginLeft = '10px';
        button.addEventListener('click', function () {
            fetchAllProjects();
        });


        breadcrumbWrapper.appendChild(button);

        const statusElement = document.createElement('span');
        statusElement.id = 'clone-status';
        statusElement.className = 'custom-gitlab-status gl-ml-3 gl-font-sm gl-font-weight-bold';
        statusElement.style.padding = '4px 8px';
        statusElement.style.borderRadius = '4px';
        statusElement.style.backgroundColor = '#e9ecef';
        statusElement.style.color = '#495057';
        statusElement.textContent = '';
        statusElement.style.display = 'none';


        breadcrumbWrapper.appendChild(statusElement);
    }

    addButton();

    const observer = new MutationObserver(() => {
        addButton();
    });
    observer.observe(document.body, { childList: true, subtree: true });
})();

AIGC 声明:本脚本部分函数由 通义千问 辅助创造,本人已验证其生成内容的真实性和有效性

使用方法


本脚本已在 Gitlab v18.0.1 - v18.8.1 验证


📌 转载信息
原作者:
LeonShaw
转载时间:
2026/1/23 15:40:54

碎碎念

上次有佬问怎么没有效果

遂直接 fork 原脚本…


新功能:

  • 筛选聊天记录
  • 导出到 google drive(需要 token)


UI 截图

主弹窗:

google drive token 配置:

team 导出:

聊天筛选:


源码:



📌 转载信息
转载时间:
2026/1/21 21:33:10

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

✨ 主要功能

1. 通知中心增强

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

screencapture

2. 主题列表增强

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

screencapture

3. 个性化设置

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

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

⬇️ 安装方法

相关链接

建议反馈

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

打磨了一周的卡片式 ui 大佬们还有更多的 功能/样式 建议吗 准备发布啦

它主要解决什么问题?

  • 长楼“找上下文”困难:不知道自己在回复谁
  • 讨论树太深,页面太长:滚动疲劳、定位困难
  • 想快速扫顶层(L0)主线程:上下跳楼不够顺手
  • 深色模式/阅读舒适度:原生样式太简陋,眼睛累


核心功能一览(当前脚本已实现)

1) 评论区卡片化重排(核心)

  • 把 HN 原生的表格树评论,重建为 卡片式树结构(视觉层级更清晰)
  • 每条评论是一个卡片:用户名/时间/操作按钮在顶部,正文在下面,子回复作为卡片嵌套
  • 支持 最大宽度、圆角、间距、缩进等布局调节

适用场景:长讨论阅读体验会明显提升,尤其是多层回复。


2) 主题与外观:浅色/深色/跟随系统 + 渐变层级背景

  • 主题模式:跟随系统 / 强制浅色 / 强制深色
  • 页面背景可自定义(浅色/深色各一套)
  • 评论卡片支持“按层级渐变”:L0/L1/L2… 背景颜色可调
  • 也提供一些预设配色(比如蓝灰、暖米色、灰阶极简、深海蓝黑等)

目标:让“长时间刷楼”变得更舒服。


3) 折叠/展开(偏扫楼用)

为了快速扫楼,它提供多种折叠策略(可开关/可配置):

  • 每条评论可折叠:右上角有“折叠/展开”按钮
  • 折叠触发方式(可选):
    • 仅按钮
    • 按钮 + 单击正文
    • 按钮 + 双击正文
  • 双击正文折叠/展开整个子树(可选):
    双击某条评论正文,直接折叠/展开它下面整棵回复树
  • 默认折叠策略(两套):
    • 按数量:顶层评论如果“后代回复总数”超过阈值,默认折叠(便于扫主楼)
    • 按深度:从指定深度开始默认折叠(避免页面无限延长)
  • 全局快捷按钮(右下角):
    • 全部折叠(收起所有有子回复的评论)
    • 全部展开(展开所有评论)


4) “展开提示” + 数量颜色提醒(更好扫大楼)

当某条评论被折叠时,会出现类似:

  • [+展开 8 条回复](单击默认展开 双击展开全部子级)

并且这个数字会根据数量变色(可调阈值/颜色):

  • 少量:更偏主题强调色
  • 中量:偏粉/亮色
  • 大量:偏红(提醒这楼很长)

另外支持把这个提示按钮放在:

  • 评论底部(默认)
  • 或者放在右上角按钮行里(更省垂直空间)


5) L0 主线程导航(扫楼神器)

每条评论右上角会有:

  • 上 L0 / 下 L0:快速跳到上一个/下一个顶层主线程
  • L0 折叠:一键折叠当前评论所在的顶层主线程(快速收起一整楼)
  • 还有一个 “层展”:只展开下一层,下一层的子回复继续保持折叠(用于逐层读)

(这块我自己用得最多,长讨论基本靠它快速扫楼。)


6) Hover 父级高亮:找上下文更快

鼠标悬停某条评论时:

  • 自动高亮它的直接父评论
    用来快速确认“我现在在回复谁/这层上下文是什么”。


7) OP(楼主)高亮

楼主(story author)的评论会有额外高亮描边,方便追踪 OP 在楼里说了什么。


8) Dead 评论处理(单选模式)

对被标记为 dead 的评论可以选择:

  • 不处理
  • 弱化(降低透明度/饱和度)
  • 隐藏(同时自动启用弱化)


9) 像素头像(基于用户名生成)

每个用户名旁边会生成一个 对称像素头像(纯前端生成,无请求外部资源):

  • 默认开启
  • 支持调整大小、是否加边框、边框颜色、边框样式
  • 有做性能优化:懒加载 + 缓存,避免长楼卡顿

用途:快速识别同一个人在楼里出现的回复。


10) 复制评论直达链接(可选)

每条评论右上角可以显示 copy

  • 一键复制 item?id=xxx#commentId 的直达链接
    方便分享某条具体回复。


11) 顶部导航吸附 + 回到顶部

  • 顶部 HN 导航栏可吸附(sticky)
  • 右下角 回到顶部按钮(滚动到一定距离才出现)


12) 快捷键(可选)

Alt + Shift:

  • C 全部折叠
  • E 全部展开
  • T 循环切换主题(auto/light/dark)

Gemini 对我有两大痛点

  • 网页端一卡就自动变黑色主题看不清
  • 来回切换 pro 和快速要动鼠标,然而我只想敲键盘

遂花了点时间用 ai 辅助搞出了这个脚本,两个核心功能

  • 锁定目前的主题
  • 设置打开网页的默认模型(推荐设置为快速,结合下面的功能)
  • 按住 ctrl + 回车强制使用 pro 模型,发送完成后切回原先模型,推荐平常就使用快速,注意:切回功能会在 gemini 回复完成后生效。

分享给有需要的佬友,欢迎反馈问题,如果有帮助你,点点大拇指吧哈哈哈

gemini 脚本 1.0.txt


📌 转载信息
原作者:
Carnivore
转载时间:
2026/1/11 08:37:05

能加载 RSS 分类,点击到具体 RSS 的时候就 500 错误了,
从 reddit 的反馈看,影响全球两百多个国家和地区的 Feedly 用户,嗯哼。
话说热度不行啊,V2EX 没有讨论,用户量应该没那么少啊。

自从去年 9 月大更新后,Feedly 的稳定性就变差了很多,这次也影响到了 APP 用户,说明是服务器炸了。
(嗯,我虽然因为 feedly 不修 bug ,叛逃去了 inoreader 但有点不适应,毕竟还没写用户脚本进行优化使用,结果叛逃第三天 Feedly 修复了 bug 所以又反叛回来了)

Error: 500 / server error / undefined

对了,可能有人要问,为什么我的界面不一样,看起来简洁许多……别问,问就是自己写的用户脚本/油猴脚本。

打磨了一周的卡片式 ui 大佬们还有更多的 功能 / 样式 建议吗 准备发布啦

它主要解决什么问题?

  • 长楼 “找上下文” 困难:不知道自己在回复谁
  • 讨论树太深,页面太长:滚动疲劳、定位困难
  • 想快速扫顶层(L0)主线程:上下跳楼不够顺手
  • 深色模式 / 阅读舒适度:原生样式太简陋,眼睛累


核心功能一览(当前脚本已实现)

1) 评论区卡片化重排(核心)

  • 把 HN 原生的表格树评论,重建为 卡片式树结构(视觉层级更清晰)
  • 每条评论是一个卡片:用户名 / 时间 / 操作按钮在顶部,正文在下面,子回复作为卡片嵌套
  • 支持 最大宽度、圆角、间距、缩进等布局调节

适用场景:长讨论阅读体验会明显提升,尤其是多层回复。


2) 主题与外观:浅色 / 深色 / 跟随系统 + 渐变层级背景

  • 主题模式:跟随系统 / 强制浅色 / 强制深色
  • 页面背景可自定义(浅色 / 深色各一套)
  • 评论卡片支持 “按层级渐变”:L0/L1/L2… 背景颜色可调
  • 也提供一些预设配色(比如蓝灰、暖米色、灰阶极简、深海蓝黑等)

目标:让 “长时间刷楼” 变得更舒服。


3) 折叠 / 展开(偏扫楼用)

为了快速扫楼,它提供多种折叠策略(可开关 / 可配置):

  • 每条评论可折叠:右上角有 “折叠 / 展开” 按钮
  • 折叠触发方式(可选):
    • 仅按钮
    • 按钮 + 单击正文
    • 按钮 + 双击正文
  • 双击正文折叠 / 展开整个子树(可选):
    双击某条评论正文,直接折叠 / 展开它下面整棵回复树
  • 默认折叠策略(两套):
    • 按数量:顶层评论如果 “后代回复总数” 超过阈值,默认折叠(便于扫主楼)
    • 按深度:从指定深度开始默认折叠(避免页面无限延长)
  • 全局快捷按钮(右下角):
    • 全部折叠(收起所有有子回复的评论)
    • 全部展开(展开所有评论)


4) “展开提示” + 数量颜色提醒(更好扫大楼)

当某条评论被折叠时,会出现类似:

  • [+展开 8 条回复](单击默认展开 双击展开全部子级)

并且这个数字会根据数量变色(可调阈值 / 颜色):

  • 少量:更偏主题强调色
  • 中量:偏粉 / 亮色
  • 大量:偏红(提醒这楼很长)

另外支持把这个提示按钮放在:

  • 评论底部(默认)
  • 或者放在右上角按钮行里(更省垂直空间)


5) L0 主线程导航(扫楼神器)

每条评论右上角会有:

  • 上 L0 / 下 L0:快速跳到上一个 / 下一个顶层主线程
  • L0 折叠:一键折叠当前评论所在的顶层主线程(快速收起一整楼)
  • 还有一个 “层展”:只展开下一层,下一层的子回复继续保持折叠(用于逐层读)

(这块我自己用得最多,长讨论基本靠它快速扫楼。)


6) Hover 父级高亮:找上下文更快

鼠标悬停某条评论时:

  • 自动高亮它的直接父评论
    用来快速确认 “我现在在回复谁 / 这层上下文是什么”。


7) OP(楼主)高亮

楼主(story author)的评论会有额外高亮描边,方便追踪 OP 在楼里说了什么。


8) Dead 评论处理(单选模式)

对被标记为 dead 的评论可以选择:

  • 不处理
  • 弱化(降低透明度 / 饱和度)
  • 隐藏(同时自动启用弱化)


9) 像素头像(基于用户名生成)

每个用户名旁边会生成一个 对称像素头像(纯前端生成,无请求外部资源):

  • 默认开启
  • 支持调整大小、是否加边框、边框颜色、边框样式
  • 有做性能优化:懒加载 + 缓存,避免长楼卡顿

用途:快速识别同一个人在楼里出现的回复。


10) 复制评论直达链接(可选)

每条评论右上角可以显示 copy

  • 一键复制 item?id=xxx#commentId 的直达链接
    方便分享某条具体回复。


11) 顶部导航吸附 + 回到顶部

  • 顶部 HN 导航栏可吸附(sticky)
  • 右下角 回到顶部按钮(滚动到一定距离才出现)


12) 快捷键(可选)

Alt + Shift:

  • C 全部折叠
  • E 全部展开
  • T 循环切换主题(auto/light/dark)

📌 转载信息
原作者:
louishino0524
转载时间:
2026/1/6 19:13:42

// ==UserScript==
// @name         Grok/X.ai 自动化注册机 (集成自动清理与循环版)
// @namespace    http://tampermonkey.net/
// @version      5.0
// @description  全自动流程:注册 -> 提取Token -> 清理Cookie -> 循环重启
// @author       Bytebender
// @match        *://*/*
// @match        https://x.ai/*
// @match        https://www.x.ai/*
// @match        https://accounts.x.ai/*
// @match        https://grok.com/*
// @match        https://www.grok.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @grant        GM_setClipboard
// @grant        GM_cookie
// @connect      mail.chatgpt.org.uk
// @connect      100.64.0.101
// @connect      api.x.ai
// @connect      x.ai
// @connect      grok.com
// @connect      www.grok.com
// @connect      accounts.x.ai
// ==/UserScript==

(function() {
    'use strict';

    // ========================================================
    // 1. 随机数据生成工具
    // ========================================================

    // 生成随机姓名 (首字母大写)
    function getRandomName() {
        const chars = 'abcdefghijklmnopqrstuvwxyz';
        const len = Math.floor(Math.random() * 5) + 4; // 长度 4-8
        let result = '';
        for (let i = 0; i < len; i++) {
            result += chars.charAt(Math.floor(Math.random() * chars.length));
        }
        return result.charAt(0).toUpperCase() + result.slice(1);
    }

    // 生成强密码 (12位,包含大小写+数字+特殊符号)
    function getRandomPassword() {
        const lower = "abcdefghijklmnopqrstuvwxyz";
        const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        const nums = "0123456789";
        const symbols = "!@#$%^&*";

        // 1. 确保每种字符至少有一个
        let pass = "";
        pass += lower[Math.floor(Math.random() * lower.length)];
        pass += upper[Math.floor(Math.random() * upper.length)];
        pass += nums[Math.floor(Math.random() * nums.length)];
        pass += symbols[Math.floor(Math.random() * symbols.length)];

        // 2. 补足剩余长度
        const allChars = lower + upper + nums + symbols;
        for (let i = 0; i < 8; i++) {
            pass += allChars[Math.floor(Math.random() * allChars.length)];
        }

        // 3. 打乱顺序 (洗牌)
        return pass.split('').sort(() => 0.5 - Math.random()).join('');
    }

    // ========================================================
    // 2. 任务编排配置
    // ========================================================
    const actions = [
        // --- 阶段一:Grok 首页跳转 ---
        {
            "step_name": "1. 点击 Grok 首页入口",
            "type": "click",
            "selector": "html > body > div:nth-of-type(2) > div > div > div > main > div > div > div:nth-of-type(3) > div:nth-of-type(2) > a:nth-of-type(2)",
            "url_keyword": "grok.com"
        },
        // --- 阶段二:进入注册页 (X.ai) ---
        {
            "step_name": "2. 点击 X.ai 注册/登录按钮",
            "type": "click",
            "selector": "html > body > div:nth-of-type(2) > div > div > div:nth-of-type(2) > div > div:nth-of-type(2) > button",
            "url_keyword": "accounts.x.ai"
        },
        // --- 阶段三:自动化邮箱 ---
        {
            "step_name": "3. 自动申请并填写临时邮箱",
            "type": "get_email",
            "selector": "html > body > div:nth-of-type(2) > div > div > div:nth-of-type(2) > div > form > div > div > input",
        },
        {
            "step_name": "4. 点击下一步 (提交邮箱)",
            "type": "click",
            "selector": "html > body > div:nth-of-type(2) > div > div > div:nth-of-type(2) > div > form > div:nth-of-type(2) > button"
        },
        // --- 阶段四:验证码 ---
        {
            "step_name": "5. 等待邮件验证码并自动填写",
            "type": "fill_code",
            "selector": "input[name='code']"
        },
        // --- 阶段五:填写个人信息 (全随机) ---
        {
            "step_name": "6. 填写名 (First Name)",
            "type": "input",
            "selector": "input[name='givenName']",
            "value": "__RANDOM__"
        },
        {
            "step_name": "7. 填写姓 (Last Name)",
            "type": "input",
            "selector": "input[name='familyName']",
            "value": "__RANDOM__"
        },
        {
            "step_name": "8. 填写密码 (强密码)",
            "type": "input",
            "selector": "input[name='password']",
            "value": "__RANDOM_PASS__"
        },
        // --- 阶段六:提交 ---
        {
            "step_name": "9. 点击最终提交",
            "type": "click",
            "selector": "button[type='submit']"
        },
        // --- 阶段七:提取 Token ---
        {
            "step_name": "10. 检查跳转并上传 Token",
            "type": "wait_url_and_upload",
            "target_url": "grok.com"
        },
        // --- 阶段八:清理环境并循环 ---
        {
            "step_name": "11. 清理 Cookie 并重启循环",
            "type": "clean_and_restart",
            "clean_targets": [
                "https://x.ai/",
                "https://www.x.ai/",
                "https://accounts.x.ai/",
                "https://grok.com/",
                "https://www.grok.com/"
            ]
        }
    ];

    // ========================================================
    // 3. 核心功能类 (邮箱/网络/Cookie)
    // ========================================================

    // 3.1 网络请求封装
    function gmFetch(url, options) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                url: url,
                method: options.method || 'GET',
                headers: options.headers || {},
                data: options.body || null,
                onload: (response) => {
                    if (response.status >= 200 && response.status < 300) {
                        try { resolve(JSON.parse(response.responseText)); }
                        catch (e) { resolve(response.responseText); }
                    } else { reject(new Error(`HTTP Error: ${response.status}`)); }
                },
                onerror: () => reject(new Error('Network Error')),
                ontimeout: () => reject(new Error('Timeout'))
            });
        });
    }

    // 3.2 临时邮箱客户端
    class TempMailClient {
        constructor() {
            this.baseUrl = "https://mail.chatgpt.org.uk/api";
            this.headers = {
                "User-Agent": "Mozilla/5.0",
                "Origin": "https://mail.chatgpt.org.uk",
                "Referer": "https://mail.chatgpt.org.uk/"
            };
        }
        async getEmail() {
            const result = await gmFetch(`${this.baseUrl}/generate-email`, {
                method: "GET",
                headers: { ...this.headers, "content-type": "application/json" }
            });
            if (result && result.success && result.data?.email) return result.data.email;
            throw new Error("邮箱API返回异常");
        }
        async fetchMessages(email) {
            const url = `${this.baseUrl}/emails?email=${encodeURIComponent(email)}`;
            const result = await gmFetch(url, {
                method: "GET",
                headers: { ...this.headers, "cache-control": "no-cache" }
            });
            return (result.success && result.data?.emails) ? result.data.emails : [];
        }
        async waitForCode(email, timeoutSec = 120) {
            console.log(`[Mail] 开始监听 ${email} ...`);
            const startTime = Date.now();
            const codeRegex = /\b[A-Z0-9]{3}-[A-Z0-9]{3}\b|\b\d{6}\b/;
            return new Promise((resolve, reject) => {
                const timer = setInterval(async () => {
                    if (Date.now() - startTime > timeoutSec * 1000) {
                        clearInterval(timer);
                        reject(new Error("等待验证码超时"));
                    }
                    try {
                        const msgs = await this.fetchMessages(email);
                        if (msgs.length > 0) {
                            for (const msg of msgs) {
                                const content = (msg.subject || "") + " " + (msg.html_content || "");
                                const match = content.match(codeRegex);
                                if (match) {
                                    clearInterval(timer);
                                    resolve(match[0]);
                                    return;
                                }
                            }
                        }
                    } catch(e) { console.warn("Polling error:", e); }
                }, 3000);
            });
        }
    }

    // 3.3 Token 上传逻辑
    async function extractAndUploadToken() {
        return new Promise((resolve, reject) => {
            GM_cookie.list({ name: "sso" }, (cookies, error) => {
                if (error || !cookies || cookies.length === 0) {
                    return reject(new Error("SSO Cookie missing"));
                }
                const ssoToken = cookies[0].value;
                console.log("获取到 Token:", ssoToken.substring(0, 10) + "...");

                GM_xmlhttpRequest({
                    url: "http://xxx/api/tokens/add",
                    method: "POST",
                    headers: {
                        "content-type": "application/json",
                        "authorization": "Bearer xxxxx"
                    },
                    data: JSON.stringify({ tokens: [ssoToken], token_type: "sso" }),
                    onload: (response) => {
                        if (response.status >= 200 && response.status < 300) {
                            console.log("Token 上传成功!");
                            resolve();
                        } else {
                            reject(new Error("Upload failed: " + response.responseText));
                        }
                    },
                    onerror: (err) => reject(err)
                });
            });
        });
    }

    // 3.4 Cookie 清理逻辑
    function executeCleanCookies(targetUrls) {
        return new Promise((resolve) => {
            if (!targetUrls || targetUrls.length === 0) return resolve();
            let completed = 0;
            targetUrls.forEach(url => {
                GM_cookie.list({ url: url }, function(cookies, error) {
                    if (cookies && cookies.length > 0) {
                        cookies.forEach(c => {
                            GM_cookie.delete({ name: c.name, url: url }, () => {});
                        });
                    }
                    completed++;
                    if (completed === targetUrls.length) {
                        setTimeout(resolve, 800); // 缓冲
                    }
                });
            });
        });
    }

    // 3.5 Native 输入模拟 (绕过 React/Vue 绑定)
    function setNativeValue(element, value) {
        const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
        const prototype = Object.getPrototypeOf(element);
        const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
        if (valueSetter && valueSetter !== prototypeValueSetter) {
            prototypeValueSetter.call(element, value);
        } else {
            valueSetter.call(element, value);
        }
        element.dispatchEvent(new Event('input', { bubbles: true }));
    }

    // 3.6 元素等待
    const waitForElement = (selector, timeout = 10000) => {
        return new Promise((resolve, reject) => {
            const el = document.querySelector(selector);
            if (el) return resolve(el);
            const observer = new MutationObserver(() => {
                const el = document.querySelector(selector);
                if (el) { observer.disconnect(); resolve(el); }
            });
            observer.observe(document.body, { childList: true, subtree: true });
            setTimeout(() => { observer.disconnect(); reject(new Error('元素超时: ' + selector)); }, timeout);
        });
    };

    // ========================================================
    // 4. 自动化执行引擎
    // ========================================================
    const mailClient = new TempMailClient();
    let isRunning = GM_getValue('script_is_running', false);
    let currentIndex = GM_getValue('script_step_index', 0);

    console.log(`🚀 [注册机状态] Running: ${isRunning} | Step: ${currentIndex}`);

    GM_registerMenuCommand(`▶️ 启动/继续`, () => {
        GM_setValue('script_is_running', true);
        isRunning = true;
        runCurrentStep();
    });

    GM_registerMenuCommand("🔄 强制重置", () => {
        GM_setValue('script_step_index', 0);
        GM_setValue('script_is_running', false);
        GM_setValue('current_temp_email', '');
        location.reload();
    });

    async function runCurrentStep() {
        if (!GM_getValue('script_is_running', false)) return;

        // 异常保护:索引越界重置
        if (currentIndex >= actions.length) {
            GM_setValue('script_step_index', 0);
            return location.reload();
        }

        const action = actions[currentIndex];
        console.log(`[Step ${currentIndex + 1}] ${action.step_name} (${action.type})`);

        // URL 检查 (如果不在目标域名,等待跳转)
        if (action.url_keyword && !location.href.includes(action.url_keyword)) {
            console.log(`等待跳转到 ${action.url_keyword}...`);
            return setTimeout(runCurrentStep, 2000);
        }

        try {
            await new Promise(r => setTimeout(r, 3000)); // 基础缓冲
            let el = null;
            if (action.selector) el = await waitForElement(action.selector);

            // --- 动作分发 ---
            if (action.type === 'get_email') {
                const email = await mailClient.getEmail();
                console.log("获取邮箱:", email);
                GM_setValue('current_temp_email', email);
                GM_setClipboard(email);

                el.click(); el.focus();
                setNativeValue(el, email);
                el.dispatchEvent(new Event('change', { bubbles: true }));
                el.blur();
            }
            else if (action.type === 'fill_code') {
                const email = GM_getValue('current_temp_email');
                const rawCode = await mailClient.waitForCode(email);
                const code = rawCode.replace(/-/g, ''); // 清洗连字符
                console.log('填入验证码:', code);

                el.scrollIntoView({block: "center"});
                el.click(); el.focus();
                const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
                nativeSetter.call(el, code);
                el.dispatchEvent(new Event('input', { bubbles: true }));
                el.dispatchEvent(new Event('change', { bubbles: true }));
                await new Promise(r => setTimeout(r, 500));
                el.blur();
            }
            else if (action.type === 'input') {
                el.focus();
                let val = action.value;

                // 处理随机变量
                if (val === '__RANDOM__') val = getRandomName();
                if (val === '__RANDOM_PASS__') val = getRandomPassword();

                console.log(`[Input] 填写值: ${val}`);
                setNativeValue(el, val);
                el.blur();
            }
            else if (action.type === 'wait_url_and_upload') {
                if (!location.href.includes(action.target_url)) {
                    return setTimeout(runCurrentStep, 1500); // URL不对,继续等待
                }

                // 尝试多次上传,防止 Cookie 未即时写入
                let retry = 0;
                while (retry < 5) {
                    try {
                        await extractAndUploadToken();
                        GM_notification({ text: 'Token 上传成功!准备清理...', title: '成功' });
                        break;
                    } catch (e) {
                        console.warn("Token提取失败,重试中...", e);
                        await new Promise(r => setTimeout(r, 2000));
                        retry++;
                    }
                }
                // 继续下一步
            }
            else if (action.type === 'clean_and_restart') {
                GM_notification({ text: '清理 Cookie 并重启循环...', title: '系统维护' });
                await executeCleanCookies(action.clean_targets);

                GM_setValue('script_step_index', 0);
                GM_setValue('current_temp_email', '');

                console.log(">>> 循环重置完成,3秒后刷新");
                setTimeout(() => {
                    window.location.href = "https://grok.com/";
                }, 3000);
                return; // 结束本次执行栈
            }
            else if (action.type === 'click') {
                el.click();
            }

            // --- 步进逻辑 ---
            currentIndex++;
            GM_setValue('script_step_index', currentIndex);
            setTimeout(runCurrentStep, 1500);

        } catch (e) {
            console.error("执行出错:", e);
            // 遇到严重错误可以考虑刷新页面重试
            // setTimeout(() => location.reload(), 5000);
        }
    }

    // 启动检测
    function tryStart() {
        if (isRunning) {
            setTimeout(runCurrentStep, 1500);
        }
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        tryStart();
    } else {
        window.addEventListener('load', tryStart);
    }

})();

修改 extractAndUploadToken 填入自己的 Grok2api 地址和凭证

开启无痕窗口打开 Grok,接着启动脚本即可

会自动生成邮箱、填写注册信息和验证码

IP 不好 CF 验证不会自动过,需要手动继续和点击下一步

IP 好就全自动了啦

循环注册 w

即梦视频去水印下载 1
即梦视频去水印下载 2
图片&视频无水印下载

// ==UserScript==
// @name         即梦AI去水印
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  通过重写XMLHttpRequest实现即梦AI 图片&视频下载去水印!
// @author       mihuc
// @match        https://jimeng.jianying.com/ai-tool/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jimeng.jianying.com
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

(function () {
    'use strict';
    // 新增:有限大小的映射表与工具函数,用于保存 history id -> 对应 data
    const jimengDataMap = new Map(); // key: history id (uuid string), value: data object
    const JIMENG_DATA_MAP_MAX = 500;

    // 将数据写入 map(带容量控制)
    function setJimengDataMap(key, value) {
        if (!key || typeof key !== 'string' || value === undefined || value === null) return;
        try {
            jimengDataMap.set(key, value);
            // 简单淘汰最旧条目
            if (jimengDataMap.size > JIMENG_DATA_MAP_MAX) {
                const oldestKey = jimengDataMap.keys().next().value;
                if (oldestKey !== undefined) jimengDataMap.delete(oldestKey);
            }
        } catch (e) {
            console.warn('jimeng: setJimengDataMap error', e);
        }
    }

    // 从 map 读取数据
    function getJimengData(key) {
        if (!key) return null;
        return jimengDataMap.has(key) ? jimengDataMap.get(key) : null;
    }

    // 清空缓存(可选)
    function clearJimengDataMap() {
        try { jimengDataMap.clear(); } catch (e) { console.warn('jimeng: clearJimengDataMap error', e); }
    }

    /**
     * 尝试从响应中记录 get_history_by_ids 或 get_asset_list 类型的数据
     * @param {object} json - 已解析的 JSON 响应
     * @param {string} url - 请求 URL(可选,用于基于 URL 的判断)
     */
    function tryRecordHistoryFromResponse(json, url) {
        try {
            if (!json || typeof json !== 'object') return;
            const data = json.data;
            if (!data || typeof data !== 'object') return;

            // 通过 URL 判断接口类型
            const isHistoryEndpoint = typeof url === 'string' && url.indexOf('get_history_by_ids') !== -1;
            const isAssetEndpoint = typeof url === 'string' && url.indexOf('get_asset_list') !== -1;

            // 简单 UUID-like 正则匹配
            const uuidLike = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;

            // 1) 处理 get_history_by_ids 风格的响应(已有逻辑)
            if (isHistoryEndpoint) {
                for (const k in data) {
                    if (!Object.prototype.hasOwnProperty.call(data, k)) continue;
                    setJimengDataMap(k, data[k]);
                    // console.info('jimeng: recorded history key (by url)=', k);

                    if (data[k].item_list) {
                        for (const item of data[k].item_list) {
                            if (item.image && item.image.large_images) {
                                const image_item = item.image.large_images[0];
                                image_item["_type"] = "img"; // 标记类型,便于后续识别
                                setJimengDataMap(image_item.image_uri, image_item);

                            }
                        }
                    }
                }
            }

            // 2) 处理 get_asset_list 风格的响应:查找 data.asset_list 中的 submit_id(与 item_list 同级)
            if (isAssetEndpoint || Array.isArray(data.asset_list)) {
                const list = Array.isArray(data.asset_list) ? data.asset_list : [];
                for (const entry of list) {
                    // entry 可能是 { video: { submit_id, item_list: [...] , ... } } 或其他嵌套形式
                    if (!entry || typeof entry !== 'object') continue;

                    // 尝试常见路径:entry.video.submit_id 或 entry.submit_id
                    const candidates = [];
                    if (entry.video && typeof entry.video === 'object') candidates.push({ obj: entry.video, type: "video" });
                    if (entry.submit_id) candidates.push({ obj: entry, type: "video" });

                    if (entry.agent_conversation_session && entry.agent_conversation_session.submit_id_data_map) {
                        for (const key in entry.agent_conversation_session.submit_id_data_map) {
                            if (!Object.prototype.hasOwnProperty.call(entry.agent_conversation_session.submit_id_data_map, key)) continue;
                            const v = entry.agent_conversation_session.submit_id_data_map[key];
                            if (v.item_list) {
                                const image_item = v.item_list[0].image.large_images[0];
                                candidates.push({ obj: image_item, type: "img" });
                            }
                        }
                    }
                    // 也尝试向下查找一层含 submit_id 的对象(防止不同嵌套)
                    for (const key in entry) {
                        if (!Object.prototype.hasOwnProperty.call(entry, key)) continue;
                        const v = entry[key];
                        if (v && typeof v === 'object' && v.submit_id) {
                            candidates.push({ obj: v, type: "video" });
                        }
                    }

                    for (const c of candidates) {
                        const obj = c.obj;
                        obj["_type"] = c.type; // 标记类型,便于后续识别
                        const submitId = obj && obj.submit_id;
                        if (submitId && typeof submitId === 'string') {
                            // 将 submit_id 对应的整个对象记录(通常包含 item_list)
                            setJimengDataMap(submitId, obj);
                            console.info('jimeng: recorded asset submit_id=', submitId);
                        }

                        const imageUri = obj && obj.image_uri;
                        if (imageUri && typeof imageUri === 'string') {
                            setJimengDataMap(imageUri, obj);
                            console.info('jimeng: recorded asset image_uri=', imageUri);
                        }
                    }
                }
            }

            // 3) 兜底:若不是明确的接口,也检测顶级 uuid-like key(旧逻辑)
            for (const k in data) {
                if (!Object.prototype.hasOwnProperty.call(data, k)) continue;
                const val = data[k];
                if (uuidLike.test(k) && val && (val.item_list || val.video || Array.isArray(val.item_list))) {
                    setJimengDataMap(k, val);
                    console.info('jimeng: recorded history key (by content)=', k);
                }
            }
        } catch (e) {
            console.warn('jimeng: tryRecordHistoryFromResponse error', e);
        }
    }

    // 暴露查询接口到 window,便于控制台快速查看(只读查询)
    try {
        Object.defineProperty(window, 'getJimengData', {
            value: function (id) { return getJimengData(id); },
            writable: false,
            configurable: true
        });
        // 仅供调试:暴露底层 Map(请勿在生产逻辑中修改)
        Object.defineProperty(window, '_jimengDataMap', {
            value: jimengDataMap,
            writable: false,
            configurable: true
        });
    } catch (e) {
        // 忽略在严格 CSP/沙箱环境下的定义失败
    }

    function decodeBase64Safe(b64) {
        if (!b64 || typeof b64 !== 'string') return null;
        // 过滤掉明显不是 base64 的短串
        if (b64.length < 16) return null;
        try {
            return atob(b64);
        } catch (e) {
            // 尝试 URL-safe 变体
            try {
                const normalized = b64.replace(/-/g, '+').replace(/_/g, '/');
                return atob(normalized);
            } catch (e2) {
                return null;
            }
        }
    }
    // 从已缓存的响应数据(jimengDataMap)或响应对象中提取 main_url(优先返回第一个可解码的 URL)
    function extractMainUrlFromData(obj) {
        if (!obj || typeof obj !== 'object') return null;
        const seen = new Set();
        const type = obj._type || null;
        function walk(o) {
            if (!o || typeof o !== 'object') return null;
            if (seen.has(o)) return null;
            seen.add(o);

            for (const k in o) {
                if (!Object.prototype.hasOwnProperty.call(o, k)) continue;
                const v = o[k];

                try {
                    // 发现 main_url 字段(通常为 base64),尝试解码并返回第一个有效 URL
                    if ((k === 'main_url' || k === 'mainUrl') && typeof v === 'string') {
                        const decoded = decodeBase64Safe(v);
                        if (decoded && decoded.startsWith('http')) return decoded;
                    }

                    // 有些视频信息以 video_list.video_1.main_url 存在
                    if (k === 'video_list' && typeof v === 'object') {
                        // 遍历 video_list 下的各质量项
                        for (const q in v) {
                            if (!Object.prototype.hasOwnProperty.call(v, q)) continue;
                            const item = v[q];
                            if (item && typeof item === 'object' && item.main_url) {
                                const dec = decodeBase64Safe(item.main_url);
                                if (dec && dec.startsWith('http')) return dec;
                            }
                        }
                    }
                    if (k === 'image_url' && typeof v === 'string' && type == "img") {
                        if (v) return v;
                    }

                    // 若字段为字符串化 JSON,尝试解析并继续查找
                    if (typeof v === 'string') {
                        try {
                            const parsed = JSON.parse(v);
                            const r = walk(parsed);
                            if (r) return r;
                        } catch (e) {
                            // ignore
                        }
                    }

                    // 递归对象或数组
                    if (typeof v === 'object') {
                        const r = walk(v);
                        if (r) return r;
                    }
                } catch (e) {
                    // ignore individual errors
                }
            }
            return null;
        }

        return walk(obj);
    }

    // 重写 XMLHttpRequest.prototype 以拦截响应文本

    function interceptXHR() {
        try {
            if (!window || !window.XMLHttpRequest) return;
            const XProto = window.XMLHttpRequest && window.XMLHttpRequest.prototype;
            if (!XProto) return;
            // 防止重复打补丁
            if (XProto.__jimeng_patched) return;

            const _open = XProto.open;
            const _send = XProto.send;

            XProto.open = function (method, url, ...rest) {
                try {
                    this.__jimeng_url = url;
                } catch (e) { /* ignore */ }
                return _open.apply(this, [method, url, ...rest]);
            };

            XProto.send = function (body) {
                try {
                    // attach listener safely
                    this.addEventListener && this.addEventListener('readystatechange', function () {
                        try {
                            if (this.readyState === 4) {
                                // 仅在特定接口上处理,避免影响其它请求
                                const reqUrl = (this.__jimeng_url || '').toString();
                                if (!(reqUrl.indexOf('get_asset_list') !== -1 || reqUrl.indexOf('get_history_by_ids') !== -1)) {
                                    return; // 非目标接口,直接忽略
                                }

                                const txt = this.responseText;
                                if (typeof txt === 'string' && txt.length > 100) {
                                    // 尝试直接解析为 JSON
                                    try {
                                        const parsed = JSON.parse(txt);
                                        // 新增:记录 get_history_by_ids 返回的顶级 key -> data
                                        tryRecordHistoryFromResponse(parsed, this.__jimeng_url);
                                    } catch (e) {
                                        // 回退:查找第一个 '{' 子串并尝试解析
                                        try {
                                            const idx = txt.indexOf('{');
                                            if (idx >= 0) {
                                                const sub = txt.slice(idx);
                                                const parsed2 = JSON.parse(sub);
                                                // 新增回退解析时也尝试记录
                                                tryRecordHistoryFromResponse(parsed2, this.__jimeng_url);
                                            }
                                        } catch (e2) {
                                            // 忽略不可解析的文本
                                        }
                                    }
                                }
                            }
                        } catch (e) {
                            // 忽略单次处理错误,避免影响页面
                        }
                    });
                } catch (e) {
                    // 忽略 attach 错误
                }
                return _send.apply(this, arguments);
            };

            // 标记已打补丁
            try { XProto.__jimeng_patched = true; } catch (e) { }
            console.info('jimeng: XHR interceptor installed');
        } catch (e) {
            console.warn('jimeng: interceptXHR failed', e);
        }
    }

    // 替换原先注入/监听调用:直接安装 XHR 拦截器(同时保留 fetch 拦截作为回退)
    try {
        interceptXHR();
    } catch (e) {
        console.warn('jimeng: failed to install XHR interceptor', e);
    }

    // 优先使用 GM_xmlhttpRequest 绕过页面 CSP,回退到 fetch
    function fetchBypassCSP(url, options = {}) {
        // options: { method, headers, responseType, onProgress } - responseType支持 'arraybuffer' 或 'blob' 等
        const method = options.method || 'GET';
        const headers = options.headers || {};
        const responseType = options.responseType || 'arraybuffer';
        const onProgress = options.onProgress; // 进度回调函数

        // 如果 Tampermonkey 提供 GM_xmlhttpRequest,则使用它(可绕过页面 CSP)
        if (typeof GM_xmlhttpRequest === 'function') {
            return new Promise((resolve, reject) => {
                try {
                    GM_xmlhttpRequest({
                        method: method,
                        url: url,
                        headers: headers,
                        responseType: responseType,
                        onprogress(progressEvent) {
                            // 调用进度回调
                            if (typeof onProgress === 'function') {
                                onProgress(progressEvent);
                            }
                        },
                        onload(res) {
                            // res.response 在 responseType=arraybuffer 时是 ArrayBuffer
                            resolve({
                                ok: (res.status >= 200 && res.status < 300),
                                status: res.status,
                                statusText: res.statusText,
                                response: res.response,
                                responseHeaders: res.responseHeaders
                            });
                        },
                        onerror(err) {
                            reject(err);
                        },
                        ontimeout() {
                            reject(new Error('timeout'));
                        }
                    });
                } catch (e) {
                    reject(e);
                }
            });
        }

        // 回退:普通 fetch(受 CSP 限制,但无法支持进度监控)
        return (async () => {
            const resp = await fetch(url, { method, headers, mode: options.mode || 'cors' });
            const blob = await resp.blob();
            const arrayBuffer = await blob.arrayBuffer();
            return {
                ok: resp.ok,
                status: resp.status,
                statusText: resp.statusText,
                response: arrayBuffer
            };
        })();
    }

    /**
     * 进度弹窗管理器 - 管理多个下载弹窗的位置和堆叠
     */
    const ToastManager = {
        toasts: [], // 存储所有活跃的toast
        baseTop: 20, // 基础顶部距离
        spacing: 10, // toast之间的间距

        /**
         * 添加新的toast到管理器
         * @param {Object} toast - toast对象
         */
        add(toast) {
            this.toasts.push(toast);
            this.updatePositions();
        },

        /**
         * 从管理器中移除toast
         * @param {Object} toast - toast对象
         */
        remove(toast) {
            const index = this.toasts.indexOf(toast);
            if (index > -1) {
                this.toasts.splice(index, 1);
                this.updatePositions();
            }
        },

        /**
         * 更新所有toast的位置
         */
        updatePositions() {
            let currentTop = this.baseTop;
            this.toasts.forEach(toast => {
                if (toast.container && toast.container.parentNode) {
                    toast.container.style.top = currentTop + 'px';
                    // 获取toast的实际高度
                    const height = toast.container.offsetHeight;
                    currentTop += height + this.spacing;
                }
            });
        }
    };

    /**
     * 创建右上角进度弹窗
     * @returns {Object} 包含容器元素和更新方法的对象
     */
    function createProgressToast(msg = "正在下载视频...") {
        const container = document.createElement('div');
        container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            min-width: 300px;
            max-width: 400px;
            background: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 16px 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
            z-index: 99999;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
            font-size: 14px;
            line-height: 1.5;
            transition: top 0.3s ease, opacity 0.3s ease;
        `;

        const title = document.createElement('div');
        title.style.cssText = `
            font-weight: 600;
            margin-bottom: 8px;
            font-size: 15px;
        `;
        title.textContent = msg;

        const progressBarBg = document.createElement('div');
        progressBarBg.style.cssText = `
            width: 100%;
            height: 4px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 2px;
            margin: 8px 0;
            overflow: hidden;
        `;

        const progressBar = document.createElement('div');
        progressBar.style.cssText = `
            width: 0%;
            height: 100%;
            background: linear-gradient(90deg, #4CAF50, #8BC34A);
            border-radius: 2px;
            transition: width 0.3s ease;
        `;
        progressBarBg.appendChild(progressBar);

        const statusText = document.createElement('div');
        statusText.style.cssText = `
            font-size: 12px;
            color: rgba(255, 255, 255, 0.8);
            margin-top: 8px;
        `;
        statusText.textContent = '准备下载...';

        container.appendChild(title);
        container.appendChild(progressBarBg);
        container.appendChild(statusText);
        document.body.appendChild(container);

        const toast = {
            container,
            title,
            progressBar,
            statusText,
            /**
             * 更新进度
             * @param {number} percent - 进度百分比 (0-100)
             * @param {string} status - 状态文本
             */
            update(percent, status) {
                progressBar.style.width = percent + '%';
                if (status) statusText.textContent = status;
            },
            /**
             * 设置为成功状态
             * @param {string} message - 成功消息
             */
            success(message) {
                title.textContent = '✓ 下载完成';
                progressBar.style.background = 'linear-gradient(90deg, #4CAF50, #66BB6A)';
                statusText.textContent = message || '视频已保存';
            },
            /**
             * 设置为错误状态
             * @param {string} message - 错误消息
             */
            error(message) {
                title.textContent = '✗ 下载失败';
                progressBar.style.background = 'linear-gradient(90deg, #f44336, #e57373)';
                statusText.textContent = message || '请重试';
            },
            /**
             * 移除弹窗
             * @param {number} delay - 延迟时间(毫秒)
             */
            remove(delay = 0) {
                setTimeout(() => {
                    container.style.opacity = '0';
                    setTimeout(() => {
                        if (container.parentNode) {
                            container.parentNode.removeChild(container);
                        }
                        // 从管理器中移除
                        ToastManager.remove(toast);
                    }, 300);
                }, delay);
            }
        };

        // 添加到管理器
        ToastManager.add(toast);

        return toast;
    }

    /**
     * 下载视频通用函数(带进度显示)
     * @param {string} url - 视频URL
     * @param {string} filename - 可选的文件名
     */
    async function downloadVideo(url, filename) {
        const toast = createProgressToast();

        try {
            const res = await fetchBypassCSP(url, {
                responseType: 'arraybuffer',
                method: 'GET',
                onProgress: (event) => {
                    if (event.lengthComputable) {
                        const percent = Math.round((event.loaded / event.total) * 100);
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        const totalMB = (event.total / 1024 / 1024).toFixed(2);
                        toast.update(percent, `${loadedMB} MB / ${totalMB} MB (${percent}%)`);
                    } else {
                        // 无法获取总大小时显示已下载量
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        toast.update(50, `已下载 ${loadedMB} MB...`);
                    }
                }
            });

            if (!res || !res.ok) throw new Error('请求失败,status=' + (res && res.status));

            toast.update(100, '处理文件中...');

            const arrayBuffer = res.response;
            const blob = new Blob([arrayBuffer], { type: 'video/mp4' });
            const objectUrl = URL.createObjectURL(blob);

            const link = document.createElement('a');
            const now = new Date();
            const dateStr = now.toISOString()
                .slice(0, 19)
                .replace('T', '_')
                .replace(/:/g, '-');
            link.download = filename || `RWater_${dateStr}.mp4`;
            link.href = objectUrl;
            link.target = '_blank';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            URL.revokeObjectURL(objectUrl);

            toast.success('视频已开始下载');
            toast.remove(1000);
        } catch (e) {
            console.error('下载失败:', e);
            toast.error(e.message || '下载失败,请重试');
            toast.remove(2000);
        }
    }

    async function downloadImage(url, filename) {
        const toast = createProgressToast("正在下载图片...");

        try {
            const res = await fetchBypassCSP(url, {
                responseType: 'arraybuffer',
                method: 'GET',
                onProgress: (event) => {
                    if (event.lengthComputable) {
                        const percent = Math.round((event.loaded / event.total) * 100);
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        const totalMB = (event.total / 1024 / 1024).toFixed(2);
                        toast.update(percent, `${loadedMB} MB / ${totalMB} MB (${percent}%)`);
                    } else {
                        // 无法获取总大小时显示已下载量
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        toast.update(50, `已下载 ${loadedMB} MB...`);
                    }
                }
            });

            if (!res || !res.ok) throw new Error('请求失败,status=' + (res && res.status));

            toast.update(100, '处理文件中...');

            const arrayBuffer = res.response;
            const blob = new Blob([arrayBuffer], { type: 'image/png' });
            const objectUrl = URL.createObjectURL(blob);

            const link = document.createElement('a');
            const now = new Date();
            const dateStr = now.toISOString()
                .slice(0, 19)
                .replace('T', '_')
                .replace(/:/g, '-');
            link.download = filename || `RWater_${dateStr}.png`;
            link.href = objectUrl;
            link.target = '_blank';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            URL.revokeObjectURL(objectUrl);

            toast.success('图片已开始下载');
            toast.remove(1000);
        } catch (e) {
            console.error('下载失败:', e);
            toast.error(e.message || '下载失败,请重试');
            toast.remove(2000);
        }
    }
    /**
     * 初始化即梦网站的功能
     */
    function initJimengSite() {
        const observer = new MutationObserver(() => {
            jimeng_addVideoDownloadButton();
            jimeng_addImageDownloadButton();
        });

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

        // Also add event listeners for mouse interactions that might trigger UI changes
        document.addEventListener('mouseover', () => {
            setTimeout(() => {
                jimeng_addVideoDownloadButton();
                jimeng_addImageDownloadButton();
            }, 100);
        }, true);

        console.log('即梦: 初始化无水印下载工具');
        jimeng_addVideoDownloadButton();
        jimeng_addImageDownloadButton();
    }

    /**
     * 创建即梦视频下载按钮
     * @returns {HTMLElement} 下载按钮元素
     */
    function jimeng_createVideoDownloadButton() {
        const button = document.createElement('div');
        button.className = 'lv-btn lv-btn-secondary lv-btn-size-default lv-btn-shape-square baseButton-LQGhrC download-btn';
        const div = document.createElement('div');
        div.textContent = '无水印下载';
        button.appendChild(div);

        button.title = '无水印下载';
        button.style = '--right-padding: 14px; align-items: center;background-color: var(--bg-block-primary-default); border-radius: 8px; color: var(--text-primary, #f5fbff); display: flex; font-family: PingFang SC;font-size: 12px; font-weight: 400; gap: 4px; justify-content: center; line-height: 20px; padding: 8px var(--right-padding) 8px 12px; position: relative;'
        return button;
    }
    function jimeng_createImageDownloadButton() {
        const button = document.createElement('span');
        button.className = 'download-btn';
        button.textContent = "无水印下载";
        button.style = 'display: block;font-size: 12px;position: absolute;top: 5px;left: 5px;background: rgba(255, 255, 255, 0.87);border-radius: 5px;padding: 3px 10px;z-index: 100;'
        return button;
    }

    function jimeng_getVideoDataId(downloadBtn) {
        console.log('即梦: 开始查找视频元素...');
        const parentItem = downloadBtn.closest('[class*="item-"]');
        return parentItem ? parentItem.getAttribute('data-id') : null;
    }
    /**
     * 获取即梦视频URL
     * @param {Element} downloadBtn - 下载按钮元素
     * @returns {string|null} 视频URL或null
     */
    function jimeng_getVideoUrl(downloadBtn) {
        console.log('即梦: 开始查找视频元素...');
        const parentItem = downloadBtn.closest('[class*="item-"]');

        const videoWrapper = parentItem.querySelector('div[class^="video-node-wrapper-"]');
        if (!videoWrapper) {
            alert('未找到视频');
            console.log('即梦: 未找到videoWrapper');
            return null;
        }

        let videoElement = videoWrapper.querySelector('video');
        if (!videoElement) {
            alert('未找到视频');
            console.log('即梦: 未找到video元素');
            return null;
        }

        if (videoElement && videoElement.src) {
            console.log('即梦: 找到视频元素:', {
                src: videoElement.src,
                className: videoElement.className,
                width: videoElement.width,
                height: videoElement.height,
                naturalWidth: videoElement.naturalWidth,
                naturalHeight: videoElement.naturalHeight,
                attributes: Array.from(videoElement.attributes).map(attr => `${attr.name}="${attr.value}"`).join(', ')
            });
            return videoElement.src;
        }

        alert('未找到视频');
        console.log('即梦: 未找到合适的视频');
        return null;
    }
    function jimeng_getImageUrl(downloadBtn) {
        console.log('即梦: 开始查找图片元素...');
        const parentItem = downloadBtn.parentNode;

        const imageElement = parentItem.querySelector('img[class^="image-"]');
        if (!imageElement) {
            alert('未找到图片');
            console.log('即梦: 未找到imageElement');
            return null;
        }
        if (imageElement && imageElement.src) {
            console.log('即梦: 找到图片元素:', {
                src: imageElement.src,
            });
            return imageElement.src;
        }

        alert('未找到图片');
        console.log('即梦: 未找到合适的图片');
        return null;
    }
    /**
     * 添加视频下载按钮到即梦页面
     */
    function jimeng_addVideoDownloadButton() {
        const videoContents = document.querySelectorAll('div[class^="video-record-"]');
        if (!videoContents || videoContents.length === 0) {
            return;
        }
        for (let i = 0; i < videoContents.length; i++) {
            const topActionBar = videoContents[i].querySelector('div[class^="record-bottom-slots-"]');
            if (!topActionBar) {
                continue;
            }
            if (topActionBar.querySelector('div[class^="image-record-content-"]')) {
                continue;
            }
            if (topActionBar.querySelector('.download-btn')) {
                continue;
            }
            jimeng_processSingleVideoButton(topActionBar);
        }
    }

    /**
     * 添加图片下载按钮到即梦页面
     */
    function jimeng_addImageDownloadButton() {
        const imgContents = document.querySelectorAll('div[class^="agentic-image-record-item-"]');
        if (!imgContents || imgContents.length === 0) {
            return;
        }
        for (let i = 0; i < imgContents.length; i++) {
            const topActionBar = imgContents[i].querySelector('div[class^="image-card-container-"]');
            if (!topActionBar) {
                continue;
            }

            if (topActionBar.querySelector('.download-btn')) {
                continue;
            }
            jimeng_processSingleImageButton(topActionBar);
        }
    }

    /**
     * 处理单个视频按钮(已简化:不再依赖 jimengVideoMap)
     * @param {Element} topActionBar - 操作栏元素
     */
    function jimeng_processSingleVideoButton(topActionBar) {
        const downloadBtn = jimeng_createVideoDownloadButton();
        topActionBar.insertBefore(downloadBtn, topActionBar.firstChild);
        console.info('即梦: 添加视频下载按钮');

        downloadBtn.addEventListener('click', async () => {
            console.log('即梦: 点击视频下载按钮');
            const videoDataId = jimeng_getVideoDataId(downloadBtn);
            const videoUrl = jimeng_getVideoUrl(downloadBtn);
            console.log('即梦: 获取到的视频URL:', videoUrl);
            let finalUrl = videoUrl;

            try {
                // 2) 若仍然没有 finalUrl,尝试用 videoDataId 从 jimengDataMap 中查找 main_url
                if (videoDataId) {
                    const cached = getJimengData(videoDataId);
                    if (cached) {
                        const extracted = extractMainUrlFromData(cached);
                        if (extracted) {
                            finalUrl = extracted;
                            console.log('即梦: 从 jimengDataMap 提取到 main_url,使用该 URL 下载', finalUrl);
                        }
                    }
                }

                if (!finalUrl) {
                    alert('未找到可下载的视频链接');
                    console.error('即梦: 未能解析出有效的下载 URL');
                    return;
                }

                downloadBtn.style.opacity = '0.5';
                downloadBtn.style.pointerEvents = 'none';
                await downloadVideo(finalUrl);
            } catch (e) {
                console.error('即梦: 下载过程中出错', e);
                alert('下载失败,请重试(查看控制台以获得更多信息)');
            } finally {
                downloadBtn.style.opacity = '1';
                downloadBtn.style.pointerEvents = 'auto';
            }
        });
    }
    function extractTosPath(url) {
        // 方法1: 使用正则表达式
        const match = url.match(/tos-[^/]+\/[a-f0-9]+/);
        return match ? match[0] : null;
    }
    function jimeng_processSingleImageButton(topActionBar) {
        const downloadBtn = jimeng_createImageDownloadButton();
        topActionBar.insertBefore(downloadBtn, topActionBar.firstChild);
        console.info('即梦: 添加图片下载按钮');

        downloadBtn.addEventListener('click', async () => {
            console.log('即梦: 点击图片下载按钮');
            const imageUrl = jimeng_getImageUrl(downloadBtn);
            console.log('即梦: 获取到的图片URL:', imageUrl);
            if (!imageUrl) {
                alert('未找到可下载的图片链接');
                return;
            }
            let finalUrl = imageUrl;

            try {
                // 使用正则和分段处理规范化 imageUri:去除域名、query、尺寸、变体(~...)与扩展名,优先返回 "xxx/yyy" 或单段 id
                let imageUri = extractTosPath(imageUrl);
                // 2) 若仍然没有 finalUrl,尝试用 videoDataId 从 jimengDataMap 中查找 main_url
                if (imageUri) {
                    const cached = getJimengData(imageUri);
                    if (cached) {
                        const extracted = extractMainUrlFromData(cached);
                        if (extracted) {
                            finalUrl = extracted;
                            console.log('即梦: 从 jimengDataMap 提取到image_url,使用该 URL 下载', finalUrl);
                        }
                    }
                }

                if (!finalUrl) {
                    alert('未找到可下载的图片链接');
                    console.error('即梦: 未能解析出有效的下载 URL');
                    return;
                }

                downloadBtn.style.opacity = '0.5';
                downloadBtn.style.pointerEvents = 'none';
                await downloadImage(finalUrl);
            } catch (e) {
                console.error('即梦: 下载过程中出错', e);
                alert('下载失败,请重试(查看控制台以获得更多信息)');
            } finally {
                downloadBtn.style.opacity = '1';
                downloadBtn.style.pointerEvents = 'auto';
            }
        });
    }
    initJimengSite();

})();

仅视频无水印下载

// ==UserScript==
// @name         即梦AI去水印
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  通过重写XMLHttpRequest实现即梦AI视频下载去水印!
// @author       mihuc
// @match        https://jimeng.jianying.com/ai-tool/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jimeng.jianying.com
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

(function () {
    'use strict';
    // 新增:有限大小的映射表与工具函数,用于保存 history id -> 对应 data
    const jimengDataMap = new Map(); // key: history id (uuid string), value: data object
    const JIMENG_DATA_MAP_MAX = 500;

    // 将数据写入 map(带容量控制)
    function setJimengDataMap(key, value) {
        if (!key || typeof key !== 'string' || value === undefined || value === null) return;
        try {
            jimengDataMap.set(key, value);
            // 简单淘汰最旧条目
            if (jimengDataMap.size > JIMENG_DATA_MAP_MAX) {
                const oldestKey = jimengDataMap.keys().next().value;
                if (oldestKey !== undefined) jimengDataMap.delete(oldestKey);
            }
        } catch (e) {
            console.warn('jimeng: setJimengDataMap error', e);
        }
    }

    // 从 map 读取数据
    function getJimengData(key) {
        if (!key) return null;
        return jimengDataMap.has(key) ? jimengDataMap.get(key) : null;
    }

    // 清空缓存(可选)
    function clearJimengDataMap() {
        try { jimengDataMap.clear(); } catch (e) { console.warn('jimeng: clearJimengDataMap error', e); }
    }

    /**
     * 尝试从响应中记录 get_history_by_ids 或 get_asset_list 类型的数据
     * @param {object} json - 已解析的 JSON 响应
     * @param {string} url - 请求 URL(可选,用于基于 URL 的判断)
     */
    function tryRecordHistoryFromResponse(json, url) {
        try {
            if (!json || typeof json !== 'object') return;
            const data = json.data;
            if (!data || typeof data !== 'object') return;

            // 通过 URL 判断接口类型
            const isHistoryEndpoint = typeof url === 'string' && url.indexOf('get_history_by_ids') !== -1;
            const isAssetEndpoint = typeof url === 'string' && url.indexOf('get_asset_list') !== -1;

            // 简单 UUID-like 正则匹配
            const uuidLike = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;

            // 1) 处理 get_history_by_ids 风格的响应(已有逻辑)
            if (isHistoryEndpoint) {
                for (const k in data) {
                    if (!Object.prototype.hasOwnProperty.call(data, k)) continue;
                    setJimengDataMap(k, data[k]);
                    console.info('jimeng: recorded history key (by url)=', k);
                }
            }

            // 2) 处理 get_asset_list 风格的响应:查找 data.asset_list 中的 submit_id(与 item_list 同级)
            if (isAssetEndpoint || Array.isArray(data.asset_list)) {
                const list = Array.isArray(data.asset_list) ? data.asset_list : [];
                for (const entry of list) {
                    // entry 可能是 { video: { submit_id, item_list: [...] , ... } } 或其他嵌套形式
                    if (!entry || typeof entry !== 'object') continue;

                    // 尝试常见路径:entry.video.submit_id 或 entry.submit_id
                    const candidates = [];

                    if (entry.video && typeof entry.video === 'object') candidates.push({ obj: entry.video, parent: entry });
                    if (entry.submit_id) candidates.push({ obj: entry, parent: entry });

                    // 也尝试向下查找一层含 submit_id 的对象(防止不同嵌套)
                    for (const key in entry) {
                        if (!Object.prototype.hasOwnProperty.call(entry, key)) continue;
                        const v = entry[key];
                        if (v && typeof v === 'object' && v.submit_id) {
                            candidates.push({ obj: v, parent: entry });
                        }
                    }

                    for (const c of candidates) {
                        const obj = c.obj;
                        const submitId = obj && obj.submit_id;
                        if (submitId && typeof submitId === 'string') {
                            // 将 submit_id 对应的整个对象记录(通常包含 item_list)
                            setJimengDataMap(submitId, obj);
                            console.info('jimeng: recorded asset submit_id=', submitId);
                        }
                    }
                }
            }

            // 3) 兜底:若不是明确的接口,也检测顶级 uuid-like key(旧逻辑)
            for (const k in data) {
                if (!Object.prototype.hasOwnProperty.call(data, k)) continue;
                const val = data[k];
                if (uuidLike.test(k) && val && (val.item_list || val.video || Array.isArray(val.item_list))) {
                    setJimengDataMap(k, val);
                    console.info('jimeng: recorded history key (by content)=', k);
                }
            }
        } catch (e) {
            console.warn('jimeng: tryRecordHistoryFromResponse error', e);
        }
    }

    // 暴露查询接口到 window,便于控制台快速查看(只读查询)
    try {
        Object.defineProperty(window, 'getJimengData', {
            value: function (id) { return getJimengData(id); },
            writable: false,
            configurable: true
        });
        // 仅供调试:暴露底层 Map(请勿在生产逻辑中修改)
        Object.defineProperty(window, '_jimengDataMap', {
            value: jimengDataMap,
            writable: false,
            configurable: true
        });
    } catch (e) {
        // 忽略在严格 CSP/沙箱环境下的定义失败
    }

    function getVideoUrlPrams(url) {
        const urlObj = new URL(url);
        const params = {};
        urlObj.searchParams.forEach((value, key) => {
            params[key] = value;
        });
        return params;
    }



    function decodeBase64Safe(b64) {
        if (!b64 || typeof b64 !== 'string') return null;
        // 过滤掉明显不是 base64 的短串
        if (b64.length < 16) return null;
        try {
            return atob(b64);
        } catch (e) {
            // 尝试 URL-safe 变体
            try {
                const normalized = b64.replace(/-/g, '+').replace(/_/g, '/');
                return atob(normalized);
            } catch (e2) {
                return null;
            }
        }
    }
    // 从已缓存的响应数据(jimengDataMap)或响应对象中提取 main_url(优先返回第一个可解码的 URL)
    function extractMainUrlFromData(obj) {
        if (!obj || typeof obj !== 'object') return null;
        const seen = new Set();

        function walk(o) {
            if (!o || typeof o !== 'object') return null;
            if (seen.has(o)) return null;
            seen.add(o);

            for (const k in o) {
                if (!Object.prototype.hasOwnProperty.call(o, k)) continue;
                const v = o[k];

                try {
                    // 发现 main_url 字段(通常为 base64),尝试解码并返回第一个有效 URL
                    if ((k === 'main_url' || k === 'mainUrl') && typeof v === 'string') {
                        const decoded = decodeBase64Safe(v);
                        if (decoded && decoded.startsWith('http')) return decoded;
                    }

                    // 有些视频信息以 video_list.video_1.main_url 存在
                    if (k === 'video_list' && typeof v === 'object') {
                        // 遍历 video_list 下的各质量项
                        for (const q in v) {
                            if (!Object.prototype.hasOwnProperty.call(v, q)) continue;
                            const item = v[q];
                            if (item && typeof item === 'object' && item.main_url) {
                                const dec = decodeBase64Safe(item.main_url);
                                if (dec && dec.startsWith('http')) return dec;
                            }
                        }
                    }

                    // 若字段为字符串化 JSON,尝试解析并继续查找
                    if (typeof v === 'string') {
                        try {
                            const parsed = JSON.parse(v);
                            const r = walk(parsed);
                            if (r) return r;
                        } catch (e) {
                            // ignore
                        }
                    }

                    // 递归对象或数组
                    if (typeof v === 'object') {
                        const r = walk(v);
                        if (r) return r;
                    }
                } catch (e) {
                    // ignore individual errors
                }
            }
            return null;
        }

        return walk(obj);
    }

    // 重写 XMLHttpRequest.prototype 以拦截响应文本

    function interceptXHR() {
        try {
            if (!window || !window.XMLHttpRequest) return;
            const XProto = window.XMLHttpRequest && window.XMLHttpRequest.prototype;
            if (!XProto) return;
            // 防止重复打补丁
            if (XProto.__jimeng_patched) return;

            const _open = XProto.open;
            const _send = XProto.send;

            XProto.open = function (method, url, ...rest) {
                try {
                    this.__jimeng_url = url;
                } catch (e) { /* ignore */ }
                return _open.apply(this, [method, url, ...rest]);
            };

            XProto.send = function (body) {
                try {
                    // attach listener safely
                    this.addEventListener && this.addEventListener('readystatechange', function () {
                        try {
                            if (this.readyState === 4) {
                                // 仅在特定接口上处理,避免影响其它请求
                                const reqUrl = (this.__jimeng_url || '').toString();
                                if (!(reqUrl.indexOf('get_asset_list') !== -1 || reqUrl.indexOf('get_history_by_ids') !== -1)) {
                                    return; // 非目标接口,直接忽略
                                }

                                const txt = this.responseText;
                                if (typeof txt === 'string' && txt.length > 100) {
                                    // 尝试直接解析为 JSON
                                    try {
                                        const parsed = JSON.parse(txt);
                                        // 新增:记录 get_history_by_ids 返回的顶级 key -> data
                                        tryRecordHistoryFromResponse(parsed, this.__jimeng_url);
                                    } catch (e) {
                                        // 回退:查找第一个 '{' 子串并尝试解析
                                        try {
                                            const idx = txt.indexOf('{');
                                            if (idx >= 0) {
                                                const sub = txt.slice(idx);
                                                const parsed2 = JSON.parse(sub);
                                                // 新增回退解析时也尝试记录
                                                tryRecordHistoryFromResponse(parsed2, this.__jimeng_url);
                                            }
                                        } catch (e2) {
                                            // 忽略不可解析的文本
                                        }
                                    }
                                }
                            }
                        } catch (e) {
                            // 忽略单次处理错误,避免影响页面
                        }
                    });
                } catch (e) {
                    // 忽略 attach 错误
                }
                return _send.apply(this, arguments);
            };

            // 标记已打补丁
            try { XProto.__jimeng_patched = true; } catch (e) { }
            console.info('jimeng: XHR interceptor installed');
        } catch (e) {
            console.warn('jimeng: interceptXHR failed', e);
        }
    }

    // 替换原先注入/监听调用:直接安装 XHR 拦截器(同时保留 fetch 拦截作为回退)
    try {
        interceptXHR();
    } catch (e) {
        console.warn('jimeng: failed to install XHR interceptor', e);
    }

    // 优先使用 GM_xmlhttpRequest 绕过页面 CSP,回退到 fetch
    function fetchBypassCSP(url, options = {}) {
        // options: { method, headers, responseType, onProgress } - responseType支持 'arraybuffer' 或 'blob' 等
        const method = options.method || 'GET';
        const headers = options.headers || {};
        const responseType = options.responseType || 'arraybuffer';
        const onProgress = options.onProgress; // 进度回调函数

        // 如果 Tampermonkey 提供 GM_xmlhttpRequest,则使用它(可绕过页面 CSP)
        if (typeof GM_xmlhttpRequest === 'function') {
            return new Promise((resolve, reject) => {
                try {
                    GM_xmlhttpRequest({
                        method: method,
                        url: url,
                        headers: headers,
                        responseType: responseType,
                        onprogress(progressEvent) {
                            // 调用进度回调
                            if (typeof onProgress === 'function') {
                                onProgress(progressEvent);
                            }
                        },
                        onload(res) {
                            // res.response 在 responseType=arraybuffer 时是 ArrayBuffer
                            resolve({
                                ok: (res.status >= 200 && res.status < 300),
                                status: res.status,
                                statusText: res.statusText,
                                response: res.response,
                                responseHeaders: res.responseHeaders
                            });
                        },
                        onerror(err) {
                            reject(err);
                        },
                        ontimeout() {
                            reject(new Error('timeout'));
                        }
                    });
                } catch (e) {
                    reject(e);
                }
            });
        }

        // 回退:普通 fetch(受 CSP 限制,但无法支持进度监控)
        return (async () => {
            const resp = await fetch(url, { method, headers, mode: options.mode || 'cors' });
            const blob = await resp.blob();
            const arrayBuffer = await blob.arrayBuffer();
            return {
                ok: resp.ok,
                status: resp.status,
                statusText: resp.statusText,
                response: arrayBuffer
            };
        })();
    }

    /**
     * 进度弹窗管理器 - 管理多个下载弹窗的位置和堆叠
     */
    const ToastManager = {
        toasts: [], // 存储所有活跃的toast
        baseTop: 20, // 基础顶部距离
        spacing: 10, // toast之间的间距
        
        /**
         * 添加新的toast到管理器
         * @param {Object} toast - toast对象
         */
        add(toast) {
            this.toasts.push(toast);
            this.updatePositions();
        },
        
        /**
         * 从管理器中移除toast
         * @param {Object} toast - toast对象
         */
        remove(toast) {
            const index = this.toasts.indexOf(toast);
            if (index > -1) {
                this.toasts.splice(index, 1);
                this.updatePositions();
            }
        },
        
        /**
         * 更新所有toast的位置
         */
        updatePositions() {
            let currentTop = this.baseTop;
            this.toasts.forEach(toast => {
                if (toast.container && toast.container.parentNode) {
                    toast.container.style.top = currentTop + 'px';
                    // 获取toast的实际高度
                    const height = toast.container.offsetHeight;
                    currentTop += height + this.spacing;
                }
            });
        }
    };

    /**
     * 创建右上角进度弹窗
     * @returns {Object} 包含容器元素和更新方法的对象
     */
    function createProgressToast() {
        const container = document.createElement('div');
        container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            min-width: 300px;
            max-width: 400px;
            background: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 16px 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
            z-index: 99999;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
            font-size: 14px;
            line-height: 1.5;
            transition: top 0.3s ease, opacity 0.3s ease;
        `;

        const title = document.createElement('div');
        title.style.cssText = `
            font-weight: 600;
            margin-bottom: 8px;
            font-size: 15px;
        `;
        title.textContent = '正在下载视频...';

        const progressBarBg = document.createElement('div');
        progressBarBg.style.cssText = `
            width: 100%;
            height: 4px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 2px;
            margin: 8px 0;
            overflow: hidden;
        `;

        const progressBar = document.createElement('div');
        progressBar.style.cssText = `
            width: 0%;
            height: 100%;
            background: linear-gradient(90deg, #4CAF50, #8BC34A);
            border-radius: 2px;
            transition: width 0.3s ease;
        `;
        progressBarBg.appendChild(progressBar);

        const statusText = document.createElement('div');
        statusText.style.cssText = `
            font-size: 12px;
            color: rgba(255, 255, 255, 0.8);
            margin-top: 8px;
        `;
        statusText.textContent = '准备下载...';

        container.appendChild(title);
        container.appendChild(progressBarBg);
        container.appendChild(statusText);
        document.body.appendChild(container);

        const toast = {
            container,
            title,
            progressBar,
            statusText,
            /**
             * 更新进度
             * @param {number} percent - 进度百分比 (0-100)
             * @param {string} status - 状态文本
             */
            update(percent, status) {
                progressBar.style.width = percent + '%';
                if (status) statusText.textContent = status;
            },
            /**
             * 设置为成功状态
             * @param {string} message - 成功消息
             */
            success(message) {
                title.textContent = '✓ 下载完成';
                progressBar.style.background = 'linear-gradient(90deg, #4CAF50, #66BB6A)';
                statusText.textContent = message || '视频已保存';
            },
            /**
             * 设置为错误状态
             * @param {string} message - 错误消息
             */
            error(message) {
                title.textContent = '✗ 下载失败';
                progressBar.style.background = 'linear-gradient(90deg, #f44336, #e57373)';
                statusText.textContent = message || '请重试';
            },
            /**
             * 移除弹窗
             * @param {number} delay - 延迟时间(毫秒)
             */
            remove(delay = 0) {
                setTimeout(() => {
                    container.style.opacity = '0';
                    setTimeout(() => {
                        if (container.parentNode) {
                            container.parentNode.removeChild(container);
                        }
                        // 从管理器中移除
                        ToastManager.remove(toast);
                    }, 300);
                }, delay);
            }
        };

        // 添加到管理器
        ToastManager.add(toast);

        return toast;
    }

    /**
     * 下载视频通用函数(带进度显示)
     * @param {string} url - 视频URL
     * @param {string} filename - 可选的文件名
     */
    async function downloadVideo(url, filename) {
        const toast = createProgressToast();
        
        try {
            const res = await fetchBypassCSP(url, { 
                responseType: 'arraybuffer', 
                method: 'GET',
                onProgress: (event) => {
                    if (event.lengthComputable) {
                        const percent = Math.round((event.loaded / event.total) * 100);
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        const totalMB = (event.total / 1024 / 1024).toFixed(2);
                        toast.update(percent, `${loadedMB} MB / ${totalMB} MB (${percent}%)`);
                    } else {
                        // 无法获取总大小时显示已下载量
                        const loadedMB = (event.loaded / 1024 / 1024).toFixed(2);
                        toast.update(50, `已下载 ${loadedMB} MB...`);
                    }
                }
            });
            
            if (!res || !res.ok) throw new Error('请求失败,status=' + (res && res.status));
            
            toast.update(100, '处理文件中...');
            
            const arrayBuffer = res.response;
            const blob = new Blob([arrayBuffer], { type: 'video/mp4' });
            const objectUrl = URL.createObjectURL(blob);

            const link = document.createElement('a');
            const now = new Date();
            const dateStr = now.toISOString()
                .slice(0, 19)
                .replace('T', '_')
                .replace(/:/g, '-');
            link.download = filename || `RWater_${dateStr}.mp4`;
            link.href = objectUrl;
            link.target = '_blank';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            URL.revokeObjectURL(objectUrl);
            
            toast.success('视频已开始下载');
            toast.remove(1000);
        } catch (e) {
            console.error('下载失败:', e);
            toast.error(e.message || '下载失败,请重试');
            toast.remove(2000);
        }
    }
    /**
     * 初始化即梦网站的功能
     */
    function initJimengSite() {
        const observer = new MutationObserver(() => {
            jimeng_addVideoDownloadButton();
        });

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

        // Also add event listeners for mouse interactions that might trigger UI changes
        document.addEventListener('mouseover', () => {
            setTimeout(() => {
                jimeng_addVideoDownloadButton();
            }, 100);
        }, true);

        console.log('即梦: 初始化无水印下载工具');
        jimeng_addVideoDownloadButton();
    }

    /**
     * 创建即梦视频下载按钮
     * @returns {HTMLElement} 下载按钮元素
     */
    function jimeng_createVideoDownloadButton() {
        const button = document.createElement('div');
        button.className = 'lv-btn lv-btn-secondary lv-btn-size-default lv-btn-shape-square baseButton-LQGhrC download-btn';
        const div = document.createElement('div');
        div.textContent = '下载无水印视频';
        button.appendChild(div);

        button.title = '无水印下载';
        button.style = '--right-padding: 14px; align-items: center;background-color: var(--bg-block-primary-default); border-radius: 8px; color: var(--text-primary, #f5fbff); display: flex; font-family: PingFang SC;font-size: 12px; font-weight: 400; gap: 4px; justify-content: center; line-height: 20px; padding: 8px var(--right-padding) 8px 12px; position: relative;'
        return button;
    }

    function jimeng_getVideoDataId(downloadBtn) {
        console.log('即梦: 开始查找视频元素...');
        const parentItem = downloadBtn.closest('[class*="item-"]');
        return parentItem ? parentItem.getAttribute('data-id') : null;
    }
    /**
     * 获取即梦视频URL
     * @param {Element} downloadBtn - 下载按钮元素
     * @returns {string|null} 视频URL或null
     */
    function jimeng_getVideoUrl(downloadBtn) {
        console.log('即梦: 开始查找视频元素...');
        const parentItem = downloadBtn.closest('[class*="item-"]');

        const videoWrapper = parentItem.querySelector('div[class^="video-node-wrapper-"]');
        if (!videoWrapper) {
            alert('未找到视频');
            console.log('即梦: 未找到videoWrapper');
            return null;
        }

        let videoElement = videoWrapper.querySelector('video');
        if (!videoElement) {
            alert('未找到视频');
            console.log('即梦: 未找到video元素');
            return null;
        }

        if (videoElement && videoElement.src) {
            console.log('即梦: 找到视频元素:', {
                src: videoElement.src,
                className: videoElement.className,
                width: videoElement.width,
                height: videoElement.height,
                naturalWidth: videoElement.naturalWidth,
                naturalHeight: videoElement.naturalHeight,
                attributes: Array.from(videoElement.attributes).map(attr => `${attr.name}="${attr.value}"`).join(', ')
            });
            return videoElement.src;
        }

        alert('未找到视频');
        console.log('即梦: 未找到合适的视频');
        return null;
    }

    /**
     * 添加视频下载按钮到即梦页面
     */
    function jimeng_addVideoDownloadButton() {
        const videoContents = document.querySelectorAll('div[class^="video-record-"]');
        if (!videoContents || videoContents.length === 0) {
            return;
        }
        for (let i = 0; i < videoContents.length; i++) {
            const topActionBar = videoContents[i].querySelector('div[class^="record-bottom-slots-"]');
            if (!topActionBar) {
                continue;
            }
            if (topActionBar.querySelector('div[class^="image-record-content-"]')) {
                continue;
            }
            if (topActionBar.querySelector('.download-btn')) {
                continue;
            }
            jimeng_processSingleVideoButton(topActionBar);
        }
    }

    /**
     * 处理单个视频按钮(已简化:不再依赖 jimengVideoMap)
     * @param {Element} topActionBar - 操作栏元素
     */
    function jimeng_processSingleVideoButton(topActionBar) {
        const downloadBtn = jimeng_createVideoDownloadButton();
        topActionBar.insertBefore(downloadBtn, topActionBar.firstChild);
        console.info('即梦: 添加视频下载按钮');

        downloadBtn.addEventListener('click', async () => {
            console.log('即梦: 点击视频下载按钮');
            const videoDataId = jimeng_getVideoDataId(downloadBtn);
            const videoUrl = jimeng_getVideoUrl(downloadBtn);
            console.log('即梦: 获取到的视频URL:', videoUrl);
            let finalUrl = videoUrl;

            try {
                // 2) 若仍然没有 finalUrl,尝试用 videoDataId 从 jimengDataMap 中查找 main_url
                if (videoDataId) {
                    const cached = getJimengData(videoDataId);
                    if (cached) {
                        const extracted = extractMainUrlFromData(cached);
                        if (extracted) {
                            finalUrl = extracted;
                            console.log('即梦: 从 jimengDataMap 提取到 main_url,使用该 URL 下载', finalUrl);
                        }
                    }
                }

                if (!finalUrl) {
                    alert('未找到可下载的视频链接');
                    console.error('即梦: 未能解析出有效的下载 URL');
                    return;
                }

                downloadBtn.style.opacity = '0.5';
                downloadBtn.style.pointerEvents = 'none';
                await downloadVideo(finalUrl);
            } catch (e) {
                console.error('即梦: 下载过程中出错', e);
                alert('下载失败,请重试(查看控制台以获得更多信息)');
            } finally {
                downloadBtn.style.opacity = '1';
                downloadBtn.style.pointerEvents = 'auto';
            }
        });
    }

    initJimengSite();
})();

cursor ultra试用油猴脚本
左小角点击按钮,稍等约一分钟,自动打开页面
cursor ultra试用油猴脚本1
cursor ultra试用油猴脚本2
代码如下,油猴直接使用

// ==UserScript==
// @name         Cursor Trial Link Generator (Fixed)
// @name:zh-CN   Cursor 试用链接生成器 (修正版)
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Adds a button on the Cursor dashboard (all languages) to generate a trial checkout link.
// @description:zh-CN 在Cursor仪表盘页面(所有语言版本)添加一个按钮,用于一键生成试用订阅链接。
// @author       YourName
// @match        https://cursor.com/dashboard*
// @match        https://cursor.com/*/dashboard*
// @grant        none
// @icon         https://www.google.com/s2/favicons?sz=64&domain=cursor.com
// ==/UserScript==

(function() {
    'use strict';

    // 创建一个函数来发送API请求
    function generateTrialLink(tier) {
        console.log(`[Cursor Script] Requesting trial link for tier: ${tier}`);
        alert(`正在为 ${tier} 套餐生成试用链接...`);

        // 使用绝对路径以确保请求地址正确
        fetch('https://cursor.com/api/checkout', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            // 浏览器会自动附加当前域的Cookie,所以不需要手动设置
            body: JSON.stringify({
                allowAutomaticPayment: true,
                allowTrial: true,
                tier: tier
            })
        })
        .then(response => {
            if (!response.ok) {
                // 如果服务器返回错误,则抛出错误以便被catch捕获
                return response.text().then(text => {
                    throw new Error(`服务器错误 (状态码: ${response.status}): ${text}`);
                });
            }
            return response.json();
        })
        .then(data => {
            console.log('[Cursor Script] Success:', data);
            // 检查返回的是否是一个URL
            if (typeof data === 'string' && data.startsWith('http')) {
                alert(`成功获取链接!将为您在新标签页中打开。`);
                // 在新标签页中打开获取到的Stripe链接
                window.open(data, '_blank');
            } else {
                 throw new Error('服务器返回的不是一个有效的URL。');
            }
        })
        .catch((error) => {
            console.error('[Cursor Script] Error:', error);
            alert(`生成链接失败,请按 F12 打开控制台查看错误详情。\n错误: ${error.message}`);
        });
    }

    // 创建UI界面(一个包含三个按钮的面板)
    function createUI() {
        // 防止重复创建
        if (document.getElementById('cursor-trial-panel')) return;

        const panel = document.createElement('div');
        panel.id = 'cursor-trial-panel';
        panel.style.position = 'fixed';
        panel.style.bottom = '20px';
        panel.style.right = '20px';
        panel.style.backgroundColor = '#222';
        panel.style.border = '1px solid #444';
        panel.style.borderRadius = '8px';
        panel.style.padding = '15px';
        panel.style.zIndex = '9999';
        panel.style.display = 'flex';
        panel.style.flexDirection = 'column';
        panel.style.gap = '10px';
        panel.style.fontFamily = 'sans-serif';

        const title = document.createElement('h3');
        title.textContent = '一键获取试用';
        title.style.color = 'white';
        title.style.margin = '0 0 10px 0';
        title.style.textAlign = 'center';
        title.style.fontSize = '16px';
        panel.appendChild(title);

        const tiers = ['pro', 'pro_plus', 'ultra'];
        tiers.forEach(tier => {
            const button = document.createElement('button');
            button.textContent = `获取 ${tier.charAt(0).toUpperCase() + tier.slice(1)} 试用`;
            // 一些简单的样式
            button.style.padding = '8px 12px';
            button.style.cursor = 'pointer';
            button.style.border = '1px solid #555';
            button.style.borderRadius = '5px';
            button.style.backgroundColor = '#333';
            button.style.color = 'white';
            button.style.fontSize = '14px';
            button.onmouseover = () => button.style.backgroundColor = '#444';
            button.onmouseout = () => button.style.backgroundColor = '#333';

            button.onclick = () => generateTrialLink(tier);
            panel.appendChild(button);
        });

        document.body.appendChild(panel);
         console.log('[Cursor Script] UI panel created successfully.');
    }

    // Cursor网站可能是个单页应用(SPA),window.onload可能不够可靠。
    // 我们使用一个定时器来检查页面是否已加载关键内容。
    const interval = setInterval(() => {
        // 通常dashboard页面会有一个特定的元素,这里我们简单检查body是否加载完成
        // 更可靠的方法是检查一个特定的、迟于加载的元素
        if (document.body) {
            clearInterval(interval);
            createUI();
        }
    }, 500);

})();