fix: preserve original Telegram reply classification as requested

This commit is contained in:
hellcat 2026-10-11 13:00:10 +08:00
parent 85246d26a6
commit 8ccc38908b
4 changed files with 20 additions and 5 deletions

View File

@ -100,9 +100,9 @@ class TelegramController extends Controller
'is_private' => $message['chat']['type'] === 'private', 'is_private' => $message['chat']['type'] === 'private',
]; ];
if (isset($message['reply_to_message']['message_id'])) { if (isset($message['reply_to_message']['text'])) {
$this->msg->message_type = 'reply_message'; $this->msg->message_type = 'reply_message';
$this->msg->reply_text = $message['reply_to_message']['text'] ?? ''; $this->msg->reply_text = $message['reply_to_message']['text'];
} }
} }

View File

@ -71,3 +71,7 @@ Resend 发信、常规 SMTP、配额回退、投递事件回调、master 一次
管理员完成绑定并开启回复后,已通过正式队列向管理员私聊发送一条隔离测试工单通知。用户确认收到并已使用回复功能发送文字,但现场核对仍未发现管理员回复、关联更新记录或 ticket_reply 邮件。绑定、通知映射、回复权限和官方回调 HTTP 200 均已核对,不能据此宣布真实闭环通过。 管理员完成绑定并开启回复后,已通过正式队列向管理员私聊发送一条隔离测试工单通知。用户确认收到并已使用回复功能发送文字,但现场核对仍未发现管理员回复、关联更新记录或 ticket_reply 邮件。绑定、通知映射、回复权限和官方回调 HTTP 200 均已核对,不能据此宣布真实闭环通过。
补充真实控制器 → 原插件 → 工单与邮件队列的隔离回归,回复按引用消息编号识别而不依赖引用原文;为已绑定管理员增加一小时自动过期、无正文的路由诊断缓存。工单与邮件测试合计 70 项、383 个断言通过,PHP 语法检查通过。修正已提交推送并通过 Git fast-forward 发布至 5b643135e1d197b10819f71a3e501907ffc2c8bf;发布前已备份旧代码及既有差异,PHP-FPM 已重载,PHP-FPM/Horizon 均 active。发布后再次核对测试工单仍无管理员回复或邮件,短期诊断尚无新的管理员消息;真实回复验收仍未通过。 补充真实控制器 → 原插件 → 工单与邮件队列的隔离回归,回复按引用消息编号识别而不依赖引用原文;为已绑定管理员增加一小时自动过期、无正文的路由诊断缓存。工单与邮件测试合计 70 项、383 个断言通过,PHP 语法检查通过。修正已提交推送并通过 Git fast-forward 发布至 5b643135e1d197b10819f71a3e501907ffc2c8bf;发布前已备份旧代码及既有差异,PHP-FPM 已重载,PHP-FPM/Horizon 均 active。发布后再次核对测试工单仍无管理员回复或邮件,短期诊断尚无新的管理员消息;真实回复验收仍未通过。
### 保留原有回复分类
用户表示此前可能回复错了消息,并明确要求保留原来的“引用包含原文才进入工单处理”规则。已恢复该分类条件;消息编号关联、权限和去重校验不变,无正文的短期诊断保留。此调整不代表先前故障已定位或真实闭环已通过;仍需对原始通知发送回复完成验收。

View File

@ -36,4 +36,4 @@
## 回复接收排查 ## 回复接收排查
回复按 reply_to_message.message_id 识别,不依赖引用原文是否包含 text。对已绑定且未禁用管理员的私聊文字消息,在缓存保留最后一次接收时间、更新编号、是否使用回复、是否命中工单关联,最多一小时;不保存消息正文、账号凭据或原始请求。该记录仅供服务器维护排错,不能以收到回调代替工单写回或邮件送达验收。 按用户最新要求保留原来的回复分类:仅引用中包含 text 时进入工单回复,进入后仍按 reply_to_message.message_id 校验关联,不从原文字样推断工单或权限。对已绑定且未禁用管理员的私聊文字消息,在缓存保留最后一次接收时间、更新编号、是否使用回复、是否命中工单关联,最多一小时;不保存消息正文、账号凭据或原始请求。该记录仅供服务器维护排错,不能以收到回调代替工单写回或邮件送达验收。

View File

@ -94,13 +94,13 @@ class TicketBridgeTest extends TestCase {
$this->assertStringContainsString('已绑定其他网站',$this->bridge->bind($this->message(['chat'=>['id'=>999],'from'=>['id'=>999]]),$code)); $this->assertStringContainsString('已绑定其他网站',$this->bridge->bind($this->message(['chat'=>['id'=>999],'from'=>['id'=>999]]),$code));
$this->assertSame(1,TicketMessage::count());$this->assertSame(123,(int)$this->admin->fresh()->telegram_id); $this->assertSame(1,TicketMessage::count());$this->assertSame(123,(int)$this->admin->fresh()->telegram_id);
} }
public function test_real_webhook_routes_reply_by_message_id_without_original_text():void { public function test_real_webhook_routes_reply_with_original_text_and_message_id():void {
$this->app->instance('hook.filters',[]); $this->app->instance('hook.filters',[]);
require_once base_path('plugins-core/Telegram/Plugin.php'); require_once base_path('plugins-core/Telegram/Plugin.php');
Http::fake(['api.telegram.org/*'=>Http::response(['ok'=>true,'result'=>['message_id'=>987]],200)]); Http::fake(['api.telegram.org/*'=>Http::response(['ok'=>true,'result'=>['message_id'=>987]],200)]);
$plugin=new \Plugin\Telegram\Plugin('telegram');$plugin->boot(); $plugin=new \Plugin\Telegram\Plugin('telegram');$plugin->boot();
admin_setting(['telegram_webhook_secret'=>'testsecret']); admin_setting(['telegram_webhook_secret'=>'testsecret']);
$message=$this->message(['message_id'=>567]); $message=$this->message(['message_id'=>567,'reply_to_message'=>['text'=>'Ticket notification']]);
$request=\Illuminate\Http\Request::create('/','POST',[],[],[],['CONTENT_TYPE'=>'application/json'],json_encode(['update_id'=>55,'message'=>$message])); $request=\Illuminate\Http\Request::create('/','POST',[],[],[],['CONTENT_TYPE'=>'application/json'],json_encode(['update_id'=>55,'message'=>$message]));
$request->headers->set('X-Telegram-Bot-Api-Secret-Token','testsecret'); $request->headers->set('X-Telegram-Bot-Api-Secret-Token','testsecret');
app(\App\Http\Controllers\V1\Guest\TelegramController::class)->webhook($request); app(\App\Http\Controllers\V1\Guest\TelegramController::class)->webhook($request);
@ -110,6 +110,17 @@ class TicketBridgeTest extends TestCase {
$this->assertTrue($diagnostic['is_reply']);$this->assertTrue($diagnostic['matched_ticket']); $this->assertTrue($diagnostic['is_reply']);$this->assertTrue($diagnostic['matched_ticket']);
$this->assertArrayNotHasKey('text',$diagnostic); $this->assertArrayNotHasKey('text',$diagnostic);
} }
public function test_webhook_preserves_original_text_requirement_for_ticket_replies():void {
$this->app->instance('hook.filters',[]);
require_once base_path('plugins-core/Telegram/Plugin.php');
Http::fake(['api.telegram.org/*'=>Http::response(['ok'=>true,'result'=>['message_id'=>987]],200)]);
$plugin=new \Plugin\Telegram\Plugin('telegram');$plugin->boot();
admin_setting(['telegram_webhook_secret'=>'testsecret']);
$request=\Illuminate\Http\Request::create('/','POST',[],[],[],['CONTENT_TYPE'=>'application/json'],json_encode(['update_id'=>56,'message'=>$this->message(['message_id'=>568])]));
$request->headers->set('X-Telegram-Bot-Api-Secret-Token','testsecret');
app(\App\Http\Controllers\V1\Guest\TelegramController::class)->webhook($request);
$this->assertSame(1,TicketMessage::count());Bus::assertNotDispatched(SendEmailJob::class);
}
public function test_routing_diagnostics_distinguish_unmatched_reply_and_ignore_customers():void { public function test_routing_diagnostics_distinguish_unmatched_reply_and_ignore_customers():void {
$this->bridge->observeIncoming($this->message(['reply_to_message'=>['message_id'=>999]]),9); $this->bridge->observeIncoming($this->message(['reply_to_message'=>['message_id'=>999]]),9);
$key='tg_ticket_inbound_'.$this->bridge->botKey().'_'.$this->admin->id; $key='tg_ticket_inbound_'.$this->bridge->botKey().'_'.$this->admin->id;