Greasy Fork

Greasy Fork is available in English.

Discourse 表情选择器 (Emoji Picker) core lite

Discourse 论坛表情选择器 - 核心功能 (Emoji picker for Discourse - Core features only)

当前为 2025-11-07 提交的版本,查看 最新版本

// ==UserScript==
// @name         Discourse 表情选择器 (Emoji Picker) core lite
// @namespace    https://github.com/stevessr/bug-v3
// @version      1.2.3
// @description  Discourse 论坛表情选择器 - 核心功能 (Emoji picker for Discourse - Core features only)
// @author       stevessr
// @match        https://linux.do/*
// @match        https://meta.discourse.org/*
// @match        https://*.discourse.org/*
// @match        http://localhost:5173/*
// @exclude      https://linux.do/a/*
// @match        https://idcflare.com/*
// @grant        none
// @license      MIT
// @homepageURL  https://github.com/stevessr/bug-v3
// @supportURL   https://github.com/stevessr/bug-v3/issues
// @run-at       document-end
// ==/UserScript==

(function() {
'use strict';

(function() {
	async function fetchPackagedJSON(url) {
		try {
			if (typeof fetch === "undefined") return null;
			const res = await fetch(url || "/assets/defaultEmojiGroups.json", {
				cache: "no-cache",
				credentials: "omit"
			});
			if (!res.ok) return null;
			return await res.json();
		} catch (err) {
			return null;
		}
	}
	async function loadAndFilterDefaultEmojiGroups(url, hostname) {
		const packaged = await fetchPackagedJSON(url);
		if (!packaged || !Array.isArray(packaged.groups)) return [];
		if (!hostname) return packaged.groups;
		return packaged.groups.map((group) => {
			const filteredEmojis = group.emojis.filter((emoji) => {
				try {
					const url$1 = emoji.url;
					if (!url$1) return false;
					const emojiHostname = new URL(url$1).hostname;
					return emojiHostname === hostname || emojiHostname.endsWith("." + hostname);
				} catch (e) {
					return true;
				}
			});
			return {
				...group,
				emojis: filteredEmojis
			};
		}).filter((group) => group.emojis.length > 0);
	}
	var STORAGE_KEY = "emoji_extension_userscript_data";
	var SETTINGS_KEY = "emoji_extension_userscript_settings";
	var USAGE_STATS_KEY = "emoji_extension_userscript_usage_stats";
	const DEFAULT_USER_SETTINGS = {
		imageScale: 30,
		gridColumns: 4,
		outputFormat: "markdown",
		forceMobileMode: false,
		defaultGroup: "nachoneko",
		showSearchBar: true,
		enableFloatingPreview: true,
		enableCalloutSuggestions: true,
		enableBatchParseImages: true
	};
	function loadDataFromLocalStorage() {
		try {
			const groupsData = localStorage.getItem(STORAGE_KEY);
			let emojiGroups = [];
			if (groupsData) try {
				const parsed = JSON.parse(groupsData);
				if (Array.isArray(parsed) && parsed.length > 0) emojiGroups = parsed;
			} catch (e) {
				console.warn("[Userscript] Failed to parse stored emoji groups:", e);
			}
			if (emojiGroups.length === 0) emojiGroups = [];
			const settingsData = localStorage.getItem(SETTINGS_KEY);
			let settings = { ...DEFAULT_USER_SETTINGS };
			if (settingsData) try {
				const parsed = JSON.parse(settingsData);
				if (parsed && typeof parsed === "object") settings = {
					...settings,
					...parsed
				};
			} catch (e) {
				console.warn("[Userscript] Failed to parse stored settings:", e);
			}
			emojiGroups = emojiGroups.filter((g) => g.id !== "favorites");
			console.log("[Userscript] Loaded data from localStorage:", {
				groupsCount: emojiGroups.length,
				emojisCount: emojiGroups.reduce((acc, g) => acc + (g.emojis?.length || 0), 0),
				settings
			});
			return {
				emojiGroups,
				settings
			};
		} catch (error) {
			console.error("[Userscript] Failed to load from localStorage:", error);
			return {
				emojiGroups: [],
				settings: { ...DEFAULT_USER_SETTINGS }
			};
		}
	}
	async function loadDataFromLocalStorageAsync(hostname) {
		try {
			const local = loadDataFromLocalStorage();
			if (local.emojiGroups && local.emojiGroups.length > 0) return local;
			const remoteUrl = localStorage.getItem("emoji_extension_remote_config_url");
			const configUrl = remoteUrl && typeof remoteUrl === "string" && remoteUrl.trim().length > 0 ? remoteUrl : "https://video2gif-pages.pages.dev/assets/defaultEmojiGroups.json";
			try {
				const groups = await loadAndFilterDefaultEmojiGroups(configUrl, hostname);
				if (groups && groups.length > 0) {
					try {
						localStorage.setItem(STORAGE_KEY, JSON.stringify(groups));
					} catch (e) {
						console.warn("[Userscript] Failed to persist fetched groups to localStorage", e);
					}
					return {
						emojiGroups: groups.filter((g) => g.id !== "favorites"),
						settings: local.settings
					};
				}
			} catch (err) {
				console.warn(`[Userscript] Failed to fetch config from ${configUrl}:`, err);
			}
			return {
				emojiGroups: [],
				settings: local.settings
			};
		} catch (error) {
			console.error("[Userscript] loadDataFromLocalStorageAsync failed:", error);
			return {
				emojiGroups: [],
				settings: { ...DEFAULT_USER_SETTINGS }
			};
		}
	}
	function saveDataToLocalStorage(data) {
		try {
			if (data.emojiGroups) localStorage.setItem(STORAGE_KEY, JSON.stringify(data.emojiGroups));
			if (data.settings) localStorage.setItem(SETTINGS_KEY, JSON.stringify(data.settings));
		} catch (error) {
			console.error("[Userscript] Failed to save to localStorage:", error);
		}
	}
	function addEmojiToUserscript(emojiData) {
		try {
			const data = loadDataFromLocalStorage();
			let userGroup = data.emojiGroups.find((g) => g.id === "user_added");
			if (!userGroup) {
				userGroup = {
					id: "user_added",
					name: "用户添加",
					icon: "⭐",
					order: 999,
					emojis: []
				};
				data.emojiGroups.push(userGroup);
			}
			if (!userGroup.emojis.some((e) => e.url === emojiData.url || e.name === emojiData.name)) {
				userGroup.emojis.push({
					packet: Date.now(),
					name: emojiData.name,
					url: emojiData.url
				});
				saveDataToLocalStorage({ emojiGroups: data.emojiGroups });
				console.log("[Userscript] Added emoji to user group:", emojiData.name);
			} else console.log("[Userscript] Emoji already exists:", emojiData.name);
		} catch (error) {
			console.error("[Userscript] Failed to add emoji:", error);
		}
	}
	function trackEmojiUsage(emojiName, emojiUrl) {
		try {
			const key = `${emojiName}|${emojiUrl}`;
			const statsData = localStorage.getItem(USAGE_STATS_KEY);
			let stats = {};
			if (statsData) try {
				stats = JSON.parse(statsData);
			} catch (e) {
				console.warn("[Userscript] Failed to parse usage stats:", e);
			}
			if (!stats[key]) stats[key] = {
				count: 0,
				lastUsed: 0
			};
			stats[key].count++;
			stats[key].lastUsed = Date.now();
			localStorage.setItem(USAGE_STATS_KEY, JSON.stringify(stats));
		} catch (error) {
			console.error("[Userscript] Failed to track emoji usage:", error);
		}
	}
	function getPopularEmojis(limit = 20) {
		try {
			const statsData = localStorage.getItem(USAGE_STATS_KEY);
			if (!statsData) return [];
			const stats = JSON.parse(statsData);
			return Object.entries(stats).map(([key, data]) => {
				const [name, url] = key.split("|");
				return {
					name,
					url,
					count: data.count,
					lastUsed: data.lastUsed
				};
			}).sort((a, b) => b.count - a.count).slice(0, limit);
		} catch (error) {
			console.error("[Userscript] Failed to get popular emojis:", error);
			return [];
		}
	}
	function clearEmojiUsageStats() {
		try {
			localStorage.removeItem(USAGE_STATS_KEY);
			console.log("[Userscript] Cleared emoji usage statistics");
		} catch (error) {
			console.error("[Userscript] Failed to clear usage stats:", error);
		}
	}
	const userscriptState = {
		emojiGroups: [],
		settings: { ...DEFAULT_USER_SETTINGS },
		emojiUsageStats: {}
	};
	function createEl(tag, opts) {
		const el = document.createElement(tag);
		if (opts) {
			if (opts.width) el.style.width = opts.width;
			if (opts.height) el.style.height = opts.height;
			if (opts.className) el.className = opts.className;
			if (opts.text) el.textContent = opts.text;
			if (opts.placeholder && "placeholder" in el) el.placeholder = opts.placeholder;
			if (opts.type && "type" in el) el.type = opts.type;
			if (opts.value !== void 0 && "value" in el) el.value = opts.value;
			if (opts.style) el.style.cssText = opts.style;
			if (opts.src && "src" in el) el.src = opts.src;
			if (opts.attrs) for (const k in opts.attrs) el.setAttribute(k, opts.attrs[k]);
			if (opts.dataset) for (const k in opts.dataset) el.dataset[k] = opts.dataset[k];
			if (opts.innerHTML) el.innerHTML = opts.innerHTML;
			if (opts.title) el.title = opts.title;
			if (opts.alt && "alt" in el) el.alt = opts.alt;
			if (opts.id) el.id = opts.id;
			if (opts.on) for (const [evt, handler] of Object.entries(opts.on)) el.addEventListener(evt, handler);
		}
		return el;
	}
	function extractEmojiFromImage(img, titleElement) {
		const url = img.src;
		if (!url || !url.startsWith("http")) return null;
		let name = "";
		const parts = (titleElement.textContent || "").split("·");
		if (parts.length > 0) name = parts[0].trim();
		if (!name || name.length < 2) name = img.alt || img.title || extractNameFromUrl$1(url);
		name = name.trim();
		if (name.length === 0) name = "表情";
		return {
			name,
			url
		};
	}
	function extractEmojiDataFromLightboxWrapper(lightboxWrapper) {
		const results = [];
		const anchor = lightboxWrapper.querySelector("a.lightbox");
		const img = lightboxWrapper.querySelector("img");
		if (!anchor || !img) return results;
		const title = anchor.getAttribute("title") || "";
		const originalUrl = anchor.getAttribute("href") || "";
		const downloadUrl = anchor.getAttribute("data-download-href") || "";
		const imgSrc = img.getAttribute("src") || "";
		let name = title || img.getAttribute("alt") || "";
		if (!name || name.length < 2) name = extractNameFromUrl$1(originalUrl || downloadUrl || imgSrc);
		name = name.replace(/\\.(webp|jpg|jpeg|png|gif)$/i, "").trim() || "表情";
		const urlToUse = originalUrl || downloadUrl || imgSrc;
		if (urlToUse && urlToUse.startsWith("http")) results.push({
			name,
			url: urlToUse
		});
		return results;
	}
	function extractNameFromUrl$1(url) {
		try {
			const nameWithoutExt = (new URL(url).pathname.split("/").pop() || "").replace(/\.[^/.]+$/, "");
			const decoded = decodeURIComponent(nameWithoutExt);
			if (/^[0-9a-f]{8,}$/i.test(decoded) || decoded.length < 2) return "表情";
			return decoded || "表情";
		} catch {
			return "表情";
		}
	}
	function createAddButton$1(emojiData) {
		const link = createEl("a", {
			className: "image-source-link emoji-add-link",
			style: `
    color: #ffffff;
    text-decoration: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    font-size: inherit;
    font-family: inherit;
    background: linear-gradient(135deg, #4f46e5, #7c3aed);
    border: 2px solid #ffffff;
    border-radius: 6px;
    padding: 4px 8px;
    margin: 0 2px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    transition: all 0.2s ease;
    font-weight: 600;
  `
		});
		link.addEventListener("mouseenter", () => {
			if (!link.innerHTML.includes("已添加") && !link.innerHTML.includes("失败")) {
				link.style.background = "linear-gradient(135deg, #3730a3, #5b21b6)";
				link.style.transform = "scale(1.05)";
				link.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.3)";
			}
		});
		link.addEventListener("mouseleave", () => {
			if (!link.innerHTML.includes("已添加") && !link.innerHTML.includes("失败")) {
				link.style.background = "linear-gradient(135deg, #4f46e5, #7c3aed)";
				link.style.transform = "scale(1)";
				link.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.2)";
			}
		});
		link.innerHTML = `
    <svg class="fa d-icon d-icon-plus svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor; margin-right: 4px;">
      <path d="M12 4c.55 0 1 .45 1 1v6h6c.55 0 1 .45 1 1s-.45 1-1 1h-6v6c0 .55-.45 1-1 1s-1-.45-1-1v-6H5c-.55 0-1-.45-1-1s.45-1 1-1h6V5c0-.55.45-1 1-1z"/>
    </svg>添加表情
  `;
		link.title = "添加到用户表情";
		link.addEventListener("click", async (e) => {
			e.preventDefault();
			e.stopPropagation();
			const originalHTML = link.innerHTML;
			const originalStyle = link.style.cssText;
			try {
				addEmojiToUserscript(emojiData);
				link.innerHTML = `
        <svg class="fa d-icon d-icon-check svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor; margin-right: 4px;">
          <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
        </svg>已添加
      `;
				link.style.background = "linear-gradient(135deg, #10b981, #059669)";
				link.style.color = "#ffffff";
				link.style.border = "2px solid #ffffff";
				link.style.boxShadow = "0 2px 4px rgba(16, 185, 129, 0.3)";
				setTimeout(() => {
					link.innerHTML = originalHTML;
					link.style.cssText = originalStyle;
				}, 2e3);
			} catch (error) {
				console.error("[Emoji Extension Userscript] Failed to add emoji:", error);
				link.innerHTML = `
        <svg class="fa d-icon d-icon-times svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor; margin-right: 4px;">
          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
        </svg>失败
      `;
				link.style.background = "linear-gradient(135deg, #ef4444, #dc2626)";
				link.style.color = "#ffffff";
				link.style.border = "2px solid #ffffff";
				link.style.boxShadow = "0 2px 4px rgba(239, 68, 68, 0.3)";
				setTimeout(() => {
					link.innerHTML = originalHTML;
					link.style.cssText = originalStyle;
				}, 2e3);
			}
		});
		return link;
	}
	function processLightbox(lightbox) {
		if (lightbox.querySelector(".emoji-add-link")) return;
		const img = lightbox.querySelector(".mfp-img");
		const title = lightbox.querySelector(".mfp-title");
		if (!img || !title) return;
		const emojiData = extractEmojiFromImage(img, title);
		if (!emojiData) return;
		const addButton = createAddButton$1(emojiData);
		const sourceLink = title.querySelector("a.image-source-link");
		if (sourceLink) {
			const separator = document.createTextNode(" · ");
			title.insertBefore(separator, sourceLink);
			title.insertBefore(addButton, sourceLink);
		} else {
			title.appendChild(document.createTextNode(" · "));
			title.appendChild(addButton);
		}
	}
	function processAllLightboxes() {
		document.querySelectorAll(".mfp-wrap.mfp-gallery").forEach((lightbox) => {
			if (lightbox.classList.contains("mfp-wrap") && lightbox.classList.contains("mfp-gallery") && lightbox.querySelector(".mfp-img")) processLightbox(lightbox);
		});
	}
	function initOneClickAdd() {
		console.log("[Emoji Extension Userscript] Initializing one-click add functionality");
		setTimeout(processAllLightboxes, 500);
		new MutationObserver((mutations) => {
			let hasNewLightbox = false;
			mutations.forEach((mutation) => {
				if (mutation.type === "childList") mutation.addedNodes.forEach((node) => {
					if (node.nodeType === Node.ELEMENT_NODE) {
						const element = node;
						if (element.classList && element.classList.contains("mfp-wrap")) hasNewLightbox = true;
					}
				});
			});
			if (hasNewLightbox) setTimeout(processAllLightboxes, 100);
		}).observe(document.body, {
			childList: true,
			subtree: true
		});
		document.addEventListener("visibilitychange", () => {
			if (!document.hidden) setTimeout(processAllLightboxes, 200);
		});
		initBatchParseButtons();
	}
	function createBatchParseButton(cookedElement) {
		const button = createEl("button", {
			className: "emoji-batch-parse-button",
			style: `
      display: inline-flex;
      align-items: center;
      gap: 6px;
      background: var(--tertiary-low);
      color: var(--d-button-default-icon-color);
      border-radius: 8px;
      padding: 8px 12px;
      margin: 10px 0;
      font-weight: 600;
      cursor: pointer;
      border: none;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
      transition: all 0.2s ease;
    `
		});
		button.innerHTML = `
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor;">
      <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
    </svg>
    一键解析并添加所有图片
  `;
		button.title = "解析当前内容中的所有图片并添加到用户表情";
		button.addEventListener("mouseenter", () => {
			if (!button.disabled) {
				button.style.background = "var(--tertiary-high)";
				button.style.transform = "scale(1.02)";
			}
		});
		button.addEventListener("mouseleave", () => {
			if (!button.disabled && !button.innerHTML.includes("已处理")) {
				button.style.background = "var(--tertiary-very-low)";
				button.style.transform = "scale(1)";
			}
		});
		button.addEventListener("click", async (e) => {
			e.preventDefault();
			e.stopPropagation();
			const originalHTML = button.innerHTML;
			const originalStyle = button.style.cssText;
			try {
				button.innerHTML = "正在解析...";
				button.style.background = "var(--primary-medium)";
				button.disabled = true;
				const lightboxWrappers = cookedElement.querySelectorAll(".lightbox-wrapper");
				const allEmojiData = [];
				lightboxWrappers.forEach((wrapper) => {
					const items = extractEmojiDataFromLightboxWrapper(wrapper);
					allEmojiData.push(...items);
				});
				if (allEmojiData.length === 0) throw new Error("未找到可解析的图片");
				let successCount = 0;
				for (const emojiData of allEmojiData) try {
					addEmojiToUserscript(emojiData);
					successCount++;
				} catch (e$1) {
					console.error("[Userscript OneClick] 添加图片失败", emojiData.name, e$1);
				}
				button.innerHTML = `
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor;">
          <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
        </svg>
        已处理 ${successCount}/${allEmojiData.length} 张图片
      `;
				button.style.background = "linear-gradient(135deg, #10b981, #059669)";
				setTimeout(() => {
					button.innerHTML = originalHTML;
					button.style.cssText = originalStyle;
					button.disabled = false;
				}, 3e3);
			} catch (error) {
				console.error("[Userscript OneClick] Batch parse failed:", error);
				button.innerHTML = `
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 1em; height: 1em; fill: currentColor;">
          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
        </svg>
        解析失败
      `;
				button.style.background = "linear-gradient(135deg, #ef4444, #dc2626)";
				setTimeout(() => {
					button.innerHTML = originalHTML;
					button.style.cssText = originalStyle;
					button.disabled = false;
				}, 3e3);
			}
		});
		return button;
	}
	function processCookedContent(cookedElement) {
		if (cookedElement.querySelector(".emoji-batch-parse-button")) return;
		if (!cookedElement.querySelector(".lightbox-wrapper")) return;
		const batchButton = createBatchParseButton(cookedElement);
		cookedElement.insertBefore(batchButton, cookedElement.firstChild);
	}
	function processCookedContents() {
		document.querySelectorAll(".cooked").forEach((element) => {
			if (element.classList.contains("cooked") && element.querySelector(".lightbox-wrapper")) processCookedContent(element);
		});
	}
	function initBatchParseButtons() {
		setTimeout(processCookedContents, 500);
		new MutationObserver((mutations) => {
			let hasNewCooked = false;
			mutations.forEach((mutation) => {
				if (mutation.type === "childList") mutation.addedNodes.forEach((node) => {
					if (node.nodeType === Node.ELEMENT_NODE) {
						const element = node;
						if (element.classList && element.classList.contains("cooked")) hasNewCooked = true;
						if (element.querySelectorAll && element.querySelectorAll(".cooked").length > 0) hasNewCooked = true;
					}
				});
			});
			if (hasNewCooked) setTimeout(processCookedContents, 100);
		}).observe(document.body, {
			childList: true,
			subtree: true
		});
	}
	function extractNameFromUrl(url) {
		try {
			const nameWithoutExt = (new URL(url).pathname.split("/").pop() || "").replace(/\.[^/.]+$/, "");
			const decoded = decodeURIComponent(nameWithoutExt);
			if (/^[0-9a-f]{8,}$/i.test(decoded) || decoded.length < 2) return "表情";
			return decoded || "表情";
		} catch {
			return "表情";
		}
	}
	function createAddButton(data) {
		const button = createEl("a", {
			className: "emoji-add-link",
			style: `
      color:#fff;
      border-radius:6px;
      padding:4px 8px;
      margin:0 2px;
      display:inline-flex;
      align-items:center;
      font-weight:600;
      text-decoration:none;
      border: 1px solid rgba(255,255,255,0.7);
      cursor: pointer;
    `,
			title: "添加到未分组表情"
		});
		button.innerHTML = `添加表情`;
		function addToUngrouped(emoji) {
			const data$1 = loadDataFromLocalStorage();
			let group = data$1.emojiGroups.find((g) => g.id === "ungrouped");
			if (!group) {
				group = {
					id: "ungrouped",
					name: "未分组",
					icon: "📦",
					order: 999,
					emojis: []
				};
				data$1.emojiGroups.push(group);
			}
			if (!group.emojis.some((e) => e.url === emoji.url || e.name === emoji.name)) {
				group.emojis.push({
					packet: Date.now(),
					name: emoji.name,
					url: emoji.url
				});
				saveDataToLocalStorage({ emojiGroups: data$1.emojiGroups });
			}
		}
		button.addEventListener("click", (e) => {
			e.preventDefault();
			e.stopPropagation();
			try {
				addToUngrouped({
					name: data.name,
					url: data.url
				});
				const original = button.textContent || "";
				button.textContent = "已添加";
				button.style.background = "linear-gradient(135deg,#10b981,#059669)";
				setTimeout(() => {
					button.textContent = original || "添加表情";
					button.style.background = "";
				}, 1500);
			} catch (err) {
				console.warn("[Userscript] add emoji failed", err);
				const original = button.textContent || "";
				button.textContent = "失败";
				button.style.background = "linear-gradient(135deg,#ef4444,#dc2626)";
				setTimeout(() => {
					button.textContent = original || "添加表情";
					button.style.background = "linear-gradient(135deg, #4f46e5, #7c3aed)";
				}, 1500);
			}
		});
		return button;
	}
	function addEmojiButtonToPswp(container) {
		const topBar = container.querySelector(".pswp__top-bar") || (container.classList.contains("pswp__top-bar") ? container : null);
		if (!topBar) return;
		if (topBar.querySelector(".emoji-add-link")) return;
		const originalBtn = topBar.querySelector(".pswp__button--original-image");
		const downloadBtn = topBar.querySelector(".pswp__button--download-image");
		let imgUrl = "";
		if (originalBtn?.href) imgUrl = originalBtn.href;
		else if (downloadBtn?.href) imgUrl = downloadBtn.href;
		if (!imgUrl) return;
		let name = "";
		const captionTitle = document.querySelector(".pswp__caption-title");
		if (captionTitle?.textContent?.trim()) name = captionTitle.textContent.trim();
		if (!name) {
			if (originalBtn?.title) name = originalBtn.title;
			else if (downloadBtn?.title) name = downloadBtn.title;
		}
		if (!name || name.length < 2) name = extractNameFromUrl(imgUrl);
		name = name.trim() || "表情";
		const addButton = createAddButton({
			name,
			url: imgUrl
		});
		if (downloadBtn?.parentElement) downloadBtn.parentElement.insertBefore(addButton, downloadBtn.nextSibling);
		else topBar.appendChild(addButton);
	}
	function scanForPhotoSwipeTopBar() {
		document.querySelectorAll(".pswp__top-bar").forEach((topBar) => addEmojiButtonToPswp(topBar));
	}
	function observePhotoSwipeTopBar() {
		scanForPhotoSwipeTopBar();
		function debounce(fn, wait = 100) {
			let timer = null;
			return (...args) => {
				if (timer !== null) window.clearTimeout(timer);
				timer = window.setTimeout(() => {
					timer = null;
					fn(...args);
				}, wait);
			};
		}
		const debouncedScan = debounce(scanForPhotoSwipeTopBar, 100);
		const observer = new MutationObserver((mutations) => {
			for (const m of mutations) {
				if (m.type === "childList" && (m.addedNodes.length > 0 || m.removedNodes.length > 0)) {
					debouncedScan();
					return;
				}
				if (m.type === "attributes") {
					debouncedScan();
					return;
				}
			}
		});
		observer.observe(document.body, {
			childList: true,
			subtree: true,
			attributes: false
		});
		return observer;
	}
	function initPhotoSwipeTopbarUserscript() {
		scanForPhotoSwipeTopBar();
		observePhotoSwipeTopBar();
	}
	function detectRuntimePlatform() {
		try {
			const isMobileSize = window.innerWidth <= 768;
			const userAgent = navigator.userAgent.toLowerCase();
			const isMobileUserAgent = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
			const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0;
			if (isMobileSize && (isMobileUserAgent || isTouchDevice)) return "mobile";
			else if (!isMobileSize && !isMobileUserAgent) return "pc";
			return "original";
		} catch {
			return "original";
		}
	}
	function getEffectivePlatform() {
		return detectRuntimePlatform();
	}
	function getPlatformUIConfig() {
		switch (getEffectivePlatform()) {
			case "mobile": return {
				emojiPickerMaxHeight: "60vh",
				emojiPickerColumns: 4,
				emojiSize: 32,
				isModal: true,
				useCompactLayout: true,
				showSearchBar: true,
				floatingButtonSize: 48
			};
			case "pc": return {
				emojiPickerMaxHeight: "400px",
				emojiPickerColumns: 6,
				emojiSize: 24,
				isModal: false,
				useCompactLayout: false,
				showSearchBar: true,
				floatingButtonSize: 40
			};
			default: return {
				emojiPickerMaxHeight: "350px",
				emojiPickerColumns: 5,
				emojiSize: 28,
				isModal: false,
				useCompactLayout: false,
				showSearchBar: true,
				floatingButtonSize: 44
			};
		}
	}
	function getPlatformToolbarSelectors() {
		const platform = getEffectivePlatform();
		const baseSelectors = [
			".d-editor-button-bar[role=\"toolbar\"]",
			".chat-composer__inner-container",
			".d-editor-button-bar"
		];
		switch (platform) {
			case "mobile": return [
				...baseSelectors,
				".mobile-composer .d-editor-button-bar",
				".discourse-mobile .d-editor-button-bar",
				"[data-mobile-toolbar]"
			];
			case "pc": return [
				...baseSelectors,
				".desktop-composer .d-editor-button-bar",
				".discourse-desktop .d-editor-button-bar",
				"[data-desktop-toolbar]"
			];
			default: return baseSelectors;
		}
	}
	function logPlatformInfo() {
		const buildPlatform = "original";
		const runtimePlatform = detectRuntimePlatform();
		const effectivePlatform = getEffectivePlatform();
		const config = getPlatformUIConfig();
		console.log("[Platform] Build target:", buildPlatform);
		console.log("[Platform] Runtime detected:", runtimePlatform);
		console.log("[Platform] Effective platform:", effectivePlatform);
		console.log("[Platform] UI config:", config);
		console.log("[Platform] Screen size:", `${window.innerWidth}x${window.innerHeight}`);
		console.log("[Platform] User agent mobile:", /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));
		console.log("[Platform] Touch device:", "ontouchstart" in window || navigator.maxTouchPoints > 0);
	}
	var _sharedPreview = null;
	function ensureHoverPreview() {
		if (_sharedPreview && document.body.contains(_sharedPreview)) return _sharedPreview;
		_sharedPreview = createEl("div", {
			className: "emoji-picker-hover-preview",
			style: "position:fixed;pointer-events:none;display:none;z-index:1000002;max-width:300px;max-height:300px;overflow:hidden;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.25);background:transparent;padding:6px;"
		});
		const img = createEl("img", {
			className: "emoji-picker-hover-img",
			style: "display:block;max-width:100%;max-height:220px;object-fit:contain;"
		});
		const label = createEl("div", {
			className: "emoji-picker-hover-label",
			style: "font-size:12px;color:var(--primary);margin-top:6px;text-align:center;"
		});
		_sharedPreview.appendChild(img);
		_sharedPreview.appendChild(label);
		document.body.appendChild(_sharedPreview);
		return _sharedPreview;
	}
	var themeStylesInjected = false;
	function injectGlobalThemeStyles() {
		if (themeStylesInjected || typeof document === "undefined") return;
		themeStylesInjected = true;
		document.head.appendChild(createEl("style", {
			id: "emoji-extension-theme-globals",
			text: `
    /* Global CSS variables for emoji extension theme support */
    :root {
      /* Light theme (default) */
      --emoji-modal-bg: #ffffff;
      --emoji-modal-text: #333333;
      --emoji-modal-border: #dddddd;
      --emoji-modal-input-bg: #ffffff;
      --emoji-modal-label: #555555;
      --emoji-modal-button-bg: #f5f5f5;
      --emoji-modal-primary-bg: #1890ff;
      
      --emoji-preview-bg: #ffffff;
      --emoji-preview-text: #222222;
      --emoji-preview-border: rgba(0,0,0,0.08);
      
      --emoji-button-gradient-start: #667eea;
      --emoji-button-gradient-end: #764ba2;
      --emoji-button-shadow: rgba(0, 0, 0, 0.15);
      --emoji-button-hover-shadow: rgba(0, 0, 0, 0.2);
    }
    
    /* Dark theme */
    @media (prefers-color-scheme: dark) {
      :root {
        --emoji-modal-bg: #2d2d2d;
        --emoji-modal-text: #e6e6e6;
        --emoji-modal-border: #444444;
        --emoji-modal-input-bg: #3a3a3a;
        --emoji-modal-label: #cccccc;
        --emoji-modal-button-bg: #444444;
        --emoji-modal-primary-bg: #1677ff;
        
        --emoji-preview-bg: rgba(32,33,36,0.94);
        --emoji-preview-text: #e6e6e6;
        --emoji-preview-border: rgba(255,255,255,0.12);
        
        --emoji-button-gradient-start: #4a5568;
        --emoji-button-gradient-end: #2d3748;
        --emoji-button-shadow: rgba(0, 0, 0, 0.3);
        --emoji-button-hover-shadow: rgba(0, 0, 0, 0.4);
      }
    }
  `
		}));
	}
	function ensureStyleInjected(id, css) {
		const style = document.createElement("style");
		style.id = id;
		style.textContent = css;
		document.documentElement.appendChild(style);
	}
	function injectEmojiPickerStyles() {
		if (typeof document === "undefined") return;
		if (document.getElementById("emoji-picker-styles")) return;
		injectGlobalThemeStyles();
		ensureStyleInjected("emoji-picker-styles", `
.emoji-picker-hover-preview{
  position:fixed;
  pointer-events:none;
  display:none;
  z-index:1000002;
  max-width:320px;
  max-height:320px;
  overflow:hidden;
  border-radius:8px;
  box-shadow:0 6px 20px rgba(0,0,0,0.32);
  background:var(--emoji-preview-bg);
  padding:8px;
  transition:opacity .3s ease, transform .12s ease;
  border: 1px solid var(--emoji-preview-border);
  backdrop-filter: blur(10px);
}
.emoji-picker-hover-preview img.emoji-picker-hover-img{
  display:block;
  max-width:100%;
  max-height:220px;
  object-fit:contain;
}
.emoji-picker-hover-preview .emoji-picker-hover-label{
  font-size:12px;
  color:var(--emoji-preview-text);
  margin-top:8px;
  text-align:center;
  word-break:break-word;
  font-weight: 500;
}
`);
	}
	function isImageUrl(value) {
		if (!value) return false;
		let v = value.trim();
		if (/^url\(/i.test(v)) {
			const inner = v.replace(/^url\(/i, "").replace(/\)$/, "").trim();
			if (inner.startsWith("\"") && inner.endsWith("\"") || inner.startsWith("'") && inner.endsWith("'")) v = inner.slice(1, -1).trim();
			else v = inner;
		}
		if (v.startsWith("data:image/")) return true;
		if (v.startsWith("blob:")) return true;
		if (v.startsWith("//")) v = "https:" + v;
		if (/\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)(\?.*)?$/i.test(v)) return true;
		try {
			const url = new URL(v);
			const protocol = url.protocol;
			if (protocol === "http:" || protocol === "https:" || protocol.endsWith(":")) {
				if (/\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)(\?.*)?$/i.test(url.pathname)) return true;
				if (/format=|ext=|type=image|image_type=/i.test(url.search)) return true;
			}
		} catch {}
		return false;
	}
	function isMobileView() {
		try {
			return getEffectivePlatform() === "mobile" || !!(userscriptState && userscriptState.settings && userscriptState.settings.forceMobileMode);
		} catch (e) {
			return false;
		}
	}
	function insertEmojiIntoEditor(emoji) {
		console.log("[Emoji Extension Userscript] Inserting emoji:", emoji);
		if (emoji.name && emoji.url) trackEmojiUsage(emoji.name, emoji.url);
		const selectors = [
			"textarea.d-editor-input",
			"textarea.ember-text-area",
			"#channel-composer",
			".chat-composer__input",
			"textarea.chat-composer__input"
		];
		const proseMirror = document.querySelector(".ProseMirror.d-editor-input");
		let textarea = null;
		for (const s of selectors) {
			const el = document.querySelector(s);
			if (el) {
				textarea = el;
				break;
			}
		}
		const contentEditable = document.querySelector("[contenteditable=\"true\"]");
		if (!textarea && !proseMirror && !contentEditable) {
			console.error("找不到输入框");
			return;
		}
		const dimensionMatch = emoji.url?.match(/_(\d{3,})x(\d{3,})\./);
		let width = "500";
		let height = "500";
		if (dimensionMatch) {
			width = dimensionMatch[1];
			height = dimensionMatch[2];
		} else if (emoji.width && emoji.height) {
			width = emoji.width.toString();
			height = emoji.height.toString();
		}
		const scale = userscriptState.settings?.imageScale || 30;
		const outputFormat = userscriptState.settings?.outputFormat || "markdown";
		if (textarea) {
			let insertText = "";
			if (outputFormat === "html") {
				const scaledWidth = Math.max(1, Math.round(Number(width) * (scale / 100)));
				const scaledHeight = Math.max(1, Math.round(Number(height) * (scale / 100)));
				insertText = `<img src="${emoji.url}" title=":${emoji.name}:" class="emoji only-emoji" alt=":${emoji.name}:" loading="lazy" width="${scaledWidth}" height="${scaledHeight}" style="aspect-ratio: ${scaledWidth} / ${scaledHeight};"> `;
			} else insertText = `![${emoji.name}|${width}x${height},${scale}%](${emoji.url}) `;
			const selectionStart = textarea.selectionStart;
			const selectionEnd = textarea.selectionEnd;
			textarea.value = textarea.value.substring(0, selectionStart) + insertText + textarea.value.substring(selectionEnd, textarea.value.length);
			textarea.selectionStart = textarea.selectionEnd = selectionStart + insertText.length;
			textarea.focus();
			const inputEvent = new Event("input", {
				bubbles: true,
				cancelable: true
			});
			textarea.dispatchEvent(inputEvent);
		} else if (proseMirror) {
			const imgWidth = Number(width) || 500;
			const scaledWidth = Math.max(1, Math.round(imgWidth * (scale / 100)));
			const htmlContent = `<img src="${emoji.url}" alt="${emoji.name}" width="${width}" height="${height}" data-scale="${scale}" style="width: ${scaledWidth}px">`;
			try {
				const dataTransfer = new DataTransfer();
				dataTransfer.setData("text/html", htmlContent);
				const pasteEvent = new ClipboardEvent("paste", {
					clipboardData: dataTransfer,
					bubbles: true
				});
				proseMirror.dispatchEvent(pasteEvent);
			} catch (error) {
				try {
					document.execCommand("insertHTML", false, htmlContent);
				} catch (fallbackError) {
					console.error("无法向富文本编辑器中插入表情", fallbackError);
				}
			}
		} else if (contentEditable) try {
			if (outputFormat === "html") {
				const imgWidth = Number(width) || 500;
				const scaledWidth = Math.max(1, Math.round(imgWidth * (scale / 100)));
				const htmlContent = `<img src="${emoji.url}" alt="${emoji.name}" width="${width}" height="${height}" data-scale="${scale}" style="width: ${scaledWidth}px"> `;
				const sel = window.getSelection();
				if (sel && sel.rangeCount > 0) {
					const range = sel.getRangeAt(0);
					const frag = document.createRange().createContextualFragment(htmlContent);
					range.deleteContents();
					range.insertNode(frag);
					range.collapse(false);
					sel.removeAllRanges();
					sel.addRange(range);
				} else contentEditable.insertAdjacentHTML("beforeend", htmlContent);
			} else {
				const insertText = `![${emoji.name}|${width}x${height},${scale}%](${emoji.url}) `;
				const textNode = document.createTextNode(insertText);
				const sel = window.getSelection();
				if (sel && sel.rangeCount > 0) {
					const range = sel.getRangeAt(0);
					range.deleteContents();
					range.insertNode(textNode);
					range.setStartAfter(textNode);
					range.collapse(true);
					sel.removeAllRanges();
					sel.addRange(range);
				} else contentEditable.appendChild(textNode);
			}
			const inputEvent = new Event("input", {
				bubbles: true,
				cancelable: true
			});
			contentEditable.dispatchEvent(inputEvent);
		} catch (e) {
			console.error("无法向 contenteditable 插入表情", e);
		}
	}
	function createMobileEmojiPicker(groups) {
		const modal = createEl("div", {
			className: "modal d-modal fk-d-menu-modal emoji-picker-content",
			attrs: {
				"data-identifier": "emoji-picker",
				"data-keyboard": "false",
				"aria-modal": "true",
				role: "dialog"
			}
		});
		const modalContainerDiv = createEl("div", { className: "d-modal__container" });
		const modalBody = createEl("div", { className: "d-modal__body" });
		modalBody.tabIndex = -1;
		const emojiPickerDiv = createEl("div", { className: "emoji-picker" });
		const filterContainer = createEl("div", { className: "emoji-picker__filter-container" });
		const filterInputContainer = createEl("div", { className: "emoji-picker__filter filter-input-container" });
		const filterInput = createEl("input", {
			className: "filter-input",
			placeholder: "按表情符号名称搜索…",
			type: "text"
		});
		filterInputContainer.appendChild(filterInput);
		const closeButton = createEl("button", {
			className: "btn no-text btn-icon btn-transparent emoji-picker__close-btn",
			type: "button",
			innerHTML: `<svg class="fa d-icon d-icon-xmark svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><use href="#xmark"></use></svg>`,
			on: { click: () => {
				(modal.closest(".modal-container") || modal)?.remove();
				document.querySelector(".d-modal__backdrop")?.remove();
			} }
		});
		filterContainer.appendChild(filterInputContainer);
		filterContainer.appendChild(closeButton);
		const content = createEl("div", { className: "emoji-picker__content" });
		const sectionsNav = createEl("div", { className: "emoji-picker__sections-nav" });
		const scrollableContent = createEl("div", { className: "emoji-picker__scrollable-content" });
		const sections = createEl("div", {
			className: "emoji-picker__sections",
			attrs: { role: "button" }
		});
		groups.forEach((group, index) => {
			if (!group?.emojis?.length) return;
			const navButton = createEl("button", {
				className: `btn no-text btn-flat emoji-picker__section-btn ${index === 0 ? "active" : ""}`,
				attrs: {
					tabindex: "-1",
					"data-section": group.id,
					type: "button"
				}
			});
			const iconVal = group.icon || "📁";
			if (isImageUrl(iconVal)) {
				const img = createEl("img", {
					src: iconVal,
					alt: group.name || "",
					className: "emoji",
					style: "width: 18px; height: 18px; object-fit: contain;"
				});
				navButton.appendChild(img);
			} else navButton.textContent = String(iconVal);
			navButton.title = group.name;
			navButton.addEventListener("click", () => {
				sectionsNav.querySelectorAll(".emoji-picker__section-btn").forEach((btn) => btn.classList.remove("active"));
				navButton.classList.add("active");
				const target = sections.querySelector(`[data-section="${group.id}"]`);
				if (target) target.scrollIntoView({
					behavior: "smooth",
					block: "start"
				});
			});
			sectionsNav.appendChild(navButton);
			const section = createEl("div", {
				className: "emoji-picker__section",
				attrs: {
					"data-section": group.id,
					role: "region",
					"aria-label": group.name
				}
			});
			const titleContainer = createEl("div", { className: "emoji-picker__section-title-container" });
			titleContainer.appendChild(createEl("h2", {
				className: "emoji-picker__section-title",
				text: group.name
			}));
			const sectionEmojis = createEl("div", { className: "emoji-picker__section-emojis" });
			group.emojis.forEach((emoji) => {
				if (!emoji || typeof emoji !== "object" || !emoji.url || !emoji.name) return;
				const img = createEl("img", {
					src: emoji.url,
					alt: emoji.name,
					className: "emoji",
					title: `:${emoji.name}:`,
					style: "width: 32px; height: 32px; object-fit: contain;",
					attrs: {
						"data-emoji": emoji.name,
						tabindex: "0",
						loading: "lazy"
					}
				});
				(function bindHover(imgEl, emo) {
					if (!userscriptState.settings?.enableFloatingPreview) return;
					const preview = ensureHoverPreview();
					const previewImg = preview.querySelector("img");
					const previewLabel = preview.querySelector(".emoji-picker-hover-label");
					let fadeTimer = null;
					function onEnter(e) {
						previewImg.src = emo.url;
						previewLabel.textContent = emo.name || "";
						preview.style.display = "block";
						preview.style.opacity = "1";
						preview.style.transition = "opacity 0.12s ease, transform 0.12s ease";
						if (fadeTimer) {
							clearTimeout(fadeTimer);
							fadeTimer = null;
						}
						fadeTimer = window.setTimeout(() => {
							preview.style.opacity = "0";
							setTimeout(() => {
								if (preview.style.opacity === "0") preview.style.display = "none";
							}, 300);
						}, 5e3);
						move(e);
					}
					function move(e) {
						const pad = 12;
						const vw = window.innerWidth;
						const vh = window.innerHeight;
						const rect = preview.getBoundingClientRect();
						let left = e.clientX + pad;
						let top = e.clientY + pad;
						if (left + rect.width > vw) left = e.clientX - rect.width - pad;
						if (top + rect.height > vh) top = e.clientY - rect.height - pad;
						preview.style.left = left + "px";
						preview.style.top = top + "px";
					}
					function onLeave() {
						if (fadeTimer) {
							clearTimeout(fadeTimer);
							fadeTimer = null;
						}
						preview.style.display = "none";
					}
					imgEl.addEventListener("mouseenter", onEnter);
					imgEl.addEventListener("mousemove", move);
					imgEl.addEventListener("mouseleave", onLeave);
				})(img, emoji);
				img.addEventListener("click", () => {
					insertEmojiIntoEditor(emoji);
					if (modal.parentElement) modal.parentElement.removeChild(modal);
				});
				img.addEventListener("keydown", (e) => {
					if (e.key === "Enter" || e.key === " ") {
						e.preventDefault();
						insertEmojiIntoEditor(emoji);
						if (modal.parentElement) modal.parentElement.removeChild(modal);
					}
				});
				sectionEmojis.appendChild(img);
			});
			section.appendChild(titleContainer);
			section.appendChild(sectionEmojis);
			sections.appendChild(section);
		});
		filterInput.addEventListener("input", (e) => {
			const q = (e.target.value || "").toLowerCase();
			sections.querySelectorAll("img").forEach((img) => {
				const emojiName = (img.dataset.emoji || "").toLowerCase();
				img.style.display = q === "" || emojiName.includes(q) ? "" : "none";
			});
			sections.querySelectorAll(".emoji-picker__section").forEach((section) => {
				const visibleEmojis = section.querySelectorAll("img:not([style*=\"display: none\"])");
				section.style.display = visibleEmojis.length > 0 ? "" : "none";
			});
		});
		scrollableContent.appendChild(sections);
		content.appendChild(sectionsNav);
		content.appendChild(scrollableContent);
		emojiPickerDiv.appendChild(filterContainer);
		emojiPickerDiv.appendChild(content);
		modalBody.appendChild(emojiPickerDiv);
		modalContainerDiv.appendChild(modalBody);
		modal.appendChild(modalContainerDiv);
		return modal;
	}
	function createDesktopEmojiPicker(groups) {
		const picker = createEl("div", {
			className: "fk-d-menu -animated -expanded",
			style: "max-width: 400px; visibility: visible; z-index: 999999;",
			attrs: {
				"data-identifier": "emoji-picker",
				role: "dialog"
			}
		});
		const innerContent = createEl("div", { className: "fk-d-menu__inner-content" });
		const emojiPickerDiv = createEl("div", { className: "emoji-picker" });
		const filterContainer = createEl("div", { className: "emoji-picker__filter-container" });
		const filterDiv = createEl("div", { className: "emoji-picker__filter filter-input-container" });
		const searchInput = createEl("input", {
			className: "filter-input",
			placeholder: "按表情符号名称搜索…",
			type: "text"
		});
		filterDiv.appendChild(searchInput);
		filterContainer.appendChild(filterDiv);
		const content = createEl("div", { className: "emoji-picker__content" });
		const sectionsNav = createEl("div", { className: "emoji-picker__sections-nav" });
		const scrollableContent = createEl("div", { className: "emoji-picker__scrollable-content" });
		const sections = createEl("div", {
			className: "emoji-picker__sections",
			attrs: { role: "button" }
		});
		groups.forEach((group, index) => {
			if (!group?.emojis?.length) return;
			const navButton = createEl("button", {
				className: `btn no-text btn-flat emoji-picker__section-btn ${index === 0 ? "active" : ""}`,
				attrs: {
					tabindex: "-1",
					"data-section": group.id
				},
				type: "button"
			});
			const iconVal = group.icon || "📁";
			if (isImageUrl(iconVal)) {
				const img = createEl("img", {
					src: iconVal,
					alt: group.name || "",
					className: "emoji-group-icon",
					style: "width: 18px; height: 18px; object-fit: contain;"
				});
				navButton.appendChild(img);
			} else navButton.textContent = String(iconVal);
			navButton.title = group.name;
			navButton.addEventListener("click", () => {
				sectionsNav.querySelectorAll(".emoji-picker__section-btn").forEach((btn) => btn.classList.remove("active"));
				navButton.classList.add("active");
				const target = sections.querySelector(`[data-section="${group.id}"]`);
				if (target) target.scrollIntoView({
					behavior: "smooth",
					block: "start"
				});
			});
			sectionsNav.appendChild(navButton);
			const section = createEl("div", {
				className: "emoji-picker__section",
				attrs: {
					"data-section": group.id,
					role: "region",
					"aria-label": group.name
				}
			});
			const titleContainer = createEl("div", { className: "emoji-picker__section-title-container" });
			const title = createEl("h2", {
				className: "emoji-picker__section-title",
				text: group.name
			});
			titleContainer.appendChild(title);
			const sectionEmojis = createEl("div", { className: "emoji-picker__section-emojis" });
			let added = 0;
			group.emojis.forEach((emoji) => {
				if (!emoji || typeof emoji !== "object" || !emoji.url || !emoji.name) return;
				const img = createEl("img", {
					width: "32px",
					height: "32px",
					className: "emoji",
					src: emoji.url,
					alt: emoji.name,
					title: `:${emoji.name}:`,
					attrs: {
						"data-emoji": emoji.name,
						tabindex: "0",
						loading: "lazy"
					}
				});
				(function bindHover(imgEl, emo) {
					if (!userscriptState.settings?.enableFloatingPreview) return;
					const preview = ensureHoverPreview();
					const previewImg = preview.querySelector("img");
					const previewLabel = preview.querySelector(".emoji-picker-hover-label");
					let fadeTimer = null;
					function onEnter(e) {
						previewImg.src = emo.url;
						previewLabel.textContent = emo.name || "";
						preview.style.display = "block";
						preview.style.opacity = "1";
						preview.style.transition = "opacity 0.12s ease, transform 0.12s ease";
						if (fadeTimer) {
							clearTimeout(fadeTimer);
							fadeTimer = null;
						}
						fadeTimer = window.setTimeout(() => {
							preview.style.opacity = "0";
							setTimeout(() => {
								if (preview.style.opacity === "0") preview.style.display = "none";
							}, 300);
						}, 5e3);
						move(e);
					}
					function move(e) {
						const pad = 12;
						const vw = window.innerWidth;
						const vh = window.innerHeight;
						const rect = preview.getBoundingClientRect();
						let left = e.clientX + pad;
						let top = e.clientY + pad;
						if (left + rect.width > vw) left = e.clientX - rect.width - pad;
						if (top + rect.height > vh) top = e.clientY - rect.height - pad;
						preview.style.left = left + "px";
						preview.style.top = top + "px";
					}
					function onLeave() {
						if (fadeTimer) {
							clearTimeout(fadeTimer);
							fadeTimer = null;
						}
						preview.style.display = "none";
					}
					imgEl.addEventListener("mouseenter", onEnter);
					imgEl.addEventListener("mousemove", move);
					imgEl.addEventListener("mouseleave", onLeave);
				})(img, emoji);
				img.addEventListener("click", () => {
					insertEmojiIntoEditor(emoji);
					picker.remove();
				});
				img.addEventListener("keydown", (e) => {
					if (e.key === "Enter" || e.key === " ") {
						e.preventDefault();
						insertEmojiIntoEditor(emoji);
						picker.remove();
					}
				});
				sectionEmojis.appendChild(img);
				added++;
			});
			if (added === 0) {
				const msg = createEl("div", {
					text: `${group.name} 组暂无有效表情`,
					style: "padding: 20px; text-align: center; color: #999;"
				});
				sectionEmojis.appendChild(msg);
			}
			section.appendChild(titleContainer);
			section.appendChild(sectionEmojis);
			sections.appendChild(section);
		});
		searchInput.addEventListener("input", (e) => {
			const q = (e.target.value || "").toLowerCase();
			sections.querySelectorAll("img").forEach((img) => {
				const emojiName = img.getAttribute("data-emoji")?.toLowerCase() || "";
				img.style.display = q === "" || emojiName.includes(q) ? "" : "none";
			});
			sections.querySelectorAll(".emoji-picker__section").forEach((section) => {
				const visibleEmojis = section.querySelectorAll("img:not([style*=\"none\"])");
				const titleContainer = section.querySelector(".emoji-picker__section-title-container");
				if (titleContainer) titleContainer.style.display = visibleEmojis.length > 0 ? "" : "none";
			});
		});
		scrollableContent.appendChild(sections);
		content.appendChild(sectionsNav);
		content.appendChild(scrollableContent);
		emojiPickerDiv.appendChild(filterContainer);
		emojiPickerDiv.appendChild(content);
		innerContent.appendChild(emojiPickerDiv);
		picker.appendChild(innerContent);
		return picker;
	}
	async function createEmojiPicker() {
		const groups = userscriptState.emojiGroups;
		const mobile = isMobileView();
		try {
			injectEmojiPickerStyles();
		} catch (e) {
			console.warn("injectEmojiPickerStyles failed", e);
		}
		if (mobile) return createMobileEmojiPicker(groups);
		else return createDesktopEmojiPicker(groups);
	}
	function showTemporaryMessage(message, duration = 2e3) {
		const messageEl = createEl("div", {
			style: `
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background: var(--emoji-modal-primary-bg);
      color: white;
      padding: 12px 24px;
      border-radius: 6px;
      z-index: 9999999;
      font-size: 14px;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
      animation: fadeInOut 2s ease-in-out;
    `,
			text: message
		});
		if (!document.querySelector("#tempMessageStyles")) {
			const style = createEl("style", {
				id: "tempMessageStyles",
				text: `
      @keyframes fadeInOut {
        0%, 100% { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
        20%, 80% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
      }
    `
			});
			document.head.appendChild(style);
		}
		document.body.appendChild(messageEl);
		setTimeout(() => {
			try {
				messageEl.remove();
			} catch {}
		}, duration);
	}
	function createModalElement(options) {
		const modal = createEl("div", {
			style: `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 999999;
      display: flex;
      align-items: center;
      justify-content: center;
    `,
			className: options.className
		});
		const content = createEl("div", { style: `
      background: var(--secondary);
      color: var(--emoji-modal-text);
      border: 1px solid var(--emoji-modal-border);
      border-radius: 8px;
      padding: 24px;
      max-width: 90%;
      max-height: 90%;
      overflow-y: auto;
      position: relative;
    ` });
		if (options.title) {
			const titleElement = createEl("div", {
				style: `
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 20px;
      `,
				innerHTML: `
        <h2 style="margin: 0; color: var(--emoji-modal-text);">${options.title}</h2>
        <button id="closeModal" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #999;">×</button>
      `
			});
			content.appendChild(titleElement);
			const closeButton = content.querySelector("#closeModal");
			if (closeButton && options.onClose) closeButton.addEventListener("click", options.onClose);
		}
		if (options.content) {
			const contentDiv = createEl("div", { innerHTML: options.content });
			content.appendChild(contentDiv);
		}
		modal.appendChild(content);
		return modal;
	}
	function showPopularEmojisModal() {
		injectGlobalThemeStyles();
		const popularEmojis = getPopularEmojis(50);
		const contentHTML = `
    <div style="margin-bottom: 16px; padding: 12px; background: var(--emoji-modal-button-bg); border-radius: 6px; border: 1px solid var(--emoji-modal-border);">
      <div style="display: flex; justify-content: space-between; align-items: center;">
        <span style="font-weight: 500; color: var(--emoji-modal-label);">表情按使用次数排序</span>
        <span style="font-size: 12px; color: var(--emoji-modal-text);">点击表情直接使用</span>
      </div>
      <div style="font-size: 12px; color: var(--emoji-modal-text);">
        总使用次数:${popularEmojis.reduce((sum, emoji) => sum + emoji.count, 0)}
      </div>
    </div>
    
    <div id="popularEmojiGrid" style="
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
      gap: 8px;
      max-height: 400px;
      overflow-y: auto;
    ">
      ${popularEmojis.length === 0 ? "<div style=\"grid-column: 1/-1; text-align: center; padding: 40px; color: var(--emoji-modal-text);\">还没有使用过表情<br><small>开始使用表情后,这里会显示常用的表情</small></div>" : popularEmojis.map((emoji) => `
          <div class="popular-emoji-item" data-name="${emoji.name}" data-url="${emoji.url}" style="
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 8px;
            border: 1px solid var(--emoji-modal-border);
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.2s;
            background: var(--emoji-modal-button-bg);
          ">
            <img src="${emoji.url}" alt="${emoji.name}" style="
              width: 40px;
              height: 40px;
              object-fit: contain;
              margin-bottom: 4px;
            ">
            <div style="
              font-size: 11px;
              font-weight: 500;
              color: var(--emoji-modal-text);
              text-align: center;
              word-break: break-all;
              line-height: 1.2;
              margin-bottom: 2px;
            ">${emoji.name}</div>
            <div style="
              font-size: 10px;
              color: var(--emoji-modal-text);
              opacity: 0.6;
              text-align: center;
            ">使用${emoji.count}次</div>
          </div>
        `).join("")}
    </div>
    
    ${popularEmojis.length > 0 ? `
      <div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--emoji-modal-border); font-size: 12px; color: var(--emoji-modal-text); opacity: 0.6; text-align: center;">
        统计数据保存在本地,清空统计将重置所有使用记录
      </div>
    ` : ""}
  `;
		const modal = createModalElement({
			title: `常用表情 (${popularEmojis.length})`,
			content: contentHTML,
			onClose: () => modal.remove()
		});
		const titleDiv = modal.querySelector("div:first-child > div:first-child, div:first-child > h2 + div");
		if (titleDiv) {
			const clearStatsButton = createEl("button", {
				id: "clearStats",
				text: "清空统计",
				style: "padding: 6px 12px; background: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; margin-right: 8px;"
			});
			titleDiv.appendChild(clearStatsButton);
			clearStatsButton.addEventListener("click", () => {
				if (confirm("确定要清空所有表情使用统计吗?此操作不可撤销。")) {
					clearEmojiUsageStats();
					modal.remove();
					showTemporaryMessage("表情使用统计已清空");
					setTimeout(() => showPopularEmojisModal(), 300);
				}
			});
		}
		const content = modal.querySelector("div:last-child");
		document.body.appendChild(modal);
		ensureStyleInjected("popular-emojis-styles", `
    .popular-emoji-item:hover {
      transform: translateY(-2px);
      border-color: var(--emoji-modal-primary-bg) !important;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }
  `);
		content.querySelectorAll(".popular-emoji-item").forEach((item) => {
			item.addEventListener("click", () => {
				const name = item.getAttribute("data-name");
				const url = item.getAttribute("data-url");
				if (name && url) {
					trackEmojiUsage(name, url);
					useEmojiFromPopular(name, url);
					modal.remove();
					showTemporaryMessage(`已使用表情: ${name}`);
				}
			});
		});
	}
	function useEmojiFromPopular(name, url) {
		const activeElement = document.activeElement;
		if (activeElement && (activeElement.tagName === "TEXTAREA" || activeElement.tagName === "INPUT")) {
			const textArea = activeElement;
			const format = userscriptState.settings.outputFormat;
			let emojiText = "";
			if (format === "markdown") emojiText = `![${name}](${url})`;
			else emojiText = `<img src="${url}" alt="${name}" style="width: ${userscriptState.settings.imageScale}px; height: ${userscriptState.settings.imageScale}px;">`;
			const start = textArea.selectionStart || 0;
			const end = textArea.selectionEnd || 0;
			const currentValue = textArea.value;
			textArea.value = currentValue.slice(0, start) + emojiText + currentValue.slice(end);
			const newPosition = start + emojiText.length;
			textArea.setSelectionRange(newPosition, newPosition);
			textArea.dispatchEvent(new Event("input", { bubbles: true }));
			textArea.focus();
		} else {
			const textAreas = document.querySelectorAll("textarea, input[type=\"text\"], [contenteditable=\"true\"]");
			const lastTextArea = Array.from(textAreas).pop();
			if (lastTextArea) {
				lastTextArea.focus();
				if (lastTextArea.tagName === "TEXTAREA" || lastTextArea.tagName === "INPUT") {
					const format = userscriptState.settings.outputFormat;
					let emojiText = "";
					if (format === "markdown") emojiText = `![${name}](${url})`;
					else emojiText = `<img src="${url}" alt="${name}" style="width: ${userscriptState.settings.imageScale}px; height: ${userscriptState.settings.imageScale}px;">`;
					const textarea = lastTextArea;
					textarea.value += emojiText;
					textarea.dispatchEvent(new Event("input", { bubbles: true }));
				}
			}
		}
	}
	var currentPicker = null;
	function closeCurrentPicker() {
		if (currentPicker) {
			currentPicker.remove();
			currentPicker = null;
		}
	}
	function injectCustomMenuButtons(menu) {
		if (menu.querySelector(".emoji-extension-menu-item")) return;
		let dropdownMenu = menu.querySelector("ul.dropdown-menu");
		if (!dropdownMenu) dropdownMenu = menu.querySelector("ul.chat-composer-dropdown__list");
		if (!dropdownMenu) {
			console.warn("[Emoji Extension Userscript] No dropdown-menu or chat-composer-dropdown__list found in expanded menu");
			return;
		}
		const isChatComposerMenu = dropdownMenu.classList.contains("chat-composer-dropdown__list");
		const itemClassName = isChatComposerMenu ? "chat-composer-dropdown__item emoji-extension-menu-item" : "dropdown-menu__item emoji-extension-menu-item";
		const btnClassName = isChatComposerMenu ? "btn btn-icon-text chat-composer-dropdown__action-btn btn-transparent" : "btn btn-icon-text";
		const emojiPickerItem = createEl("li", { className: itemClassName });
		const emojiPickerBtn = createEl("button", {
			className: btnClassName,
			type: "button",
			title: "表情包选择器",
			innerHTML: `
      <svg class="fa d-icon d-icon-smile svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><use href="#far-face-smile"></use></svg>
      <span class="d-button-label">表情包选择器</span>
    `
		});
		emojiPickerBtn.addEventListener("click", async (e) => {
			e.stopPropagation();
			if (menu.parentElement) menu.remove();
			if (currentPicker) {
				closeCurrentPicker();
				return;
			}
			currentPicker = await createEmojiPicker();
			if (!currentPicker) return;
			document.body.appendChild(currentPicker);
			currentPicker.style.position = "fixed";
			currentPicker.style.top = "0";
			currentPicker.style.left = "0";
			currentPicker.style.right = "0";
			currentPicker.style.bottom = "0";
			currentPicker.style.zIndex = "999999";
			setTimeout(() => {
				const handleClick = (e$1) => {
					if (currentPicker && !currentPicker.contains(e$1.target)) {
						closeCurrentPicker();
						document.removeEventListener("click", handleClick);
					}
				};
				document.addEventListener("click", handleClick);
			}, 100);
		});
		emojiPickerItem.appendChild(emojiPickerBtn);
		dropdownMenu.appendChild(emojiPickerItem);
		console.log("[Emoji Extension Userscript] Custom menu buttons injected");
	}
	function injectEmojiButton(toolbar) {
		if (toolbar.querySelector(".emoji-extension-button")) return;
		const isChatComposer = toolbar.classList.contains("chat-composer__inner-container");
		const button = createEl("button", {
			className: "btn no-text btn-icon toolbar__button nacho-emoji-picker-button emoji-extension-button",
			title: "表情包",
			type: "button",
			innerHTML: "🐈‍⬛"
		});
		const popularButton = createEl("button", {
			className: "btn no-text btn-icon toolbar__button nacho-emoji-popular-button emoji-extension-button",
			title: "常用表情",
			type: "button",
			innerHTML: "⭐"
		});
		if (isChatComposer) {
			button.classList.add("fk-d-menu__trigger", "emoji-picker-trigger", "chat-composer-button", "btn-transparent", "-emoji");
			button.setAttribute("aria-expanded", "false");
			button.setAttribute("data-identifier", "emoji-picker");
			button.setAttribute("data-trigger", "");
			popularButton.classList.add("fk-d-menu__trigger", "popular-emoji-trigger", "chat-composer-button", "btn-transparent", "-popular");
			popularButton.setAttribute("aria-expanded", "false");
			popularButton.setAttribute("data-identifier", "popular-emoji");
			popularButton.setAttribute("data-trigger", "");
		}
		button.addEventListener("click", async (e) => {
			e.stopPropagation();
			if (currentPicker) {
				closeCurrentPicker();
				return;
			}
			currentPicker = await createEmojiPicker();
			if (!currentPicker) return;
			document.body.appendChild(currentPicker);
			const buttonRect = button.getBoundingClientRect();
			if (currentPicker.classList.contains("modal") || currentPicker.className.includes("d-modal")) {
				currentPicker.style.position = "fixed";
				currentPicker.style.top = "0";
				currentPicker.style.left = "0";
				currentPicker.style.right = "0";
				currentPicker.style.bottom = "0";
				currentPicker.style.zIndex = "999999";
			} else {
				currentPicker.style.position = "fixed";
				const margin = 8;
				const vpWidth = window.innerWidth;
				const vpHeight = window.innerHeight;
				currentPicker.style.top = buttonRect.bottom + margin + "px";
				currentPicker.style.left = buttonRect.left + "px";
				const pickerRect = currentPicker.getBoundingClientRect();
				const spaceBelow = vpHeight - buttonRect.bottom;
				const neededHeight = pickerRect.height + margin;
				let top = buttonRect.bottom + margin;
				if (spaceBelow < neededHeight) top = Math.max(margin, buttonRect.top - pickerRect.height - margin);
				let left = buttonRect.left;
				if (left + pickerRect.width + margin > vpWidth) left = Math.max(margin, vpWidth - pickerRect.width - margin);
				if (left < margin) left = margin;
				currentPicker.style.top = top + "px";
				currentPicker.style.left = left + "px";
			}
			setTimeout(() => {
				const handleClick = (e$1) => {
					if (currentPicker && !currentPicker.contains(e$1.target) && e$1.target !== button) {
						closeCurrentPicker();
						document.removeEventListener("click", handleClick);
					}
				};
				document.addEventListener("click", handleClick);
			}, 100);
		});
		popularButton.addEventListener("click", (e) => {
			e.stopPropagation();
			closeCurrentPicker();
			showPopularEmojisModal();
		});
		try {
			if (isChatComposer) {
				const existingEmojiTrigger = toolbar.querySelector(".emoji-picker-trigger:not(.emoji-extension-button)");
				if (existingEmojiTrigger) {
					toolbar.insertBefore(button, existingEmojiTrigger);
					toolbar.insertBefore(popularButton, existingEmojiTrigger);
				} else {
					toolbar.appendChild(button);
					toolbar.appendChild(popularButton);
				}
			} else {
				toolbar.appendChild(button);
				toolbar.appendChild(popularButton);
			}
		} catch (error) {
			console.error("[Emoji Extension Userscript] Failed to inject button:", error);
		}
	}
	var domObserver = null;
	function setupDomObserver() {
		if (domObserver) return;
		domObserver = new MutationObserver((mutations) => {
			let hasChanges = false;
			for (const mutation of mutations) {
				if (mutation.type === "childList" && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) {
					hasChanges = true;
					break;
				}
				if (mutation.type === "attributes") {
					hasChanges = true;
					break;
				}
			}
			if (hasChanges) {}
		});
		domObserver.observe(document, {
			childList: true,
			subtree: true,
			attributes: true,
			attributeFilter: ["class", "id"]
		});
		console.log("[Emoji Extension Userscript] DOM observer set up for force mobile mode");
	}
	var toolbarOptionsTriggerInitialized = false;
	var chatComposerTriggerInitialized = false;
	function setupForceMobileMenuTriggers() {
		if (!(userscriptState.settings?.forceMobileMode || false)) return;
		const portalContainer = document.querySelector("#d-menu-portals");
		if (!portalContainer) {
			console.log("[Emoji Extension Userscript] #d-menu-portals not found, skipping force mobile menu triggers");
			return;
		}
		console.log("[Emoji Extension Userscript] Force mobile mode enabled, setting up menu triggers");
		setupDomObserver();
		new MutationObserver((mutations) => {
			mutations.forEach((mutation) => {
				mutation.addedNodes.forEach((node) => {
					if (node.nodeType === Node.ELEMENT_NODE) {
						const element = node;
						if (element.classList.contains("toolbar-menu__options-content") || element.classList.contains("chat-composer-dropdown__content") || element.classList.contains("chat-composer-dropdown__menu-content")) {
							console.log("[Emoji Extension Userscript] Menu expanded in portal, injecting custom buttons");
							injectCustomMenuButtons(element);
						}
					}
				});
			});
		}).observe(portalContainer, {
			childList: true,
			subtree: true
		});
		new MutationObserver((mutations) => {
			mutations.forEach((mutation) => {
				mutation.addedNodes.forEach((node) => {
					if (node.nodeType === Node.ELEMENT_NODE) {
						const element = node;
						if (element.classList.contains("modal-container")) {
							let modalMenu = element.querySelector(".toolbar-menu__options-content[data-identifier=\"toolbar-menu__options\"]");
							if (!modalMenu) modalMenu = element.querySelector(".chat-composer-dropdown__menu-content[data-identifier=\"chat-composer-dropdown__menu\"]");
							if (modalMenu) {
								console.log("[Emoji Extension Userscript] Modal menu detected (immediate), injecting custom buttons");
								injectCustomMenuButtons(modalMenu);
							} else {
								const modalContentObserver = new MutationObserver(() => {
									let delayedMenu = element.querySelector(".toolbar-menu__options-content[data-identifier=\"toolbar-menu__options\"]");
									if (!delayedMenu) delayedMenu = element.querySelector(".chat-composer-dropdown__menu-content[data-identifier=\"chat-composer-dropdown__menu\"]");
									if (delayedMenu) {
										console.log("[Emoji Extension Userscript] Modal menu detected (delayed), injecting custom buttons");
										injectCustomMenuButtons(delayedMenu);
										modalContentObserver.disconnect();
									}
								});
								modalContentObserver.observe(element, {
									childList: true,
									subtree: true
								});
								setTimeout(() => modalContentObserver.disconnect(), 1e3);
							}
						}
					}
				});
			});
		}).observe(document.body, {
			childList: true,
			subtree: false
		});
		try {
			const existingModal = document.querySelector(".modal-container");
			if (existingModal) {
				const existingMenu = existingModal.querySelector(".toolbar-menu__options-content[data-identifier=\"toolbar-menu__options\"]");
				if (existingMenu) {
					console.log("[Emoji Extension Userscript] Found existing modal menu at init, injecting custom buttons");
					injectCustomMenuButtons(existingMenu);
				}
			}
		} catch (e) {}
		const toolbarOptionsTrigger = document.querySelector("button.toolbar-menu__options-trigger[data-identifier=\"toolbar-menu__options\"]:not(.emoji-detected)");
		if (toolbarOptionsTrigger) {
			toolbarOptionsTrigger.classList.add("emoji-detected");
			toolbarOptionsTrigger.classList.add("emoji-attached");
			if (!toolbarOptionsTrigger.dataset.emojiListenerAttached) {
				toolbarOptionsTrigger.addEventListener("click", () => {
					const checkMenu = (attempt = 0) => {
						const modalContainer = document.querySelector(".modal-container");
						let menu = null;
						if (modalContainer) menu = modalContainer.querySelector(".toolbar-menu__options-content[data-identifier=\"toolbar-menu__options\"]");
						if (!menu) menu = document.querySelector(".toolbar-menu__options-content[data-identifier=\"toolbar-menu__options\"]");
						if (menu) injectCustomMenuButtons(menu);
						else if (attempt < 5) setTimeout(() => checkMenu(attempt + 1), 20);
					};
					checkMenu();
				});
				toolbarOptionsTrigger.dataset.emojiListenerAttached = "true";
				console.log("[Emoji Extension Userscript] Toolbar options trigger listener added");
			}
			toolbarOptionsTriggerInitialized = true;
		}
		const chatComposerTrigger = document.querySelector("button.chat-composer-dropdown__trigger-btn[data-identifier=\"chat-composer-dropdown__menu\"]:not(.emoji-detected), button.chat-composer-dropdown__menu-trigger[data-identifier=\"chat-composer-dropdown__menu\"]:not(.emoji-detected), button.chat-composer-dropdown__menu-trigger.chat-composer-dropdown__trigger-btn:not(.emoji-detected)");
		if (chatComposerTrigger) {
			chatComposerTrigger.classList.add("emoji-detected");
			chatComposerTrigger.classList.add("emoji-attached");
			if (!chatComposerTrigger.dataset.emojiListenerAttached) {
				chatComposerTrigger.addEventListener("click", () => {
					setTimeout(() => {
						const menu = document.querySelector(".chat-composer-dropdown__content[data-identifier=\"chat-composer-dropdown__menu\"], .chat-composer-dropdown__menu-content[data-identifier=\"chat-composer-dropdown__menu\"]");
						if (menu) injectCustomMenuButtons(menu);
					}, 100);
				});
				chatComposerTrigger.dataset.emojiListenerAttached = "true";
				console.log("[Emoji Extension Userscript] Chat composer trigger listener added");
			}
			chatComposerTriggerInitialized = true;
		}
	}
	var toolbarTriggersAttached = /* @__PURE__ */ new Set();
	var chatComposerTriggersAttached = /* @__PURE__ */ new Set();
	function setupForceMobileToolbarListeners() {
		if (!(userscriptState.settings?.forceMobileMode || false)) return;
		getPlatformToolbarSelectors().forEach((selector) => {
			Array.from(document.querySelectorAll(selector)).forEach((toolbar) => {
				try {
					Array.from(toolbar.querySelectorAll("button.toolbar-menu__options-trigger[data-identifier=\"toolbar-menu__options\"]:not(.emoji-detected):not(.emoji-attached), button.toolbar-menu__options-trigger:not(.emoji-detected):not(.emoji-attached)")).forEach((trigger) => {
						const triggerId = `toolbar-${trigger.id || Math.random().toString(36).substr(2, 9)}`;
						if (toolbarTriggersAttached.has(triggerId)) return;
						trigger.classList.add("emoji-detected");
						trigger.classList.add("emoji-attached");
						const handler = () => {
							const checkMenu = (attempt = 0) => {
								const modalContainer = document.querySelector(".modal-container");
								let menu = null;
								if (modalContainer) menu = modalContainer.querySelector(".toolbar-menu__options-content[data-identifier=\"toolbar-menu__options\"]");
								if (!menu) menu = document.querySelector(".toolbar-menu__options-content[data-identifier=\"toolbar-menu__options\"]");
								if (menu) injectCustomMenuButtons(menu);
								else if (attempt < 5) setTimeout(() => checkMenu(attempt + 1), 20);
							};
							checkMenu();
						};
						trigger.addEventListener("click", handler);
						trigger.dataset.emojiListenerAttached = "true";
						toolbarTriggersAttached.add(triggerId);
					});
					Array.from(toolbar.querySelectorAll("button.chat-composer-dropdown__trigger-btn:not(.emoji-detected):not(.emoji-attached), button.chat-composer-dropdown__menu-trigger:not(.emoji-detected):not(.emoji-attached), button.chat-composer-dropdown__trigger-btn:not(.emoji-detected):not(.emoji-attached), button.chat-composer-dropdown__menu-trigger:not(.emoji-detected):not(.emoji-attached)")).forEach((trigger) => {
						const triggerId = `chat-${trigger.id || Math.random().toString(36).substr(2, 9)}`;
						if (chatComposerTriggersAttached.has(triggerId)) return;
						trigger.classList.add("emoji-detected");
						trigger.classList.add("emoji-attached");
						const handler = () => {
							setTimeout(() => {
								const menu = document.querySelector(".chat-composer-dropdown__content[data-identifier=\"chat-composer-dropdown__menu\"], .chat-composer-dropdown__menu-content[data-identifier=\"chat-composer-dropdown__menu\"]");
								if (menu) injectCustomMenuButtons(menu);
							}, 80);
						};
						trigger.addEventListener("click", handler);
						trigger.dataset.emojiListenerAttached = "true";
						chatComposerTriggersAttached.add(triggerId);
					});
				} catch (e) {
					console.warn("[Emoji Extension Userscript] Failed to attach force-mobile listeners to toolbar", e);
				}
			});
		});
	}
	var _forceMobileToolbarIntervalId = null;
	var _domChangeCheckIntervalId = null;
	var _buttonExistenceCheckIntervalId = null;
	function startForceMobileToolbarListenerInterval(intervalMs = 1e3) {
		if (!(userscriptState.settings?.forceMobileMode || false)) return;
		if (_forceMobileToolbarIntervalId !== null) return;
		try {
			setupForceMobileToolbarListeners();
		} catch (e) {}
		_forceMobileToolbarIntervalId = window.setInterval(() => {
			try {
				setupForceMobileToolbarListeners();
			} catch (e) {}
		}, intervalMs);
	}
	function startDomChangeCheckInterval() {
		if (!(userscriptState.settings?.forceMobileMode || false)) return;
		if (_domChangeCheckIntervalId !== null) return;
		try {
			checkButtonsAndInjectIfNeeded();
		} catch (e) {}
		_domChangeCheckIntervalId = window.setInterval(() => {
			try {
				checkButtonsAndInjectIfNeeded();
			} catch (e) {}
		}, 1e3);
	}
	function startButtonExistenceCheckInterval() {
		if (_buttonExistenceCheckIntervalId !== null) return;
		_buttonExistenceCheckIntervalId = window.setInterval(() => {
			try {
				if (document.querySelectorAll(".emoji-extension-menu-item").length === 0) {
					setupForceMobileMenuTriggers();
					setupForceMobileToolbarListeners();
				}
			} catch (e) {}
		}, 1e4);
	}
	function checkButtonsAndInjectIfNeeded() {
		if (!(userscriptState.settings?.forceMobileMode || false)) return;
		const toolbarTrigger = document.querySelector("button.toolbar-menu__options-trigger[data-identifier=\"toolbar-menu__options\"]:not(.emoji-detected)");
		const chatComposerTrigger = document.querySelector("button.chat-composer-dropdown__trigger-btn[data-identifier=\"chat-composer-dropdown__menu\"]:not(.emoji-detected), button.chat-composer-dropdown__menu-trigger[data-identifier=\"chat-composer-dropdown__menu\"]:not(.emoji-detected), button.chat-composer-dropdown__menu-trigger.chat-composer-dropdown__trigger-btn:not(.emoji-detected)");
		if (toolbarTrigger && !toolbarOptionsTriggerInitialized) {
			if (document.querySelector("button.toolbar-menu__options-trigger[data-identifier=\"toolbar-menu__options\"]:not(.emoji-detected)")) setupForceMobileMenuTriggers();
		}
		if (chatComposerTrigger && !chatComposerTriggerInitialized) {
			if (document.querySelector("button.chat-composer-dropdown__trigger-btn[data-identifier=\"chat-composer-dropdown__menu\"]:not(.emoji-detected), button.chat-composer-dropdown__menu-trigger[data-identifier=\"chat-composer-dropdown__menu\"]:not(.emoji-detected), button.chat-composer-dropdown__menu-trigger.chat-composer-dropdown__trigger-btn:not(.emoji-detected)")) setupForceMobileMenuTriggers();
		}
		const selectors = getPlatformToolbarSelectors();
		for (const selector of selectors) {
			const elements = Array.from(document.querySelectorAll(selector));
			for (const toolbar of elements) if (Array.from(toolbar.querySelectorAll("button.toolbar-menu__options-trigger:not(.emoji-detected), button.chat-composer-dropdown__trigger-btn:not(.emoji-detected), button.chat-composer-dropdown__menu-trigger:not(.emoji-detected), button.chat-composer-dropdown__menu-trigger.chat-composer-dropdown__trigger-btn:not(.emoji-detected)")).length > 0) {
				setupForceMobileToolbarListeners();
				break;
			}
		}
	}
	function startAllForceMobileIntervals() {
		startForceMobileToolbarListenerInterval(1e3);
		startDomChangeCheckInterval();
		startButtonExistenceCheckInterval();
	}
	function shouldSkipToolbarInjection() {
		if (!(userscriptState.settings?.forceMobileMode || false)) return false;
		return !!document.querySelector("#d-menu-portals");
	}
	function findAllToolbars() {
		if (shouldSkipToolbarInjection()) {
			console.log("[Emoji Extension Userscript] Force mobile mode with #d-menu-portals detected, skipping toolbar injection");
			return [];
		}
		const toolbars = [];
		const selectors = getPlatformToolbarSelectors();
		for (const selector of selectors) {
			const elements = document.querySelectorAll(selector);
			toolbars.push(...Array.from(elements));
		}
		return toolbars;
	}
	function attemptInjection() {
		const toolbars = findAllToolbars();
		let injectedCount = 0;
		toolbars.forEach((toolbar) => {
			if (!toolbar.querySelector(".emoji-extension-button")) {
				console.log("[Emoji Extension Userscript] Toolbar found, injecting button.");
				injectEmojiButton(toolbar);
				injectedCount++;
			}
		});
		setupForceMobileMenuTriggers();
		try {
			setupForceMobileToolbarListeners();
			try {
				startAllForceMobileIntervals();
			} catch (e) {}
		} catch (e) {}
		return {
			injectedCount,
			totalToolbars: toolbars.length
		};
	}
	function startPeriodicInjection() {
		setInterval(() => {
			findAllToolbars().forEach((toolbar) => {
				if (!toolbar.querySelector(".emoji-extension-button")) {
					console.log("[Emoji Extension Userscript] New toolbar found, injecting button.");
					injectEmojiButton(toolbar);
				}
			});
			setupForceMobileMenuTriggers();
			try {
				setupForceMobileToolbarListeners();
				startAllForceMobileIntervals();
			} catch (e) {}
		}, 3e4);
	}
	function userscriptNotify(message, type = "info", timeout = 4e3) {
		try {
			let container = document.getElementById("emoji-ext-userscript-toast");
			if (!container) {
				container = createEl("div", {
					attrs: {
						id: "emoji-ext-userscript-toast",
						"aria-live": "polite"
					},
					style: `
        position: fixed;
        right: 12px; 
        bottom: 12px; 
        z-index: 2147483646; 
        display:flex; 
        flex-direction:column; 
        gap:8px;
        `
				});
				try {
					if (document.body) document.body.appendChild(container);
					else document.documentElement.appendChild(container);
				} catch (e) {
					document.documentElement.appendChild(container);
				}
				container.style.position = "fixed";
				container.style.right = "12px";
				container.style.bottom = "12px";
				container.style.zIndex = String(2147483646);
				try {
					container.style.setProperty("z-index", String(2147483646), "important");
				} catch (_e) {}
				container.style.display = "flex";
				container.style.flexDirection = "column";
				container.style.gap = "8px";
				container.style.pointerEvents = "auto";
			}
			const el = createEl("div", {
				text: message,
				style: `padding:8px 12px; border-radius:6px; color:#fff; font-size:13px; max-width:320px; word-break:break-word; opacity:0; transform: translateY(8px); transition: all 220ms ease;`
			});
			if (type === "success") el.style.setProperty("background", "#16a34a", "important");
			else if (type === "error") el.style.setProperty("background", "#dc2626", "important");
			else el.style.setProperty("background", "#0369a1", "important");
			container.appendChild(el);
			el.offsetHeight;
			el.style.opacity = "1";
			el.style.transform = "translateY(0)";
			const id = setTimeout(() => {
				try {
					el.style.opacity = "0";
					el.style.transform = "translateY(8px)";
					setTimeout(() => el.remove(), 250);
				} catch (_e) {}
				clearTimeout(id);
			}, timeout);
			try {
				console.log("[UserscriptNotify] shown:", message, "type=", type);
			} catch (_e) {}
			return () => {
				try {
					el.remove();
				} catch (_e) {}
				clearTimeout(id);
			};
		} catch (_e) {
			return () => {};
		}
	}
	var floatingButton = null;
	var isButtonVisible = false;
	var FLOATING_BUTTON_STYLES = `
.emoji-extension-floating-container {
  position: fixed !important;
  bottom: 20px !important;
  right: 20px !important;
  display: flex !important;
  flex-direction: column !important;
  gap: 10px !important;
  z-index: 999999 !important;
}

.emoji-extension-floating-button {
  width: 56px !important;
  height: 56px !important;
  border-radius: 50% !important;
  background: transparent;
  border: none !important;
  box-shadow: 0 4px 12px var(--emoji-button-shadow) !important;
  cursor: pointer !important;
  color: white !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  transition: all 0.2s ease !important;
  opacity: 0.95 !important;
}

.emoji-extension-floating-button:hover { 
  transform: scale(1.05) !important;
 }
.emoji-extension-floating-button:active { transform: scale(0.95) !important; }

.emoji-extension-floating-button.secondary {
  background: linear-gradient(135deg, #56ab2f 0%, #a8e6cf 100%) !important;
}

.emoji-extension-floating-button.hidden {
  opacity: 0 !important;
  pointer-events: none !important;
  transform: translateY(20px) !important;
}

@media (max-width: 768px) {
  .emoji-extension-floating-button { 
  width: 48px !important; 
  height: 48px !important; 
  font-size: 20px !important; }
  .emoji-extension-floating-container { bottom: 15px !important; right: 15px !important; }
}
`;
	function injectStyles() {
		injectGlobalThemeStyles();
		ensureStyleInjected("emoji-extension-floating-button-styles", FLOATING_BUTTON_STYLES);
	}
	function createManualButton() {
		const button = createEl("button", {
			className: "emoji-extension-floating-button",
			title: "手动注入表情按钮 (Manual Emoji Injection)",
			innerHTML: "🐈‍⬛"
		});
		button.addEventListener("click", async (e) => {
			e.stopPropagation();
			e.preventDefault();
			button.style.transform = "scale(0.9)";
			button.innerHTML = "⏳";
			try {
				if (attemptInjection().injectedCount > 0) {
					button.innerHTML = "✅";
					setTimeout(() => {
						button.innerHTML = "🐈‍⬛";
						button.style.transform = "scale(1)";
					}, 1500);
				} else {
					button.innerHTML = "❌";
					setTimeout(() => {
						button.innerHTML = "🐈‍⬛";
						button.style.transform = "scale(1)";
					}, 1500);
				}
			} catch (error) {
				button.innerHTML = "⚠️";
				setTimeout(() => {
					button.innerHTML = "🐈‍⬛";
					button.style.transform = "scale(1)";
				}, 1500);
				console.error("[Emoji Extension Userscript] Manual injection error:", error);
			}
		});
		return button;
	}
	async function invokeAutoRead(showNotify = false) {
		try {
			const fn = window.callAutoReadRepliesV2 || window.autoReadAllRepliesV2;
			console.log("[Emoji Extension] invokeAutoRead: found fn=", !!fn, " typeof=", typeof fn, " showNotify=", showNotify);
			if (fn && typeof fn === "function") {
				const res = await fn();
				console.log("[Emoji Extension] invokeAutoRead: fn returned", res);
				if (showNotify) userscriptNotify("自动阅读已触发", "success");
			} else {
				console.warn("[Emoji Extension] autoRead function not available on window");
				console.log("[Emoji Extension] invokeAutoRead: attempting userscriptNotify fallback");
				userscriptNotify("自动阅读功能当前不可用", "error");
			}
		} catch (err) {
			console.error("[Emoji Extension] auto-read menu invocation failed", err);
			if (showNotify) userscriptNotify("自动阅读调用失败:" + (err && err.message ? err.message : String(err)), "error");
		}
	}
	function createAutoReadMenuItem(variant = "dropdown") {
		if (variant === "dropdown") {
			const li$1 = createEl("li", { className: "submenu-item emoji-extension-auto-read" });
			const a = createEl("a", {
				className: "submenu-link",
				attrs: {
					href: "#",
					title: "像插件一样自动阅读话题 (Auto-read topics)"
				},
				innerHTML: `
      <svg class="fa d-icon d-icon-book svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><use href="#book"></use></svg>
      自动阅读
    `
			});
			a.addEventListener("click", async (e) => {
				e.preventDefault();
				e.stopPropagation();
				await invokeAutoRead(true);
			});
			li$1.appendChild(a);
			return li$1;
		}
		const li = createEl("li", {
			className: "submenu-item emoji-extension-auto-read sidebar-section-link-wrapper",
			style: "list-style: none; padding-left: 0;"
		});
		const btn = createEl("button", {
			className: "fk-d-menu__trigger sidebar-more-section-trigger sidebar-section-link sidebar-more-section-links-details-summary sidebar-row --link-button sidebar-section-link sidebar-row",
			attrs: {
				type: "button",
				title: "像插件一样自动阅读话题 (Auto-read topics)",
				"aria-expanded": "false",
				"data-identifier": "emoji-ext-auto-read",
				"data-trigger": ""
			},
			innerHTML: `
      <span class="sidebar-section-link-prefix icon">
        <svg class="fa d-icon d-icon-book svg-icon svg-string" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><use href="#book"></use></svg>
      </span>
      <span class="sidebar-section-link-content-text">自动阅读</span>
    `,
			style: `
    background: transparent; 
    border: none;
    `
		});
		btn.addEventListener("click", async (e) => {
			e.preventDefault();
			e.stopPropagation();
			await invokeAutoRead(true);
		});
		li.appendChild(btn);
		return li;
	}
	function showFloatingButton() {
		if (userscriptState.settings?.forceMobileMode || false) {
			if (document.querySelector("#d-menu-portals")) {
				console.log("[Emoji Extension Userscript] Force mobile mode with #d-menu-portals detected, skipping floating button");
				return;
			}
		}
		if (floatingButton) return;
		injectStyles();
		const manual = createManualButton();
		const wrapper = createEl("div", { className: "emoji-extension-floating-container" });
		wrapper.appendChild(manual);
		document.body.appendChild(wrapper);
		floatingButton = wrapper;
		isButtonVisible = true;
		console.log("[Emoji Extension Userscript] Floating manual injection button shown (bottom-right)");
	}
	async function injectIntoUserMenu() {
		const SELECTOR_SIDEBAR = "#sidebar-section-content-community";
		const SELECTOR_OTHER_ANCHOR = "a.menu-item[title=\"其他服务\"], a.menu-item.vdm[title=\"其他服务\"]";
		const SELECTOR_OTHER_DROPDOWN = ".d-header-dropdown .d-dropdown-menu";
		for (;;) {
			const otherAnchor = document.querySelector(SELECTOR_OTHER_ANCHOR);
			if (otherAnchor) {
				const dropdown = otherAnchor.querySelector(SELECTOR_OTHER_DROPDOWN);
				if (dropdown) {
					dropdown.appendChild(createAutoReadMenuItem("dropdown"));
					isButtonVisible = true;
					console.log("[Emoji Extension Userscript] Auto-read injected into 其他服务 dropdown");
					return;
				}
			}
			const sidebar = document.querySelector(SELECTOR_SIDEBAR);
			if (sidebar) {
				sidebar.appendChild(createAutoReadMenuItem("sidebar"));
				isButtonVisible = true;
				console.log("[Emoji Extension Userscript] Auto-read injected into sidebar #sidebar-section-content-community");
				return;
			}
			await new Promise((resolve) => setTimeout(resolve, 500));
		}
	}
	async function showAutoReadInMenu() {
		injectStyles();
		try {
			await injectIntoUserMenu();
			return;
		} catch (e) {
			console.warn("[Emoji Extension Userscript] injecting menu item failed", e);
		}
	}
	function hideFloatingButton() {
		if (floatingButton) {
			floatingButton.classList.add("hidden");
			setTimeout(() => {
				if (floatingButton) {
					floatingButton.remove();
					floatingButton = null;
					isButtonVisible = false;
				}
			}, 300);
			console.log("[Emoji Extension Userscript] Floating manual injection button hidden");
		}
	}
	function autoShowFloatingButton() {
		if (!isButtonVisible) {
			console.log("[Emoji Extension Userscript] Auto-showing floating button due to injection difficulties");
			showFloatingButton();
		}
	}
	function checkAndShowFloatingButton() {
		if (userscriptState.settings?.forceMobileMode || false) {
			if (document.querySelector("#d-menu-portals")) {
				if (isButtonVisible) hideFloatingButton();
				return;
			}
		}
		const existingButtons = document.querySelectorAll(".emoji-extension-button");
		if (existingButtons.length === 0 && !isButtonVisible) setTimeout(() => {
			autoShowFloatingButton();
		}, 2e3);
		else if (existingButtons.length > 0 && isButtonVisible) hideFloatingButton();
	}
	function createE(tag, opts) {
		const el = document.createElement(tag);
		if (opts) {
			if (opts.wi) el.style.width = opts.wi;
			if (opts.he) el.style.height = opts.he;
			if (opts.class) el.className = opts.class;
			if (opts.text) el.textContent = opts.text;
			if (opts.ph && "placeholder" in el) el.placeholder = opts.ph;
			if (opts.type && "type" in el) el.type = opts.type;
			if (opts.val !== void 0 && "value" in el) el.value = opts.val;
			if (opts.style) el.style.cssText = opts.style;
			if (opts.src && "src" in el) el.src = opts.src;
			if (opts.attrs) for (const k in opts.attrs) el.setAttribute(k, opts.attrs[k]);
			if (opts.dataset) for (const k in opts.dataset) el.dataset[k] = opts.dataset[k];
			if (opts.in) el.innerHTML = opts.in;
			if (opts.ti) el.title = opts.ti;
			if (opts.alt && "alt" in el) el.alt = opts.alt;
			if (opts.id) el.id = opts.id;
			if (opts.accept && "accept" in el) el.accept = opts.accept;
			if (opts.multiple !== void 0 && "multiple" in el) el.multiple = opts.multiple;
			if (opts.role) el.setAttribute("role", opts.role);
			if (opts.tabIndex !== void 0) el.tabIndex = Number(opts.tabIndex);
			if (opts.ld && "loading" in el) el.loading = opts.ld;
			if (opts.on) for (const [evt, handler] of Object.entries(opts.on)) el.addEventListener(evt, handler);
			if (opts.child) opts.child.forEach((child) => el.appendChild(child));
		}
		return el;
	}
	const DOA = document.body.appendChild.bind(document.body);
	document.head.appendChild.bind(document.head);
	const DEBI = document.getElementById.bind(document);
	document.addEventListener.bind(document);
	const DQSA = document.querySelectorAll.bind(document);
	const DQS = document.querySelector.bind(document);
	async function postTimings(topicId, timings) {
		function readCsrfToken() {
			try {
				const meta = DQS("meta[name=\"csrf-token\"]");
				if (meta && meta.content) return meta.content;
				const input = DQS("input[name=\"authenticity_token\"]");
				if (input && input.value) return input.value;
				const match = document.cookie.match(/csrf_token=([^;]+)/);
				if (match) return decodeURIComponent(match[1]);
			} catch (e) {
				console.warn("[timingsBinder] failed to read csrf token", e);
			}
			return null;
		}
		const csrf = readCsrfToken() || "";
		const map = {};
		if (Array.isArray(timings)) for (let i = 0; i < timings.length; i++) map[i] = timings[i];
		else for (const k of Object.keys(timings)) {
			const key = Number(k);
			if (!Number.isNaN(key)) map[key] = timings[key];
		}
		const params = new URLSearchParams();
		let maxTime = 0;
		for (const idxStr of Object.keys(map)) {
			const idx = Number(idxStr);
			const val = String(map[idx]);
			params.append(`timings[${idx}]`, val);
			const num = Number(val);
			if (!Number.isNaN(num) && num > maxTime) maxTime = num;
		}
		params.append("topic_time", String(maxTime));
		params.append("topic_id", String(topicId));
		const url = `https://${window.location.hostname}/topics/timings`;
		const headers = {
			"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
			"x-requested-with": "XMLHttpRequest"
		};
		if (csrf) headers["x-csrf-token"] = csrf;
		const MAX_RETRIES = 5;
		const BASE_DELAY = 500;
		for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
			const resp = await fetch(url, {
				method: "POST",
				body: params.toString(),
				credentials: "same-origin",
				headers
			});
			if (resp.status !== 429) return resp;
			if (attempt === MAX_RETRIES) return resp;
			const retryAfter = resp.headers.get("Retry-After");
			let waitMs = 0;
			if (retryAfter) {
				const asInt = parseInt(retryAfter, 10);
				if (!Number.isNaN(asInt)) waitMs = asInt * 1e3;
				else {
					const date = Date.parse(retryAfter);
					if (!Number.isNaN(date)) waitMs = Math.max(0, date - Date.now());
				}
			}
			if (!waitMs) waitMs = BASE_DELAY * Math.pow(2, attempt) + Math.floor(Math.random() * BASE_DELAY);
			await new Promise((resolve) => setTimeout(resolve, waitMs));
		}
		throw new Error("postTimings: unexpected execution path");
	}
	function notify(message, type = "info", timeout = 4e3) {
		try {
			let container = DEBI("emoji-ext-toast-container");
			if (!container) {
				container = createE("div", {
					id: "emoji-ext-toast-container",
					style: `
          position: fixed;
          right: 12px;
          bottom: 12px;
          z-index: 2147483647;
          display: flex;
          flex-direction: column;
          gap: 8px;
        `
				});
				DOA(container);
			}
			const el = createE("div", {
				text: message,
				style: `
        padding: 8px 12px;
        border-radius: 6px;
        box-shadow: 0 2px 8px rgba(0,0,0,0.12);
        color: #ffffff;
        font-size: 13px;
        max-width: 320px;
        word-break: break-word;
        transform: translateY(20px);
      `
			});
			if (type === "success") el.style.background = "#16a34a";
			else if (type === "error") el.style.background = "#dc2626";
			else if (type === "transparent") el.style.background = "transparent";
			else if (type === "rainbow") {
				el.style.background = "linear-gradient(90deg, red, orange, yellow, green, blue, indigo, violet, red)";
				el.style.backgroundSize = "400% 100%";
				el.style.animation = "color-shift 15s linear infinite";
				if (!DEBI("color-shift-keyframes")) DOA(createE("style", {
					id: "color-shift-keyframes",
					text: `
                @keyframes color-shift {
                    0% { background-position: 0% 50%; }
                    100% { background-position: 100% 50%; }
                }
              `
				}));
				el.style.animation = "color-shift 1s linear infinite";
			} else el.style.background = "#0369a1";
			container.appendChild(el);
			const id = setTimeout(() => {
				el.remove();
				clearTimeout(id);
			}, timeout);
			return () => {
				el.remove();
				clearTimeout(id);
			};
		} catch {
			try {
				alert(message);
			} catch {}
			return () => {};
		}
	}
	function sleep(ms) {
		return new Promise((resolve) => setTimeout(resolve, ms));
	}
	async function fetchPostsForTopic(topicId) {
		const url = `/t/${topicId}/posts.json`;
		const resp = await fetch(url, { credentials: "same-origin" });
		if (!resp.ok) throw new Error(`failed to fetch posts.json: ${resp.status}`);
		const data = await resp.json();
		let posts = [];
		let totalCount = 0;
		if (data && data.post_stream && Array.isArray(data.post_stream.posts)) {
			posts = data.post_stream.posts;
			if (posts.length > 0 && typeof posts[0].posts_count === "number") totalCount = posts[0].posts_count + 1;
		}
		if ((!posts || posts.length === 0) && data && Array.isArray(data.posts)) posts = data.posts;
		if (!totalCount) {
			if (data && typeof data.highest_post_number === "number") totalCount = data.highest_post_number;
			else if (data && typeof data.posts_count === "number") totalCount = data.posts_count;
			else if (posts && posts.length > 0) totalCount = posts.length;
		}
		return {
			posts,
			totalCount
		};
	}
	function getCSRFToken() {
		try {
			const meta = document.querySelector("meta[name=\"csrf-token\"]");
			if (meta && meta.content) return meta.content;
			const meta2 = document.querySelector("meta[name=\"x-csrf-token\"]");
			if (meta2 && meta2.content) return meta2.content;
			const anyWin = window;
			if (anyWin && anyWin.csrfToken) return anyWin.csrfToken;
			if (anyWin && anyWin._csrf_token) return anyWin._csrf_token;
			const m = document.cookie.match(/(?:XSRF-TOKEN|_csrf)=([^;]+)/);
			if (m && m[1]) return decodeURIComponent(m[1]);
		} catch (e) {}
		return null;
	}
	async function setNotificationLevel(topicId, level = 1) {
		const token = getCSRFToken();
		if (!token) {
			notify("无法获取 CSRF token,未能设置追踪等级", "error");
			return;
		}
		const url = `${location.origin}/t/${topicId}/notifications`;
		const body = `notification_level=${encodeURIComponent(String(level))}`;
		try {
			const resp = await fetch(url, {
				method: "POST",
				headers: {
					accept: "*/*",
					"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
					"x-csrf-token": token,
					"x-requested-with": "XMLHttpRequest",
					"discourse-logged-in": "true",
					"discourse-present": "true",
					priority: "u=1, i"
				},
				body,
				mode: "cors",
				credentials: "include"
			});
			if (!resp.ok) throw new Error(`设置追踪等级请求失败:${resp.status}`);
			notify(`话题 ${topicId} 的追踪等级已设置为 ${level}`, "rainbow");
		} catch (e) {
			notify("设置追踪等级失败:" + (e && e.message ? e.message : String(e)), "error");
		}
	}
	async function autoReadAll(topicId, startFrom = 1) {
		try {
			let tid = topicId || 0;
			if (!tid) {
				const m1 = window.location.pathname.match(/t\/topic\/(\d+)/);
				const m2 = window.location.pathname.match(/t\/(\d+)/);
				if (m1 && m1[1]) tid = Number(m1[1]);
				else if (m2 && m2[1]) tid = Number(m2[1]);
				else {
					const el = DQS("[data-topic-id]");
					if (el) tid = Number(el.getAttribute("data-topic-id")) || 0;
				}
			}
			if (!tid) {
				notify("无法推断 topic_id,自动阅读取消", "error");
				return;
			}
			notify(`开始自动阅读话题 ${tid} 的所有帖子...`, "info");
			const { posts, totalCount } = await fetchPostsForTopic(tid);
			if ((!posts || posts.length === 0) && !totalCount) {
				notify("未获取到任何帖子或总数信息", "error");
				return;
			}
			const total = totalCount || posts.length;
			const postNumbers = [];
			if (startFrom > total) {
				notify(`起始帖子号 ${startFrom} 超过总帖子数 ${total},已跳过`, "transparent");
				return;
			}
			for (let n = startFrom; n <= total; n++) postNumbers.push(n);
			let BATCH_SIZE = Math.floor(Math.random() * 951) + 50;
			const ran = () => {
				BATCH_SIZE = Math.floor(Math.random() * 1e3) + 50;
			};
			for (let i = 0; i < postNumbers.length; i += BATCH_SIZE) {
				const batch = postNumbers.slice(i, i + BATCH_SIZE);
				ran();
				const timings = {};
				for (const pn of batch) timings[pn] = Math.random() * 1e4;
				try {
					await postTimings(tid, timings);
					notify(`已标记 ${Object.keys(timings).length} 个帖子为已读(发送)`, "success");
				} catch (e) {
					notify("发送阅读标记失败:" + (e && e.message ? e.message : String(e)), "error");
				}
				await sleep(500 + Math.floor(Math.random() * 1e3));
			}
			try {
				await setNotificationLevel(tid, 1);
			} catch (e) {}
			notify("自动阅读完成", "success");
		} catch (e) {
			notify("自动阅读异常:" + (e && e.message ? e.message : String(e)), "error");
		}
	}
	async function autoReadAllv2(topicId) {
		let tid = topicId || 0;
		if (!tid) {
			const m1 = window.location.pathname.match(/t\/topic\/(\d+)/);
			const m2 = window.location.pathname.match(/t\/(\d+)/);
			if (m1 && m1[1]) tid = Number(m1[1]);
			else if (m2 && m2[1]) tid = Number(m2[1]);
			else {
				const anchors = Array.from(DQSA("a[href]"));
				const seen = /* @__PURE__ */ new Set();
				for (const a of anchors) {
					const m = (a.getAttribute("href") || "").match(/^\/t\/topic\/(\d+)(?:\/(\d+))?$/);
					if (!m) continue;
					const id = Number(m[1]);
					const readPart = m[2] ? Number(m[2]) : void 0;
					const start = readPart && !Number.isNaN(readPart) ? readPart : 2;
					if (!id || seen.has(id)) continue;
					seen.add(id);
					await autoReadAll(id, start);
					await sleep(200);
				}
			}
		}
	}
	window.autoReadAllReplies = autoReadAll;
	window.autoReadAllRepliesV2 = autoReadAllv2;
	if (!window.autoReadAllRepliesV2) window.autoReadAllRepliesV2 = autoReadAllv2;
	async function initializeUserscriptData() {
		const data = await loadDataFromLocalStorageAsync(window.location.hostname).catch((err) => {
			console.warn("[Emoji Picker] loadDataFromLocalStorageAsync failed, falling back to sync loader", err);
			return loadDataFromLocalStorage();
		});
		userscriptState.emojiGroups = data.emojiGroups || [];
		userscriptState.settings = data.settings || userscriptState.settings;
	}
	function isDiscoursePage() {
		if (document.querySelectorAll("meta[name*=\"discourse\"], meta[content*=\"discourse\"], meta[property*=\"discourse\"]").length > 0) {
			console.log("[Emoji Picker] Discourse detected via meta tags");
			return true;
		}
		const generatorMeta = document.querySelector("meta[name=\"generator\"]");
		if (generatorMeta) {
			if ((generatorMeta.getAttribute("content")?.toLowerCase() || "").includes("discourse")) {
				console.log("[Emoji Picker] Discourse detected via generator meta");
				return true;
			}
		}
		if (document.querySelectorAll("#main-outlet, .ember-application, textarea.d-editor-input, .ProseMirror.d-editor-input").length > 0) {
			console.log("[Emoji Picker] Discourse elements detected");
			return true;
		}
		console.log("[Emoji Picker] Not a Discourse site");
		return false;
	}
	async function initializeEmojiFeature(maxAttempts = 10, delay = 1e3) {
		console.log("[Emoji Picker] Initializing...");
		logPlatformInfo();
		await initializeUserscriptData();
		try {
			if (userscriptState.settings?.enableBatchParseImages !== false) {
				initOneClickAdd();
				initPhotoSwipeTopbarUserscript();
				console.log("[Emoji Picker] One-click batch parse images enabled");
			} else console.log("[Emoji Picker] One-click batch parse images disabled by setting");
		} catch (e) {
			console.warn("[Emoji Picker] initOneClickAdd failed", e);
		}
		try {
			showAutoReadInMenu();
		} catch (e) {
			console.warn("[Emoji Picker] showAutoReadInMenu failed", e);
		}
		function exposeAutoReadWrapper() {
			try {
				const existing = window.autoReadAllRepliesV2;
				if (existing && typeof existing === "function") {
					window.callAutoReadRepliesV2 = (topicId) => {
						try {
							return existing(topicId);
						} catch (e) {
							console.warn("[Emoji Picker] callAutoReadRepliesV2 invocation failed", e);
						}
					};
					console.log("[Emoji Picker] callAutoReadRepliesV2 is exposed");
					return;
				}
				window.callAutoReadRepliesV2 = (topicId) => {
					try {
						const fn = window.autoReadAllRepliesV2;
						if (fn && typeof fn === "function") return fn(topicId);
					} catch (e) {
						console.warn("[Emoji Picker] callAutoReadRepliesV2 invocation failed", e);
					}
					console.warn("[Emoji Picker] autoReadAllRepliesV2 not available on this page yet");
				};
			} catch (e) {
				console.warn("[Emoji Picker] exposeAutoReadWrapper failed", e);
			}
		}
		exposeAutoReadWrapper();
		let attempts = 0;
		function attemptToolbarInjection() {
			attempts++;
			const result = attemptInjection();
			if (result.injectedCount > 0 || result.totalToolbars > 0) {
				console.log(`[Emoji Picker] Injection successful: ${result.injectedCount} buttons injected into ${result.totalToolbars} toolbars`);
				return;
			}
			if (attempts < maxAttempts) {
				console.log(`[Emoji Picker] Toolbar not found, attempt ${attempts}/${maxAttempts}. Retrying in ${delay / 1e3}s.`);
				setTimeout(attemptToolbarInjection, delay);
			} else {
				console.error("[Emoji Picker] Failed to find toolbar after multiple attempts.");
				console.log("[Emoji Picker] Showing floating button as fallback");
				showFloatingButton();
			}
		}
		if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", attemptToolbarInjection);
		else attemptToolbarInjection();
		startPeriodicInjection();
		setInterval(() => {
			checkAndShowFloatingButton();
		}, 5e3);
	}
	if (isDiscoursePage()) {
		console.log("[Emoji Picker] Discourse detected, initializing emoji picker feature");
		initializeEmojiFeature();
	} else console.log("[Emoji Picker] Not a Discourse site, skipping injection");
})();

})();