Telegram Bot API问题:含表情时无法基于UTF-16编码单位提取URL
解决Telegram Bot API提取含表情消息中URL的问题
我之前也踩过Telegram这个UTF-16编码单位的坑!当消息里有表情这类Unicode补充平面字符时,直接用PHP的字符串截取函数肯定会出错,根本原因就是Telegram的MessageEntity里的offset和length是按UTF-16编码单位计算的,而PHP默认是按UTF-8字节数处理字符串。
问题根源拆解
- 普通UTF-8字符(如英文、中文)通常占1-3字节,对应1个UTF-16编码单元(2字节);
- 表情符号(比如😀)属于Unicode补充平面字符,UTF-8下占4字节,对应2个UTF-16编码单元;
- 如果你直接用
mb_substr(默认UTF-8编码)按offset和length截取,会把表情当成1个字符(1个UTF-8单元),但Telegram把它算成2个UTF-16单元,位置自然就对不上了。
解决方案1:编码转换法(最简洁)
思路是先把UTF-8的消息文本转换成UTF-16编码,按offset和length对应的字节数截取,再转换回UTF-8。因为每个UTF-16编码单元固定占2字节,所以直接用offset*2和length*2就能得到正确的字节偏移和长度。
function extractTelegramEntityText(string $messageText, int $offset, int $length): string { // 将UTF-8文本转换为UTF-16编码 $utf16Text = mb_convert_encoding($messageText, 'UTF-16', 'UTF-8'); // 计算UTF-16字节串的偏移和长度(每个单元2字节) $byteOffset = $offset * 2; $byteLength = $length * 2; // 截取UTF-16子串 $utf16Substr = substr($utf16Text, $byteOffset, $byteLength); // 转换回UTF-8编码并返回 return mb_convert_encoding($utf16Substr, 'UTF-8', 'UTF-16'); }
使用示例:
假设消息文本是😀https://example.com,对应的MessageEntity的offset=1,length=23(链接的UTF-16单元数),调用这个函数就能准确提取出https://example.com,而不会因为表情的存在导致截取位置偏移。
解决方案2:字符单元计数法(无需编码转换)
如果你不想做编码转换,可以遍历每个字符,计算它们对应的UTF-16单元数,手动定位起始和结束位置。这种方法更直观,适合需要精细控制的场景。
function extractTelegramEntityTextAlternative(string $messageText, int $offset, int $length): string { // 将UTF-8文本拆分为单个Unicode字符数组 $chars = mb_str_split($messageText); $currentUnitCount = 0; $startCharIndex = 0; // 找到对应offset的起始字符索引 foreach ($chars as $index => $char) { if ($currentUnitCount >= $offset) { $startCharIndex = $index; break; } // 判断当前字符占1个还是2个UTF-16单元 $charCode = mb_ord($char); $currentUnitCount += $charCode > 0xFFFF ? 2 : 1; } // 从起始位置开始收集字符,直到凑够length个UTF-16单元 $result = ''; $collectedUnits = 0; for ($i = $startCharIndex; $i < count($chars); $i++) { $char = $chars[$i]; $charCode = mb_ord($char); $unitCount = $charCode > 0xFFFF ? 2 : 1; if ($collectedUnits + $unitCount > $length) { break; } $result .= $char; $collectedUnits += $unitCount; } return $result; }
两种方案对比
- 编码转换法:代码简洁,执行效率高,适合大多数常规场景;
- 字符单元计数法:无需编码转换,逻辑更透明,适合需要对每个字符做额外处理的场景。
内容的提问来源于stack exchange,提问作者Yaroslav Voloh
相关产品推荐
相关产品推荐

