You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:42:27