包含关键字 typecho 的文章

Jetbrains家的KeyGen网上有很多,直接百度一下就能搜出来。
主要是ReSharper有联网验证,而且是.NET平台的软件,不能像Java一样用--javaagent直接进行注入。
这里给一个用Visual Studio拓展来解决的思路。

using HarmonyLib;
using System;

namespace ReSharperCrack
{
    [HarmonyPatch]
    internal static class Patches
    {
        static Patches()
        {
            try
            {
                var harmonyVersion = typeof(Harmony).Assembly.GetName().Version?.ToString() ?? "unknown";
                Logger.Log($"Patch loaded. Start. Harmony={harmonyVersion}, AppDomain={AppDomain.CurrentDomain.FriendlyName}");
            }
            catch (Exception ex)
            {
                Logger.Log($"Patch static ctor exception: {ex}");
            }
        }

        [HarmonyPatch("JetBrains.Application.License2.UserLicenses.UserLicenseViewSubmodel", "AddLicense")]
        [HarmonyPrefix]
        internal static bool AddLicense_Prefix(ref bool validateLicenseKey)
        {
            try
            {
                Logger.Log($"AddLicense_Prefix called. validateLicenseKey(before)={validateLicenseKey}");
                validateLicenseKey = false;
                Logger.Log($"AddLicense_Prefix finished. validateLicenseKey(after)={validateLicenseKey}");
                return true;
            }
            catch (Exception ex)
            {
                Logger.Log($"AddLicense_Prefix exception: {ex}");
                // be conservative: let original run if our prefix fails
                return true;
            }
        }

        [HarmonyPatch("JetBrains.Application.License2.NewLicenses.UserLicenseService", "VerifyCertificate")]
        [HarmonyPostfix]
        internal static void VerifyCertificate_Postfix(ref object __result)
        {
            try
            {
                Logger.Log($"VerifyCertificate_Postfix called. __result(before)={(__result == null ? "<null>" : __result + " (" + __result.GetType().FullName + ")")}");

                if ((int)__result != 0)
                {
                    __result = 0;
                }

                Logger.Log($"VerifyCertificate_Postfix finished. __result(after)={(__result == null ? "<null>" : __result + " (" + __result.GetType().FullName + ")")}");
            }
            catch (Exception ex)
            {
                Logger.Log($"VerifyCertificate_Postfix exception: {ex}");
            }
        }
    }
}

以及入口类:

/*...*/
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[Guid(ReSharperCrackPackage.PackageGuidString)]
[ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
public sealed class ReSharperCrackPackage : AsyncPackage
{
    protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        // 在这里加载LibHarmony的Hook
    }
}
/*...*/

编译成vsix拓展,装载在Visual Studio上。

接着,使用License Key激活就可以了。

ReSharper稳定破解

即梦视频去水印下载 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();
})();

[bsgit user="fish2018"]YPrompt[/bsgit]

功能

  • 支持OpenAI、Anthropic、Google Gemini和自定义AI服务商
  • 支持内置提供商,将 builtin-providers.example.json 复制为 builtin-providers.json 并修改您需要的配置即可。
  • 自动生成和手动步进两种执行模式
  • 支持Markdown/XML格式切换和中英文互译
  • 支持对话引导挖掘用户需求后自动生成系统提示词
  • 支持系统提示词优化
  • 支持基于系统提示词和对话上下文的用户提示词优化
  • 支持效果对比,同时也是一个调试台,可以自定义模型,系统提示词
  • 支持提示词版本管理
  • 支持sqlite和mysql数据库
  • docker一键部署
  • 支持用户密码/linux.do认证

在线体验

使用Linux.do登录 或 使用账号:demo/demo

生成提示词
生成提示词
系统提示词优化和对比
系统提示词优化和对比
用户提示词优化和对比
用户提示词优化和对比
提示词版本管理
提示词版本管理

这是一款免费开源的漫画阅读器,以插件的形式提供了来自全球的上千个漫画图源,可筛选包含中文、英文等多种语言的超一千个图源,不管是包子某漫还是蛙蛙一耽,统统可以一键搜索,甚至可以直接通过国外图源看生肉.
软件需要导入源仓库地址,这有三个有效的地址,里面的插件都差不多,随便复制一个就好

仓库的导入方式也很简单,全程开启魔法 ,在软件下方 “浏览”——“插件”——“插件仓库”,点击右下方添加,粘贴上方的仓库地址就 ok

IOS 无限制漫画神器,自带 999 + 漫画源 1
仓库添加好后刷新插件界面,右上角可以筛选插件语言,选择简体中文,下载你心仪的漫画网插件,就可以去书架搜索想要的漫画
IOS 无限制漫画神器,自带 999 + 漫画源 2
软件叫 Tachimanga,用苹果美区账户直接搜索下载即可,网上有很多相关的教程,这个软件不仅仅可以看漫画,通过不同的插件可以实现很多功能,各位佬友可自行探索。安卓和 ios 的图源是通用的,安卓端叫 Mihon 需要注意的是,使用软件全程需要魔法
IOS 无限制漫画神器,自带 999 + 漫画源

![[第二弹] Nano Banana 新玩法!附带提示词!(懒人必备...)](https://xiaohack.oss-cn-zhangjiakou.aliyuncs.com/typecho/2025/11/2671742069.png!mark)
Nano Banana 图片生成,又有新玩法,并且非常火爆,这里重新整理了第二波 Nano Banana 神级指令词,希望对大家有用!
第一次:
[bspost cid="5068"]
Nano banana 使用渠道: https://imini.com/nano-banana

玩法一:运动风个人写真 (哄女朋友开心)

运动风个人写真提示词
提示词:

参考我上传的照片,输出一张高分辨率彩色艺术人像摄影,与照片不同的姿势,真实摄影。 主体是一位年轻女性,高马尾造型,几缕鬓发自然垂落,身材高挑,腰臀比例完美,马甲线清晰,双腿修长。她身穿白色高腰紧身运动背心,灰色短款瑜伽裤、白色长筒袜与白色老爹鞋,造型简洁又具力量感。 人物坐在白色桌面的边缘,身体微微向镜头扭转,右手后撑在桌面上保持平衡,左手举着一只切割纹理的玻璃杯,轻轻靠近锁骨位置。左腿弯曲收于身前,右腿自然向前下方伸展,脚尖轻点地面。她的头部微微上扬,眼神柔和自信,唇角轻扬,微笑自然不过分夸张,流露出轻松与优雅。 环境为纯白色摄影棚空间,墙地一体,极简留白。一张白色桌子是画面核心道具,桌面与墙面反射自然光形成高调干净的视觉平衡。玻璃杯与桌面上呈现出细微的高光反射与窗格投影,使画面更具空间感与真实光影层次。 光线来自左前上方的大型漫射窗光作为主光,整体为高调明亮的自然光氛围,皮肤高光细腻且层次分明。右侧以大白板反光轻柔填充,平衡暗部细节;右后方加入极弱的轮廓光,勾勒出发髻与肩背线条,使人物从背景中轻微分离。桌面上斜向延伸的窗格阴影柔和自然,为纯白场景增添节奏与层次。整体对比度中等,白平衡略偏冷,控制高光不过曝,保持通透而克制的质感。 带有轻微的雕塑感与时尚的克制力量。搭配 85mm 定焦镜头;机位略低,与桌面形成 5–10° 的轻微俯视角度。微提对比与清晰度,保持自然与质感的平衡。 能看到微小毛孔与柔细汗毛;颈部与肩膀的体积光顺滑自然;玻璃杯表面的高光折射逼真;窗格阴影的几何形状清晰而柔和。整体叠加轻微胶片颗粒,提升真实肤感与现代时尚氛围。 比例为 3:4 。

玩法二:But love pray for me

But love pray for me*

这个玩法最近蛮火的,非常有创意。并且效果极好。

提示词:

上传图片的人物向前伸出拳头,拳头居于画面中间位置,无名指上戴着一枚金色戒指。戒指左侧有白色手写体英文“But love”,右侧有白色手写体英文“Pray for me”,两段英文与戒指处于同一水平线且紧密挨着。背景呈暖色调,整体风格为写实摄影,画面长宽比协调,营造出一种聚焦于拳头和戒指细节的视觉效果。

玩法三:苹果高管风 工作照

苹果高管风 工作照提示词

“LinkedIn-style executive headshot, half-body (chest-up); face straight to camera, body slightly angled; calm, confident micro-smile. Wavy hair neatly styled; black sleeveless dress; minimalist professional look. Tight framing to emphasize facial features; eyes tack-sharp; natural skin texture. Soft directional lighting with subtle catchlights. Background: light-gray smooth gradient with clean separation. ( Create 4 variations with small pose/expression changes.) --no text, logos, busy patterns, heavy retouching, clutter 。

ps:刚才这个提示词是生成四宫格照片的,如果大家只想要一张照片,把括号里的文字删掉就可以啦~”

玩法四:韩系照片 堪比海马体效果

韩系照片 堪比海马体效果提示词

提示词:(上传图片最好清晰一点)

Studio portrait of a young East Asian woman with long black hair, wearing an off-shoulder cream - colored top. She holds strawberries and grapefruit slices as props. Soft natural lighting, clean white background, fresh makeup with pink blush, 8K ultra - realistic, cinematic composition, aesthetic and minimalist.Professional studio lighting, softbox illumination, off - shoulder cream top, flowing black hair, strawberries and grapefruit slices as props, clean white background, shallow depth of field, 8K resolution, ultra - detailed skin texture, fresh makeup with pink blush and glossy lips, cinematic color grading, aesthetic composition, minimalist photography.

玩法五:Q 版萌萌头像

Q 版萌萌头像提示词

提示词:

把这张照片设计成一个 3D 风格 Q 版 APP 图标,保留人物特征,尤其是精细的五官。采用柔和且鲜明的打光风格,使其呈现出精致的高品质效果。角色应略微超出应用图标的边框,以增强 3D 效果和趣味性。确保人物五官与原照片一致,风格应给人一种值得收藏且萌趣可爱的感觉,类似于迷你手办或黏土人。

玩法六:衣服穿搭

衣服穿搭提示词

提示词:

选择图 1 中的人,让他们穿上图 2 中的所有服装和配饰。在户外拍摄一系列写实的 OOTD 风格照片,使用自然光线,时尚的街头风格,清晰的全身镜头。保持图 1 中人物的身份和姿势,但以连贯时尚的方式展示图 2 中的完整服装和配饰

Nano Banana 使用方法:

1.打开 imini: https://imini.com/nano-banana

2.上传图片+输入指令等待图片生成即可。

Nano Banana 使用方法

开源了一个基于 Docker 的星露谷物语服务器一键部署解决方案。

项目地址:
[bsgit user="truman-world"]puppy-stardew-server[/bsgit]
Stardew Server - Docker 一键部署星露谷物语开联机服务器

核心特性

这个项目最大的亮点是整合了几个自定义模组,带来了更好的多人游戏体验:

即时睡眠功能:任何一个玩家在床上选择睡觉,游戏会立刻为所有在线玩家存档并结束当天。再也不用互相等待离线或 AFK 的玩家。

24/7 专用服务器:基于 Docker 容器化,服务器可以 7x24 小时独立运行,房主无需在线。适合部署在 VPS 、云服务器或家用 NAS 上。

一键脚本部署:一条命令,3 分钟即可完成所有环境配置和启动。无需手动安装 SMAPI 、配置模组或设置环境。

跨平台联机:支持 PC 、Mac 、Linux ,以及 iOS 和 Android 玩家在同一个服务器中游戏。所有平台互通,无需复杂配置。

房主自动隐藏:主机玩家作为服务器运行,在游戏中自动隐身,不占用玩家名额,也不会干扰正常游戏。

存档自动加载:服务器重启后会自动加载最新的存档,无需手动操作。首次部署通过 VNC 创建存档后,之后完全自动化。

内置 VNC:首次创建世界时,可以通过浏览器或 VNC 客户端远程访问图形界面,操作简单。

自然技能升级( v1.0.58 新增):防止服务器强制房主升到 10 级,保持基于真实经验值的技能等级。玩家可以手动选择技能专精路线,保留游戏原有的成长乐趣。

一键启动

在任何一台安装了 Docker 的服务器上运行:

curl -sSL https://raw.githubusercontent.com/truman-world/puppy-stardew-server/main/quick-start.sh | bash

脚本会自动完成环境检查、配置文件生成、目录创建、权限设置和容器启动。

手动部署

如果你更喜欢手动控制:

1. 创建 docker-compose.yml

version: '3.8'
services:
  stardew-server:
    image: truemanlive/puppy-stardew-server:latest
    container_name: puppy-stardew
    restart: unless-stopped
    stdin_open: true
    tty: true
    environment:
      - STEAM_USERNAME=your_steam_username
      - STEAM_PASSWORD=your_steam_password
      - ENABLE_VNC=true
      - VNC_PASSWORD=stardew123
    ports:
      - "24642:24642/udp"
      - "5900:5900/tcp"
    volumes:
      - ./data/saves:/home/steam/.config/StardewValley:rw
      - ./data/game:/home/steam/stardewvalley:rw
      - ./data/steam:/home/steam/Steam:rw
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G

2. 设置权限并启动

mkdir -p data/{saves,game,steam}
chown -R 1000:1000 data/
docker compose up -d

3. 首次设置

使用 VNC 连接到 your-server-ip:5900(密码:stardew123),创建或导入存档。

4. 玩家连接

游戏中:Co-op → Join LAN Game → 输入 your-server-ip:24642

预装模组

  • SMAPI 4.3.2 - 官方模组加载器
  • Always On Server v1.20.3 - 无头服务器运行
  • Skill Level Guard v1.4.0 - 防止强制升级,保持自然技能进度
  • ServerAutoLoad v1.2.1 - 自动加载存档
  • AutoHideHost v1.2.2 - 隐藏房主,即时睡眠

系统要求

服务器端:

  • Docker 和 Docker Compose
  • 2GB 内存( 4+ 玩家推荐 4GB )
  • 2GB 磁盘空间
  • Steam 账号(拥有星露谷物语)

客户端:

  • 星露谷物语(任何平台)
  • 与服务器相同的游戏版本

v1.0.58 更新

修复:

  • Always On Server 自动启用问题(服务器现在可以自动暂停/恢复)
  • 玩家技能专精选择问题(可以手动选择路线)

改进:

  • 自然技能升级(基于真实经验值,不再强制 10 级)
  • 服务器空闲时自动暂停(节省资源)

已解决的问题:

  • 长时间空闲后无法连接
  • 地震等特殊事件冻结游戏
  • 无限等待其他玩家
  • 无需邀请码(直接用 IP 连接)

文档与支持

许可证

  • 项目: MIT License
  • 游戏: 需合法拥有星露谷物语( Steam )
  • SMAPI & Always On Server: GPL-3.0
  • 自定义模组: MIT License

有些时候我们需要开发调试修改 HTTP 的 Header (例如 User-Agent),UA3F 可以做到对客户端无感的透明重写。

UA3F 作为一个 HTTP 、SOCKS5 、TPROXY 、REDIRECT 、NFQUEUE 代理服务对 HTTP/1.1 请求 Header 进行透明重写。

UA3F 还可以自定义重写规则,根据匹配条件的不同进行不同行为的重写:
匹配规则类型支持:KEYWORD 、REGEX 、DEST-PORT 、SRC-IP 、IP-CIDR
重写策略支持:DELETE 、REPLACE 、REPLACE-PART 、DROP
后续会有更多规则加入。

UA3F: 透明重写 HTTP Header

[bsgit user="SunBK201"]UA3F[/bsgit]

v1 版本收到许多反馈,主要还是集中在男女的任务不能分开,导致许多任务不好执行,于是新做一个版本。新版本实现如下特性:

  • 双人实时对战:房间创建 / 加入、掷骰、回合推进、胜负判定
  • 主题题库:每位玩家选择各自主题,星 / 陷阱 / 撞机触发任务
  • AI 生成任务:基于个人偏好与主题调用生成批量任务
  • 偏好设置:性别与兴趣标签,影响 AI 生成的任务风格
  • 隐私注册:支持随机账户注册,提供一键复制账号与密码
  • 历史归档:对局结束自动入库摘要,任务完成情况可追溯
最初打算基于 go 开发,但是考虑到部署需要服务器,换成了 next+supabase 这种纯白嫖方案,这样也方便大家私有化的部署,缺点就是卡卡的,也没法支撑太多人游戏。目前我只是简单的测试就上线了,可能还有些 bug 需要等待反馈后修复,当然我觉得能凑合着玩就行,有点 bug 是正常的。v2 版本更适合私有化部署,本身 nextjs+supabase 这套方案不太适合多人使用。

[bsgit user="woniu9524"]love-ludo[/bsgit]

试用地址:https://v2.cpfly.top/

情侣飞行棋 v2, 支持 AI 生成任务,自有主题题库,双人实时对战,私有化部署

Prizrak-Box 是一个跨平台的轻量桌面客户端(Pandora-Box 的分支项目),适配 Mihomo 内核,支持多种代理协议、规则自动分组与 TUN 模式。

Prizrak-Box 在原 Pandora-Box 的基础上做了以下扩展:

1、改进了订阅页面的流量使用和订阅信息显示方式。原版是显示在顶部,只能显示当前使用订阅的信息。俄版将信息显示在每个卡片上,更直观和整体掌握订阅的流量使用情况。

流量使用和订阅信息显示方式

2、在设置页面添加了自定义控制面板。

自定义控制面板

3、软件背景可以自定义上传设置
自定义背景设置

4、针对macOS系统的软件安装包添加了签名,防止在软件安装时系统提示“软件已损坏”

5、软件详细使用如下,需要自行网页翻译
使用介绍

题外话:
这两个软件的作者有意思,软件名字起的都这么诡异。

“Pandora-Box”,是一个来自希腊神话的文化隐喻,意指一个看似无害但一旦开启就会释放出灾难和混乱的事物。

而 “Prizrak”(Призрак) 在俄语中通常翻译为“幽灵”、“鬼魂”或“幻影”。

他们似乎在说:富强需谨慎,自由非无界,探索亦有度

最近 ChatGPT 在印度推出了一年的白嫖服务,主包昨晚成功上车,5 分钟不到搞定,开通流程如下

前提:一个未开通会员的 ChatGPT 账号,一个 PayPal 账号(可以是国区),一个可用的印度节点,电脑网页操作

  1. ChatGPT 切换为印度节点,刷新网页会弹窗 “免费试用 Go” 的提示,点击试用
  2. 跳转后点击右下角的 “印度”,切换为 “法国”,这么做是因为会出现 PayPal 支付选项
  3. 切换后再点击 “升级至 Go”,跳转到支付页面,此时右侧有 “PayPal” 选项,选择 PayPal 并填写账单地址(汉字大陆地址也可以),注意检查页面是否为€0.00
  4. 点击 “订阅” 跳转 PayPal 登录页面,登录账号后直接订阅成功,同时收到开通成功邮件

ChatGPT Go 套餐一年 白嫖

「可萌」基于知识库与知识图谱的专域聊天助手
开源分享: 基于 LightRAG、LangGraph、MCP、RagFlow、微调LLMs宝可梦主题的专有领域智能聊天助手

[bsgit user="skygazer42"]pokemon-chat[/bsgit]

? 项目介绍

宝可梦(Pokémon)作为全球最具影响力的 IP 之一,拥有庞大的世界观设定与海量角色数据。在游戏、动画、卡牌、电影等多领域的多年积累下,其知识体系庞杂且高度结构化,非常适合应用于知识图谱建模与智能问答场景。

随着大语言模型(LLM)与知识增强技术的发展,将宝可梦宇宙构建为一个多模态、结构化、可交互的 AI 系统成为可能。本项目以 百度贴吧 与维基百科等数据源为基础,构建出覆盖宝可梦角色、属性、技能、地区、演化路径等元素的知识图谱,并结合大模型能力,打造一个专属宝可梦世界的智能对话助手 ——「可萌」。

在此基础上,我们融合了 LangGraph 推理流程编排、 GraphRAG 检索增强技术,以及知识图谱可视化探索能力,使用户不仅可以通过自然语言提问获得精确答案,还能以图谱形式直观探索宝可梦世界。同时支持基于地理位置的地图定位功能,将宝可梦世界与真实世界坐标一一映射,实现 宝可梦地点知识的空间可视化 ? 。

本项目致力于打造一个可迁移、可扩展、面向爱好者的专域智能助手模板系统,你可以轻松将其迁移至其他角色(如「苏轼」、「金融」、「 政务服务」等)中打造专域的智能助手,仅需更换知识源与图谱结构,即可实现高质量的语义问答与可视化知识探索体验。

?系统架构

通过本项目的实施,我们不仅完成了vue3+fastapi的一个完整项目,同时构建了一个基于宝可梦知识图谱的智能问答系统。积累了语义结构建模如bert+tf-idf+规则匹配机制、以及图谱融合与生成式问答的丰富实践经验。系统支持对宝可梦的进化关系、属性克制、技能特征、地理分布等内容进行精准问答,极大提升了用户在交互式探索中的体验感。

未来,我们将持续优化系统在多轮问答、复杂图谱推理、地图导航等场景下的表现,并扩展更多支持任务类型,如:基于图谱的推理问答、Pokédex 自动补全、角色对战策略建议等。同时,知识图谱将持续更新和扩展,以确保其时效性、完整性与一致性,助力宝可梦领域的智能系统构建与 AI 应用拓展。

以下是本项目的核心技术架构图:
核心技术架构图

?项目特色

  1. 基于爬取的数据微调了基于宝可梦的专域大模型——可萌
  2. 基于爬取数据构建了宝可梦知识图谱(维基百科)。
  3. 自动化标注训练NER数据,使用roberta+TF-IDF+规则匹配来命中图谱中的实体与属性。
  4. 使用whisper来实现ASR功能。
  5. 实现MCP服务,如获取宝可梦世界地点、宝可梦在对应真实世界的经纬度坐标显示在前端上。
  6. 抽取RAGflow中的deepdoc来强化知识库的解析和抽取能力。
  7. 使用Langraph框架基于自己的数据实现graphrag+ web searcher + 知识库 智能体。
  8. 封装agent 基类实现多智能体功能。
  9. 支持知识图谱搜索、网络搜索、知识库搜索、MCP搜索、语音搜索,可以同时集成也可以任选其一。

? 快速开始

前置要求:已安装 Docker / Docker Compose、Node.js ≥ 18、Python ≥ 3.11
  1. 把数据放到resources文件夹下
  2. 克隆仓库 & 配置环境变量

    git clone 
    cd Smart-Assistant
    cp src/.env.template src/.env   # 按需填写 API-KEY,可留空
    cp Smart-Assistant/config/settings_example.py  config/settings.py  # 填写 
  3. 安装依赖

    pip install -r requirements.txt
  4. 启动核心服务

    cd docker
    docker compose up -d           
  5. 导入图谱与地图数据

    cd scripts
    python import_graph.py          # 写入 Neo4j
    python import_pokemon_map.py    # 写入 MySQL
  6. 启动后端服务

    cd server
    python main.py                  # FastAPI + LangGraph
    cd ../src/mcp
    python mcp_server.py            # SSE 模式示例
  7. 启动前端

    cd web
    npm install
    npm run dev
    # 浏览器访问 http://localhost:3100/

? 参考项目

✨ 特性

  • ? 多 AI 模型支持 - 支持 OpenAI、Google Gemini、Anthropic Claude 等主流 AI 模型
  • ? 智能向导 - 通过向导式引导快速创建小说项目,AI 自动生成大纲、角色和世界观
  • ? 角色管理 - 创建和管理小说角色,包括人物关系、组织架构等
  • ? 章节编辑 - 支持章节的创建、编辑、重新生成和润色功能
  • ? 世界观设定 - 构建完整的故事世界观和背景设定
  • ? 多种登录方式 - 支持 LinuxDO OAuth 登录和本地账户登录
  • ? Docker 部署 - 一键部署,开箱即用
  • ? 数据持久化 - 基于 SQLite 的本地数据存储,支持多用户隔离
  • ? 现代化 UI - 基于 Ant Design 的美观界面,响应式设计

 一款基于 AI 的智能小说创作助手,帮助你轻松创作精彩故事

更新记录

25.11.3更新:
1.优化AI请求替换OpenAI SDK调用,使用httpx和自定义头请求,避免触发部分公益站的cloudflare
2.修复deepseek模型调用问题,舍弃思考过程AI响应内容,只获取结果内容
3.新增会话过期机制,更新后添加到.env中
4.支持用户在生成章节内容时设置字数

25.11.4更新:
注意 本次更新已合并到main主分支
1.支持用户项目数据导入导出
2.支持向量数据库,实现RAG的记忆提取存储功能,并引用到生成上下文中,提高生成文章内容质量

25.11.5更新:
1.新增AI生成组织功能,扩展优化组织字段(所在地 代表颜色 格言/口号)
2.适配移动端项目管理-剧情分析UI页面

25.11.6更新:
1.优化大纲续写和章节内容生成上下文构建方式 实现智能构建提示词(支持超长章节内容)
2.实现章节概要提取,并保存到向量数据库,为后续大纲生成和内容提供骨架
3.新增章节内容批量生成功能

25.11.7更新:
1.更新mcp插件功能,目前只支持remote调用

? 快速开始

前置要求

  • Docker 部署:Docker 和 Docker Compose
  • 本地开发:Python 3.11+ 和 Node.js 18+
  • 必需:至少一个 AI 服务的 API Key(OpenAI/Gemini/Anthropic)
    方式一:从源码构建 Docker 镜像

    # 1. 克隆项目
    git clone https://github.com/xiamuceer-j/MuMuAINovel.git
    cd MuMuAINovel
    
    # 2. 配置环境变量
    cp backend/.env.example .env
    # 编辑 .env 文件,填入你的 API Keys
    
    # 3. 启动服务(会自动构建镜像)
    docker-compose up -d
    
    # 4. 访问应用
    # 打开浏览器访问 http://localhost:8000

方式二:本地开发
后端设置

# 进入后端目录
cd backend

# 创建虚拟环境
python -m venv .venv

# 激活虚拟环境
# Windows:
.venv\Scripts\activate
# Linux/Mac:
source .venv/bin/activate

# 安装依赖
pip install -r requirements.txt

# 配置环境变量
cp .env.example .env
# 编辑 .env 文件,填入你的配置

# 启动后端服务
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

前端设置

# 进入前端目录
cd frontend

# 安装依赖
npm install

# 开发模式(需要后端已启动)
npm run dev

# 或构建生产版本
npm run build

? 部署方式

Docker Compose 部署
使用 Docker Hub 镜像(推荐)
项目已发布到 Docker Hub,可直接拉取使用:

# 查看可用版本
docker pull mumujie/mumuainovel:latest

# 启动服务
docker-compose up -d

# 查看日志
docker-compose logs -f

# 停止服务
docker-compose down

# 重启服务
docker-compose restart

# 更新到最新版本
docker-compose pull
docker-compose up -d

Docker Compose 配置文件示例
使用 Docker Hub 镜像的完整配置:

services:
  ai-story:
    image: mumujie/mumuainovel:latest
    container_name: mumuainovel
    ports:
      - "8800:8000"  # 宿主机端口:容器端口
    volumes:
      # 持久化数据库和日志
      - ./data:/app/data
      - ./logs:/app/logs
      # 挂载环境变量文件
      - ./.env:/app/.env:ro
    environment:
      - APP_NAME=mumuainovel
      - APP_VERSION=1.0.0
      - APP_HOST=0.0.0.0
      - APP_PORT=8000
      - DEBUG=false
      # 其他环境变量会从 .env 文件自动加载
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
    networks:
      - ai-story-network

networks:
  ai-story-network:
    driver: bridge

生产环境部署建议

1. 环境变量配置
必需配置:

  • OPENAI_API_KEY 或 GEMINI_API_KEY:至少配置一个 AI 服务
  • LOCAL_AUTH_PASSWORD:修改为强密码

推荐配置:

  • OPENAI_BASE_URL:如果使用中转 API,修改为中转服务地址
  • DEFAULT_AI_PROVIDER:根据你的 API Key 选择 openai、gemini 或 anthropic
  • DEFAULT_MODEL:选择合适的模型(如 gpt-4o-mini、gemini-2.0-flash-exp)
  • 数据持久化
    数据目录已通过 volume 挂载,数据不会丢失:
  • ./data:SQLite 数据库文件
  • ./logs:应用日志文件
  • 端口配置
    默认端口映射:8800:8000
  • 宿主机端口:8800(可自定义修改)
  • 容器内端口:8000(固定,不要修改)
    访问地址:http://your-server-ip:8800

配置后记得更新 .env 中的 LINUXDO_REDIRECT_URI 和 FRONTEND_URL。

5. 资源限制(可选)
在 docker-compose.yml 中添加资源限制:

services:
  ai-story:
    # ... 其他配置
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M

端口说明

  • 默认端口:8800(宿主机)→ 8000(容器)
  • 可自定义:修改 docker-compose.yml 中的 ports 配置
  • 健康检查:容器内部使用 8000 端口进行健康检查

⚙️ 配置说明

环境变量
创建 .env 文件并配置以下变量:

# ===== AI 服务配置(必填)=====
# OpenAI 配置(支持官方API和中转API)
OPENAI_API_KEY=your_openai_key_here
OPENAI_BASE_URL=https://api.openai.com/v1

# Anthropic 配置
# ANTHROPIC_API_KEY=your_anthropic_key_here
# ANTHROPIC_BASE_URL=https://api.anthropic.com

# 中转API配置示例(使用OpenAI格式)
# New API 中转服务
# OPENAI_API_KEY=your_newapi_key_here
# OPENAI_BASE_URL=https://api.new-api.com/v1

# 默认 AI 提供商和模型
DEFAULT_AI_PROVIDER=openai
DEFAULT_MODEL=gpt-4o-mini
DEFAULT_TEMPERATURE=0.8
DEFAULT_MAX_TOKENS=32000

# ===== 应用配置 =====
APP_NAME=MuMuAINovel
APP_VERSION=1.0.0
APP_HOST=0.0.0.0
APP_PORT=8000
DEBUG=false

# ===== LinuxDO OAuth 配置(可选)=====
LINUXDO_CLIENT_ID=your_client_id_here
LINUXDO_CLIENT_SECRET=your_client_secret_here
LINUXDO_REDIRECT_URI=http://localhost:8000/api/auth/callback
FRONTEND_URL=http://localhost:8000

# ===== 本地账户登录配置 =====
LOCAL_AUTH_ENABLED=true
LOCAL_AUTH_USERNAME=admin
LOCAL_AUTH_PASSWORD=your_secure_password_here
LOCAL_AUTH_DISPLAY_NAME=管理员

# 会话配置
# 会话过期时间(分钟),默认120分钟(2小时)
SESSION_EXPIRE_MINUTES=120
# 会话刷新阈值(分钟),剩余时间少于此值时可刷新,默认30分钟
SESSION_REFRESH_THRESHOLD_MINUTES=30

# ===== CORS 配置(生产环境)=====
# CORS_ORIGINS=https://your-domain.com,https://www.your-domain.com

AI 模型配置
项目支持多个 AI 提供商,你可以根据需要配置:

提供商推荐模型用途
OpenAIgpt-4, gpt-3.5-turbo高质量文本生成
Anthropicclaude-3-opus, claude-3-sonnet长文本创作

使用中转API服务
如果你无法直接访问 OpenAI 官方 API,或者想使用更经济实惠的中转服务,本项目完全支持各种 OpenAI 兼容格式的中转 API:

配置方法
只需修改 .env 文件中的两个参数:

# 1. 填入中转服务提供的 API Key
OPENAI_API_KEY=your_api_key_from_proxy_service

# 2. 修改 Base URL 为中转服务的地址
OPENAI_BASE_URL=https://your-proxy-service.com/v1

常见中转服务配置示例
New API

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
OPENAI_BASE_URL=https://api.new-api.com/v1

API2D

OPENAI_API_KEY=fk-xxxxxxxxxxxxxxxx
OPENAI_BASE_URL=https://api.api2d.com/v1

OpenAI-SB

OPENAI_API_KEY=sb-xxxxxxxxxxxxxxxx
OPENAI_BASE_URL=https://api.openai-sb.com/v1

自建 One API / New API

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
OPENAI_BASE_URL=https://your-domain.com/v1

注意事项

  • ✅ 所有支持 OpenAI 接口格式的服务都可以使用
  • ✅ 确保中转服务的 Base URL 以 /v1 结尾
  • ✅ 根据中转服务支持的模型,修改 DEFAULT_MODEL 参数
  • ⚠️ 不同中转服务的模型名称可能不同,请参考服务商文档
  • ⚠️ 部分中转服务可能对请求频率或并发有限制

推荐的中转服务
如果你需要中转服务,以下是一些常见选择:

  1. New API - 开源的 API 分发系统,支持多种模型
  2. API2D - 国内稳定的 API 中转服务
  3. OpenAI-SB - 提供多种 AI 模型的中转
  4. 自建服务 - 使用 One API 或 New API 自行搭建
? 提示:使用中转服务时,请确保服务提供商的可靠性和数据安全性

? 项目结构

MuMuAINovel/
├── backend/ # 后端服务
│ ├── app/
│ │ ├── api/ # API 路由
│ │ │ ├── auth.py # 认证接口
│ │ │ ├── projects.py # 项目管理
│ │ │ ├── chapters.py # 章节管理
│ │ │ ├── characters.py # 角色管理
│ │ │ ├── wizard_stream.py # 向导流式生成
│ │ │ └── ...
│ │ ├── models/ # 数据模型
│ │ ├── schemas/ # Pydantic 模型
│ │ ├── services/ # 业务逻辑
│ │ │ ├── ai_service.py # AI 服务封装
│ │ │ └── oauth_service.py # OAuth 服务
│ │ ├── middleware/ # 中间件
│ │ ├── utils/ # 工具函数
│ │ ├── config.py # 配置管理
│ │ ├── database.py # 数据库连接
│ │ └── main.py # 应用入口
│ ├── data/ # 数据存储目录
│ ├── static/ # 前端静态文件(构建后)
│ ├── requirements.txt # Python 依赖
│ └── .env.example # 环境变量示例
├── frontend/ # 前端应用
│ ├── src/
│ │ ├── pages/ # 页面组件
│ │ │ ├── ProjectList.tsx # 项目列表
│ │ │ ├── ProjectWizardNew.tsx # 创建向导
│ │ │ ├── Chapters.tsx # 章节管理
│ │ │ ├── Characters.tsx # 角色管理
│ │ │ └── ...
│ │ ├── components/ # 通用组件
│ │ ├── services/ # API 服务
│ │ ├── store/ # 状态管理(Zustand)
│ │ ├── types/ # TypeScript 类型
│ │ └── utils/ # 工具函数
│ ├── package.json
│ └── vite.config.ts
├── docker-compose.yml # Docker Compose 配置
├── Dockerfile # Docker 镜像构建
└── README.md # 项目说明文档

1万亿免费Gemini,GPT API来了
事情的起因是Manus做了这个活动,但是这个消耗的这么慢,所以我们来帮她加一下速吧,我发现他们这个原理是在环境里定义了API,所以我们不能直接获取到, 所以我弄了一个2API
地址是:https://3000-ihdbd6r5dut6rueqparvn-a428ee41.manus-asia.computer
或者:https://manuschat-va6rmpup.manus.space
https://3000-ihdbd6r5dut6rueqparvn-a428ee41.manus-asia.computer/v1/models
API:manus-free-api-key-no-auth-required
我测试下来能确定的是应该有gemini 2.5 flesh可以用,GPT 4.1 Mini应该也有,虽然请求的列表有这些:

  • Gemini 2.5 pro
  • GPT-5-2025-08-07
  • GPT-5-pro-2025-10-06
  • GPT-4.1-2025-04-14
  • o3-deep-research-2025-06-26
  • o4-mini-deep-research-2025-06-26
  • computer-use-preview-2025-03-11
  • Claude 4.5 sonnet

但是实际上应该只有刚刚两个,建议大家可以试用一下

Alpha Arena 加密货币交易
更新视频教程 https://www.bilibili.com/video/BV1VH1aBtER1

前两天我的社交网站全被一个叫做 Alpha Arena by Nof1 的 AI 量化交易系统刷屏,这是 Nof1 公司利用多种大语言构建的一个在加密货币市场中进行交易的竞争平台,用来测评未经训练的 AI 模型在金融市场中的真实表现。

Alpha Arena 是由 AI 研究实验室 nof1.ai 在 2025 年 10 月 17 日正式启动的项目。这是一场大胆且创新的实验,旨在测量 AI 在真实金融市场中的实际表现能力。项目的核心理念非常简洁:给予 6 个最先进的 AI 大语言模型真实资金,让它们完全自主交易,并实时公开竞争结果。

Alpha Arena 是全球首个以 AI 模型为对象,在真实市场中进行投资竞赛的基准测试。Alpha Arena 给每个 AI 模型分配相同的初始资金,在真实市场数据中进行投资交易,所有的模型接受相同的输入和数据,目前是最大化风险调整后的收益。

目前 Arena 集成了 6 家目前顶级的 AI 模型,包括了 Claude,DeepSeek,Gemini,GPT-5,Grok,Qwen。

这是截止到 2025 年 11 月 2 日的走势表现。
手把手教你部署 AI 自动化交易 nofx

Alpha Arena 的实验条件:统一每一个 AI 模型都可以获得 1 万美金的真实资金。会使用分散的去中心化永续合约交易平台 HyperLiquid,主要的加密资产包括了比特币(BTC)、以太坊(ETH)、Solana(SOL)、瑞波币(XRP)、狗狗币(DOGE)、币安币(BNB)等。

AI 允许使用适量的杠杆。所有的 AI 模型都会接受相同的提示词、相同的市场数据和统一的风险管理规则,完全由 AI 自主进行决策和执行交易。

所有交易完全由 AI 独立执行。所有的交易记录、持仓情况、盈亏数据以及 AI 独自的内心思考过程都会公开在区块链上,保证完整的透明。

公开在相同的市场条件下,展示了 AI 模型在交易策略、风险管理和决策质量上的真实差异。

RockFlow RockAlpha

受到 Alpha Arena 的启发,RockFlow 公司也发布了自己研发的 RockAlpha 针对真实的美股证券市场,交易的标的包括 NVDA,TSLA,GOOGL,MSFT,COIN,BABA,SPY,GLD,IBIT,UVIX。

RockAlpha 的灵感来自 nof1.ai 在加密货币交易领域的成功实验。RockFlow 团队决定将这一概念扩展到美国股票市场,这是一个更深层、更受监管、也更加复杂的领域。

手把手教你部署 AI 自动化交易 nofx1

RockAlpha 中,六个顶级的 AI 模型初始资金是每个账户十万美元,交易的品种为十支每股关键的股票。可以适度地使用杠杆交易,成本和利息与真实的投资者相同。

所有的 AI 决策都是由 AI 完全自主进行,没有人类的干预或事后编辑。每个模型都会收到相同的提示系统,逐行报告所有的操作情况。

每五分钟,AI 模型都会收到新的数据,包括实时的价格、投资组合更新、新闻标题,甚至是其他 AI 的交易和评论。

Nofx

受到 Nof1.ai 的启发,在 GitHub 上 Tinkle 社区迅速复刻并开源了一个叫做 Nofx 的通用 AI 交易项目。基于 DeepSeek,Qwen 大大语言模型,打造了一款通用架构 AI 交易员,完成从决策,到交易,复盘的闭环。

Nofx 是一个通用的交易 AI Agent,将 nof1.ai 加密货币交易理念扩展到了多个金融市场,包括股票、期权、期货、外汇等,致力于打造一个跨市场、跨交易所的 AI 交易生态。

和 Nof1.ai 一样,AI 会自主完成整个交易闭环。
特点:

  • 自动分析市场数据做出交易决策
  • 从历史交易中学习优化策略
  • 提供专业的监控界面,实时显示账户权益曲线,持仓详情,AI 推理过程
  • 支持 Binance,Hyperliquid,Aster DEX 三大交易所

    • Binance 中心化交易所,全球最大交易所,流动性强。
    • HyperLiquid,去中心化永续合约交易所 ,高性能 DEX,低延迟
    • Aster DEX,去中心化交易所,链上交易,完全去中心化
  • 支持 Docker 一键部署或手动安装

Nofx 提供完整的交易操作能力

  • 多头空投交易,支持双向交易。
  • 支持杠杆交易。
  • 止盈止损自动化
  • 风险管理
  • 低延迟路由

提前准备

  • 交易所 API,可以使用币安或者 HyperLiquid
  • DeepSeek API
  • 安装好 Docker 环境
  • 本地安装环境,或者 VPS

获取必要的 API 密钥
Nofx 需要配置两种类型的 API 密钥:AI 模型密钥 和 交易所密钥。

AI 模型密钥 ​
DeepSeek(推荐):

  • 成本约为 GPT-4 的 1/10
  • 响应速度快
  • 决策质量优秀

获取方式:访问 https://platform.deepseek.com ,注册账户,充值余额,生成 sk- 前缀的 API 密钥
Qwen(阿里巴巴):

  • 通过阿里云 DashScope 服务
  • 需要阿里云账户
  • 需要激活服务

交易所 API 密钥
Nofx 支持三大交易所

交易所类型API 获取方式
Binance中心化交易所访问 Binance 账户设置,创建 API 密钥,必须启用 Futures 权限,建议添加 IP 白名单
Hyperliquid去中心化交易所使用 MetaMask 私钥(移除 0x 前缀),无需传统 API 密钥
Aster DEXBinance 兼容 DEX连接钱包到 Aster API Wallet 页面,创建 API 钱包,保存 User Address、Signer Address 和 API Wallet Private Key

如果没有 Binance 账号,点击注册

  • 使用邮箱或手机号完成注册
  • 完成 KYC 认证
  • 开启 Futures 交易权限

    • 主页 → Derivatives → USD-M Futures
    • Click “Open Now” 启用
  • 创建 API Key

    • Account → API Management
    • 创建 API Key,并勾选 Futures 权限
    • 保存 API Key 和 Secret Key
    • 将 IP 地址添加到白名单

获取项目源代码

git clone https://github.com/NoFxAiOS/nofx.git
cd nofx

准备配置文件

cp config.json.example config.json

安装流程
推荐使用 Docker 安装,自动处理所有的依赖。

我们首先要编辑配置文件,可以使用自己习惯的编辑器打开 config.json 文件,填写必要的配置。

交易员配置

[
  {
    "id": "qwen_trader",
    "name": "Qwen 交易员",
    "ai_model": "qwen",
    "qwen_key": "your_qwen_api_key",
    "initial_balance": 1000,
    "scan_interval_minutes": 3,
    "exchange": "binance",
    "binance_api_key": "your_binance_api_key",
    "binance_secret_key": "your_binance_secret_key",
    "btc_eth_leverage": 5,
    "altcoin_leverage": 5,
    "use_default_coins": true
  }
]

参数说明

字段说明示例值
id交易员唯一标识qwen_trader 或 deepseek_trader
name交易员显示名称Qwen 交易员
ai_modelAI 模型选择qwen 或 deepseek
initial_balance初始账户余额(USDT)1000
scan_interval_minutes决策周期(分钟)3 到 5 推荐
exchange交易所选择binance、hyperliquid 或 aster
btc_eth_leverageBTC/ETH 最大杠杆5(Binance 子账户限制)
altcoin_leverage其他币种最大杠杆5 到 20
use_default_coins使用默认币池true 或 false

这里以 Binance 为例

{
  "exchange": "binance",
  "binance_api_key": "your_key",
  "binance_secret_key": "your_secret"
}

启动

# 给启动脚本执行权限
chmod +x start.sh

# 推荐方式:使用启动脚本
./start.sh start --build

# 或者使用 Docker Compose
docker compose up -d --build

Docker 启动之后,可以在浏览器访问 http://localhost:3000

可以看到账户的实时交易、多 AI 的对比排行、AI 的决策过程、仓位和损益表。

其他有用的命令

# 查看运行日志
./start.sh logs

# 检查服务状态
./start.sh status

# 停止服务
./start.sh stop

Nofx 工作流程
Nofx 交易周期由 7 个步骤组成,默认每三分钟执行一次。

步骤过程说明
1历史表现分析AI 分析过去 20 个周期的表现,获取反馈
2账户状态获取获取实时账户余额、已用杠杆、未实现 P&L
3持仓审查检查现有持仓与市场数据
4新机会评估筛选币池中最有前景的交易机会
5AI 综合决策AI 进行链式思维(CoT)推理,输出交易决策
6交易执行优先平仓,再开仓
7日志归档保存完整决策和执行记录

Nofx 的核心创新是 AI 的自学习机制,AI 会分析最近 20 个周期的表现,进行动态的策略调整。

日志存储在 decision_logs/ 目录中,每个 JSON 文件包含:

  • cot_trace:AI 的完整思维链(Chain of Thought)
  • account_state:账户快照
  • positions:当前持仓
  • decisions:交易决策详情
  • execution_log:执行结果

常见错误

执行决策失败 (BNBUSDT open_short): 开空仓失败: < APIError > code=-4061, msg=Order’s position side does not match user’s setting.

仓位模式需要选择正确。

仓位模式需要选择正确。

执行决策失败 (BTCUSDT open_short): 开空仓失败: < APIError > code=-2019, msg=Margin is insufficient.

这种错误一般就是保证金不够,因为我仓位只有 100 美元,才出现的问题。

实测

从昨天晚上 11 点左右开始执行,到今天早上 8 点,净亏损 3 刀,并消耗月 2 元人民币 DeepSeek API 调用。
手把手教你部署 AI 自动化交易 nofx1

文章转载自:https://linux.do/t/topic/1118884

一键提取 TikTok 字幕:免费免注册,支持 .srt/.txt
一键提取 TikTok 字幕:免费免注册,支持 .srt/.txt
立即使用:tiktoktranscript.org

最近把一个小想法做成了在线工具:粘贴 TikTok 视频链接,一键提取字幕;支持复制或下载 .srt/.txt ,多语言也都能拿。名字很直白:TikTok Transcript Generator 。

  • 为什么做这个

    • 刷到好内容想精读做笔记,抄字幕太折磨
    • 做剪辑/二创需要干净的字幕文件
    • 教培/自媒体同学经常问“有没快捷拿字幕的办法”
  • 怎么用(超级简单)

    • 打开页面 → 粘贴 TikTok 链接 → 点“Extract”
    • 有字幕就直接出结果;可复制或下载 .srt/.txt
  • 核心特点

    • 完全免费、无需注册
    • 多语言字幕提取(视频里有啥语言就拿啥)
    • 即时预览与复制
    • 一键下载 .srt / .txt
  • 已知限制

    • 视频本身没有字幕 → 不能“凭空生成”
    • 私密/受限视频可能拿不到
    • 偶发失败(网络/上游波动),重试一般能好
  • 想继续做的

    • 批量提取(一次贴一组链接)
    • 可能考虑 chrome 插件方式
    • API 开放(方便自动化)

EZPPT 是一个基于 FastAPI + Web 前端的本地化演示文稿生成平台,适用于快速搭建主题演讲、技术分享、项目汇报等内容的初稿。

? 功能与亮点

  • 大模型生成

    • 大纲生成:根据主题、受众、风格与参考资料,生成章节与要点。
    • 布局规划:为核心内容页生成“布局指令”,保持页面多样性与统一性。
    • 页面生成:调用大模型生成HTML代码。
  • 图片能力(可选) 通过 SearXNG 聚合搜索图片。
  • 自定义参考资料:支持自定义参考资料,用于生成内容,减少模型自身知识的幻觉。
  • 在线预览编辑 可在浏览器中实时预览与编辑。
  • 导出 PDF 或 PPTX 导出的 PPTX 文件可保持 HTML 预览 90% 的效果,并且内容可二次编辑。

?️ 环境要求

  • Python 3
  • 可访问的 LLM API(支持 OpenAI/Gemini 规范接口)
  • 可用的 SearXNG 实例(公共或自建,用于图片搜索,可选)

? 平台支持

  • Linux (x64 / ARM64)
  • Windows (x64)
  • macOS(未经测试,理论可行)

✨ 快速开始(本机运行)

1. 准备配置
复制根目录下的 .env.template 文件为 .env,并根据您的环境填写配置:

  • OUTLINE_*:大纲生成模型的配置(API_TYPE / API_KEY / API_URL / MODEL)
  • PPT_:页面生成模型的配置(若留空,则使用 OUTLINE_ 的配置)
  • PIC_*:图片理解模型的配置(需支持多模态,启用图片搜索时需要,可选)
  • SEARXNG_URL:SearXNG 搜索实例地址(用于图片搜索,可选)
  • APRYSE_LICENSE_KEY:Apryse License Key(用于导出 PPTX,已内置)
cp .env.template .env

2. 安装依赖(任选其一)
- 方式一:使用 uv (推荐)

# 创建并激活虚拟环境
uv venv

# 同步安装依赖
uv sync

# 首次运行时,准备浏览器与 Apryse 资源
uv run setup.py

- 方式二:使用 pip

# 创建并激活虚拟环境
python -m venv .venv && source .venv/bin/activate  # Windows 用户请使用: .venv\Scripts\activate

# 安装 apryse-sdk (需要额外源)
pip install --extra-index-url https://pypi.apryse.com apryse-sdk

# 安装其他依赖
pip install fastapi uvicorn sqlmodel requests pillow playwright pypdf lxml bs4 python-dotenv

# 安装额外依赖
python setup.py

3. 启动服务

# 如果使用 uv
uv run main.py

# 如果使用 pip
python main.py

4. 打开浏览器
访问控制台主页:http://127.0.0.1:8000

? Docker 运行

使用预构建镜像(推荐)
我们已提供打包好的 Docker 镜像:cat3399/ezppt

docker run --name ezppt \
  -p 8000:8000 \
  -v "$(pwd)/data:/work/data" \
  -v "$(pwd)/.env:/work/.env" \
  cat3399/ezppt:latest

从源码构建镜像
1.构建镜像

docker build -t ezppt .

2.运行容器

docker run --name ezppt \
  -p 8000:8000 \
  -v "$(pwd)/data:/work/data" \
  -v "$(pwd)/.env:/work/.env" \
  ezppt

? 使用指南 (WebUI)

  • 新建项目:在首页点击“新建项目”,输入主题、受众、风格、页数,可选“启用图片搜索”和参考资料。
  • 查看进度:右侧项目详情卡片会显示生成进度百分比,点击进入详情页可查看各部分统计。
  • 预览编辑:点击项目卡片上的“打开预览”,支持侧边栏切换页面、内容实时编辑与自动保存。
  • 导出文件:

    • 项目生成完成后,在“更多操作”菜单中选择“导出为 PDF/PPTX”。
    • 导出任务完成后,“下载 PDF/PPTX”按钮将自动变为可用状态。
  • 重新生成:支持对整个项目或单个页面进行重新生成。

文件存储: 生成的文件位于 data/projects/<项目名>/ 目录下:

  • html_files/:存放每一页的 HTML 文件。
  • <项目名>.pdf:导出后生成的合并 PDF 文件。
  • <项目名>.pptx:导出后生成的 PPTX 文件。

效果展示

EZPPT —— 一键生成可导出 PDF/PPTX 的演示文稿平台

项目地址

[bsgit user="cat3399"]ezppt[/bsgit]

本次开源了一个我自用的工具 IPA-Harbor, 基于 ipatool ,用来下载 ipa ,避免重复的抓包下载 ipa 等操作,使用 Web 面板访问,支持 App 搜索、历史版本下载,支持 Docker 部署。

Docker 仓库地址 https://hub.docker.com/r/uuphy/ipa-harbor

Github 源码地址 https://github.com/ij369/ipa-harbor

GitHub 文档里有更详细的说明,感兴趣的可以点个小星星,有问题提 issue.

我以前每次想下一个旧版 ipa 都要抓包,然后 AirDrop 给 iPhone , 后面逛帖子时发现 ipatool ,后面拿电脑抠命令,是在是厌烦了,可读的版本号也没有,所以有了想法写这个。

另外,有一个 ipatool.ts 的项目,也非常好,不过我不想维护 ipatool 核心的部分,直接去 ipatool 项目的发版页下载最新的二进制文件,拷贝到我这个项目的 bin 目录即可,正所谓大树下好乘凉,感谢 ipatool 的贡献者,同时省去大家时间。

目前我 ipatool 自用到现在已经有一年时间,两个地区的 ID (美区和日区)都没被封过,非常建议使用的话拿独立的 Apple ID 独立的容器运行,看了源码且如果对 ipatool 项目信任的话,再使用主力 Apple ID 。

没有花钱购买应用的 ID ,这样能避免损失,具体可以去 App Store 进行切换登录,其实折腾这个的不一定只有一个 ID 吧。

整个项目拿 Cursor 断断续续写的, 前期几乎是 Vibe Coding ,后续人手改,所以后端实现以及界面啥的都有点糙,不想投入大精力在这方面,主打安全,能用,后续慢慢打磨。 因为我的文件夹辗转腾挪,我导出过提示词,看了下很多都包含敏感内容,脱敏工作量有点大,就 git 忽略了,后续我如果有空再阅读完整理下放出来。

侧载功能我按照好几个帖子试了下,好像是不可用, 前端已经暂时隐藏了该功能, 看看有没有大佬熟悉这这块帮忙看下能不能实现。

我目前一直挂在外网在用,方便手机领免费应用啥的,以下截图的域名我已经做了更换。 截图里的内容仅供参考,仅作为功能演示:

开源一个可以 Docker 容器部署的 ipa 下载工具,用于下载历史版本的 iOS 应用
开源一个可以 Docker 容器部署的 ipa 下载工具,用于下载历史版本的 iOS 应用1

项目最开始的目的是为了在私有环境给非开发人员使用,提高工作效率。按最开始官方版本的UI界面复刻的,打造更适合企业内部使用的版本。

[bsgit user="lpdswing"]mineru-web[/bsgit]

项目简介

MinerU Web 是一个现代化的文档智能处理平台,基于先进的 AI 技术,提供文档解析、信息提取和智能分析功能。本项目采用前后端分离架构,结合容器化技术,为用户提供高效、可靠的文档处理解决方案。

界面展示

Mineru-web,为mineru打造的ui界面1
Mineru-web,为mineru打造的ui界面2
Mineru-web,为mineru打造的ui界面3

介绍

1.免费且开源,代码无加密可自行修改(不要用于二次售卖)。
2.适当配置可生成收录不错的内容。
3.关注参考内容配置,可生成适合GEO的内容。
4.关于授权问题,其实可以直接覆盖跳过,授权是为了了解使用人数。
5.不提供任何技术支持)。

这不是一款很标准的文章生成工具,用它的目的一是辅助老网站保持长期更新,二是可以辅助老网站稳定排名(配合专用的主题,可以不改变老网站任何代码前提下,优化主站内容排名),三是生成适合GEO的内容。

AI自动生成文章的wordpress插件
插件只是辅助,再强大也不会直接获取很好的自然排名,甚至过度依赖会导致网站被惩罚,请根据个人经验合理应用。

下载地址

content-auto-manager(主插件).zip

前言

前阵子看到paddle发布SOTA的OCR模型,预览效果很nb,但看很少人去尝试使用。刚好公司有一些书籍类型的pdf(含公式,图片,表格等),内容双栏显示。使用场景够复杂了吧,也是日常会遇到的情况。于是就开始折腾部署PaddleOCR-VL模型。

配置

  • RTX6000(46G显存)
  • Windows11(配置wsl,Ubuntu-22.04)
  • python==3.12
  • 确保wsl内的CUDA 版本必须大于或等于 12.6(nvidia-smi查看)

开始操作

所有流程都在wsl中操作,包括python虚拟环境创建包安装等。我使用非Docker的方式安装
1.创建虚拟环境
conda create -n paddleocr python==3.12
2.安装paddle包

# 以下命令安装 CUDA 12.6 版本的 PaddlePaddle,对于其他 CUDA 版本以及 CPU 版本,请参考 https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/develop/install/pip/linux-pip.html
python -m pip install paddlepaddle-gpu==3.2.0 -i https://www.paddlepaddle.org.cn/packages/stable/cu126/

连续使用以下命令即可

python -m pip install -U "paddleocr[doc-parser]"
python -m pip install https://paddle-whl.bj.bcebos.com/nightly/cu126/safetensors/safetensors-0.6.2.dev0-cp38-abi3-linux_x86_64.whl

3.python代码(单个pdf文档)

from pathlib import Path
from paddleocr import PaddleOCRVL

input_file = "./your_pdf_file.pdf"
output_path = Path("./output")

pipeline = PaddleOCRVL()
output = pipeline.predict(input=input_file)

markdown_list = []
markdown_images = []

for res in output:
    md_info = res.markdown
    markdown_list.append(md_info)
    markdown_images.append(md_info.get("markdown_images", {}))

markdown_texts = pipeline.concatenate_markdown_pages(markdown_list)

mkd_file_path = output_path / f"{Path(input_file).stem}.md"
mkd_file_path.parent.mkdir(parents=True, exist_ok=True)

with open(mkd_file_path, "w", encoding="utf-8") as f:
    f.write(markdown_texts)

for item in markdown_images:
    if item:
        for path, image in item.items():
            file_path = output_path / path
            file_path.parent.mkdir(parents=True, exist_ok=True)
            image.save(file_path)

4.完成到这部就可以运行了,第一次使用会下载模型比较慢。可能之前安装依赖中断过几次的原因,运行代码报错缺失系统依赖,错误日志丢ai解决即可。
我使用的文档是书中截取的三页内容,并非全书。消耗显存5.9G左右

效果

效果图
可以看到即使两栏内容,paddleocr也能很好的识别连接,公式使用latex,图表这里不知道为什么没识别成表格(官方演示效果)但还是完整保留下来了。有部分公式格式异常,导致公式没有正常渲染。后面写了脚本把二十多个pdf都进行ocr,占用显存44.5G。

结论

目前看效果已经非常好了,个人还没有做多模态检索,所以图片数据不是很重要,但确实已经很好的保留书籍的完整结构。这种文档结构识别再进行ocr的架构(类似于工作流)应该是未来OCR的方向了。

参考连接:使用教程 - PaddleOCR 文档

【PromPub】你的私人提示词仓库,【面板开源】并为大家服务

项目地址

PromPub地址:https://prompub.com/
[bsgit user="ChinaSiro"]open-prompt-manager-for-prompub[/bsgit]

基本功能

  • 发布分享/收藏/提示词
  • 私人仓库/管理提示词
  • 创作中心/实时调试提示词
  • 支持第三方中转API

注意:APIKEY不上传服务器,存在浏览器本地
PromPub

亮点(开源部分)

  • 私人仓库 - 管理你的海量提示词
  • 支持搜索 / 分类 / 模型筛选
  • 实时创作 - 开发写作实时调试
  • 对话中系统提示词实时更新
  • 支持第三方中转API

PromPub

基本部署

基于Vite环境开发

npm install
npm run dev
  • 分类设置.env
  • 不想搭建的可以直接使用线上demo
  • 开源是为了方便需要更私密的用户

数据无处不在,自动化完成复杂的数据科学任务是智能发展的长期目标之一。现有方法通过构建工作流程来让大模型完成数据分析、可视化等特定任务,取得了可喜的进展。

LLM是否能完全自主地完成数据科学任务,不依赖任何固定的workflow(工作流程)呢?

来自人大和清华的团队发布了DeepAnalyze,是首个面向数据科学的能动的大模型。

DeepAnalyze-8B摆脱了任何固定的workflow,完全能像数据科学家一样自主完成各种数据科学任务,包括:

  • 数据任务:支持自动化数据准备、数据分析、数据建模、数据可视化、数据洞察
  • 数据研究:可在非结构化数据(TXT、Markdown)、半结构化数据(JSON、XML、YAML)及结构化数据(数据库、CSV、Excel)中进行开放式深度 research(研究),生成研究报告

DeepAnalyze:首个自主的数据科学/分析大模型
DeepAnalyze的论文、代码、模型均已开源,欢迎大家体验和交流!