Telegram PHP Bot emoji/特殊字符下实体offset与length异常问题
解决Telegram Bot中含Emoji/特殊字符时Entity偏移量偏差问题
问题背景
开发基于PHP的Telegram Bot时,需要提取文本内嵌的text_link类型Entity并替换为明文URL,但当文本包含Emoji或多字节特殊字符时,Telegram返回的Entity的offset和length参数会出现偏差,导致截取的文本错误。
核心原因
Telegram API中Entity的offset和length是基于UTF-16代码单元计数的,而PHP默认的字符串操作(包括mb_substr)是按UTF-8字符计数。例如Emoji这类字符在UTF-16中占2个代码单元,直接用Telegram的参数去截取UTF-8文本必然会错位。
解决方案
将原始文本转换为UTF-16编码,按Telegram的参数截取对应部分后再转回UTF-8;同时调整代码逻辑,避免转义字符干扰替换,改用精准的位置替换而非全局字符串替换。
修改后的完整代码
<?php $admin = ''; // 填入你的用户ID $update = file_get_contents('php://input'); $update = json_decode($update, TRUE); if (!empty($update['message'])) { $message = $update['message']; $text = $message['text'] ?? ''; $links = []; if (!empty($message["entities"])) { foreach ($message["entities"] as $entity) { if ($entity["type"] === "text_link") { // 筛选亚马逊相关链接 if (preg_match("/(https?:\/\/(?:www\.)?(?:amazon\.[a-z\.]+|amzn\.to+|amzn\.eu)\/[^\s]+)/i", $entity["url"], $matches)) { $links[] = [ "url" => $entity["url"], "offset" => $entity["offset"], "length" => $entity["length"] ]; } } } } if (!empty($links)) { // 处理UTF-16与UTF-8的计数差异 foreach ($links as $link) { $url = $link["url"]; $offset = $link["offset"]; $length = $link["length"]; // 1. 将UTF-8文本转成Telegram使用的UTF-16LE编码 $utf16Text = mb_convert_encoding($text, 'UTF-16LE', 'UTF-8'); // 2. 按UTF-16代码单元截取:每个单元占2字节,所以偏移量和长度都乘以2 $utf16Original = substr($utf16Text, $offset * 2, $length * 2); // 3. 转回UTF-8得到正确的原始文本片段 $originalText = mb_convert_encoding($utf16Original, 'UTF-8', 'UTF-16LE'); // 用mb_substr_replace精准替换对应位置的文本,避免重复内容误替换 $text = mb_substr_replace($text, $url, $offset, $length); } } // 最后转义HTML特殊字符,避免发送消息时的格式冲突 $text = htmlspecialchars($text, ENT_QUOTES); inviaMessaggio($admin, $text, null, "true"); } function inviaMessaggio($chat_id, $text, $tastiera, $anteprima = "true") { $args = []; $args['chat_id'] = $chat_id; $args['text'] = $text; $args['parse_mode'] = "HTML"; $args['disable_web_page_preview'] = $anteprima; $rm = null; if (!empty($tastiera)) { $rm = json_encode(['inline_keyboard' => $tastiera]); } $args['reply_markup'] = $rm; // 发送消息 $response = curlRequest($GLOBALS['website'].'/sendMessage', $args); // 从响应中提取消息ID $messageData = json_decode($response, true); if (isset($messageData['result'])) { $messageId = $messageData['result']['message_id']; return ['response' => $response, 'message_id' => $messageId]; } } // 补充缺失的curl请求函数 function curlRequest($url, $data) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); curl_close($ch); return $result; } ?>
关键修改说明
- 编码转换逻辑:通过
mb_convert_encoding实现UTF-8与UTF-16LE的互转,完全匹配Telegram的计数规则,解决偏移量偏差问题。 - 精准替换:用
mb_substr_replace替代str_replace,直接按位置替换目标文本,避免重复内容导致的误替换。 - 逻辑优化:
- 初始化
$text变量,避免未定义错误; - 合并Entity遍历逻辑,减少重复循环;
- 将
htmlspecialchars移到替换之后,防止转义字符干扰原始文本匹配; - 补充了缺失的
curlRequest函数,确保代码可直接运行。
- 初始化
内容的提问来源于stack exchange,提问作者Vainucleo
相关产品推荐
相关产品推荐

