Greasy Fork

Greasy Fork is available in English.

MP自定义站点索引配置助手

自动获取 RSS订阅 的分类并生成 NexusPHP JSON和Base64配置,搭配MoviePilot的 自定义索引站点 插件使用。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MP自定义站点索引配置助手
// @author       [email protected]
// @description  自动获取 RSS订阅 的分类并生成 NexusPHP JSON和Base64配置,搭配MoviePilot的 自定义索引站点 插件使用。
// @version      1.0.2
// @icon         data:image/gif;base64,R0lGODlhgAGAAZECAPr7/ERNVv///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo5OTk5ODkxZS0wMWE2LTRlMTItYWM1Mi00YTIzMzI1MTViYjQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkU2QTk5NkYxM0UzMTFFQzg5RkRCRTMwRTcyQUZCRjIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkU2QTk5NkUxM0UzMTFFQzg5RkRCRTMwRTcyQUZCRjIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxOTQyMDFlMi00OGRjLTQ3OTgtOTFlNi05ZjkyNzhjNTJlZTUiIHN0UmVmOmRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDplMjk3YTUwZC00MTM4LWVmNDYtYTY3Ni1kZTkzNDU1ZjNmOTkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRm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
// @match        https://*/*
// @match        http://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-idle
// @license      MIT
// @namespace    http://greasyfork.icu/users/1453515
// ==/UserScript==

(function () {
  'use strict';

  const SECTION_NAME_MAP = new Map([
    ['分类', 'category'],
    ['类别', 'category'],
    ['類別', 'category'],
    ['分類', 'category'],
    ['类型', 'category'],
    ['類型', 'category'],
    ['檢索分類', 'category'],
    ['检索分类', 'category'],
    ['來源', 'source'],
    ['来源', 'source']
  ]);

  const STORAGE_KEYS = {
    togglerPosition: 'mp_custom_toggler_position',
    panelPosition: 'mp_custom_panel_position',
    customDesc: 'mp_custom_category_desc',
    togglerVisible: 'mp_custom_toggler_visible'
  };

  let customDescRaw = '';
  let customDescMap = new Map();
  let togglerPosition = null;
  let togglerVisible = true;
  let panelPosition = null;
  let panelDragging = false;
  const ALLOWED_PREFIX_RE = /^cat\d*$/i;
  const DEFAULT_SCHEMA = 'NexusPhp';
  const DEFAULT_ENCODING = 'UTF-8';
  const SECOND_LEVEL_TLDS = new Set(['co', 'com', 'net', 'org', 'gov', 'edu', 'ac']);
  const TOGGLER_DRAG_THRESHOLD_PX = 4;

  const CATEGORY_LOCALIZATION = new Map([
    ['movies', '电影'],
    ['movie', '电影'],
    ['tv series', '电视剧'],
    ['tv shows', '综艺'],
    ['tv show', '综艺'],
    ['animations', '动漫'],
    ['animation', '动漫'],
    ['documentaries', '纪录片'],
    ['documentary', '纪录片'],
    ['music videos', 'MV'],
    ['music video', 'MV'],
    ['music', '音乐'],
    ['misc', '音乐'],
    ['other', '其他'],
    ['3d', '3D'],
    ['sports', '体育'],
    ['sport', '体育'],
    ['photo', '写真'],
    ['Books', '书籍'],
    ['pc games', 'PC游戏'],
    ['pc game', 'PC游戏'],
    ['hqaudio', '音频'],
    ['hq audio', '音频']
  ]);

  const state = {
    panel: null,
    toggler: null,
    status: null,
    output: null,
    base64Output: null,
    generateBtn: null,
    copyJsonBtn: null,
    copyBase64Btn: null,
    jsonSplit: null,
    base64Split: null,
    customDescInput: null,
    typingTimeout: null,
    loadingCustomDesc: false,
    maximized: false,
    previousDimensions: null,
    inputs: {},
    toggleJsonBtn: null,
    toggleBase64Btn: null,
    customView: null,
    jsonView: null,
    base64View: null,
    activeView: 'custom'
  };

  const menuCommandHandles = [];
  let menuInitialized = false;

  function clamp(value, min, max) {
    if (Number.isNaN(value)) return min;
    if (max < min) return min;
    return Math.min(Math.max(value, min), max);
  }

  function constrainPanelPosition(left, top, width, height) {
    const viewportWidth = window.innerWidth || document.documentElement.clientWidth || width;
    const viewportHeight = window.innerHeight || document.documentElement.clientHeight || height;
    const maxLeft = Math.max(0, viewportWidth - width);
    const maxTop = Math.max(0, viewportHeight - height);
    return {
      left: clamp(left, 0, maxLeft),
      top: clamp(top, 0, maxTop)
    };
  }

  function constrainPanelWithinViewport() {
    if (!state.panel || state.maximized) return;
    const rect = state.panel.getBoundingClientRect();
    const width = rect.width || state.panel.offsetWidth || state.panel.clientWidth;
    const height = rect.height || state.panel.offsetHeight || state.panel.clientHeight;
    if (!width || !height) return;
    const { left, top } = constrainPanelPosition(rect.left, rect.top, width, height);
    state.panel.style.left = `${left}px`;
    state.panel.style.top = `${top}px`;
    state.panel.style.right = 'auto';
    state.panel.style.bottom = 'auto';
  }

  function cssEscape(ident) {
    if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
      return CSS.escape(ident);
    }
    return ident.replace(/[^\w-]/g, '\\$&');
  }

  function slugify(str) {
    if (!str) return 'section';
    return str
      .normalize('NFKC')
      .replace(/[::]/g, '')
      .replace(/[\s\u00A0]+/g, '_')
      .replace(/[^\p{L}\p{N}_]+/gu, '_')
      .replace(/^_+|_+$/g, '')
      .toLowerCase() || 'section';
  }

  function sanitizeTrackerName(raw) {
    if (!raw) return '';
    let name = raw;
    name = name.replace(/\s*(?:(?::|:){1,2}|-+)?\s*RSS\s*订阅.*$/i, '');
    name = name.split(/\s*::\s*/)[0];
    name = name.split(/\s*::\s*/)[0];
    return name.trim();
  }

  function deriveTrackerName(hostname) {
    const separators = [' - ', ' | ', ' — ', ' – '];
    let candidate = sanitizeTrackerName(document.title || '');
    for (const separator of separators) {
      if (candidate.includes(separator)) {
        candidate = candidate.split(separator)[0];
        break;
      }
    }
    if (!candidate) {
      candidate = hostname;
    }
    return candidate.trim() || hostname;
  }

  function sanitizeTrackerId(raw) {
    return (raw || '').replace(/[^a-z0-9]+/gi, '').toLowerCase();
  }

  function parseToUrl(domain) {
    if (!domain) {
      throw new Error('Invalid domain');
    }
    try {
      return new URL(domain);
    } catch (error) {
      const trimmed = domain.trim();
      return new URL(trimmed.startsWith('http') ? trimmed : `https://${trimmed}`);
    }
  }

  function normalizeDomainOrigin(domain) {
    const url = parseToUrl(domain);
    return `${url.protocol}//${url.host}`;
  }

  function normalizeHostname(domain) {
    const url = parseToUrl(domain);
    return url.hostname.replace(/^www\./i, '') || url.hostname;
  }

  function extractBaseDomain(domain) {
    const hostname = normalizeHostname(domain);
    if (!hostname) return '';
    const segments = hostname.split('.').filter(Boolean);
    if (segments.length <= 2) {
      return segments.join('.');
    }
    const secondLast = segments[segments.length - 2].toLowerCase();
    if (SECOND_LEVEL_TLDS.has(secondLast) && segments.length >= 3) {
      return segments.slice(-3).join('.');
    }
    return segments.slice(-2).join('.');
  }

  function deriveTrackerId(domain) {
    if (!domain) return '';
    const hostname = normalizeHostname(domain);
    const segments = hostname.split('.').filter(Boolean);
    let base = '';
    if (segments.length >= 2) {
      base = segments[segments.length - 2];
    } else {
      base = segments[0] || hostname;
    }
    return sanitizeTrackerId(base || hostname);
  }

  function encodeToBase64(str) {
    try {
      return btoa(unescape(encodeURIComponent(str)));
    } catch (error) {
      console.error('[NexusPHP RSS Config Helper] Base64 encode failed:', error);
      throw new Error('Base64 编码失败');
    }
  }

  function prettifyCustomKey(key) {
    if (!key) return '';
    let result = key.trim().replace(/_/g, ' ');
    result = result.replace(/(^|[\s/\-])([a-z])/g, (match, sep, char) => `${sep}${char.toUpperCase()}`);
    if (/\d/.test(result)) {
      result = result.replace(/[a-z]+/gi, (segment) => segment.toUpperCase());
    }
    return result.replace(/\s+/g, ' ').trim();
  }

  function getDefaultCustomDescText() {
    const pairs = new Map();
    CATEGORY_LOCALIZATION.forEach((value, key) => {
      if (!value) return;
      const formattedKey = prettifyCustomKey(key);
      if (!formattedKey || pairs.has(formattedKey)) return;
      pairs.set(formattedKey, value);
    });
    return Array.from(pairs.entries())
      .map(([k, v]) => `${k}=${v}`)
      .join('\n');
  }

  function createPanel() {
    if (state.panel) return;

    GM_addStyle(`
      :root {
        --tm-color-white: #ffffff;
        --tm-color-text: #1f2c46;
        --tm-color-subtle: #506690;
        --tm-color-primary: #4285f4;
        --tm-color-primary-dark: #1f63ff;
        --tm-color-secondary: #1f3f72;
        --tm-color-border: #dce4f7;
        --tm-color-surface: #f7f9ff;
        --tm-shadow-large: 0 18px 40px rgba(30, 60, 110, 0.16);
        --tm-shadow-medium: 0 12px 30px rgba(30, 60, 110, 0.12);
      }

      .tm-rss-panel {
        position: fixed;
        top: 24px;
        left: 24px;
        width: clamp(460px, 52vw, 720px);
        min-width: 360px;
        max-width: calc(100vw - 48px);
        max-height: calc(100vh - 48px);
        resize: horizontal;
        background: var(--tm-color-white);
        color: var(--tm-color-text);
        border-radius: 14px;
        font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
        display: none;
        flex-direction: column;
        overflow: hidden;
        z-index: 99999;
        border: 1px solid var(--tm-color-border);
        box-shadow: var(--tm-shadow-large);
      }
      .tm-rss-panel.is-visible {
        display: flex;
      }
      .tm-rss-panel.is-maximized {
        left: 12px !important;
        top: 12px !important;
        right: 12px !important;
        bottom: 12px !important;
        width: auto !important;
        height: auto !important;
        max-width: calc(100vw - 24px) !important;
        max-height: calc(100vh - 24px) !important;
        border-radius: 10px;
        box-shadow: none;
        resize: none;
      }
      .tm-rss-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 14px 20px;
        border-bottom: 1px solid var(--tm-color-border);
        background: linear-gradient(135deg, rgba(66, 133, 244, 0.08), rgba(66, 133, 244, 0.02));
        font-size: 16px;
        font-weight: 600;
        letter-spacing: 0.2px;
        cursor: move;
      }
      .tm-rss-close {
        background: none;
        border: none;
        color: var(--tm-color-subtle);
        font-size: 20px;
        cursor: pointer;
        padding: 0 4px;
        transition: transform 0.25s ease, color 0.25s ease;
      }
      .tm-rss-close:hover {
        transform: rotate(90deg);
        color: var(--tm-color-primary);
      }
      .tm-rss-body {
        padding: 18px 22px 22px;
        overflow-y: auto;
        background: var(--tm-color-white);
      }
      .tm-rss-layout {
        display: grid;
        grid-template-columns: minmax(220px, 230px) minmax(380px, 1fr);
        column-gap: 20px;
        row-gap: 12px;
        margin-bottom: 18px;
        align-items: start;
      }
      .tm-rss-panel.is-maximized .tm-rss-layout {
        grid-template-columns: minmax(220px, 230px) minmax(540px, 1fr);
        column-gap: 24px;
        row-gap: 16px;
      }
      .tm-rss-column {
        display: flex;
        flex-direction: column;
        gap: 18px;
      }
      .tm-rss-column--custom {
        height: auto;
        margin-left: 0;
      }
      .tm-rss-panel.is-maximized .tm-rss-column--custom {
        margin-left: 0;
      }
      .tm-rss-field label {
        font-size: 13px;
        letter-spacing: 0.3px;
        font-weight: 600;
        color: var(--tm-color-subtle);
      }
      .tm-rss-field input[type="text"],
      .tm-rss-field input[type="url"] {
        border-radius: 8px;
        border: 1px solid var(--tm-color-border);
        background: var(--tm-color-surface);
        color: var(--tm-color-text);
        padding: 9px 12px;
        font-size: 13px;
        transition: border 0.15s ease, box-shadow 0.15s ease;
        width: 100%;
        box-sizing: border-box;
      }
      .tm-rss-field textarea {
        border-radius: 8px;
        border: 1px solid var(--tm-color-border);
        background: var(--tm-color-surface);
        color: var(--tm-color-text);
        width: 100%;
        padding: 10px 12px;
        box-sizing: border-box;
        overflow: auto;
        white-space: pre-wrap;
        word-break: break-word;
        font-family: inherit;
        font-size: 12px;
        resize: vertical;
        line-height: 1.45;
        transition: border 0.15s ease, box-shadow 0.15s ease;
        min-height: 286px;
      }
      .tm-rss-field input[type="text"]:focus,
      .tm-rss-field input[type="url"]:focus,
      .tm-rss-field textarea:focus {
        border-color: var(--tm-color-primary);
        outline: none;
        box-shadow: 0 0 0 4px rgba(66, 133, 244, 0.16);
      }
      .tm-rss-field input[type="checkbox"] {
        vertical-align: middle;
      }
      .tm-rss-hint {
        display: block;
        font-size: 11px;
        opacity: 0.7;
        margin-top: 4px;
        color: var(--tm-color-subtle);
      }
      .tm-rss-actions {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 10px;
        margin: 6px 0 12px;
        align-items: stretch;
      }
      .tm-rss-actions > * {
        display: flex;
        min-width: 0;
      }
      .tm-rss-actions button {
        flex: 1 1 auto;
        border: none;
        border-radius: 999px;
        padding: 10px 16px;
        font-size: 13px;
        font-weight: 600;
        cursor: pointer;
        transition: transform 0.15s ease, box-shadow 0.15s ease;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      .tm-rss-actions .tm-generate {
        background: linear-gradient(135deg, var(--tm-color-primary), var(--tm-color-primary-dark));
        color: #ffffff;
      }
      .tm-rss-actions button:hover {
        transform: translateY(-1px);
        box-shadow: var(--tm-shadow-medium);
        border-color: transparent;
      }
      .tm-rss-split {
        display: inline-flex;
        align-items: stretch;
        border: 1px solid var(--tm-color-border);
        border-radius: 999px;
        overflow: hidden;
        background: var(--tm-color-white);
        flex: 1 1 auto;
        min-width: 0;
      }
      .tm-rss-split button {
        border: none;
        background: transparent;
        padding: 9px 16px;
        font-size: 12px;
        font-weight: 600;
        cursor: pointer;
        color: var(--tm-color-primary);
        transition: background 0.15s ease, color 0.15s ease;
        display: flex;
        align-items: center;
        justify-content: center;
        min-width: 0;
      }
      .tm-rss-split button + button {
        border-left: 1px solid var(--tm-color-border);
      }
      .tm-rss-split button:first-child {
        border-radius: 999px 0 0 999px;
      }
      .tm-rss-split button:last-child {
        border-radius: 0 999px 999px 0;
      }
      .tm-rss-split .tm-copy {
        background: rgba(66, 133, 244, 0.08);
        color: var(--tm-color-primary);
      }
      .tm-rss-split .tm-toggle {
        background: transparent;
        color: var(--tm-color-secondary);
      }
      .tm-rss-split button:hover {
        background: rgba(66, 133, 244, 0.12);
        color: var(--tm-color-primary);
      }
      .tm-rss-split.is-active {
        border-color: rgba(66, 133, 244, 0.45);
        background: rgba(66, 133, 244, 0.08);
      }
      .tm-rss-split.is-active .tm-toggle {
        color: var(--tm-color-primary);
      }
      .tm-rss-status {
        min-height: 20px;
        font-size: 12px;
        color: var(--tm-color-secondary);
        margin-bottom: 10px;
        letter-spacing: 0.3px;
      }
      .tm-rss-field--views {
        position: relative;
      }
      .tm-rss-views {
        position: relative;
        min-height: 260px;
      }
      .tm-rss-view {
        display: none;
        flex-direction: column;
        gap: 8px;
      }
      .tm-rss-view.is-active {
        display: flex;
      }
      .tm-rss-view label {
        font-size: 13px;
        letter-spacing: 0.3px;
        font-weight: 600;
        color: var(--tm-color-subtle);
      }
      .tm-rss-view .tm-rss-hint {
        display: block;
        font-size: 11px;
        opacity: 0.7;
        margin-top: -2px;
        color: var(--tm-color-subtle);
      }
      .tm-rss-output,
      .tm-rss-base64 {
        width: 100%;
        padding: 10px 12px;
        box-sizing: border-box;
        min-height: 286px;
        border-radius: 8px;
        border: 1px solid var(--tm-color-border);
        background: var(--tm-color-surface);
        color: var(--tm-color-text);
        font-size: 12px;
        line-height: 1.45;
        resize: vertical;
        transition: border 0.15s ease, box-shadow 0.15s ease;
        overflow: auto;
        white-space: pre-wrap;
        word-break: break-word;
        font-family: inherit;
      }
      .tm-rss-toggler {
        position: fixed;
        left: 28px;
        bottom: 32px;
        width: 48px;
        height: 48px;
        background: #ffffff;
        color: #4a5568;
        border: 1px solid rgba(79, 84, 101, 0.25);
        border-radius: 18px;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 99998;
        cursor: grab;
        transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
      }
      .tm-rss-toggler:hover {
        background: #f3f6ff;
        color: #1f2c46;
        border-color: rgba(66, 133, 244, 0.35);
      }
      .tm-rss-toggler.is-dragging {
        cursor: grabbing;
        background: #e2e8f6;
        border-color: rgba(66, 133, 244, 0.4);
      }
      .tm-rss-toggler-icon {
        display: inline-flex;
        width: 20px;
        height: 20px;
        align-items: center;
        justify-content: center;
      }
      .tm-rss-toggler svg {
        width: 100%;
        height: 100%;
        fill: currentColor;
      }
      @media (max-width: 640px) {
        .tm-rss-panel {
          right: 12px;
          left: 12px;
          width: auto;
        }
      }
    `);

    state.panel = document.createElement('div');
    state.panel.className = 'tm-rss-panel';
    state.panel.innerHTML = `
      <div class="tm-rss-header">
        <span>MP自定义站点索引配置生成</span>
        <button type="button" class="tm-rss-close" aria-label="Close">×</button>
      </div>
      <div class="tm-rss-body">
        <div class="tm-rss-layout">
          <div class="tm-rss-column">
            <div class="tm-rss-field">
              <label for="tm-field-schema">架构 Schema</label>
              <input id="tm-field-schema" type="text" data-field="schema" placeholder="NexusPhp" value="${DEFAULT_SCHEMA}">
            </div>
            <div class="tm-rss-field">
              <label for="tm-field-tracker-id">标识 ID</label>
              <input id="tm-field-tracker-id" type="text" data-field="trackerId" placeholder="example">
            </div>
            <div class="tm-rss-field">
              <label for="tm-field-tracker-name">名称 Name</label>
              <input id="tm-field-tracker-name" type="text" data-field="trackerName" placeholder="站点名称">
            </div>
            <div class="tm-rss-field">
              <label for="tm-field-domain">域名 Domain</label>
              <input id="tm-field-domain" type="url" data-field="domain" placeholder="https://example.com">
            </div>
            <div class="tm-rss-field">
              <label for="tm-field-encoding">字符编码 Encoding</label>
              <input id="tm-field-encoding" type="text" data-field="encoding" placeholder="UTF-8" value="${DEFAULT_ENCODING}">
            </div>
          </div>
          <div class="tm-rss-column tm-rss-column--custom">
            <div class="tm-rss-field tm-rss-field--views">
              <div class="tm-rss-views">
                <div class="tm-rss-view is-active" data-view="custom">
                  <label for="tm-field-custom-desc">自定义分类描述</label>
                  <span class="tm-rss-hint">自动读取全站分类,“id:cat=desc”,可编辑翻译</span>
                  <textarea id="tm-field-custom-desc" data-field="customDesc" placeholder="Movies=电影&#10;401:movies=电影"></textarea>
                </div>
                <div class="tm-rss-view" data-view="json">
                  <label for="tm-field-json-output">JSON 输出</label>
                  <span class="tm-rss-hint">生成后可在此查看并复制 JSON 配置内容。</span>
                  <textarea id="tm-field-json-output" class="tm-rss-output" readonly></textarea>
                </div>
                <div class="tm-rss-view" data-view="base64">
                  <label for="tm-field-base64-output">Base64 输出</label>
                  <span class="tm-rss-hint">生成后可在此查看并复制 Base64 编码配置。</span>
                  <textarea id="tm-field-base64-output" class="tm-rss-base64" readonly></textarea>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="tm-rss-actions">
          <button type="button" class="tm-generate">生成</button>
          <div class="tm-rss-split tm-rss-split--json">
            <button type="button" class="tm-copy tm-copy-json">复制 JSON</button>
            <button type="button" class="tm-toggle tm-toggle-json">查看 JSON</button>
          </div>
          <div class="tm-rss-split tm-rss-split--base64">
            <button type="button" class="tm-copy tm-copy-base64">复制 Base64</button>
            <button type="button" class="tm-toggle tm-toggle-base64">查看 Base64</button>
          </div>
        </div>
        <div class="tm-rss-status"></div>
      </div>
    `;

    document.body.appendChild(state.panel);

    state.status = state.panel.querySelector('.tm-rss-status');
    state.output = state.panel.querySelector('.tm-rss-output');
    state.base64Output = state.panel.querySelector('.tm-rss-base64');
    state.generateBtn = state.panel.querySelector('.tm-generate');
    state.copyJsonBtn = state.panel.querySelector('.tm-copy-json');
    state.copyBase64Btn = state.panel.querySelector('.tm-copy-base64');
    state.toggleJsonBtn = state.panel.querySelector('.tm-toggle-json');
    state.toggleBase64Btn = state.panel.querySelector('.tm-toggle-base64');
    state.jsonSplit = state.panel.querySelector('.tm-rss-split--json');
    state.base64Split = state.panel.querySelector('.tm-rss-split--base64');
    state.customView = state.panel.querySelector('.tm-rss-view[data-view="custom"]');
    state.jsonView = state.panel.querySelector('.tm-rss-view[data-view="json"]');
    state.base64View = state.panel.querySelector('.tm-rss-view[data-view="base64"]');
    state.customDescInput = state.panel.querySelector('[data-field="customDesc"]');

    state.inputs.schema = state.panel.querySelector('[data-field="schema"]');
    state.inputs.trackerId = state.panel.querySelector('[data-field="trackerId"]');
    state.inputs.trackerName = state.panel.querySelector('[data-field="trackerName"]');
    state.inputs.domain = state.panel.querySelector('[data-field="domain"]');
    state.inputs.encoding = state.panel.querySelector('[data-field="encoding"]');

    const closeBtn = state.panel.querySelector('.tm-rss-close');
    closeBtn.addEventListener('click', hidePanel);
    const header = state.panel.querySelector('.tm-rss-header');
    enablePanelDrag(header);
    header.addEventListener('dblclick', handleHeaderDoubleClick);

    state.generateBtn.addEventListener('click', handleGenerate);
    state.copyJsonBtn.addEventListener('click', handleCopyJson);
    state.copyBase64Btn.addEventListener('click', handleCopyBase64);
    if (state.toggleJsonBtn) {
      state.toggleJsonBtn.addEventListener('click', () => {
        handleToggleView('json');
      });
    }
    if (state.toggleBase64Btn) {
      state.toggleBase64Btn.addEventListener('click', () => {
        handleToggleView('base64');
      });
    }
    if (state.customDescInput) {
      state.customDescInput.value = customDescRaw || '';
      state.customDescInput.addEventListener('change', handleCustomDescChange);
      state.customDescInput.addEventListener('input', handleCustomDescInput);
    }

    const host = location.hostname;
    state.inputs.trackerId.value = deriveTrackerId(host);
    state.inputs.trackerName.value = deriveTrackerName(host);
    state.inputs.domain.value = location.origin.endsWith('/') ? location.origin : `${location.origin}`;
    applyPanelPosition();
    setActiveView('custom');
  }

  function ensurePanelVisible() {
    createPanel();
    refreshCustomDescValue(true).catch((error) => {
      console.warn('[NexusPHP RSS Config Helper] Failed to refresh custom descriptions', error);
    });
    state.panel.classList.add('is-visible');
    requestAnimationFrame(constrainPanelWithinViewport);
  }

  function hidePanel() {
    if (!state.panel) return;
    state.panel.classList.remove('is-visible');
  }

  function isPanelVisible() {
    return Boolean(state.panel && state.panel.classList.contains('is-visible'));
  }

  function updateToggleButtonStates() {
    if (state.toggleJsonBtn) {
      const active = state.activeView === 'json';
      state.toggleJsonBtn.textContent = active ? '返回 分类描述' : '查看 JSON';
      if (state.jsonSplit) {
        state.jsonSplit.classList.toggle('is-active', active);
      }
    }
    if (state.toggleBase64Btn) {
      const active = state.activeView === 'base64';
      state.toggleBase64Btn.textContent = active ? '返回 分类描述' : '查看 Base64';
      if (state.base64Split) {
        state.base64Split.classList.toggle('is-active', active);
      }
    }
  }

  function setActiveView(view) {
    if (!state.customView || !state.jsonView || !state.base64View) return;
    const allowed = new Set(['custom', 'json', 'base64']);
    const target = allowed.has(view) ? view : 'custom';
    state.customView.classList.toggle('is-active', target === 'custom');
    state.jsonView.classList.toggle('is-active', target === 'json');
    state.base64View.classList.toggle('is-active', target === 'base64');
    state.activeView = target;
    updateToggleButtonStates();
  }

  function handleToggleView(view) {
    if (state.activeView === view) {
      setActiveView('custom');
    } else {
      setActiveView(view);
    }
  }

  function applyTogglerVisibility(visible) {
    if (!state.toggler) return;
    if (visible) {
      state.toggler.style.display = '';
      state.toggler.removeAttribute('aria-hidden');
    } else {
      state.toggler.style.display = 'none';
      state.toggler.setAttribute('aria-hidden', 'true');
    }
  }

  function setTogglerVisibility(visible, options = {}) {
    const { persist = true } = options;
    const nextVisible = Boolean(visible);

    if (!state.toggler) {
      createToggler();
    }

    if (togglerVisible === nextVisible) {
      applyTogglerVisibility(nextVisible);
      if (persist) {
        setPreference(STORAGE_KEYS.togglerVisible, nextVisible);
      }
      return;
    }

    togglerVisible = nextVisible;
    applyTogglerVisibility(togglerVisible);

    if (persist) {
      setPreference(STORAGE_KEYS.togglerVisible, togglerVisible);
    }

    refreshMenuCommands();
  }

  function refreshMenuCommands() {
    if (typeof GM_registerMenuCommand !== 'function') {
      return;
    }

    const canUnregister = typeof GM_unregisterMenuCommand === 'function';

    if (canUnregister && menuCommandHandles.length) {
      menuCommandHandles.splice(0, menuCommandHandles.length).forEach((id) => {
        try {
          GM_unregisterMenuCommand(id);
        } catch (error) {
          console.warn('[NexusPHP RSS Config Helper] Failed to unregister menu command', error);
        }
      });
    } else if (!canUnregister && menuInitialized) {
      return;
    }

    const openId = GM_registerMenuCommand('打开 MP自定义站点索引配置器', ensurePanelVisible);
    if (canUnregister && openId) {
      menuCommandHandles.push(openId);
    }

    if (canUnregister) {
      const toggleId = GM_registerMenuCommand(togglerVisible ? '隐藏悬浮按钮' : '显示悬浮按钮', () => {
        setTogglerVisibility(!togglerVisible);
      });
      if (toggleId) {
        menuCommandHandles.push(toggleId);
      }
    } else {
      GM_registerMenuCommand('显示悬浮按钮', () => {
        setTogglerVisibility(true);
      });
      GM_registerMenuCommand('隐藏悬浮按钮', () => {
        setTogglerVisibility(false);
      });
    }

    menuInitialized = true;
  }

  function createToggler() {
    if (state.toggler) return;
    state.toggler = document.createElement('button');
    state.toggler.type = 'button';
    state.toggler.className = 'tm-rss-toggler';
    state.toggler.setAttribute('aria-label', '打开配置面板');
    state.toggler.innerHTML = `
      <span class="tm-rss-toggler-icon" aria-hidden="true">
        <svg viewBox="0 0 24 24" focusable="false">
          <path d="M4 3h7a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Zm9 0h7a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Zm-9 11h7a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1Zm9 0h7a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1Z" />
        </svg>
      </span>
    `;
    document.body.appendChild(state.toggler);
    state.toggler.addEventListener('click', (event) => {
      if (state.toggler.dataset.dragging === 'true' || state.toggler.dataset.dragMoved === 'true') {
        event.preventDefault();
        event.stopPropagation();
        return;
      }
      if (isPanelVisible()) {
        hidePanel();
      } else {
        ensurePanelVisible();
      }
    });
    applyTogglerPosition();
    enableTogglerDrag();
    applyTogglerVisibility(togglerVisible);
  }

  function registerMenu() {
    refreshMenuCommands();
  }

  function setStatus(message, isError = false) {
    if (!state.status) return;
    state.status.textContent = message || '';
    state.status.style.color = isError ? '#ff5c5c' : '#1f3f72';
  }

  function handleHeaderDoubleClick(event) {
    event.preventDefault();
    if (state.maximized) {
      restorePanelSize();
    } else {
      maximizePanel();
    }
  }

  function maximizePanel() {
    if (!state.panel || state.maximized) return;
    const style = state.panel.style;
    state.previousDimensions = {
      left: style.left,
      top: style.top,
      right: style.right,
      bottom: style.bottom,
      width: style.width,
      height: style.height,
      maxWidth: style.maxWidth,
      maxHeight: style.maxHeight,
      borderRadius: style.borderRadius,
      boxShadow: style.boxShadow
    };
    state.maximized = true;
    state.panel.classList.add('is-maximized');
    style.left = '';
    style.top = '';
    style.right = '';
    style.bottom = '';
    style.width = '';
    style.height = '';
    style.maxWidth = '';
    style.maxHeight = '';
    style.borderRadius = '';
    style.boxShadow = '';
  }

  function restorePanelSize() {
    if (!state.panel || !state.maximized) return;
    state.maximized = false;
    state.panel.classList.remove('is-maximized');
    const prev = state.previousDimensions || {};
    const style = state.panel.style;
    style.left = prev.left !== undefined ? prev.left : style.left;
    style.top = prev.top !== undefined ? prev.top : style.top;
    style.right = prev.right !== undefined ? prev.right : style.right;
    style.bottom = prev.bottom !== undefined ? prev.bottom : style.bottom;
    style.width = prev.width !== undefined ? prev.width : style.width;
    style.height = prev.height !== undefined ? prev.height : style.height;
    style.maxWidth = prev.maxWidth !== undefined ? prev.maxWidth : style.maxWidth;
    style.maxHeight = prev.maxHeight !== undefined ? prev.maxHeight : style.maxHeight;
    style.borderRadius = prev.borderRadius !== undefined ? prev.borderRadius : style.borderRadius;
    style.boxShadow = prev.boxShadow !== undefined ? prev.boxShadow : style.boxShadow;
    state.previousDimensions = null;
    applyPanelPosition();
  }

  function isNexusPhpSite() {
    if (document.querySelector('form[action*="getrss.php"]')) return true;
    if (document.querySelector('a[href*="getrss.php"]')) return true;
    if (document.querySelector('table.torrents')) return true;
    const generator = document.querySelector('meta[name="generator"]');
    if (generator && /NexusPHP/i.test(generator.content)) return true;
    return false;
  }

  function parseRssResponse(html) {
    const parser = new DOMParser();
    return parser.parseFromString(html, 'text/html');
  }

  function resolveSectionInfo(input) {
    let row = input.closest('tr');
    while (row) {
      const embedded = row.querySelector('td.embedded');
      if (embedded) {
        const rawTitle = embedded.textContent.replace(/\s+/g, '').replace(/[::]/g, '');
        const label = embedded.textContent.trim().replace(/[::]/g, '');
        const key = SECTION_NAME_MAP.get(rawTitle) || SECTION_NAME_MAP.get(label) || slugify(label || rawTitle);
        return { key, label: label || rawTitle };
      }
      row = row.previousElementSibling;
    }
    return null;
  }

  function getLabelText(input, doc) {
    const labelByFor = input.id ? doc.querySelector(`label[for="${cssEscape(input.id)}"]`) : null;
    const directLabel = input.closest('label');
    const labelNode = labelByFor || directLabel;
    if (labelNode) {
      const text = labelNode.textContent.replace(/\s+/g, ' ').trim();
      if (text) return text;
    }

    const container = input.closest('td') || input.parentElement;
    if (!container) return '';

    const img = container.querySelector('img[alt], img[title]');
    if (img) {
      return (img.getAttribute('title') || img.getAttribute('alt') || '').trim();
    }

    const anchor = container.querySelector('a');
    if (anchor) {
      const text = anchor.textContent.replace(/\s+/g, ' ').trim();
      if (text) return text;
    }

    const text = container.textContent.replace(/\s+/g, ' ').trim();
    return text;
  }

  function extractOption(input, doc) {
    const idSource = input.id || input.name || '';
    const idMatch = idSource.match(/\d+/);
    const optionId = idMatch ? Number(idMatch[0]) : idSource;
    const labelText = getLabelText(input, doc);
    const normalizedKey = (labelText || '').trim().toLowerCase();
    const idKey = (idSource || '').trim().toLowerCase();
    const customDesc = customDescMap.get(normalizedKey) || customDescMap.get(idKey);
    const localizedDesc = customDesc || CATEGORY_LOCALIZATION.get(normalizedKey) || CATEGORY_LOCALIZATION.get(idKey) || labelText || idSource;

    return {
      id: optionId,
      cat: labelText || idSource,
      desc: localizedDesc
    };
  }

  function collectSections(doc) {
    const inputs = Array.from(doc.querySelectorAll('input[type="checkbox"]'));
    const sections = {};

    inputs.forEach((input) => {
      const fieldId = input.id || input.name || '';
      if (!ALLOWED_PREFIX_RE.test(fieldId)) return;

      const sectionInfo = resolveSectionInfo(input);
      if (!sectionInfo) return;

      if (!sections[sectionInfo.key]) {
        sections[sectionInfo.key] = {
          title: sectionInfo.label,
          items: []
        };
      }

      sections[sectionInfo.key].items.push(extractOption(input, doc));
    });

    const normalized = {};
    Object.entries(sections).forEach(([key, payload]) => {
      const unique = [];
      const seen = new Set();
      payload.items.forEach((item) => {
        const identity = `${item.id}-${item.cat}`;
        if (!seen.has(identity)) {
          seen.add(identity);
          unique.push(item);
        }
      });
      unique.sort((a, b) => {
        if (typeof a.id === 'number' && typeof b.id === 'number') {
          return a.id - b.id;
        }
        return String(a.cat).localeCompare(String(b.cat), 'zh-Hans-CN');
      });
      normalized[key] = unique;
    });

    return normalized;
  }

  function buildBaseConfig(meta, sections) {
    const config = {
      schema: meta.schema || DEFAULT_SCHEMA,
      id: meta.trackerId,
      name: meta.trackerName,
      domain: meta.domain,
      encoding: meta.encoding || DEFAULT_ENCODING,
      public: meta.isPublic,
      search: {
        paths: [
          {
            path: 'torrents.php',
            method: 'get'
          }
        ],
        params: {
          search: '{keyword}',
          search_area: 0
        },
        batch: {
          delimiter: ' ',
          space_replace: '_'
        }
      },
      category: sections,
      torrents: {
        list: {
          selector: 'table.torrents > tr:has(table.torrentname)'
        },
        fields: {
          id: {
            selector: 'a[href*="details.php?id="]',
            attribute: 'href',
            filters: [
              {
                name: 'regexp',
                args: ['id=(\\d+)']
              }
            ]
          },
          title_default: {
            selector: 'a[href*="details.php?id="]'
          },
          title_optional: {
            optional: true,
            selector: 'a[title][href*="details.php?id="]',
            attribute: 'title'
          },
          title: {
            text: '{% if fields.title_optional %}{{ fields.title_optional }}{% else %}{{ fields.title_default }}{% endif %}'
          },
          details: {
            selector: 'a[href*="details.php?id="]',
            attribute: 'href'
          },
          download: {
            selector: 'a[href*="download.php?id="]',
            attribute: 'href'
          },
          date_elapsed: {
            selector: 'td:nth-child(4) > span',
            optional: true
          },
          date_added: {
            selector: 'td:nth-child(4) > span',
            attribute: 'title',
            optional: true
          },
          size: {
            selector: 'td:nth-child(5)'
          },
          seeders: {
            selector: 'td:nth-child(6)'
          },
          leechers: {
            selector: 'td:nth-child(7)'
          },
          grabs: {
            selector: 'td:nth-child(8)'
          },
          downloadvolumefactor: {
            case: {
              'img.pro_free': 0,
              'img.pro_free2up': 0,
              'img.pro_50pctdown': 0.5,
              'img.pro_50pctdown2up': 0.5,
              'img.pro_30pctdown': 0.3,
              '*': 1
            }
          },
          uploadvolumefactor: {
            case: {
              'img.pro_50pctdown2up': 2,
              'img.pro_free2up': 2,
              'img.pro_2up': 2,
              '*': 1
            }
          }
        }
      }
    };

    if (!Object.keys(config.category || {}).length) {
      delete config.category;
    }

    return config;
  }

  async function handleGenerate() {
    if (!state.generateBtn) return;

    const meta = {
      schema: state.inputs.schema.value.trim() || DEFAULT_SCHEMA,
      trackerId: state.inputs.trackerId.value.trim(),
      trackerName: sanitizeTrackerName(state.inputs.trackerName.value.trim()),
      domain: state.inputs.domain.value.trim(),
      encoding: state.inputs.encoding.value.trim() || DEFAULT_ENCODING,
      isPublic: false
    };

    state.inputs.trackerName.value = meta.trackerName;

    meta.trackerId = sanitizeTrackerId(meta.trackerId);
    if (!meta.trackerId) {
      meta.trackerId = deriveTrackerId(meta.domain || location.hostname);
    }
    state.inputs.trackerId.value = meta.trackerId;

    if (!meta.trackerName) {
      meta.trackerName = deriveTrackerName(location.hostname);
      state.inputs.trackerName.value = meta.trackerName;
    }

    if (!meta.trackerId) {
      setStatus('请填写 Tracker ID。', true);
      state.inputs.trackerId.focus();
      return;
    }

    if (!meta.trackerName) {
      setStatus('请填写 Tracker 名称。', true);
      state.inputs.trackerName.focus();
      return;
    }

    if (!meta.domain) {
      setStatus('请填写 Domain。', true);
      state.inputs.domain.focus();
      return;
    }

    let requestUrl;
    try {
      const normalizedDomain = normalizeDomainOrigin(meta.domain);
      meta.domain = normalizedDomain;
      requestUrl = new URL('getrss.php', normalizedDomain).toString();
    } catch (error) {
      setStatus('Domain 格式不正确,请确认包含协议(例如 https://example.com)。', true);
      return;
    }

    state.generateBtn.disabled = true;
    setStatus('正在获取 getrss.php ...');

    try {
      const response = await fetch(requestUrl, {
        credentials: 'include',
        headers: {
          'X-Requested-With': 'Tampermonkey'
        }
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      const html = await response.text();
      const doc = parseRssResponse(html);
      const sections = collectSections(doc);

      if (!Object.keys(sections).length) {
        setStatus('未能从 getrss.php 中解析到分类,请确认已登录且页面结构未变。', true);
        state.base64Output.value = '';
        return;
      }

      const config = buildBaseConfig(meta, sections);
      const json = JSON.stringify(config, null, 2);

      state.output.value = json;
      try {
        const baseDomain = extractBaseDomain(meta.domain) || normalizeHostname(meta.domain);
        const host = baseDomain.replace(/^www\./i, '');
        const encoded = encodeToBase64(json);
        state.base64Output.value = `${host}|${encoded}`;
        setActiveView('base64');
      } catch (base64Error) {
        console.error('[NexusPHP RSS Config Helper] Base64 error:', base64Error);
        state.base64Output.value = '';
        setStatus(`解析完成,但 Base64 生成失败:${base64Error.message}`, true);
        return;
      }
      setStatus('解析完成。可以复制 JSON,或继续调整字段。');
    } catch (error) {
      console.error('[NexusPHP RSS Config Helper] Failed:', error);
      setStatus(`获取或解析失败:${error.message}`, true);
      state.base64Output.value = '';
    } finally {
      state.generateBtn.disabled = false;
    }
  }

  function handleCopyJson() {
    if (!state.output || !state.output.value) {
      setStatus('没有可复制的内容,请先生成。', true);
      return;
    }

    try {
      const text = state.output.value;
      if (typeof GM_setClipboard === 'function') {
        GM_setClipboard(text, { type: 'text', mimetype: 'text/plain' });
      } else if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text);
      } else {
        throw new Error('浏览器不支持自动复制');
      }
      setStatus('已复制到剪贴板。');
    } catch (error) {
      setStatus(`复制失败:${error.message}`, true);
    }
  }

  function handleCopyBase64() {
    if (!state.base64Output || !state.base64Output.value) {
      setStatus('没有 Base64 内容,请先生成。', true);
      return;
    }

    try {
      const text = state.base64Output.value;
      if (typeof GM_setClipboard === 'function') {
        GM_setClipboard(text, { type: 'text', mimetype: 'text/plain' });
      } else if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text);
      } else {
        throw new Error('浏览器不支持自动复制');
      }
      setStatus('Base64 已复制到剪贴板。');
    } catch (error) {
      setStatus(`Base64 复制失败:${error.message}`, true);
    }
  }

  function handleCustomDescChange(event) {
    updateCustomDesc(event.target.value);
  }

  function handleCustomDescInput(event) {
    if (state.typingTimeout) {
      clearTimeout(state.typingTimeout);
    }
    state.typingTimeout = setTimeout(() => {
      updateCustomDesc(event.target.value);
    }, 500);
  }

  function updateCustomDesc(rawText) {
    if (customDescRaw === rawText) {
      return;
    }
    customDescRaw = rawText;
    customDescMap = parseCustomDesc(customDescRaw);
    setPreference(STORAGE_KEYS.customDesc, customDescRaw);
  }

  function parseCustomDesc(rawText) {
    const map = new Map();
    if (!rawText) return map;
    rawText
      .split(/\r?\n/)
      .map((line) => line.trim())
      .filter(Boolean)
      .forEach((line) => {
        const [keyPartRaw, valuePartRaw] = line.split('=').map((segment) => segment.trim());
        if (!keyPartRaw || !valuePartRaw) return;

        let idToken = '';
        let labelToken = '';
        let keyToken = keyPartRaw;

        if (keyPartRaw.includes(':')) {
          const [left, right] = keyPartRaw.split(':');
          idToken = (left || '').trim();
          labelToken = (right || '').trim();
        } else {
          labelToken = keyPartRaw.trim();
        }

        const register = (token) => {
          if (!token) return;
          map.set(token.toLowerCase(), valuePartRaw);
        };

        register(keyToken);
        register(labelToken);

        const registerId = (token) => {
          if (!token) return;
          const cleaned = token.replace(/^cat/i, '');
          if (cleaned) {
            register(cleaned);
            register(`cat${cleaned}`);
          }
          register(token);
        };

        registerId(idToken);
      });
    return map;
  }

  async function refreshCustomDescValue(forceFetch = false) {
    if (!state.customDescInput) return;
    if (!forceFetch && customDescRaw.trim()) {
      state.customDescInput.value = customDescRaw;
      return;
    }

    if (state.loadingCustomDesc) {
      return;
    }

    state.loadingCustomDesc = true;
    try {
      const domain = state.inputs?.domain?.value?.trim() || location.origin;
      let requestUrl;
      try {
        requestUrl = new URL('getrss.php', domain).toString();
      } catch (error) {
        console.warn('[NexusPHP RSS Config Helper] Invalid domain for custom desc fetch:', domain, error);
        fallbackCustomDesc();
        return;
      }

      const response = await fetch(requestUrl, { credentials: 'include' });
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      const html = await response.text();
      const doc = parseRssResponse(html);
      const { entries, map } = extractCustomDescFromDoc(doc);
      if (entries.length) {
        customDescRaw = entries
          .map(({ idFragment, label, desc }) => `${idFragment}:${label}=${desc}`)
          .join('\n');
        customDescMap = map;
        setPreference(STORAGE_KEYS.customDesc, customDescRaw);
        state.customDescInput.value = customDescRaw;
      } else {
        fallbackCustomDesc();
      }
    } catch (error) {
      console.warn('[NexusPHP RSS Config Helper] Failed to fetch custom descriptions', error);
      fallbackCustomDesc();
    } finally {
      state.loadingCustomDesc = false;
    }
  }

  function fallbackCustomDesc() {
    if (!state.customDescInput) return;
    const stored = (customDescRaw || '').trim();
    if (stored) {
      state.customDescInput.value = customDescRaw;
      return;
    }
    const defaults = getDefaultCustomDescText();
    customDescRaw = defaults;
    customDescMap = parseCustomDesc(defaults);
    state.customDescInput.value = defaults;
    setPreference(STORAGE_KEYS.customDesc, defaults);
  }

  function extractCustomDescFromDoc(doc) {
    if (!doc) return { entries: [], map: new Map() };
    const inputs = Array.from(doc.querySelectorAll('input[type="checkbox"]'));
    const map = new Map();
    const entries = [];
    inputs.forEach((input) => {
      const fieldId = input.id || input.name || '';
      if (!fieldId) return;
      if (!ALLOWED_PREFIX_RE.test(fieldId)) return;
      const sectionInfo = resolveSectionInfo(input);
      if (!sectionInfo || sectionInfo.key !== 'category') return;
      const label = getLabelText(input, doc);
      if (!label) return;
      const normalizedId = fieldId.trim();
      const displayLabel = label.trim();
      const numericId = (normalizedId.match(/\d+/) || [''])[0];
      const localizationKeyCandidates = [
        displayLabel.trim().toLowerCase(),
        normalizedId.toLowerCase()
      ];
      if (numericId) {
        localizationKeyCandidates.push(`cat${numericId}`.toLowerCase(), numericId.toLowerCase());
      }
      const localizedDesc = localizationKeyCandidates.reduce((result, key) => {
        if (result) return result;
        return CATEGORY_LOCALIZATION.get(key);
      }, null) || displayLabel;

      const entry = {
        id: normalizedId,
        numericId,
        label: displayLabel,
        desc: localizedDesc,
        idFragment: numericId || normalizedId
      };
      entries.push(entry);

      const register = (token) => {
        if (!token) return;
        map.set(token.toLowerCase(), localizedDesc);
      };

      register(displayLabel);
      register(normalizedId);
      if (numericId) {
        register(numericId);
        register(`cat${numericId}`);
      }
    });
    return { entries, map };
  }

  function enableTogglerDrag() {
    if (!state.toggler) return;
    let startX = 0;
    let startY = 0;
    let initialLeft = 0;
    let initialTop = 0;
    let hasDraggedBeyondThreshold = false;

    const onPointerMove = (event) => {
      if (state.toggler.dataset.dragging !== 'true') return;
      const deltaX = event.clientX - startX;
      const deltaY = event.clientY - startY;
      if (!hasDraggedBeyondThreshold) {
        const distance = Math.hypot(deltaX, deltaY);
        if (distance < TOGGLER_DRAG_THRESHOLD_PX) {
          return;
        }
        hasDraggedBeyondThreshold = true;
      }
      const newLeft = initialLeft + deltaX;
      const newTop = initialTop + deltaY;
      state.toggler.style.left = `${newLeft}px`;
      state.toggler.style.top = `${newTop}px`;
      state.toggler.style.right = 'auto';
      state.toggler.style.bottom = 'auto';
      state.toggler.dataset.dragMoved = 'true';
    };

    const onPointerUp = (event) => {
      if (state.toggler.dataset.dragging !== 'true') return;
      event.preventDefault();
      state.toggler.classList.remove('is-dragging');
      state.toggler.dataset.dragging = 'false';
      document.removeEventListener('pointermove', onPointerMove);
      document.removeEventListener('pointerup', onPointerUp);
      document.removeEventListener('pointercancel', onPointerUp);
      if (state.toggler.dataset.dragMoved === 'true') {
        const rect = state.toggler.getBoundingClientRect();
        const position = {
          left: rect.left,
          top: rect.top
        };
        togglerPosition = position;
        setPreference(STORAGE_KEYS.togglerPosition, position);
        setTimeout(() => {
          state.toggler.dataset.dragMoved = 'false';
        }, 50);
      } else {
        state.toggler.dataset.dragMoved = 'false';
      }
      hasDraggedBeyondThreshold = false;
    };

    state.toggler.addEventListener('pointerdown', (event) => {
      if (event.button !== 0) return;
      event.preventDefault();
      state.toggler.dataset.dragging = 'true';
      state.toggler.dataset.dragMoved = 'false';
      hasDraggedBeyondThreshold = false;
      state.toggler.classList.add('is-dragging');
      startX = event.clientX;
      startY = event.clientY;
      const rect = state.toggler.getBoundingClientRect();
      initialLeft = rect.left;
      initialTop = rect.top;
      state.toggler.style.left = `${initialLeft}px`;
      state.toggler.style.top = `${initialTop}px`;
      state.toggler.style.right = 'auto';
      state.toggler.style.bottom = 'auto';
      document.addEventListener('pointermove', onPointerMove);
      document.addEventListener('pointerup', onPointerUp);
      document.addEventListener('pointercancel', onPointerUp);
    });
  }

  function applyTogglerPosition() {
    if (!state.toggler) return;
    if (!togglerPosition || typeof togglerPosition.left !== 'number' || typeof togglerPosition.top !== 'number') {
      return;
    }
    state.toggler.style.left = `${togglerPosition.left}px`;
    state.toggler.style.top = `${togglerPosition.top}px`;
    state.toggler.style.right = 'auto';
    state.toggler.style.bottom = 'auto';
  }

  function enablePanelDrag(handle) {
    if (!handle || !state.panel) return;
    let startX = 0;
    let startY = 0;
    let initialLeft = 0;
    let initialTop = 0;
    let panelWidth = 0;
    let panelHeight = 0;

    const onPointerMove = (event) => {
      if (!panelDragging) return;
      const deltaX = event.clientX - startX;
      const deltaY = event.clientY - startY;
      const desiredLeft = initialLeft + deltaX;
      const desiredTop = initialTop + deltaY;
      const { left, top } = constrainPanelPosition(desiredLeft, desiredTop, panelWidth, panelHeight);
      state.panel.style.left = `${left}px`;
      state.panel.style.top = `${top}px`;
      state.panel.style.right = 'auto';
      state.panel.style.bottom = 'auto';
    };

    const onPointerUp = () => {
      if (!panelDragging) return;
      panelDragging = false;
      panelPosition = getPanelPosition();
      setPreference(STORAGE_KEYS.panelPosition, panelPosition);
      document.removeEventListener('pointermove', onPointerMove);
      document.removeEventListener('pointerup', onPointerUp);
      document.removeEventListener('pointercancel', onPointerUp);
    };

    handle.addEventListener('pointerdown', (event) => {
      if (event.button !== 0) return;
      if (state.maximized) {
        return;
      }
      event.preventDefault();
      panelDragging = true;
      const rect = state.panel.getBoundingClientRect();
      panelWidth = rect.width || state.panel.offsetWidth || state.panel.clientWidth;
      panelHeight = rect.height || state.panel.offsetHeight || state.panel.clientHeight;
      const constrainedStart = constrainPanelPosition(rect.left, rect.top, panelWidth, panelHeight);
      initialLeft = constrainedStart.left;
      initialTop = constrainedStart.top;
      state.panel.style.left = `${initialLeft}px`;
      state.panel.style.top = `${initialTop}px`;
      state.panel.style.right = 'auto';
      state.panel.style.bottom = 'auto';
      startX = event.clientX;
      startY = event.clientY;
      document.addEventListener('pointermove', onPointerMove);
      document.addEventListener('pointerup', onPointerUp);
      document.addEventListener('pointercancel', onPointerUp);
    });
  }

  function getPanelPosition() {
    if (!state.panel) return null;
    const rect = state.panel.getBoundingClientRect();
    return {
      left: rect.left,
      top: rect.top
    };
  }

  function applyPanelPosition() {
    if (!state.panel) return;
    if (state.maximized) return;
    if (!panelPosition || typeof panelPosition.left !== 'number' || typeof panelPosition.top !== 'number') {
      state.panel.style.left = '24px';
      state.panel.style.top = '24px';
      state.panel.style.right = 'auto';
      state.panel.style.bottom = 'auto';
      requestAnimationFrame(constrainPanelWithinViewport);
      return;
    }
    state.panel.style.left = `${panelPosition.left}px`;
    state.panel.style.top = `${panelPosition.top}px`;
    state.panel.style.right = 'auto';
    state.panel.style.bottom = 'auto';
    requestAnimationFrame(constrainPanelWithinViewport);
  }

  function getPreference(key, fallback) {
    try {
      if (typeof GM_getValue === 'function') {
        const value = GM_getValue(key, fallback);
        return value === undefined ? fallback : value;
      }
      const value = localStorage.getItem(key);
      return value ? JSON.parse(value) : fallback;
    } catch (error) {
      console.warn('[NexusPHP RSS Config Helper] Failed to get preference', key, error);
      return fallback;
    }
  }

  function setPreference(key, value) {
    try {
      if (typeof GM_setValue === 'function') {
        GM_setValue(key, value);
      } else {
        localStorage.setItem(key, JSON.stringify(value));
      }
    } catch (error) {
      console.warn('[NexusPHP RSS Config Helper] Failed to set preference', key, error);
    }
  }

  function init() {
    if (!isNexusPhpSite()) {
      return;
    }
    customDescRaw = getPreference(STORAGE_KEYS.customDesc, '');
    customDescMap = parseCustomDesc(customDescRaw);
    togglerPosition = getPreference(STORAGE_KEYS.togglerPosition, null);
    const storedVisibility = getPreference(STORAGE_KEYS.togglerVisible, null);
    togglerVisible = storedVisibility === null ? true : Boolean(storedVisibility);
    panelPosition = getPreference(STORAGE_KEYS.panelPosition, null);
    createPanel();
    createToggler();
    registerMenu();
    window.addEventListener('resize', constrainPanelWithinViewport);
  }

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