/** * 抖音表情共享逻辑:短代码正则 / URL / 展开为 markdown 图片 / span 收集。 * 纯逻辑模块(无 React 依赖),服务端与客户端组件均可安全引入。 */ /** * 抖音表情短代码:[:key:]。key 有两种形态: * - 默认表情:中文语义名(如 猫猫),经 DY_EMOJI_ALIASES 映射到实际文件 * - 合成表情:组合文件名(fus),原样透传 * 字符集排除 [ ] : 空白 . @(. 防止误吞文件扩展名写法;@ 保证与 @提及 span 不重叠) */ export const DY_EMOJI_RE = /\[:([^\[\]:\n\s.@]+):\]/g; export const DY_EMOJI_BASE = "/emoji/dy"; /** * 默认表情语义别名(人工看图命名),顺序与 manifest.json 的 tos 一致。 * 面板「默认」tab 与消息内容均以中文短代码为准;更新表情资产时需同步维护本表。 * 用有序数组做单一数据源:对象键会把整数型 key(如 "666")重排到最前,不能依赖键顺序。 */ const DY_EMOJI_ALIAS_PAIRS: readonly (readonly [string, string])[] = [ ["猫猫", "003d8daaa58e4a2fb324bcd3baf4c785"], ["大哭", "0043de2fff65408ab7522538f7b3292a"], ["晕", "01ccc692f9094a0f9839706665c7e8bb"], ["炸弹", "02a91594d67e462faf7c77e827a16eec"], ["禁止", "02fcc587c8b4499080e0c03994967511"], ["5V5", "032007fd6c254a72a162d8c5bf78d512"], ["坏笑", "0431a35382c641679fe74f690241b533"], ["纸飞机", "0636e634f58f4410a8b22675d36f52a9"], ["玫瑰", "06abb0913446422cbac6ab18ea8fa44a"], ["翻白眼", "06df57cc81954ef8bf65dad06a01a69a"], ["无语", "072caed2f2bd4ed5ba35142965845d85"], ["摸头", "0795718a5fe247b19ff80b064d02c66d"], ["礼物", "08d888562e1f4ea086fc4f3eff2ef517"], ["火苗", "094c1f852c2343658b2677023755249b"], ["嘟嘴", "0e13c8681abe4239b3d468635b723d40"], ["心碎", "0eb6e5cde36346289150a83a118bfa62"], ["精选", "1482ac09926b45a1bab8f1dc98a68d22"], ["笑哭", "167a2c32c7874da889fc902f8cf4500a"], ["不屑", "1691cd385ecd4fb6b888d000900d74bc"], ["鞭炮", "16c850aade5b475fa83b67e0266f0222"], ["庆祝", "18d02bf442484371bb918e1f07c43a05"], ["粽子", "1e8aef1795db468682f8b63521834f39"], ["酷哭", "1eabd1db85dd4bddbbd842abe9cdce49"], ["大喊", "1ec163608a2a49ffbbc766e505885bb1"], ["怒", "1edc3de7713647c2b269f7f48b494b2d"], ["发奋", "1f71ffdc4df041ac836cdc81940f68ff"], ["OK", "21b1e1cdd15742e9aacc6cfd764260cc"], ["气球", "24a048472a4c429c8d01d0401e846285"], ["抱抱", "24b237f43dee47c5a068bf4457b97145"], ["生气", "2609e5a35a3f436084bd618ef819227f"], ["卖萌", "2618a9c1f9424c78aaf7e6174f0242b4"], ["尴尬", "28d74529207c43178cc72cba847daf12"], ["得意", "29288834d417481f97eb63fee5c30b53"], ["凋谢", "2b7cfe4c3c524b709bd3f15537c06911"], ["大笑", "2d1fca45aceb46b8b3a8d55084977851"], ["太阳", "2fb7d94d6e7642349793def00e5b3e0c"], ["彩带", "30d222de7c4a42cdb6d7a9ea7bea162b"], ["睡觉", "30d9ccb7e4ae4a43997e76fbabd6b5cd"], ["奸笑", "3139a081129f4832a70da8d7808114fa"], ["OK手", "317a9b7fe5f94023a8b2987173708362"], ["露齿笑", "33745171e4a544ad913b52ebb6b46b60"], ["捂脸笑", "3545637c30284f6bb732a444f91196ab"], ["吐舌", "365405db173b45978ea6a45bd47e9d87"], ["手枪", "3b366738018343ae9831a79fa17b3dae"], ["惊恐", "3c8520db7cbd418f95aea788ee6a7cf0"], ["舔屏", "3d13332de5da45498cd1ef193cc4afff"], ["捂脸", "3df5f3ca07e546b697b301ab9e2e3c23"], ["难受", "3e8a89ca5dc74cb88b4000551a1312a7"], ["眯眼", "4201288b0e1b47b59f9f031a53c3d687"], ["嘿嘿", "42fd189ab7ad4f478f220d34046916fb"], ["亲亲", "434e5830fe874fdca427b0d8b23a751d"], ["花痴", "4364693f63a34e2999c54cbbc75df4e9"], ["眨眼", "44ddc9a6bcd9471a858a33c382f6fafa"], ["鸡腿", "45a1632fd059454b91c71d8d54e46a1b"], ["小蓝", "45f8ce9a22b0466fbcd1bb2a841d2225"], ["愤怒", "476e92daceb14bee99e36b8f50a40e81"], ["祈祷", "478afb0911604f5289d9cbfd4183b404"], ["贱笑", "47eb8964fef7479c80a1088f405beb43"], ["汗", "47fe86652a1b4cf288de5f929cba5d80"], ["擦汗", "49b5a54103ae43b0b02a564a3feec5a8"], ["思考", "49bb5e7832d644dd99f50d3b6733a4c6"], ["咖啡", "4b5f538005d148248338d97c47e20f9e"], ["晕圈", "4c0f3ba463734e50981103650f440be0"], ["点赞", "4c4a175aaf2b4e0c98b8d46684d64564"], ["可怜", "4cd68b81179d421ebaf2c4e36b3630c6"], ["开心", "4ec5e9c2001249b79bf4ff0c6a431cae"], ["圣诞帽", "4f1aeeedf61a4051aaf0bc96a6ead49f"], ["爱心眼", "4f6a86e4bfd14f8c8434f6d643782a41"], ["碰拳", "4f815c8c1795412cbb67233685d785a1"], ["冒烟", "517f7385630e4641af73c3ea93e44c7d"], ["委屈", "52aeca47432745729c08609d46211ed1"], ["指左", "54994d1e88d94722a71960508a8e9ce6"], ["二哈", "54afb78ebf6a45a09c6dc7d77fc3d3fb"], ["加油", "54e9a10981a242dba426a4f63d4103ab"], ["疑问", "55656c6385754a35acce9f807c4afa32"], ["惊呆", "55c305ac8ebe4f2a8e5fed639f188614"], ["笑泪", "590d12b8677d4738ad9e989ea0280f81"], ["绝", "5aaaff01fd1f493a82821b1a3943bf68"], ["香蕉", "5b0b93a490394665bed2a52ae67a2975"], ["福", "5cdc7b1819c3484db61fa60eeb989e2e"], ["捧腹", "5d7d5f3c3f9548679fb73090a69f538a"], ["红包", "5defde4959044aaeae628e060e82043f"], ["猫震惊", "5faa8e1516204480b74b6f6a3122d578"], ["呆", "600e9cfaf3a14174b451e2206baf1e0f"], ["柴犬", "603e30989b05474788e3709d7eebd4ef"], ["666", "61267a18cbf847b3af73190bc52ae36a"], ["握手", "613459694fa24ae689aa306fd07944f8"], ["吐", "616147770ba044a3abed4224c46fe605"], ["惊讶", "622ddf449a5d4ac6a0e15445356cfb94"], ["套头", "626ec790acdf4c10b5309a99ae75ecb1"], ["嫌弃", "62d34256ab7845709616c5a7bf42aee1"], ["给力", "63b22c1e04f34e8a8816ba2b8ac46ae3"], ["斜眼", "6572851608ba4ab3acc0f0c879594d1b"], ["星星脸", "66fb1287c3f04b4bbb427a1368d12a22"], ["蓝怪", "6b280673ae0740e78d82178ee018a811"], ["猪猪", "6c3589f8daec4143bd9b9d9b41e802ec"], ["点踩", "6c80753d816442718d2f5ca44e1eb13a"], ["关注我", "6ed70f4a306a48bcbc12a138165fb09d"], ["巧克力", "71e20043b5f34b03b7dd85afb69f0503"], ["调皮", "72045b3ac8654458a94767f463804c82"], ["红心", "740e29f4ca6547ccb4f7cf3fe0463629"], ["泪喷", "770948b40fe14c51a73e18dff5a8d29c"], ["红唇", "77a76f873a1f4e6ba5eeff343391b213"], ["怪笑", "77e480a1a9234d94bfcab3429f5f96bb"], ["钻戒", "7d2da9bea4f0477aa530340355fda832"], ["崩溃", "7e606e0d73d843a2bc1c15219a1318cd"], ["绿帽", "7e79587db89a4404a0ebcf4aabba3d2d"], ["圣诞树", "8260b9fef99e430aa08ff9259355e907"], ["敲打", "831ef27451104b0e9eb89660c3d7b716"], ["龇牙", "86d772fc25a0404090613bad4a123947"], ["扎心", "8a054c74604844498fa9c4ae809f1c79"], ["爱心枪", "8a7a9fb41d444aa68dd5568ba72040aa"], ["心动", "8bb95fd220bf4efeb44c9b3e6c63afdf"], ["墨镜笑", "8cd39208016c428ca80d3468e24edb0a"], ["贴贴", "8dcb4606e9f14613a3bbf98a1646e56f"], ["微笑", "907e61eb04e54074a101b43035d50145"], ["挨揍", "91656d99d6d645a5995187454e3293f4"], ["耳机", "91aa9af22f664ba08ca3c66184f2a838"], ["打响指", "94266b5d083d45afb4309ed6ed905dd6"], ["转眼", "94c011b83a3e40cbbcfeb09c6adb2b43"], ["狂笑", "9501220f825547d699c8409184846166"], ["麻将", "956e469de272416ebeb0aafd780bd78e"], ["雪花", "98c4f361b8cc49e2aeff0b546664af42"], ["飞吻", "9a0887a55ff94cbbbadbbbdb14c7396a"], ["指右", "9a35f66a62804e90b31455ab6f0d99ee"], ["沉思", "9e2b0289cd994a91a77676265d5ab7a7"], ["裂开", "9eb467c1c4d849f39b45ad4e7beff0ce"], ["烟花", "9ebf943dbb974a5e8dd13e5a339cff18"], ["仓鼠", "9ff343dc38e54a9f98e7fa896dd95715"], ["揉脸", "a3d1ce9301c5410d88cc2470a3e11c30"], ["鼓掌", "a4d28f23c10e4efaa979b4d119b7ade4"], ["黑脸", "a4d9f336b64f4554a0790eed0f0e1a71"], ["数钱", "a7a5a4db025642a392ee10d5c74e65c9"], ["哇", "a90f7139a14140dbba575425193ca6c5"], ["尬笑", "a993e64d05cd428e876600a75129332f"], ["舒心", "ab54a06f377346f88f094fe6281103ff"], ["作揖", "abf7cc107d4740e4bc1e238b770ac889"], ["窃喜", "aca47d7bc40a442b933812e3f482549d"], ["跌倒", "aceb3d3827574b68a40f7c7ebf1e3049"], ["楚楚", "ad1d6ca9bc694b78b8b3ad816aaccf0d"], ["拍脸", "ada62e0815f141cdbdc5a76e6781786d"], ["击掌", "ae9cd379b8c448d5a3c3aa107c3272d8"], ["香吻", "aec920e3a21444c79a79defd76a45a75"], ["抓狂", "afa666d51d474fc6b2f0a10aca57c372"], ["摇扇", "b1c3d09acfe84b9280a28c4190fa0056"], ["doge", "b217d170f0c349fc8ea86615738a5c81"], ["咋舌", "b21a518ebd8b48caab9c814e40b63115"], ["咧嘴", "b341f7cdcaea452a83a78eb81b5a30b4"], ["掩面", "b379ea709e484581830373adf1bce572"], ["狗拳", "b485611a693d4eadadf29f5f055511f6"], ["糖葫芦", "b67a198b447a45d0a36041196e26e447"], ["黄瓜", "ba6c382121e94f52bb44f41b9156f447"], ["脸红", "bbcb745bbb1c463089a0d40177623c9e"], ["叫好", "bd1068daba564862be71631451611ba7"], ["白菜狗", "bd5541fc02514e7e8aefde9b0aabc652"], ["捂嘴", "bf6b3de54ab34732af82809635c704f8"], ["互粉", "bfa5963893bd41519ff31c7415f6956f"], ["蛋糕", "c08640142c304d76959ee198cc8a2efd"], ["撇嘴", "c10ec8abb6da4adbb3388d435a980568"], ["求机位", "c5c18a6bfb3d43f9a96f833f9a758d71"], ["心心眼", "c6605942715f4717805101c311b41f24"], ["蓝毛", "c6646c7cd6fc4f8d89f332c2d46e4597"], ["元宝", "c67bf9ed7bb34e00af6ef863d6ecfdad"], ["吐血", "c98569a7f04943d7a8de0c15cef36151"], ["便便", "cabe6bcd9889449daa87c2df765f379e"], ["鞠躬", "cb9f16ad45d84f7ca4256f20048e422a"], ["苹果", "cc5884dd4b814d81a9982bbcee136e45"], ["痴笑", "cca02095fe5f448ea2289ece0e2ac49f"], ["哈士奇", "cce25443e61846699d1f08782fb365bb"], ["猜拳", "cd619c9151a14094ab32f44f7487b980"], ["棒棒糖", "cd8647d2106b48fd9142c9c41562c8f7"], ["掩嘴", "cf1a3c5b317f4c0399ed39888d73abe7"], ["彩虹", "cfeddc926d3846eabda23e750364e82d"], ["酷脸", "d15c89c9f3364485b514d51b43b16771"], ["吃橘", "d2a361001193458ab3250ab557c7ec4b"], ["西瓜", "d2c0b2c0484b4bd59d6b266c5a80c199"], ["躺平", "d3084c0ac6e74eae84dd39cbc26d990d"], ["掩脸", "d34c2cb72f9f45a1bd21669f832b9a94"], ["鬼脸", "d3cbf267d0d8426e90edfb9c75059314"], ["加油牌", "d525a68955854abda1c8bdf7f62ebadb"], ["出拳", "d8a316cb0427498b87e396350b83b504"], ["加一", "d9840b29c0d84082b758580ff6da0281"], ["吃瓜", "db6bb519e6bc4b9b819e04b05e194586"], ["瞪眼", "dcaa1bb24fed447b9ef155462db2a890"], ["黑球", "ddd30bbe0a194430af6bcd6fef98570f"], ["狼狗", "de102f5602824afb88009091971a862d"], ["猫哭", "df7b9c648eef4720962f604f1c81a77c"], ["捂眼", "dfaa3f289fd54142ad6c8088ee516852"], ["蹲下", "e44b432ef18e4d84844e9eb1e7d135ac"], ["干笑", "e4b71ab984154f0ab1709bd88e9defda"], ["不悦", "e5275fd92a234e34984044e3b2213ebc"], ["捧脸", "e58ab050664b44eaa7a7c8b8d6fe9cf7"], ["羞涩", "e7f9e93ae5da435884e17c57442729f1"], ["盯", "e80c05fa6c4943759dba8486512901e0"], ["开怀", "e9078540aac945b4992ae19dbf4d0783"], ["红酒", "e959a423dd0a43fd8fcd5dd0499928cf"], ["灯笼", "ea0d63f135594b82ac3e386cf684046a"], ["干杯", "ea97458fd890435c9fa6df3520f66b1f"], ["军帽", "eb8d8e8a04e34db082437c083539dd2f"], ["禁烟花", "ebff2644a4ab4149a41f1bc7282879b1"], ["减一", "efa80a6e10f04dcc8de89092744b2bf9"], ["叼玫瑰", "f1d000c0d62a4d77b14641076a05f5b9"], ["嚣张", "f266b481b9e14e588228016138913c75"], ["拍我", "f2efb004f2f842b1bc20284bd2e010e3"], ["干饭", "f68c5e1160c449508d55cbe2acc0bd47"], ["德牧", "f730fe9f0bd44b59b8f0ed5069a747dc"], ["糖果", "f75de5be772c458e9a5469b32c75413e"], ["甜点", "f840c0bdd606425a8dd1190b80126674"], ["比心", "f87a6fb3ac774f2e8f9f1760e3d2de45"], ["迷恋", "fba0f99d4b8049cd905bfd076d4efbf2"], ["惊呼", "fba1c2ac23394a31bc1c3405148bd363"], ["郁闷", "fbbbf98a169642d386020f79d06af11c"], ["含泪", "fbd4350861fe49cd991e76e82e382715"], ["三文鱼", "fd24c9f2258f4640af610472b442f9ec"], ]; /** * 默认表情分组:同类型相邻排列(参考抖音/飞书面板)。 * keys 必须恰好覆盖 DY_EMOJI_ALIAS_PAIRS 的全部中文 key 且不重复(有单测锁定)。 */ export const DY_EMOJI_GROUPS: readonly { label: string; keys: string[] }[] = [ { label: "笑脸", keys: [ "微笑", "露齿笑", "笑哭", "笑泪", "捂脸笑", "嘿嘿", "贱笑", "奸笑", "怪笑", "痴笑", "狂笑", "大笑", "捧腹", "开怀", "窃喜", "尬笑", "干笑", "咧嘴", "龇牙", "吐舌", "舔屏", "星星脸", "花痴", "得意", "坏笑", "墨镜笑", "嚣张", "酷哭", "酷脸", "调皮", "卖萌", "呆", "鬼脸", "脸红", "羞涩", "楚楚", "捧脸", "舒心", "开心", "精选", ], }, { label: "情绪", keys: [ "大哭", "含泪", "泪喷", "委屈", "难受", "郁闷", "不悦", "无语", "汗", "擦汗", "晕", "晕圈", "崩溃", "抓狂", "怒", "生气", "愤怒", "黑脸", "尴尬", "可怜", "裂开", "吐", "吐血", "嘟嘴", "撇嘴", "嫌弃", "不屑", "翻白眼", "斜眼", "哇", "大喊", "惊呆", "惊讶", "惊呼", "惊恐", "瞪眼", "盯", "眨眼", "眯眼", "转眼", "疑问", "思考", "沉思", "咋舌", "绝", "睡觉", "躺平", ], }, { label: "手势", keys: [ "OK", "OK手", "点赞", "点踩", "碰拳", "击掌", "出拳", "猜拳", "狗拳", "握手", "作揖", "鞠躬", "祈祷", "打响指", "鼓掌", "加油", "加油牌", "发奋", "指左", "指右", "摸头", "揉脸", "捂脸", "掩面", "掩脸", "掩嘴", "捂嘴", "捂眼", "拍脸", "拍我", "套头", "挨揍", "跌倒", "蹲下", "抱抱", ], }, { label: "爱心", keys: [ "红心", "心碎", "爱心眼", "心心眼", "爱心枪", "心动", "比心", "迷恋", "贴贴", "亲亲", "香吻", "飞吻", "红唇", "绿帽", ], }, { label: "动物", keys: [ "猫猫", "猫震惊", "猫哭", "二哈", "哈士奇", "柴犬", "白菜狗", "德牧", "狼狗", "猪猪", "仓鼠", "小蓝", "蓝怪", "蓝毛", "三文鱼", "doge", ], }, { label: "食物", keys: [ "鸡腿", "咖啡", "香蕉", "苹果", "西瓜", "黄瓜", "吃瓜", "吃橘", "糖葫芦", "棒棒糖", "巧克力", "蛋糕", "糖果", "甜点", "粽子", "红酒", "干饭", "干杯", ], }, { label: "物品", keys: [ "炸弹", "禁止", "手枪", "火苗", "冒烟", "太阳", "雪花", "彩虹", "气球", "纸飞机", "玫瑰", "凋谢", "叼玫瑰", "礼物", "钻戒", "元宝", "红包", "数钱", "便便", "黑球", "耳机", "摇扇", "麻将", "军帽", "圣诞帽", "圣诞树", "灯笼", "福", "鞭炮", "烟花", "禁烟花", "彩带", "庆祝", "叫好", "求机位", "关注我", "互粉", "5V5", "666", "加一", "减一", "给力", "敲打", "扎心", ], }, ]; /** 中文语义名 → 实际文件名查找表 */ export const DY_EMOJI_ALIASES: Record = Object.fromEntries(DY_EMOJI_ALIAS_PAIRS); /** key → 实际文件名:中文语义名查别名表,合成表情组合名原样透传 */ export function dyEmojiFileKey(key: string): string { return DY_EMOJI_ALIASES[key] ?? key; } export function dyEmojiUrl(key: string): string { return `${DY_EMOJI_BASE}/${encodeURIComponent(dyEmojiFileKey(key))}.png`; } /** * 把 [:key:] 短代码展开为 markdown 行内图片(供 Markdown 渲染管线)。 * 注意:不感知代码围栏,围栏内的短代码也会被展开(v1 已知限制)。 */ export function expandDyEmojiShortcodes(text: string): string { if (!text || !text.includes("[:")) return text; return text.replace(DY_EMOJI_RE, (_m, key: string) => { return `![dy:${key}](${dyEmojiUrl(key)})`; }); } export type DyEmojiManifest = { tos: string[]; fus: string[] }; /** 收集文本中的表情短代码位置(聊天等纯文本渲染用;每次用独立正则避免 g 的 lastIndex 状态) */ export function collectDyEmojiSpans( text: string ): { start: number; end: number; key: string }[] { if (!text || !text.includes("[:")) return []; const spans: { start: number; end: number; key: string }[] = []; const re = new RegExp(DY_EMOJI_RE.source, "g"); for (let m = re.exec(text); m; m = re.exec(text)) { spans.push({ start: m.index, end: m.index + m[0].length, key: m[1] }); } return spans; }