dpl/plugins-core/Telegram/Plugin.php

385 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Plugin\Telegram;
use App\Models\Order;
use App\Models\Ticket;
use App\Models\User;
use App\Services\Plugin\AbstractPlugin;
use App\Services\Plugin\HookManager;
use App\Services\TelegramService;
use App\Services\TicketService;
use App\Utils\Helper;
use Illuminate\Support\Facades\Log;
class Plugin extends AbstractPlugin
{
protected array $commands = [];
protected TelegramService $telegramService;
protected array $commandConfigs = [
'/start' => ['description' => '开始使用', 'handler' => 'handleStartCommand'],
'/bind' => ['description' => '绑定账号', 'handler' => 'handleBindCommand'],
'/traffic' => ['description' => '查看流量', 'handler' => 'handleTrafficCommand'],
'/getlatesturl' => ['description' => '获取订阅链接', 'handler' => 'handleGetLatestUrlCommand'],
'/unbind' => ['description' => '解绑账号', 'handler' => 'handleUnbindCommand'],
];
public function boot(): void
{
$this->telegramService = new TelegramService();
$this->registerDefaultCommands();
$this->filter('telegram.message.handle', [$this, 'handleMessage'], 10);
$this->listen('telegram.message.unhandled', [$this, 'handleUnknownCommand'], 10);
$this->listen('telegram.message.error', [$this, 'handleError'], 10);
$this->filter('telegram.bot.commands', [$this, 'addBotCommands'], 10);
$this->listen('ticket.create.after', [$this, 'sendTicketNotify'], 10);
$this->listen('ticket.reply.user.after', [$this, 'sendTicketNotify'], 10);
$this->listen('payment.notify.success', [$this, 'sendPaymentNotify'], 10);
}
public function sendPaymentNotify(Order $order): void
{
if (!$this->getConfig('enable_payment_notify', true)) {
return;
}
$payment = $order->payment;
if (!$payment) {
Log::warning('支付通知失败:订单关联的支付方式不存在', ['order_id' => $order->id]);
return;
}
$message = sprintf(
"💰成功收款%s元\n" .
"———————————————\n" .
"支付接口:%s\n" .
"支付渠道:%s\n" .
"本站订单:`%s`",
$order->total_amount / 100,
Helper::escapeMarkdown($payment->payment),
Helper::escapeMarkdown($payment->name),
$order->trade_no
);
$this->telegramService->sendMessageWithAdmin($message, true);
}
public function sendTicketNotify(Ticket $ticket): void
{
if (!$this->getConfig('enable_ticket_notify', true)) {
return;
}
try { app(\App\Services\TelegramTickets\TicketBridge::class)->enqueue($ticket); }
catch (\Throwable $e) { Log::warning('Telegram ticket notification could not be queued', ['ticket_id' => $ticket->id]); }
}
protected function registerDefaultCommands(): void
{
foreach ($this->commandConfigs as $command => $config) {
$this->registerTelegramCommand($command, [$this, $config['handler']]);
}
$this->registerTelegramCommand('/staffbind', [$this, 'handleStaffBind']);
$this->registerReplyHandler('/(📮.*?工单提醒.*?#?|工单ID: ?)(\\d+)/', [$this, 'handleTicketReply']);
}
public function registerTelegramCommand(string $command, callable $handler): void
{
$this->commands['commands'][$command] = $handler;
}
public function registerReplyHandler(string $regex, callable $handler): void
{
$this->commands['replies'][$regex] = $handler;
}
/**
* 发送消息给用户
*/
protected function sendMessage(object $msg, string $message): void
{
$this->telegramService->sendMessage($msg->chat_id, $message, 'markdown');
}
/**
* 检查是否为私聊
*/
protected function checkPrivateChat(object $msg): bool
{
if (!$msg->is_private) {
$this->sendMessage($msg, '请在私聊中使用此命令');
return false;
}
return true;
}
/**
* 获取绑定的用户
*/
protected function getBoundUser(object $msg): ?User
{
$user = User::where('telegram_id', $msg->chat_id)->first();
if (!$user) {
$this->sendMessage($msg, '请先绑定账号');
return null;
}
return $user;
}
public function handleStaffBind(object $msg): void
{
$result = app(\App\Services\TelegramTickets\TicketBridge::class)->bind($msg->raw_message ?? [], (string) ($msg->args[0] ?? ''));
$this->telegramService->sendMessage($msg->chat_id, $result);
}
public function handleStartCommand(object $msg): void
{
$welcomeTitle = $this->getConfig('start_welcome_title', '🎉 欢迎使用 XBoard Telegram Bot!');
$botDescription = $this->getConfig('start_bot_description', '🤖 我是您的专属助手,可以帮助您:\\n• 绑定您的 XBoard 账号\\n• 查看流量使用情况\\n• 获取最新订阅链接\\n• 管理账号绑定状态');
$footer = $this->getConfig('start_footer', '💡 提示:所有命令都需要在私聊中使用');
$welcomeText = $welcomeTitle . "\n\n" . $botDescription . "\n\n";
$user = User::where('telegram_id', $msg->chat_id)->first();
if ($user) {
$welcomeText .= "✅ 您已绑定账号:{$user->email}\n\n";
$welcomeText .= $this->getConfig('start_unbind_guide', '📋 可用命令:\\n/traffic - 查看流量使用情况\\n/getlatesturl - 获取订阅链接\\n/unbind - 解绑账号');
} else {
$welcomeText .= $this->getConfig('start_bind_guide', '🔗 请先绑定您的 XBoard 账号:\\n1. 登录您的 XBoard 账户\\n2. 复制您的订阅链接\\n3. 发送 /bind + 订阅链接') . "\n\n";
$welcomeText .= $this->getConfig('start_bind_commands', '📋 可用命令:\\n/bind [订阅链接] - 绑定账号');
}
$welcomeText .= "\n\n" . $footer;
$welcomeText = str_replace('\\n', "\n", $welcomeText);
$this->sendMessage($msg, $welcomeText);
}
public function handleMessage(bool $handled, array $data): bool
{
list($msg) = $data;
if ($handled)
return $handled;
try {
return match ($msg->message_type) {
'message' => $this->handleCommandMessage($msg),
'reply_message' => $this->handleReplyMessage($msg),
default => false
};
} catch (\Exception $e) {
if (($msg->message_type ?? '') === 'reply_message') { throw new \App\Exceptions\ApiException('工单回复暂时未完成,请重试', 500); }
Log::error('Telegram 命令处理意外错误', [
'command' => $msg->command ?? 'unknown',
'chat_id' => $msg->chat_id ?? 'unknown',
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
]);
if (isset($msg->chat_id)) {
$this->telegramService->sendMessage($msg->chat_id, '系统繁忙,请稍后重试');
}
return true;
}
}
protected function handleCommandMessage(object $msg): bool
{
if (!isset($this->commands['commands'][$msg->command])) {
return false;
}
call_user_func($this->commands['commands'][$msg->command], $msg);
return true;
}
protected function handleReplyMessage(object $msg): bool
{
if (!isset($this->commands['replies'])) {
return false;
}
$bridge = app(\App\Services\TelegramTickets\TicketBridge::class);
$result = $bridge->reply($msg->raw_message ?? [], (int) ($msg->update_id ?? 0));
if ($result !== null) { $this->sendMessage($msg, $result); return true; }
return false;
}
public function handleUnknownCommand(array $data): void
{
list($msg) = $data;
if (!$msg->is_private || $msg->message_type !== 'message')
return;
$helpText = $this->getConfig('help_text', '未知命令,请查看帮助');
$this->telegramService->sendMessage($msg->chat_id, $helpText);
}
public function handleError(array $data): void
{
list($msg, $e) = $data;
Log::error('Telegram 消息处理错误', [
'chat_id' => $msg->chat_id ?? 'unknown',
'command' => $msg->command ?? 'unknown',
'message_type' => $msg->message_type ?? 'unknown',
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
]);
}
public function handleBindCommand(object $msg): void
{
if (!$this->checkPrivateChat($msg)) {
return;
}
$subscribeUrl = $msg->args[0] ?? null;
if (!$subscribeUrl) {
$this->sendMessage($msg, '参数有误,请携带订阅地址发送');
return;
}
$token = $this->extractTokenFromUrl($subscribeUrl);
if (!$token) {
$this->sendMessage($msg, '订阅地址无效');
return;
}
$user = User::where('token', $token)->first();
if (!$user) {
$this->sendMessage($msg, '用户不存在');
return;
}
if ($user->telegram_id) {
$this->sendMessage($msg, '该账号已经绑定了Telegram账号');
return;
}
$user->telegram_id = $msg->chat_id;
if (!$user->save()) {
$this->sendMessage($msg, '设置失败');
return;
}
HookManager::call('user.telegram.bind.after', [$user]);
$this->sendMessage($msg, '绑定成功');
}
protected function extractTokenFromUrl(string $url): ?string
{
$parsedUrl = parse_url($url);
if (isset($parsedUrl['query'])) {
parse_str($parsedUrl['query'], $query);
if (isset($query['token'])) {
return $query['token'];
}
}
if (isset($parsedUrl['path'])) {
$pathParts = explode('/', trim($parsedUrl['path'], '/'));
$lastPart = end($pathParts);
return $lastPart ?: null;
}
return null;
}
public function handleTrafficCommand(object $msg): void
{
if (!$this->checkPrivateChat($msg)) {
return;
}
$user = $this->getBoundUser($msg);
if (!$user) {
return;
}
$transferUsed = $user->u + $user->d;
$transferTotal = $user->transfer_enable;
$transferRemaining = $transferTotal - $transferUsed;
$usagePercentage = $transferTotal > 0 ? ($transferUsed / $transferTotal) * 100 : 0;
$text = sprintf(
"📊 流量使用情况\n\n已用流量:%sG\n总流量:%sG\n剩余流量:%sG\n使用率:%.2f%%",
$this->transferToGBString($transferUsed),
$this->transferToGBString($transferTotal),
$this->transferToGBString($transferRemaining),
$usagePercentage
);
$this->sendMessage($msg, $text);
}
public function handleGetLatestUrlCommand(object $msg): void
{
if (!$this->checkPrivateChat($msg)) {
return;
}
$user = $this->getBoundUser($msg);
if (!$user) {
return;
}
$subscribeUrl = Helper::getSubscribeUrl($user->token);
$text = sprintf("🔗 您的订阅链接:\n\n%s", $subscribeUrl);
$this->sendMessage($msg, $text);
}
public function handleUnbindCommand(object $msg): void
{
if (!$this->checkPrivateChat($msg)) {
return;
}
$user = $this->getBoundUser($msg);
if (!$user) {
return;
}
$user->telegram_id = null;
if (!$user->save()) {
$this->sendMessage($msg, '解绑失败');
return;
}
$this->sendMessage($msg, '解绑成功');
}
public function handleTicketReply(object $msg, array $matches): void
{
// Legacy text-derived ticket IDs are no longer accepted.
$this->sendMessage($msg, '请直接回复机器人发送的新工单提醒消息。');
}
/**
* 添加 Bot 命令到命令列表
*/
public function addBotCommands(array $commands): array
{
foreach ($this->commandConfigs as $command => $config) {
$commands[] = [
'command' => $command,
'description' => $config['description']
];
}
return $commands;
}
private function transferToGBString(float $transfer_enable, int $decimals = 2): string
{
return number_format(Helper::transferToGB($transfer_enable), $decimals, '.', '');
}
}