ChatGPT Helper

一站式ChatGPT增强工具,支持Access Token获取、服务降级检测等功能

Versión del día 13/11/2024. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         ChatGPT Helper
// @namespace    https://linux.do/u/f-droid
// @version      1.0
// @description  一站式ChatGPT增强工具,支持Access Token获取、服务降级检测等功能
// @license      GNU Affero General Public License v3.0 or later
// @author       F-Droid
// @match        https://chatgpt.com/*
// @match        https://new.oaifree.com/*
// @match        https://*.new.oaifree.com/*
// @match        https://shared.oaifree.com/*
// @icon         https://linux.do/user_avatar/linux.do/f-droid/288/228666_2.png
// @grant        GM_xmlhttpRequest
// @grant        GM_setClipboard
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    const currentUrl = window.location.href;
    if (!(
        currentUrl.startsWith('https://chatgpt.com/') ||
        currentUrl.startsWith('https://new.oaifree.com/') ||
        currentUrl.startsWith('https://shared.oaifree.com/') ||
        currentUrl.match(/^https:\/\/(?:[^\/]+\.)?new\.oaifree\.com\//)
    )) {
        return;
    }

    const panel = document.createElement('div');
    panel.style.cssText = `
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 350px;
        background: linear-gradient(145deg, #f8f9fa, #e9ecef);
        border-radius: 15px;
        box-shadow: 0 15px 35px rgba(0,0,0,0.1), 0 5px 15px rgba(0,0,0,0.05);
        padding: 20px;
        z-index: 10001;
        font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
        color: #2c3e50;
        transition: all 0.3s ease;
        display: none;
    `;

    const titleBar = document.createElement('div');
    titleBar.innerHTML = `
        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
            <h2 style="margin: 0; font-size: 18px; color: #2c3e50;">
                ChatGPT Helper
            </h2>
            <button id="close-btn" style="background: none; border: none; color: #6c757d; font-size: 20px; cursor: pointer;">×</button>
        </div>
    `;
    panel.appendChild(titleBar);

    const statusSection = document.createElement('div');
    statusSection.innerHTML = `
        <div style="background-color: #f1f3f5; border-radius: 10px; padding: 15px; margin-bottom: 0;">
            <div style="font-weight: 600;">服务降级检测</div>
            <div>PoW难度: <span id="difficulty">N/A</span> <span id="difficulty-level"></span></div>
            <div style="font-size: 12px; color: #6c757d; margin-top: 5px;">
                <em>这个值越小,代表PoW难度越高,ChatGPT认为你的IP风险越高。</em>
            </div>
            <div>IP质量: <span id="ip-quality">N/A</span></div>
            <div id="persona-container" style="display: none;">用户类型: <span id="persona">N/A</span></div>
        </div>
    `;
    panel.appendChild(statusSection);

    const tokenSection = document.createElement('div');
    tokenSection.innerHTML = `
        <div style="background-color: #f1f3f5; border-radius: 10px; padding: 15px; margin-bottom: 15px;">
            <div style="font-weight: 600; font-size: 16px; text-align: center; margin-bottom: 10px;">Access Token Tool</div>
            <div style="margin-bottom: 15px;">
                <textarea id="token-display" style="
                    width: 100%;
                    height: 100px;
                    border: 1px solid #ced4da;
                    border-radius: 8px;
                    padding: 10px;
                    resize: none;
                    font-family: monospace;
                    background-color: #f8f9fa;
                "></textarea>
            </div>
            <div style="display: flex; justify-content: center; gap: 10px;">
                <button id="fetch-btn" style="
                    flex: 1;
                    padding: 10px;
                    background-color: #2ecc71;
                    color: white;
                    border: none;
                    border-radius: 8px;
                    cursor: pointer;
                    transition: background-color 0.3s;
                ">获取</button>
                <button id="copy-btn" style="
                    flex: 1;
                    padding: 10px;
                    background-color: #3498db;
                    color: white;
                    border: none;
                    border-radius: 8px;
                    cursor: pointer;
                    transition: background-color 0.3s;
                ">复制</button>
            </div>
        </div>
    `;
    panel.appendChild(tokenSection);

    const createToast = (message) => {
        const toast = document.createElement('div');
        toast.innerText = message;
        toast.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: rgba(76, 175, 80, 0.9);
            color: white;
            border-radius: 8px;
            padding: 10px 20px;
            box-shadow: 0 5px 15px rgba(0,0,0,0.3);
            font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
            z-index: 10001;
            transition: opacity 0.5s ease;
        `;
        document.body.appendChild(toast);
        setTimeout(() => {
            toast.style.opacity = '0';
            setTimeout(() => {
                document.body.removeChild(toast);
            }, 500);
        }, 1000);
    };

    const ball = document.createElement('div');

    const getFavicon = () => {
        const favicon = document.querySelector("link[rel~='icon']") ||
                        document.querySelector("link[rel='shortcut icon']") ||
                        document.querySelector("link[rel='icon']");

        if (favicon) {
            const faviconUrl = favicon.href;
            ball.style.backgroundImage = `url(${faviconUrl})`;
            ball.style.backgroundSize = 'cover';
            ball.style.backgroundPosition = 'center';
        } else {
            ball.style.backgroundColor = 'rgba(0, 123, 255, 0.8)';
        }
    };

    ball.style.cssText = `
        position: fixed;
        top: 25%;
        right: 20px;
        width: 40px;
        height: 40px;
        border-radius: 50%;
        cursor: pointer;
        z-index: 10000;
        box-shadow: 0 4px 6px rgba(0,0,0,0.1);
        transition: transform 0.2s;
    `;

    getFavicon();

    ball.addEventListener('mouseenter', () => {
        ball.style.transform = 'scale(1.1)';
    });
    ball.addEventListener('mouseleave', () => {
        ball.style.transform = 'scale(1)';
    });

    document.body.appendChild(ball);

    ball.onclick = () => {
        panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
    };

    let isDragging = false;
    let currentX;
    let currentY;
    let initialX;
    let initialY;
    let xOffset = 0;
    let yOffset = 0;
    const dragStart = (e) => {
        initialX = e.type === 'mousedown' ? e.clientX - xOffset : e.touches[0].clientX - xOffset;
        initialY = e.type === 'mousedown' ? e.clientY - yOffset : e.touches[0].clientY - yOffset;
        if (e.target === titleBar) {
            isDragging = true;
        }
    };
    const drag = (e) => {
        if (isDragging) {
            e.preventDefault();
            currentX = e.type === 'mousemove' ? e.clientX - initialX : e.touches[0].clientX - initialX;
            currentY = e.type === 'mousemove' ? e.clientY - initialY : e.touches[0].clientY - initialY;
            xOffset = currentX;
            yOffset = currentY;
            setTranslate(currentX, currentY, panel);
        }
    };
    const dragEnd = () => {
        initialX = currentX;
        initialY = currentY;
        isDragging = false;
    };
    const setTranslate = (xPos, yPos, el) => {
        el.style.transform = `translate(-50%, -50%) translate(${xPos}px, ${yPos}px)`;
    };
    titleBar.addEventListener('mousedown', dragStart);
    titleBar.addEventListener('touchstart', dragStart);
    document.addEventListener('mousemove', drag);
    document.addEventListener('touchmove', drag);
    document.addEventListener('mouseup', dragEnd);
    document.addEventListener('touchend', dragEnd);

    document.body.appendChild(panel);
    const tokenDisplay = document.getElementById('token-display');
    const closeBtn = document.getElementById('close-btn');
    const fetchBtn = document.getElementById('fetch-btn');
    const copyBtn = document.getElementById('copy-btn');

    fetchBtn.onclick = function() {
        GM_xmlhttpRequest({
            method: "GET",
            url: "/api/auth/session",
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    tokenDisplay.value = data.accessToken || '获取失败';
                } catch(e) {
                    tokenDisplay.value = '获取失败:' + e.message;
                }
            },
            onerror: function() {
                tokenDisplay.value = '网络错误,请重试';
            }
        });
    };

    copyBtn.onclick = function() {
        if (tokenDisplay.value) {
            GM_setClipboard(tokenDisplay.value);
            createToast('Access Token已复制到剪贴板');
        }
    };

    closeBtn.onclick = function() {
        panel.style.display = 'none';
    };

    const updateDifficultyIndicator = (difficulty) => {
        const difficultyLevel = document.getElementById('difficulty-level');
        const ipQuality = document.getElementById('ip-quality');
        if (difficulty === 'N/A') {
            difficultyLevel.innerText = '';
            ipQuality.innerHTML = 'N/A';
            return;
        }
        const cleanDifficulty = difficulty.replace('0x', '').replace(/^0+/, '');
        const hexLength = cleanDifficulty.length;
        let level, qualityText;
        if (hexLength <= 2) {
            level = '(困难)';
            qualityText = '高风险';
        } else if (hexLength === 3) {
            level = '(中等)';
            qualityText = '中等';
        } else if (hexLength === 4) {
            level = '(简单)';
            qualityText = '良好';
        } else {
            level = '(极易)';
            qualityText = '优秀';
        }
        difficultyLevel.innerHTML = `<span style="color: #4CAF50">${level}</span>`;
        ipQuality.innerHTML = `<span style="color: #4CAF50">${qualityText}</span>`;
    };

    const originalFetch = window.fetch;
    window.fetch = async function(resource, options) {
        try {
            const response = await originalFetch(resource, options);
            const url = typeof resource === 'string' ? resource : resource.url;

            if (url.includes('/backend-api/sentinel/chat-requirements') ||
                url.includes('/backend-anon/sentinel/chat-requirements')) {

                GM_xmlhttpRequest({
                    method: "POST",
                    url: url,
                    headers: options?.headers,
                    data: options?.body,
                    onload: function(response) {
                        try {
                            const data = JSON.parse(response.responseText);
                            const difficulty = data.proofofwork?.difficulty || 'N/A';
                            document.getElementById('difficulty').innerText = difficulty;
                            updateDifficultyIndicator(difficulty);
                        } catch(e) {
                            console.error('解析响应时出错:', e);
                        }
                    }
                });
            }
            return response;
        } catch(e) {
            console.error('请求拦截时出错:', e);
            return originalFetch(resource, options);
        }
    };

    const initCheck = () => {
        GM_xmlhttpRequest({
            method: "POST",
            url: "/backend-api/sentinel/chat-requirements",
            headers: {
                "Content-Type": "application/json"
            },
            data: JSON.stringify({}),
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    const difficulty = data.proofofwork?.difficulty || 'N/A';
                    document.getElementById('difficulty').innerText = difficulty;
                    updateDifficultyIndicator(difficulty);
                } catch(e) {
                    console.error('初始化检查时出错:', e);
                }
            }
        });
    };

    setTimeout(initCheck, 2000);

    const footer = document.createElement('div');
    footer.style.marginTop = '15px';
    footer.style.textAlign = 'center';
    footer.innerHTML = `Copyright &copy; ${new Date().getFullYear()} <a href="https://linux.do/u/f-droid" target="_blank" style="color: #007BFF; text-decoration: none;">F-Droid</a> retain all rights reserved.<br>如果您喜欢这个工具,请给作者点个赞吧!😊`;
    panel.appendChild(footer);
})();