import { pinyin } from "pinyin-pro"; const HAN_IDEOGRAPH_RE = /\p{Unified_Ideograph}/u; export const normalizeStr = (s) => (s ?? "").toString().toLowerCase().trim(); const normalizeLooseFromNormalized = (normalized) => normalized.replace(/[\s_-]+/g, "").replace(/[()()【】\[\]{}·•]+/g, ""); export const normalizeLoose = (s) => normalizeLooseFromNormalized(normalizeStr(s)); const memoizeStringFn = (fn) => { const cache = new Map(); return (raw) => { const key = (raw ?? "").toString(); if (cache.has(key)) { return cache.get(key); } const value = fn(key); cache.set(key, value); return value; }; }; const getNormalizedText = memoizeStringFn(normalizeStr); const getLooseText = memoizeStringFn((text) => normalizeLooseFromNormalized(getNormalizedText(text)), ); export const toPinyinText = memoizeStringFn((text) => pinyin(text, { toneType: "none" }) .toLowerCase() .replace(/\s+/g, ""), ); export const toInitials = memoizeStringFn((text) => pinyin(text, { pattern: "first", toneType: "none" }) .toLowerCase() .replace(/\s+/g, ""), ); export const buildSearchQuery = (raw) => { const norm = getNormalizedText(raw); if (!norm) return null; return { norm, loose: getLooseText(raw), }; }; export const matchesText = (value, query) => { if (value == null || !query?.norm) return false; const text = String(value); const normalizedValue = getNormalizedText(text); const looseValue = query.loose ? getLooseText(text) : null; if (normalizedValue.includes(query.norm)) return true; if (query.loose && looseValue?.includes(query.loose)) return true; if (!HAN_IDEOGRAPH_RE.test(text)) return false; const pinyinValue = toPinyinText(text); if (pinyinValue.includes(query.norm)) return true; const initialsValue = toInitials(text); if (initialsValue.includes(query.norm)) return true; return false; }; export const getPluginSearchFields = (plugin) => { const supportPlatforms = Array.isArray(plugin?.support_platforms) ? plugin.support_platforms.join(" ") : ""; const tags = Array.isArray(plugin?.tags) ? plugin.tags.join(" ") : ""; return [ plugin?.name, plugin?.trimmedName, plugin?.display_name, plugin?.desc, plugin?.author, plugin?.repo, plugin?.version, plugin?.astrbot_version, supportPlatforms, tags, ]; }; export const matchesPluginSearch = (plugin, query) => { if (!query) return true; return getPluginSearchFields(plugin).some((candidate) => matchesText(candidate, query), ); };