TikTok Profile Inspector

Extract detailed TikTok profile info with draggable/resizable UI

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         TikTok Profile Inspector
// @author       anxiass (https://www.tiktok.com/@anxiass)
// @namespace    https://tiktok.com/
// @version      2.8.0
// @license      MIT License
// @icon         https://www.tiktok.com/favicon.ico
// @description  Extract detailed TikTok profile info with draggable/resizable UI
// @match        https://www.tiktok.com/*
// @run-at       document-end
// @grant        GM_setClipboard
// @grant        GM_notification
// ==/UserScript==

(function () {
  'use strict';

  let uiVisible = true;
  let altPressedOnce = false;
  let altTimeout = null;

  let isDragging = false;
  let dragOffsetX = 0;
  let dragOffsetY = 0;

  let isResizing = false;
  let resizeDirection = '';
  let resizeStartX = 0;
  let resizeStartY = 0;
  let resizeStartWidth = 0;
  let resizeStartHeight = 0;
  let resizeStartLeft = 0;
  let resizeStartTop = 0;

  let lastProfileData = null;
  let urlCheckInterval = null;
  let lastCheckedUsername = null;
  let highlightTimeout = null;

  const STORAGE_KEY = 'tiktok_profile_inspector_data';
  const HIGHLIGHT_KEY = 'tiktok_profile_inspector_highlight_dismissed';

  // Check if highlight should be shown
  function shouldShowHighlight() {
    try {
      const dismissed = localStorage.getItem(HIGHLIGHT_KEY);
      return !dismissed;
    } catch (e) {
      return true;
    }
  }

  // Dismiss highlight permanently
  function dismissHighlight() {
    try {
      localStorage.setItem(HIGHLIGHT_KEY, 'true');
      const tooltip = document.getElementById('tt-refresh-tooltip');
      if (tooltip) {
        tooltip.style.opacity = '0';
        setTimeout(() => tooltip.remove(), 300);
      }
    } catch (e) {
      console.error('Failed to dismiss highlight:', e);
    }
  }

  // Reset highlight (called from settings)
  function resetHighlight() {
    try {
      localStorage.removeItem(HIGHLIGHT_KEY);
      showNotification('Refresh button highlight reset');
      // Recreate tooltip if we're on the page
      const refreshBtn = document.getElementById('tt-refresh-btn');
      if (refreshBtn && !document.getElementById('tt-refresh-tooltip')) {
        showRefreshTooltip();
      }
    } catch (e) {
      console.error('Failed to reset highlight:', e);
    }
  }

  // Show refresh tooltip
  function showRefreshTooltip() {
    if (!shouldShowHighlight()) return;

    const refreshBtn = document.getElementById('tt-refresh-btn');
    if (!refreshBtn) return;

    const tooltip = document.createElement('div');
    tooltip.id = 'tt-refresh-tooltip';
    tooltip.className = 'tt-refresh-tooltip';
    tooltip.innerHTML = `
      <div class="tt-refresh-tooltip-content">
        <strong>Refresh Button</strong><br>
        Reloads page to fetch updated profile data
        <button class="tt-refresh-tooltip-close">×</button>
      </div>
    `;

    refreshBtn.parentElement.style.position = 'relative';
    refreshBtn.parentElement.appendChild(tooltip);

    // Pulse animation on button
    refreshBtn.style.animation = 'pulse 2s infinite';

    // Close button
    tooltip.querySelector('.tt-refresh-tooltip-close').onclick = () => {
      dismissHighlight();
      refreshBtn.style.animation = '';
    };

    // Auto-dismiss after 10 seconds
    highlightTimeout = setTimeout(() => {
      dismissHighlight();
      refreshBtn.style.animation = '';
    }, 10000);
  }

  // Monitor URL changes and auto-fill username
  function monitorURL() {
    const currentUsername = getCurrentUsername();

    if (currentUsername && currentUsername !== lastCheckedUsername) {
      lastCheckedUsername = currentUsername;

      // Auto-fill the username input
      const input = document.getElementById('tt-username-input');
      if (input) {
        input.value = currentUsername;
        showNotification(`Profile detected: @${currentUsername}`);
      }

      // Auto-load profile data
      setTimeout(() => {
        updateFromUserInfo();
      }, 1500);
    }
  }

  // LocalStorage helpers
  function saveToStorage(username, data) {
    try {
      const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
      stored[username] = {
        data: data,
        timestamp: Date.now()
      };
      localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
    } catch (e) {
      console.error('Failed to save to storage:', e);
    }
  }

  function loadFromStorage(username) {
    try {
      const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
      return stored[username]?.data || null;
    } catch (e) {
      console.error('Failed to load from storage:', e);
      return null;
    }
  }

  function clearStorage() {
    try {
      localStorage.removeItem(STORAGE_KEY);
      showNotification('Storage cleared');
    } catch (e) {
      console.error('Failed to clear storage:', e);
    }
  }

  function getAllStoredProfiles() {
    try {
      const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
      return Object.keys(stored).map(username => ({
        username,
        timestamp: stored[username].timestamp
      }));
    } catch (e) {
      return [];
    }
  }

  // Copy to clipboard
  function copyToClipboard(text, label = 'Text') {
    if (typeof GM_setClipboard !== 'undefined') {
      GM_setClipboard(text);
      showNotification(`${label} copied!`);
    } else {
      navigator.clipboard.writeText(text).then(() => {
        showNotification(`${label} copied!`);
      }).catch(() => {
        showNotification('Copy failed', true);
      });
    }
  }

  // Show notification
  function showNotification(message, isError = false) {
    const notification = document.createElement('div');
    notification.className = 'tt-notification' + (isError ? ' tt-notification-error' : '');
    notification.textContent = message;
    document.body.appendChild(notification);

    setTimeout(() => notification.classList.add('tt-notification-show'), 10);

    setTimeout(() => {
      notification.classList.remove('tt-notification-show');
      setTimeout(() => notification.remove(), 300);
    }, 2500);
  }

  function getUserInfoObject() {
    const s = [...document.querySelectorAll('script')]
      .find(s => s.textContent.includes('"userInfo"'));
    if (!s) return null;

    const m = s.textContent.match(/"userInfo":(\{[^]*?\})(?=,"[^"]+":)/);
    if (!m) return null;

    try {
      const jsonStr = m[1]
        .replace(/\\u002F/g, '/')
        .replace(/\\/g, '\\\\')
        .replace(/\\\\"/g, '\\"')
        .replace(/\\\\u/g, '\\u');
      return JSON.parse(jsonStr);
    } catch (e) {
      try {
        const text = s.textContent;
        const start = text.indexOf('"userInfo":') + 11;
        let braceCount = 0, inString = false, escape = false, end = start;

        for (let i = start; i < text.length; i++) {
          const char = text[i];
          if (escape) { escape = false; continue; }
          if (char === '\\') { escape = true; continue; }
          if (char === '"') { inString = !inString; continue; }
          if (!inString) {
            if (char === '{') braceCount++;
            if (char === '}') {
              braceCount--;
              if (braceCount === 0) { end = i + 1; break; }
            }
          }
        }
        return JSON.parse(text.substring(start, end).replace(/\\u002F/g, '/'));
      } catch (e2) {
        console.error('Fallback failed', e2);
        return null;
      }
    }
  }

  function getCurrentUsername() {
    const match = window.location.pathname.match(/\/@([^\/\?]+)/);
    return match ? match[1] : null;
  }

  function goToProfile(username) {
    if (!username) return;
    username = username.replace(/^@+/, '').trim();
    if (!username) return;
    location.href = `https://www.tiktok.com/@${encodeURIComponent(username)}`;
    setTimeout(() => updateFromUserInfo(), 3000);
  }

  function formatNumber(num) {
    if (num >= 1e9) return (num / 1e9).toFixed(1) + 'B';
    if (num >= 1e6) return (num / 1e6).toFixed(1) + 'M';
    if (num >= 1e3) return (num / 1e3).toFixed(1) + 'K';
    return num.toString();
  }

  function formatDate(ts) {
    if (!ts) return 'Unknown';
    return new Date(ts * 1000).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
  }

  // Detect changes between old and new data
  function detectChanges(oldData, newData) {
    if (!oldData || !newData) return [];
    const changes = [];

    const oldStats = oldData.stats || oldData.statsV2 || {};
    const newStats = newData.stats || newData.statsV2 || {};

    const compareField = (label, oldVal, newVal) => {
      if (oldVal !== newVal) {
        changes.push(`${label}: ${oldVal} → ${newVal}`);
      }
    };

    compareField('Followers', oldStats.followerCount, newStats.followerCount);
    compareField('Following', oldStats.followingCount, newStats.followingCount);
    compareField('Likes', oldStats.heartCount || oldStats.heart, newStats.heartCount || newStats.heart);
    compareField('Friends', oldStats.friendCount, newStats.friendCount);
    compareField('Videos', oldStats.videoCount, newStats.videoCount);
    compareField('Nickname', oldData.user?.nickname, newData.user?.nickname);
    compareField('Bio', oldData.user?.signature, newData.user?.signature);

    return changes;
  }

  // Show export menu
  function showExportMenu() {
    const info = getUserInfoObject();
    if (!info) {
      showNotification('No data to export', true);
      return;
    }

    const modal = document.createElement('div');
    modal.className = 'tt-modal';
    modal.innerHTML = `
      <div class="tt-modal-content">
        <div class="tt-modal-header">
          <h3>Export Profile Data</h3>
          <button class="tt-modal-close">×</button>
        </div>
        <div class="tt-modal-body">
          <p class="tt-export-desc">Select the data you want to export:</p>
          <div class="tt-export-options">
            <label class="tt-checkbox-label">
              <input type="checkbox" value="basic" checked> Basic Info (ID, Username, Nickname)
            </label>
            <label class="tt-checkbox-label">
              <input type="checkbox" value="stats" checked> Statistics (Followers, Likes, etc.)
            </label>
            <label class="tt-checkbox-label">
              <input type="checkbox" value="settings" checked> Privacy Settings
            </label>
            <label class="tt-checkbox-label">
              <input type="checkbox" value="advanced" checked> Advanced Info (SecUID, Room ID, etc.)
            </label>
            <label class="tt-checkbox-label">
              <input type="checkbox" value="raw"> Raw JSON (Complete data)
            </label>
          </div>
        </div>
        <div class="tt-modal-footer">
          <button class="tt-btn" id="tt-export-cancel">Cancel</button>
          <button class="tt-btn tt-btn-primary" id="tt-export-confirm">Export</button>
        </div>
      </div>
    `;

    document.body.appendChild(modal);
    setTimeout(() => modal.classList.add('tt-modal-show'), 10);

    modal.querySelector('.tt-modal-close').onclick = () => closeModal(modal);
    modal.querySelector('#tt-export-cancel').onclick = () => closeModal(modal);
    modal.onclick = (e) => { if (e.target === modal) closeModal(modal); };

    modal.querySelector('#tt-export-confirm').onclick = () => {
      const checkboxes = modal.querySelectorAll('input[type="checkbox"]:checked');
      const selected = Array.from(checkboxes).map(cb => cb.value);

      if (selected.length === 0) {
        showNotification('Please select at least one option', true);
        return;
      }

      exportSelectedData(info, selected);
      closeModal(modal);
    };
  }

  function closeModal(modal) {
    modal.classList.remove('tt-modal-show');
    setTimeout(() => modal.remove(), 300);
  }

  function exportSelectedData(info, selected) {
    const u = info.user;
    const s = info.stats || info.statsV2 || {};
    let exportData = {};

    if (selected.includes('raw')) {
      exportData = info;
    } else {
      if (selected.includes('basic')) {
        exportData.basic = {
          id: u.id,
          shortId: u.shortId,
          uniqueId: u.uniqueId,
          nickname: u.nickname,
          signature: u.signature,
          verified: u.verified,
          privateAccount: u.privateAccount,
          createTime: u.createTime,
          language: u.language,
          region: u.region
        };
      }
      if (selected.includes('stats')) {
        exportData.stats = {
          followerCount: s.followerCount,
          followingCount: s.followingCount,
          heartCount: s.heartCount || s.heart,
          videoCount: s.videoCount,
          diggCount: s.diggCount,
          friendCount: s.friendCount
        };
      }
      if (selected.includes('settings')) {
        exportData.settings = {
          commentSetting: u.commentSetting,
          duetSetting: u.duetSetting,
          stitchSetting: u.stitchSetting,
          downloadSetting: u.downloadSetting,
          followingVisibility: u.followingVisibility,
          openFavorite: u.openFavorite
        };
      }
      if (selected.includes('advanced')) {
        exportData.advanced = {
          secUid: u.secUid,
          roomId: u.roomId,
          relation: u.relation,
          ttSeller: u.ttSeller,
          ftc: u.ftc,
          secret: u.secret,
          isADVirtual: u.isADVirtual,
          profileEmbedPermission: u.profileEmbedPermission
        };
      }
    }

    const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `tiktok-${u.uniqueId || 'profile'}-${Date.now()}.json`;
    a.click();
    URL.revokeObjectURL(a.href);
    showNotification('Data exported!');
  }

  // Show settings menu
  function showSettingsMenu() {
    const profiles = getAllStoredProfiles();

    const modal = document.createElement('div');
    modal.className = 'tt-modal';
    modal.innerHTML = `
      <div class="tt-modal-content">
        <div class="tt-modal-header">
          <h3>Settings</h3>
          <button class="tt-modal-close">×</button>
        </div>
        <div class="tt-modal-body">
          <div class="tt-settings-section">
            <h4>Stored Profiles</h4>
            <p class="tt-settings-desc">${profiles.length} profile(s) cached</p>
            ${profiles.length > 0 ? `
              <div class="tt-stored-profiles">
                ${profiles.map(p => `
                  <div class="tt-stored-profile">
                    <span>@${p.username}</span>
                    <span class="tt-stored-date">${new Date(p.timestamp).toLocaleDateString()}</span>
                  </div>
                `).join('')}
              </div>
            ` : '<p class="tt-empty-text">No profiles cached yet</p>'}
          </div>
          <div class="tt-settings-section">
            <h4>Data Management</h4>
            <button class="tt-btn" id="tt-clear-storage">Clear All Cached Data</button>
          </div>
          <div class="tt-settings-section">
            <h4>Interface</h4>
            <button class="tt-btn" id="tt-reset-highlight">Reset Refresh Button Highlight</button>
          </div>
        </div>
        <div class="tt-modal-footer">
          <button class="tt-btn tt-btn-primary" id="tt-settings-close">Close</button>
        </div>
      </div>
    `;

    document.body.appendChild(modal);
    setTimeout(() => modal.classList.add('tt-modal-show'), 10);

    modal.querySelector('.tt-modal-close').onclick = () => closeModal(modal);
    modal.querySelector('#tt-settings-close').onclick = () => closeModal(modal);
    modal.onclick = (e) => { if (e.target === modal) closeModal(modal); };

    modal.querySelector('#tt-clear-storage').onclick = () => {
      if (confirm('Are you sure you want to clear all cached profile data?')) {
        clearStorage();
        closeModal(modal);
      }
    };

    modal.querySelector('#tt-reset-highlight').onclick = () => {
      resetHighlight();
      closeModal(modal);
    };
  }

  function startDrag(e) {
    if (e.target.closest('.tt-resize-handle') || !e.target.closest('.tt-header')) return;
    isDragging = true;
    const panel = document.getElementById('tt-info-panel-inner');
    const rect = panel.getBoundingClientRect();
    dragOffsetX = e.clientX - rect.left;
    dragOffsetY = e.clientY - rect.top;
    panel.style.transition = 'none';
    document.body.style.userSelect = 'none';
    document.body.style.cursor = 'grabbing';
  }

  function drag(e) {
    if (!isDragging) return;
    const panel = document.getElementById('tt-info-panel-inner');
    const container = document.getElementById('tt-info-panel');
    let newLeft = Math.max(0, Math.min(e.clientX - dragOffsetX, window.innerWidth - panel.offsetWidth));
    let newTop = Math.max(0, Math.min(e.clientY - dragOffsetY, window.innerHeight - panel.offsetHeight));
    container.style.left = newLeft + 'px';
    container.style.top = newTop + 'px';
    container.style.right = 'auto';
  }

  function stopDrag() {
    if (!isDragging) return;
    isDragging = false;
    const panel = document.getElementById('tt-info-panel-inner');
    panel.style.transition = '';
    document.body.style.userSelect = '';
    document.body.style.cursor = '';
  }

  function startResize(e, direction) {
    e.stopPropagation();
    isResizing = true;
    resizeDirection = direction;

    const panel = document.getElementById('tt-info-panel-inner');
    const container = document.getElementById('tt-info-panel');
    const rect = panel.getBoundingClientRect();
    const containerRect = container.getBoundingClientRect();

    resizeStartX = e.clientX;
    resizeStartY = e.clientY;
    resizeStartWidth = rect.width;
    resizeStartHeight = rect.height;
    resizeStartLeft = containerRect.left;
    resizeStartTop = containerRect.top;

    panel.style.transition = 'none';
    document.body.style.userSelect = 'none';
    document.body.style.cursor = direction.includes('n') || direction.includes('s') ?
      (direction.includes('e') || direction.includes('w') ? 'nwse-resize' : 'ns-resize') :
      'ew-resize';
  }

  function resize(e) {
    if (!isResizing) return;

    const panel = document.getElementById('tt-info-panel-inner');
    const container = document.getElementById('tt-info-panel');

    const deltaX = e.clientX - resizeStartX;
    const deltaY = e.clientY - resizeStartY;

    let newWidth = resizeStartWidth;
    let newHeight = resizeStartHeight;
    let newLeft = resizeStartLeft;
    let newTop = resizeStartTop;

    if (resizeDirection.includes('e')) {
      newWidth = Math.max(320, Math.min(900, resizeStartWidth + deltaX));
    }
    if (resizeDirection.includes('w')) {
      newWidth = Math.max(320, Math.min(900, resizeStartWidth - deltaX));
      newLeft = resizeStartLeft + (resizeStartWidth - newWidth);
    }
    if (resizeDirection.includes('s')) {
      newHeight = Math.max(400, Math.min(window.innerHeight - 100, resizeStartHeight + deltaY));
    }
    if (resizeDirection.includes('n')) {
      newHeight = Math.max(400, Math.min(window.innerHeight - 100, resizeStartHeight - deltaY));
      newTop = resizeStartTop + (resizeStartHeight - newHeight);
    }

    panel.style.width = newWidth + 'px';
    panel.style.maxHeight = newHeight + 'px';
    container.style.left = newLeft + 'px';
    container.style.top = newTop + 'px';
    container.style.right = 'auto';
  }

  function stopResize() {
    if (!isResizing) return;
    isResizing = false;
    const panel = document.getElementById('tt-info-panel-inner');
    panel.style.transition = '';
    document.body.style.userSelect = '';
    document.body.style.cursor = '';
  }

  function createUI() {
    if (document.getElementById('tt-info-panel')) return;

    const style = `
      @keyframes pulse {
        0%, 100% { box-shadow: 0 0 0 0 rgba(32, 213, 236, 0.4); }
        50% { box-shadow: 0 0 0 6px rgba(32, 213, 236, 0); }
      }
      .tt-refresh-tooltip{position:absolute;top:calc(100% + 8px);right:0;background:linear-gradient(135deg,rgba(32,213,236,0.95),rgba(10,189,227,0.95));color:#fff;padding:12px 16px;border-radius:8px;font-size:12px;z-index:10;min-width:220px;box-shadow:0 4px 12px rgba(0,0,0,.3);opacity:0;animation:fadeIn .3s forwards}
      @keyframes fadeIn{to{opacity:1}}
      .tt-refresh-tooltip-content{position:relative;line-height:1.5}
      .tt-refresh-tooltip-content strong{display:block;margin-bottom:4px;font-size:13px}
      .tt-refresh-tooltip-close{position:absolute;top:-8px;right:-12px;background:rgba(0,0,0,.2);border:none;color:#fff;width:24px;height:24px;border-radius:50%;cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;transition:background .2s}
      .tt-refresh-tooltip-close:hover{background:rgba(0,0,0,.4)}
      .tt-notification{position:fixed;top:20px;right:20px;padding:12px 20px;background:rgba(32,213,236,0.95);color:#fff;border-radius:8px;font-size:13px;font-weight:600;z-index:99999999;opacity:0;transform:translateX(400px);transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 12px rgba(0,0,0,.3);max-width:450px;line-height:1.4}
      .tt-notification-show{opacity:1;transform:translateX(0)}
      .tt-notification-error{background:rgba(254,44,85,0.95)}
      #tt-info-panel{position:fixed;top:70px;right:20px;z-index:9999999;font-family:TikTokFont,Arial,Tahoma,PingFangSC,sans-serif}
      #tt-info-panel-inner{width:400px;max-height:90vh;overflow:hidden;background:linear-gradient(135deg,rgba(18,18,18,0.98) 0%,rgba(25,25,35,0.98) 100%);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.5),0 0 0 1px rgba(255,255,255,.08),inset 0 1px 0 rgba(255,255,255,.05);display:flex;flex-direction:column;position:relative;backdrop-filter:blur(20px)}
      #tt-info-panel-content{overflow-y:auto;flex:1}
      #tt-info-panel-content::-webkit-scrollbar{width:0;height:0}
      .tt-header{padding:18px 24px 14px;border-bottom:1px solid rgba(255,255,255,.08);display:flex;justify-content:space-between;align-items:center;cursor:grab;user-select:none;background:linear-gradient(180deg,rgba(255,255,255,.03) 0%,transparent 100%);flex-shrink:0}
      .tt-header:active{cursor:grabbing}
      .tt-header-left{display:flex;flex-direction:column;gap:4px}
      .tt-header-title{font-size:16px;font-weight:700;color:rgba(255,255,255,.95);letter-spacing:-.02em}
      .tt-header-subtitle{font-size:11px;color:rgba(255,255,255,.4);font-weight:500}
      .tt-header-actions{display:flex;gap:6px}
      .tt-resize-handle{position:absolute;width:12px;height:12px;z-index:10}
      .tt-resize-handle-nw{top:0;left:0;cursor:nw-resize}
      .tt-resize-handle-ne{top:0;right:0;cursor:ne-resize}
      .tt-resize-handle-sw{bottom:0;left:0;cursor:sw-resize}
      .tt-resize-handle-se{bottom:0;right:0;cursor:se-resize}
      .tt-resize-handle-n{top:0;left:50%;transform:translateX(-50%);width:50px;height:4px;cursor:n-resize}
      .tt-resize-handle-s{bottom:0;left:50%;transform:translateX(-50%);width:50px;height:4px;cursor:s-resize}
      .tt-resize-handle-w{left:0;top:50%;transform:translateY(-50%);width:4px;height:50px;cursor:w-resize}
      .tt-resize-handle-e{right:0;top:50%;transform:translateY(-50%);width:4px;height:50px;cursor:e-resize}
      .tt-btn{height:32px;padding:0 14px;border:none;border-radius:6px;background:rgba(255,255,255,.08);color:rgba(255,255,255,.9);font-size:12px;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:all .2s cubic-bezier(.4,0,.2,1);border:1px solid rgba(255,255,255,.05)}
      .tt-btn:hover{background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.1);transform:translateY(-1px)}
      .tt-btn:active{transform:translateY(0)}
      .tt-btn-primary{background:linear-gradient(135deg,#fe2c55 0%,#ff4d6d 100%);border-color:transparent;box-shadow:0 2px 8px rgba(254,44,85,.3)}
      .tt-btn-primary:hover{background:linear-gradient(135deg,#e62649 0%,#ff3d5d 100%);box-shadow:0 4px 12px rgba(254,44,85,.4)}
      .tt-btn-small{height:26px;padding:0 12px;font-size:11px;border-radius:5px}
      .tt-btn-icon{padding:0;width:32px;height:32px;border-radius:6px;font-size:16px}
      .tt-search-section{padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.08);background:rgba(0,0,0,.1);flex-shrink:0}
      .tt-search-label{font-size:12px;font-weight:600;color:rgba(255,255,255,.6);margin-bottom:10px;display:block}
      .tt-search-input{width:100%;height:40px;padding:0 14px;border:1px solid rgba(255,255,255,.12);border-radius:8px;font-size:13px;color:rgba(255,255,255,.95);background:rgba(255,255,255,.04);box-sizing:border-box;margin-bottom:12px;transition:all .2s}
      .tt-search-input:focus{outline:none;border-color:rgba(254,44,85,.5);background:rgba(255,255,255,.06);box-shadow:0 0 0 3px rgba(254,44,85,.1)}
      .tt-search-input::placeholder{color:rgba(255,255,255,.3)}
      .tt-button-group{display:grid;grid-template-columns:1fr 1fr;gap:8px}
      .tt-profile-section{padding:24px}
      .tt-profile-header{display:flex;gap:16px;margin-bottom:20px}
      .tt-avatar-wrapper{position:relative;width:72px;height:72px;flex-shrink:0}
      .tt-avatar{width:72px;height:72px;border-radius:50%;overflow:hidden;background:rgba(255,255,255,.05);border:2px solid rgba(255,255,255,.1);box-shadow:0 4px 12px rgba(0,0,0,.3)}
      .tt-avatar img{width:100%;height:100%;object-fit:cover}
      .tt-verified-badge{position:absolute;bottom:-2px;right:-2px;width:22px;height:22px;background:linear-gradient(135deg,#20d5ec 0%,#0abde3 100%);border-radius:50%;display:flex;align-items:center;justify-content:center;border:2px solid rgba(18,18,18,.98);box-shadow:0 2px 8px rgba(32,213,236,.4)}
      .tt-verified-badge svg{width:12px;height:12px;fill:#fff}
      .tt-profile-info{flex:1;min-width:0}
      .tt-nickname{font-size:18px;font-weight:700;color:rgba(255,255,255,.95);margin-bottom:4px;line-height:1.3}
      .tt-username{font-size:14px;color:rgba(255,255,255,.45);margin-bottom:10px}
      .tt-badges{display:flex;flex-wrap:wrap;gap:6px}
      .tt-badge{padding:4px 10px;border-radius:4px;font-size:11px;font-weight:600;background:rgba(255,255,255,.08);color:rgba(255,255,255,.7);border:1px solid rgba(255,255,255,.05)}
      .tt-bio{margin:12px 0 20px;font-size:13px;color:rgba(255,255,255,.75);line-height:1.6;word-break:break-word;padding:14px;background:rgba(255,255,255,.03);border-radius:8px;border:1px solid rgba(255,255,255,.05)}
      .tt-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px}
      .tt-stat-item{padding:14px;display:flex;flex-direction:column;align-items:center;gap:4px;background:rgba(255,255,255,.04);border-radius:10px;border:1px solid rgba(255,255,255,.06);transition:all .2s}
      .tt-stat-item:hover{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.1);transform:translateY(-2px)}
      .tt-stat-value{font-size:16px;font-weight:700;color:rgba(255,255,255,.95)}
      .tt-stat-label{font-size:11px;color:rgba(255,255,255,.5);text-transform:uppercase;letter-spacing:.05em}
      .tt-info-section{padding:0 24px 24px}
      .tt-info-title{font-size:13px;font-weight:700;color:rgba(255,255,255,.6);margin-bottom:12px;text-transform:uppercase;letter-spacing:.05em}
      .tt-info-row{display:flex;justify-content:space-between;align-items:center;padding:12px 14px;border-radius:6px;margin-bottom:4px;background:rgba(255,255,255,.02);transition:all .2s;gap:12px}
      .tt-info-row:hover{background:rgba(255,255,255,.04)}
      .tt-info-row-content{display:flex;justify-content:space-between;align-items:center;flex:1;min-width:0}
      .tt-info-label{font-size:13px;color:rgba(255,255,255,.5);font-weight:500;flex-shrink:0}
      .tt-info-value{font-size:13px;color:rgba(255,255,255,.95);text-align:right;word-break:break-word;font-weight:600;flex:1;min-width:0;padding-left:12px}
      .tt-info-value-mono{font-family:'SF Mono',Monaco,monospace;font-size:11px}
      .tt-info-value-link{color:#20d5ec;text-decoration:none;transition:color .2s}
      .tt-info-value-link:hover{color:#0abde3;text-decoration:underline}
      .tt-info-copy{flex-shrink:0}
      .tt-footer{padding:16px 24px;background:rgba(0,0,0,.2);border-top:1px solid rgba(255,255,255,.08);font-size:11px;color:rgba(255,255,255,.4);display:flex;justify-content:space-between;align-items:center;flex-shrink:0}
      .tt-kbd{padding:3px 8px;border-radius:4px;background:rgba(255,255,255,.06);font-family:monospace;font-size:10px;margin:0 3px;border:1px solid rgba(255,255,255,.08)}
      .tt-empty-state{padding:60px 24px;text-align:center}
      .tt-empty-title{font-size:15px;font-weight:700;color:rgba(255,255,255,.6);margin-bottom:8px}
      .tt-empty-text{font-size:13px;color:rgba(255,255,255,.4);line-height:1.6}
      .tt-modal{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.7);z-index:99999999;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .3s;backdrop-filter:blur(4px)}
      .tt-modal-show{opacity:1}
      .tt-modal-content{background:linear-gradient(135deg,rgba(25,25,35,0.98) 0%,rgba(18,18,18,0.98) 100%);border-radius:12px;max-width:500px;width:90%;max-height:80vh;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.5),0 0 0 1px rgba(255,255,255,.08);transform:scale(0.9);transition:transform .3s}
      .tt-modal-show .tt-modal-content{transform:scale(1)}
      .tt-modal-header{padding:20px 24px;border-bottom:1px solid rgba(255,255,255,.08);display:flex;justify-content:space-between;align-items:center}
      .tt-modal-header h3{margin:0;font-size:18px;font-weight:700;color:rgba(255,255,255,.95)}
      .tt-modal-close{background:none;border:none;color:rgba(255,255,255,.6);font-size:28px;cursor:pointer;padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:6px;transition:all .2s}
      .tt-modal-close:hover{background:rgba(255,255,255,.1);color:rgba(255,255,255,.9)}
      .tt-modal-body{padding:24px;max-height:60vh;overflow-y:auto}
      .tt-modal-body::-webkit-scrollbar{width:8px}
      .tt-modal-body::-webkit-scrollbar-track{background:transparent}
      .tt-modal-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,.15);border-radius:4px}
      .tt-modal-footer{padding:16px 24px;border-top:1px solid rgba(255,255,255,.08);display:flex;justify-content:flex-end;gap:12px}
      .tt-export-desc,.tt-settings-desc{font-size:13px;color:rgba(255,255,255,.6);margin-bottom:16px}
      .tt-export-options{display:flex;flex-direction:column;gap:12px}
      .tt-checkbox-label{display:flex;align-items:center;gap:12px;padding:12px;background:rgba(255,255,255,.03);border-radius:8px;border:1px solid rgba(255,255,255,.05);cursor:pointer;transition:all .2s;font-size:14px;color:rgba(255,255,255,.9)}
      .tt-checkbox-label:hover{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.1)}
      .tt-checkbox-label input{cursor:pointer;width:18px;height:18px}
      .tt-settings-section{margin-bottom:24px}
      .tt-settings-section:last-child{margin-bottom:0}
      .tt-settings-section h4{margin:0 0 12px 0;font-size:15px;font-weight:600;color:rgba(255,255,255,.8)}
      .tt-stored-profiles{display:flex;flex-direction:column;gap:8px;max-height:200px;overflow-y:auto;padding:12px;background:rgba(255,255,255,.02);border-radius:8px;border:1px solid rgba(255,255,255,.05)}
      .tt-stored-profile{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;background:rgba(255,255,255,.03);border-radius:6px;font-size:13px;color:rgba(255,255,255,.9)}
      .tt-stored-date{font-size:11px;color:rgba(255,255,255,.5)}
    `;

    const container = document.createElement('div');
    container.id = 'tt-info-panel';
    container.innerHTML = `
      <style>${style}</style>
      <div id="tt-info-panel-inner">
        <div class="tt-header">
          <div class="tt-header-left">
            <div class="tt-header-title">Profile Inspector</div>
            <div class="tt-header-subtitle" id="tt-current-url">No profile loaded</div>
          </div>
          <div class="tt-header-actions">
            <button id="tt-refresh-btn" class="tt-btn tt-btn-small">Refresh</button>
            <button id="tt-export-btn" class="tt-btn tt-btn-small">Export</button>
            <button id="tt-settings-btn" class="tt-btn tt-btn-icon">⚙️</button>
          </div>
        </div>
        <div id="tt-info-panel-content">
          <div class="tt-search-section">
            <label class="tt-search-label">Navigate to Profile</label>
            <input id="tt-username-input" class="tt-search-input" type="text" placeholder="Enter username..."/>
            <button id="tt-go-btn" class="tt-btn tt-btn-primary" style="width: 100%;">Go to Profile</button>
          </div>
          <div id="tt-content-area">
            <div class="tt-empty-state">
              <div class="tt-empty-title">No profile loaded</div>
              <div class="tt-empty-text">Enter a username or navigate to a profile<br>then click Refresh to load data</div>
            </div>
          </div>
        </div>
        <div class="tt-footer">
          <div>Toggle <span class="tt-kbd">Alt</span></div>
          <div>Destroy <span class="tt-kbd">Alt</span> × 2</div>
        </div>
        <div class="tt-resize-handle tt-resize-handle-nw"></div>
        <div class="tt-resize-handle tt-resize-handle-ne"></div>
        <div class="tt-resize-handle tt-resize-handle-sw"></div>
        <div class="tt-resize-handle tt-resize-handle-se"></div>
        <div class="tt-resize-handle tt-resize-handle-n"></div>
        <div class="tt-resize-handle tt-resize-handle-s"></div>
        <div class="tt-resize-handle tt-resize-handle-w"></div>
        <div class="tt-resize-handle tt-resize-handle-e"></div>
      </div>
    `;

    document.body.appendChild(container);

    const input = container.querySelector('#tt-username-input');
    const goBtn = container.querySelector('#tt-go-btn');
    const refreshBtn = container.querySelector('#tt-refresh-btn');
    const exportBtn = container.querySelector('#tt-export-btn');
    const settingsBtn = container.querySelector('#tt-settings-btn');
    const header = container.querySelector('.tt-header');

    // Show refresh tooltip if not dismissed
    setTimeout(() => showRefreshTooltip(), 1000);

    goBtn.addEventListener('click', () => goToProfile(input.value));
    input.addEventListener('keydown', e => { if (e.key === 'Enter') goToProfile(input.value); });
    refreshBtn.addEventListener('click', () => {
      showNotification('Refreshing profile...');
      // Always reload the page to get fresh data
      // This works on all pages including video pages with comments
      setTimeout(() => {
        location.reload();
      }, 500);
    });
    exportBtn.addEventListener('click', showExportMenu);
    settingsBtn.addEventListener('click', showSettingsMenu);
    header.addEventListener('mousedown', startDrag);

    // Add resize handle listeners
    container.querySelectorAll('.tt-resize-handle').forEach(handle => {
      const direction = handle.className.split('-').pop();
      handle.addEventListener('mousedown', (e) => startResize(e, direction));
    });

    document.addEventListener('mousemove', drag);
    document.addEventListener('mouseup', stopDrag);
    document.addEventListener('mousemove', resize);
    document.addEventListener('mouseup', stopResize);

    updateFromUserInfo();
    updateCurrentURL();
  }

  function updateCurrentURL() {
    const urlDisplay = document.getElementById('tt-current-url');
    if (!urlDisplay) return;
    const username = getCurrentUsername();
    urlDisplay.textContent = username ? `@${username}` : 'Not on a profile page';
  }

  function createCopyButton(text, label) {
    const btn = document.createElement('button');
    btn.className = 'tt-btn tt-btn-small';
    btn.textContent = 'Copy';
    btn.title = `Copy ${label}`;
    btn.addEventListener('click', e => { e.stopPropagation(); copyToClipboard(text, label); });
    return btn;
  }

  function updateFromUserInfo() {
    const info = getUserInfoObject();
    const container = document.getElementById('tt-info-panel');
    if (!container) return;

    const contentArea = container.querySelector('#tt-content-area');
    updateCurrentURL();

    if (!info || !info.user) {
      contentArea.innerHTML = '<div class="tt-empty-state"><div class="tt-empty-title">No data found</div><div class="tt-empty-text">Make sure you\'re on a profile page<br>Wait a moment, then click Refresh</div></div>';
      return;
    }

    const u = info.user;
    const s = info.stats || info.statsV2 || {};

    // Save to storage
    if (u.uniqueId) {
      saveToStorage(u.uniqueId, info);
    }

    // Store current data for change detection
    lastProfileData = info;

    let badges = '';
    if (u.verified) badges += '<span class="tt-badge">Verified</span>';
    if (u.privateAccount) badges += '<span class="tt-badge">Private</span>';
    if (u.commerceUserInfo?.commerceUser) badges += '<span class="tt-badge">Shop</span>';

    const avatarUrl = (u.avatarLarger || u.avatarMedium || u.avatarThumb || '').replace(/\\u002F/g, '/');
    const verifiedBadge = u.verified ? '<div class="tt-verified-badge"><svg viewBox="0 0 48 48"><path d="M0 24C0 10.7 10.7 0 24 0s24 10.7 24 24-10.7 24-24 24S0 37.3 0 24z"/><path d="M19.5 33.5l-9-9 2.83-2.83 6.17 6.17 14.67-14.67L37 16z" fill="#fff"/></svg></div>' : '';

    const stats = [
      { label: 'Following', value: formatNumber(parseInt(s.followingCount) || 0) },
      { label: 'Followers', value: formatNumber(parseInt(s.followerCount) || 0) },
      { label: 'Likes', value: formatNumber(parseInt(s.heartCount || s.heart) || 0) },
      { label: 'Friends', value: formatNumber(parseInt(s.friendCount) || 0) }
    ];

    const detailRows = [
      { label: 'User ID', value: u.id || 'N/A', copyable: true },
      { label: 'Short ID', value: u.shortId || 'N/A', copyable: true },
      { label: 'Unique ID', value: u.uniqueId || 'N/A', copyable: true },
      { label: 'Videos', value: s.videoCount || '0' },
      { label: 'Liked Videos (if count is 0, liked is set to private)', value: formatNumber(parseInt(s.diggCount) || 0) },
      { label: 'Created', value: formatDate(u.createTime) },
      { label: 'Language', value: u.language ? u.language.toUpperCase() : 'N/A' },
      { label: 'Region', value: u.region || 'Unknown', isLink: true, linkUrl: 'https://omar-thing.site/' },
      { label: 'Nickname Modified', value: u.nickNameModifyTime ? formatDate(u.nickNameModifyTime) : 'N/A' },
      { label: 'UniqueID Modified', value: u.uniqueIdModifyTime ? formatDate(u.uniqueIdModifyTime) : 'N/A' }
    ];

    const settingsRows = [
      { label: 'Comments', value: u.commentSetting === 0 ? 'Everyone' : 'Friends' },
      { label: 'Duet', value: u.duetSetting === 0 ? 'Everyone' : (u.duetSetting === 1 ? 'Friends' : 'Off') },
      { label: 'Stitch', value: u.stitchSetting === 0 ? 'Everyone' : (u.stitchSetting === 1 ? 'Friends' : 'Off') },
      { label: 'Download', value: u.downloadSetting === 0 ? 'On' : 'Off' },
      { label: 'Following List', value: u.followingVisibility === 0 ? 'Public' : (u.followingVisibility === 1 ? 'Friends' : 'Private') },
      { label: 'Open Favorite', value: u.openFavorite ? 'Yes' : 'No' }
    ];

    const advancedRows = [
      { label: 'Relation', value: u.relation === 0 ? 'Not following' : (u.relation === 1 ? 'Following' : (u.relation === 2 ? 'Friends' : 'Unknown')) },
      { label: 'TT Seller', value: u.ttSeller ? 'Yes' : 'No' },
      { label: 'FTC', value: u.ftc ? 'Yes' : 'No' },
      { label: 'Secret Account', value: u.secret ? 'Yes' : 'No' },
      { label: 'AD Virtual', value: u.isADVirtual ? 'Yes' : 'No' },
      { label: 'Room ID', value: u.roomId || 'None', copyable: u.roomId },
      { label: 'Profile Embed', value: u.profileEmbedPermission === 1 ? 'Allowed' : 'Disabled' },
      { label: 'SecUID', value: u.secUid || 'N/A', copyable: true, mono: true }
    ];

    const renderRow = (row) => {
      const valueContent = row.isLink
        ? `<a href="${row.linkUrl}" target="_blank" class="tt-info-value-link">${row.value} →</a>`
        : row.value;

      return `
        <div class="tt-info-row">
          <div class="tt-info-row-content">
            <div class="tt-info-label">${row.label}</div>
            <div class="tt-info-value ${row.mono ? 'tt-info-value-mono' : ''}">${valueContent}</div>
          </div>
          ${row.copyable && row.value !== 'N/A' && row.value !== 'None' ? `<div class="tt-info-copy" data-copy="${row.value}" data-label="${row.label}"></div>` : ''}
        </div>
      `;
    };

    contentArea.innerHTML = `
      <div class="tt-profile-section">
        <div class="tt-profile-header">
          <div class="tt-avatar-wrapper">
            <div class="tt-avatar">${avatarUrl ? `<img src="${avatarUrl}" alt="Avatar">` : ''}</div>
            ${verifiedBadge}
          </div>
          <div class="tt-profile-info">
            <div class="tt-nickname">${u.nickname || 'No nickname'}</div>
            <div class="tt-username">@${u.uniqueId || 'unknown'}</div>
            ${badges ? `<div class="tt-badges">${badges}</div>` : ''}
          </div>
        </div>
        ${u.signature ? `<div class="tt-bio">${u.signature}</div>` : ''}
        <div class="tt-stats">${stats.map(s => `<div class="tt-stat-item"><div class="tt-stat-value">${s.value}</div><div class="tt-stat-label">${s.label}</div></div>`).join('')}</div>
      </div>
      <div class="tt-info-section">
        <div class="tt-info-title">Account Details</div>
        ${detailRows.map(renderRow).join('')}
      </div>
      <div class="tt-info-section">
        <div class="tt-info-title">Privacy Settings</div>
        ${settingsRows.map(renderRow).join('')}
      </div>
      <div class="tt-info-section">
        <div class="tt-info-title">Additional Info</div>
        ${advancedRows.map(renderRow).join('')}
      </div>
    `;

    contentArea.querySelectorAll('.tt-info-copy').forEach(el => {
      el.appendChild(createCopyButton(el.getAttribute('data-copy'), el.getAttribute('data-label')));
    });
  }

  function toggleVisibility() {
    const container = document.getElementById('tt-info-panel');
    if (!container) return;
    uiVisible = !uiVisible;
    container.style.display = uiVisible ? 'block' : 'none';
  }

  function destroyUI() {
    if (urlCheckInterval) clearInterval(urlCheckInterval);
    if (highlightTimeout) clearTimeout(highlightTimeout);
    const container = document.getElementById('tt-info-panel');
    if (container?.parentNode) container.parentNode.removeChild(container);
    document.removeEventListener('mousemove', drag);
    document.removeEventListener('mouseup', stopDrag);
    document.removeEventListener('mousemove', resize);
    document.removeEventListener('mouseup', stopResize);
    window.removeEventListener('keydown', keyHandler, true);
  }

  function keyHandler(e) {
    if (e.key !== 'Alt') return;
    e.preventDefault();
    if (!altPressedOnce) {
      altPressedOnce = true;
      toggleVisibility();
      altTimeout = setTimeout(() => { altPressedOnce = false; }, 400);
    } else {
      clearTimeout(altTimeout);
      altPressedOnce = false;
      destroyUI();
    }
  }

  function init() {
    createUI();
    window.addEventListener('keydown', keyHandler, true);

    // Start URL monitoring (checks every 1.5 seconds)
    urlCheckInterval = setInterval(() => {
      monitorURL();
    }, 1500);

    // Initial check
    monitorURL();

    // Also monitor for URL changes via mutation observer
    let lastUrl = location.href;
    new MutationObserver(() => {
      const currentUrl = location.href;
      if (currentUrl !== lastUrl) {
        lastUrl = currentUrl;
        lastCheckedUsername = null; // Reset to trigger new check
        updateCurrentURL();

        // Small delay then check for username
        setTimeout(() => {
          monitorURL();
        }, 1000);
      }
    }).observe(document, { subtree: true, childList: true });
  }

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