搜索结果

×

搜索结果将在这里显示。

抖音显示用户UID

// ==UserScript==
// @name         抖音显示用户UID
// @namespace    https://xjrx.net
// @version      1.1.0
// @description  在抖音用户主页显示用户UID,点击即可复制(支持路由切换)
// @author       YourName
// @license      MIT
// @match        *://*.douyin.com/*
// @match        *://*.iesdouyin.com/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // ============ 配置 ============
    const CONFIG = {
        // 是否显示复制成功提示
        showToast: true,
        // Toast显示时间(毫秒)
        toastDuration: 2000,
        // 是否在控制台输出日志
        debug: false
    };

    // ============ 工具函数 ============
    function log(...args) {
        if (CONFIG.debug) {
            console.log('[UID]', ...args);
        }
    }

    // ============ 样式 ============
    const STYLE = `
        .gm-user-uid-display {
            color: var(--color-text-t3, #8a8a8a);
            margin-right: 20px;
            font-size: 12px;
            line-height: 20px;
            cursor: pointer;
            user-select: all;
            padding: 2px 8px;
            border-radius: 4px;
            transition: background-color 0.2s;
            display: inline-block;
        }
        .gm-user-uid-display:hover {
            background-color: var(--color-bg-b1, rgba(0,0,0,0.05));
        }
        .gm-user-uid-display .uid-label {
            opacity: 0.6;
        }
        .gm-user-uid-display .uid-value {
            font-weight: 600;
            color: var(--color-text-t1, #1a1a1a);
        }
        .gm-user-uid-copied {
            animation: uid-copied-flash 0.6s ease;
        }
        @keyframes uid-copied-flash {
            0%, 100% { background-color: transparent; }
            50% { background-color: #4caf50; color: white; }
        }
    `;

    // ============ Toast ============
    let toastTimer = null;

    function showToast(message) {
        if (!CONFIG.showToast) return;

        const existing = document.querySelector('.gm-uid-toast');
        if (existing) {
            existing.remove();
            if (toastTimer) {
                clearTimeout(toastTimer);
                toastTimer = null;
            }
        }

        const toast = document.createElement('div');
        toast.className = 'gm-uid-toast';
        toast.textContent = message;
        Object.assign(toast.style, {
            position: 'fixed',
            bottom: '80px',
            left: '50%',
            transform: 'translateX(-50%)',
            backgroundColor: 'rgba(0,0,0,0.85)',
            color: '#fff',
            padding: '10px 24px',
            borderRadius: '8px',
            fontSize: '14px',
            zIndex: '99999',
            maxWidth: '90%',
            textAlign: 'center',
            animation: 'fadeInOut 2s ease forwards',
            pointerEvents: 'none',
            fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
        });

        if (!document.querySelector('#gm-uid-toast-style')) {
            const style = document.createElement('style');
            style.id = 'gm-uid-toast-style';
            style.textContent = `
                @keyframes fadeInOut {
                    0% { opacity: 0; transform: translateX(-50%) translateY(10px); }
                    15% { opacity: 1; transform: translateX(-50%) translateY(0); }
                    85% { opacity: 1; transform: translateX(-50%) translateY(0); }
                    100% { opacity: 0; transform: translateX(-50%) translateY(-10px); }
                }
            `;
            document.head.appendChild(style);
        }

        document.body.appendChild(toast);
        toastTimer = setTimeout(() => {
            if (toast.parentNode) {
                toast.remove();
            }
            toastTimer = null;
        }, CONFIG.toastDuration);
    }

    // ============ 核心功能 ============

    // 判断是否在用户主页
    function isUserPage() {
        const path = window.location.pathname;
        return path.startsWith('/user/') && !path.includes('/search/');
    }

    // 从URL提取sec_uid
    function getSecUidFromUrl() {
        const match = window.location.pathname.match(/\/user\/([^/?]+)/);
        return match ? match[1] : null;
    }

    // 通过多种方式获取UID
    function getUidFromPage() {
        // 方法1:从React Fiber中提取
        const userInfoSelectors = [
            '[data-e2e="user-detail"] [data-e2e="user-info"]',
            '[data-e2e="user-info"]',
            '.user-info-container'
        ];

        for (const selector of userInfoSelectors) {
            const el = document.querySelector(selector);
            if (!el) continue;

            try {
                // 查找React属性
                const reactKey = Object.keys(el).find(key => 
                    key.startsWith('__reactFiber$') || 
                    key.startsWith('__reactInternalInstance$')
                );

                if (reactKey) {
                    let fiber = el[reactKey];
                    let depth = 0;
                    while (fiber && depth < 30) {
                        const props = fiber.memoizedProps || fiber.pendingProps;
                        if (props) {
                            // 尝试多种可能的uid路径
                            const uidPaths = [
                                props.userInfo?.uid,
                                props.user?.uid,
                                props.data?.userInfo?.uid,
                                props.data?.user?.uid,
                                props.author?.uid,
                                props.userInfo?.userId,
                                props.user?.userId
                            ];

                            for (const uid of uidPaths) {
                                if (uid && typeof uid === 'string' && uid.length > 5) {
                                    log('从React获取到UID:', uid);
                                    return uid;
                                }
                            }
                        }
                        if (fiber.return) {
                            fiber = fiber.return;
                            depth++;
                        } else {
                            break;
                        }
                    }
                }
            } catch (e) {
                log('React解析失败:', e);
            }
        }

        // 方法2:从页面文本中提取
        const textNodes = document.querySelectorAll('[data-e2e="user-info"] *');
        for (const node of textNodes) {
            const text = node.textContent || '';
            // 匹配 "抖音号:xxx" 或 "UID:xxx"
            let match = text.match(/抖音号[::]\s*([a-zA-Z0-9_-]+)/);
            if (match) {
                log('从文本获取到抖音号:', match[1]);
                return match[1];
            }
            match = text.match(/UID[::]\s*(\d+)/);
            if (match) {
                log('从文本获取到UID:', match[1]);
                return match[1];
            }
        }

        // 方法3:从页面全局数据中获取
        try {
            // 检查 __INITIAL_STATE__
            if (window.__INITIAL_STATE__) {
                const state = window.__INITIAL_STATE__;
                const uidPaths = [
                    state.userInfo?.uid,
                    state.user?.uid,
                    state.userInfo?.userId,
                    state.user?.userId
                ];
                for (const uid of uidPaths) {
                    if (uid && typeof uid === 'string' && uid.length > 5) {
                        log('从__INITIAL_STATE__获取到UID:', uid);
                        return uid;
                    }
                }
            }
        } catch (e) {}

        // 方法4:使用sec_uid作为备选
        const secUid = getSecUidFromUrl();
        if (secUid && secUid.length > 5) {
            log('使用sec_uid作为UID:', secUid);
            return secUid;
        }

        return null;
    }

    // 创建UID显示元素
    function createUidElement(uid) {
        const container = document.createElement('span');
        container.className = 'gm-user-uid-display';
        container.innerHTML = `
            <span class="uid-label">UID:</span>
            <span class="uid-value">${uid}</span>
        `;

        container.addEventListener('click', async function(e) {
            e.stopPropagation();
            try {
                if (navigator.clipboard && navigator.clipboard.writeText) {
                    await navigator.clipboard.writeText(uid);
                } else {
                    // 降级方案
                    const textarea = document.createElement('textarea');
                    textarea.value = uid;
                    textarea.style.cssText = 'position:fixed;opacity:0;pointer-events:none;';
                    document.body.appendChild(textarea);
                    textarea.select();
                    document.execCommand('copy');
                    document.body.removeChild(textarea);
                }

                this.classList.add('gm-user-uid-copied');
                setTimeout(() => this.classList.remove('gm-user-uid-copied'), 600);
                showToast('✅ 已复制: ' + uid);
            } catch (err) {
                console.error('复制失败:', err);
                showToast('❌ 复制失败,请手动复制');
            }
        });

        return container;
    }

    // 注入UID到页面
    function injectUid() {
        // 只在用户主页执行
        if (!isUserPage()) {
            log('不在用户主页,跳过');
            return false;
        }

        // 查找用户信息容器
        const userInfoSelectors = [
            '[data-e2e="user-detail"] [data-e2e="user-info"]',
            '[data-e2e="user-info"]',
            '.user-info-container',
            '.user-detail .user-info'
        ];

        let targetElement = null;
        for (const selector of userInfoSelectors) {
            const el = document.querySelector(selector);
            if (el && el.offsetParent !== null) { // 确保元素可见
                targetElement = el;
                break;
            }
        }

        if (!targetElement) {
            log('未找到用户信息元素');
            return false;
        }

        // 检查是否已经注入
        if (targetElement.querySelector('.gm-user-uid-display')) {
            log('UID已存在,跳过');
            return true;
        }

        // 获取UID
        const uid = getUidFromPage();
        if (!uid) {
            log('未获取到UID');
            return false;
        }

        // 注入UID
        const uidElement = createUidElement(uid);

        // 找到合适的插入位置
        const insertTarget = targetElement.querySelector('p:last-child') || 
                           targetElement.querySelector('.user-info-desc') ||
                           targetElement.querySelector('.user-info-stats') ||
                           targetElement.lastElementChild;

        if (insertTarget && insertTarget !== targetElement) {
            insertTarget.after(uidElement);
        } else {
            targetElement.appendChild(uidElement);
        }

        log('✅ UID注入成功:', uid);
        return true;
    }

    // ============ 路由监听 ============

    // 清理旧的UID元素
    function cleanupUidElements() {
        document.querySelectorAll('.gm-user-uid-display').forEach(el => el.remove());
    }

    // 防抖执行
    function debounce(fn, delay = 300) {
        let timer = null;
        return function(...args) {
            if (timer) clearTimeout(timer);
            timer = setTimeout(() => {
                fn.apply(this, args);
                timer = null;
            }, delay);
        };
    }

    // 检查并注入(带防抖)
    const debouncedInject = debounce(() => {
        cleanupUidElements();
        injectUid();
    }, 300);

    // 监听路由变化
    function watchRouteChange() {
        let lastUrl = window.location.href;

        // 方法1:监听 popstate
        window.addEventListener('popstate', () => {
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                log('路由变化(popstate):', currentUrl);
                debouncedInject();
            }
        });

        // 方法2:监听 hashchange
        window.addEventListener('hashchange', () => {
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                log('路由变化(hashchange):', currentUrl);
                debouncedInject();
            }
        });

        // 方法3:监听 DOM 变化(SPA 路由)
        let observerTimer = null;
        const domObserver = new MutationObserver(() => {
            // 检查URL是否变化
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                log('路由变化(DOM):', currentUrl);

                // 清理旧的UID
                cleanupUidElements();

                // 延迟等待新页面渲染
                if (observerTimer) clearTimeout(observerTimer);
                observerTimer = setTimeout(() => {
                    injectUid();
                    observerTimer = null;
                }, 500);
            }
        });

        domObserver.observe(document, {
            subtree: true,
            childList: true,
            attributes: true,
            attributeFilter: ['href']
        });

        // 方法4:监听 history.pushState 和 replaceState
        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        history.pushState = function(...args) {
            const result = originalPushState.apply(this, args);
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                log('路由变化(pushState):', currentUrl);
                debouncedInject();
            }
            return result;
        };

        history.replaceState = function(...args) {
            const result = originalReplaceState.apply(this, args);
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                log('路由变化(replaceState):', currentUrl);
                debouncedInject();
            }
            return result;
        };
    }

    // ============ 初始化 ============

    function init() {
        // 添加样式
        const styleEl = document.createElement('style');
        styleEl.textContent = STYLE;
        document.head.appendChild(styleEl);

        // 监听路由变化
        watchRouteChange();

        // 初始注入
        setTimeout(() => {
            if (isUserPage()) {
                injectUid();
            }
        }, 500);

        // 页面完全加载后再尝试一次
        if (document.readyState === 'complete') {
            setTimeout(() => {
                if (isUserPage()) {
                    injectUid();
                }
            }, 1000);
        } else {
            window.addEventListener('load', () => {
                setTimeout(() => {
                    if (isUserPage()) {
                        injectUid();
                    }
                }, 1000);
            });
        }

        log('🚀 抖音UID显示脚本已启动');
        log('当前页面:', window.location.href);
        log('是否用户主页:', isUserPage());
    }

    // 执行
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();
发布时间: