|
|
@@ -5,9 +5,11 @@ namespace App\Services;
|
|
|
use App\Models\ActivityReward;
|
|
|
use Endroid\QrCode\Builder\Builder;
|
|
|
use Endroid\QrCode\Writer\PngWriter;
|
|
|
+use GuzzleHttp\Client;
|
|
|
use Telegram\Bot\Api;
|
|
|
use App\Models\Config;
|
|
|
use Telegram\Bot\FileUpload\InputFile;
|
|
|
+use Telegram\Bot\HttpClients\GuzzleHttpClient;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
use App\Jobs\SendTelegramMessageJob;
|
|
|
use App\Jobs\SendTelegramGroupMessageJob;
|
|
|
@@ -195,20 +197,83 @@ abstract class BaseService
|
|
|
return app(Api::class);
|
|
|
}
|
|
|
|
|
|
+ protected static function telegramForAttempt(int $attempt): Api
|
|
|
+ {
|
|
|
+ $transport = self::getTelegramTransportForAttempt($attempt);
|
|
|
+ $clientOptions = [];
|
|
|
+
|
|
|
+ if (!empty($transport['proxy_url'])) {
|
|
|
+ $clientOptions['proxy'] = $transport['proxy_url'];
|
|
|
+ }
|
|
|
+
|
|
|
+ $telegram = new Api(config('services.telegram.token'), false, new GuzzleHttpClient(new Client($clientOptions)));
|
|
|
+ $telegram->setTimeOut((int)$transport['timeout']);
|
|
|
+ $telegram->setConnectTimeOut((int)$transport['connect_timeout']);
|
|
|
+
|
|
|
+ return $telegram;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static function getTelegramTransportForAttempt(int $attempt): array
|
|
|
+ {
|
|
|
+ if ($attempt <= 1 || !config('services.telegram.proxy.enabled', true)) {
|
|
|
+ return [
|
|
|
+ 'name' => 'direct',
|
|
|
+ 'proxy_url' => '',
|
|
|
+ 'timeout' => (int)config('services.telegram.first_timeout', 5),
|
|
|
+ 'connect_timeout' => (int)config('services.telegram.first_connect_timeout', 3),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'name' => 'squid',
|
|
|
+ 'proxy_url' => self::getTelegramProxyUrl(),
|
|
|
+ 'timeout' => (int)config('services.telegram.proxy_timeout', 20),
|
|
|
+ 'connect_timeout' => (int)config('services.telegram.proxy_connect_timeout', 8),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static function getTelegramProxyUrl(): string
|
|
|
+ {
|
|
|
+ $scheme = config('services.telegram.proxy.scheme', 'http');
|
|
|
+ $host = config('services.telegram.proxy.host', '');
|
|
|
+ $port = config('services.telegram.proxy.port', '3128');
|
|
|
+ $username = (string)config('services.telegram.proxy.username', '');
|
|
|
+ $password = (string)config('services.telegram.proxy.password', '');
|
|
|
+
|
|
|
+ if (empty($host)) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($username === '' && $password === '') {
|
|
|
+ return "{$scheme}://{$host}:{$port}";
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($password === '') {
|
|
|
+ return "{$scheme}://" . rawurlencode($username) . "@{$host}:{$port}";
|
|
|
+ }
|
|
|
+
|
|
|
+ return "{$scheme}://" . rawurlencode($username) . ':' . rawurlencode($password) . "@{$host}:{$port}";
|
|
|
+ }
|
|
|
+
|
|
|
protected static function sendTelegramRequest(callable $callback, string $action, array $context = [])
|
|
|
{
|
|
|
- $maxAttempts = max(1, (int)config('services.telegram.retry_attempts', 3));
|
|
|
+ $maxAttempts = max(1, (int)config('services.telegram.retry_attempts', 2));
|
|
|
$delaySeconds = max(1, (int)config('services.telegram.retry_delay_seconds', 3));
|
|
|
|
|
|
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
|
|
|
+ $transport = self::getTelegramTransportForAttempt($attempt);
|
|
|
try {
|
|
|
- return $callback();
|
|
|
+ return $callback(self::telegramForAttempt($attempt));
|
|
|
} catch (\Throwable $exception) {
|
|
|
$safeMessage = self::sanitizeTelegramError($exception->getMessage());
|
|
|
$logContext = array_merge($context, [
|
|
|
'action' => $action,
|
|
|
'attempt' => $attempt,
|
|
|
'max_attempts' => $maxAttempts,
|
|
|
+ 'transport' => $transport['name'],
|
|
|
+ 'using_proxy' => !empty($transport['proxy_url']),
|
|
|
+ 'timeout' => $transport['timeout'],
|
|
|
+ 'connect_timeout' => $transport['connect_timeout'],
|
|
|
'error' => $safeMessage,
|
|
|
'exception' => get_class($exception),
|
|
|
]);
|
|
|
@@ -251,9 +316,22 @@ abstract class BaseService
|
|
|
return min($maxDelaySeconds, (int)$matches[1] + 1);
|
|
|
}
|
|
|
|
|
|
+ if (self::isTelegramConnectionError($message)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
return min($maxDelaySeconds, $delaySeconds * $attempt);
|
|
|
}
|
|
|
|
|
|
+ protected static function isTelegramConnectionError(string $message): bool
|
|
|
+ {
|
|
|
+ return str_contains($message, 'cURL error 7')
|
|
|
+ || str_contains($message, 'cURL error 28')
|
|
|
+ || str_contains($message, 'Failed to connect')
|
|
|
+ || str_contains($message, 'Connection timed out')
|
|
|
+ || str_contains($message, 'Operation timed out');
|
|
|
+ }
|
|
|
+
|
|
|
// /**
|
|
|
// * @description: 群组通知(自动分段发送,支持中文与多字节字符)
|
|
|
// * @param string $text 通知内容
|
|
|
@@ -337,7 +415,7 @@ abstract class BaseService
|
|
|
if (count($keyboard) > 0) {
|
|
|
$botMsg['reply_markup'] = json_encode(['inline_keyboard' => $keyboard]);
|
|
|
}
|
|
|
- $response = self::sendTelegramRequest(fn() => self::telegram()->sendPhoto($botMsg), 'sendPhoto', [
|
|
|
+ $response = self::sendTelegramRequest(fn(Api $telegram) => $telegram->sendPhoto($botMsg), 'sendPhoto', [
|
|
|
'chat_id' => $botMsg['chat_id'],
|
|
|
'has_image' => true,
|
|
|
]);
|
|
|
@@ -368,7 +446,7 @@ abstract class BaseService
|
|
|
];
|
|
|
if ($index > 0) {
|
|
|
$res[] = $botMsg;
|
|
|
- self::sendTelegramRequest(fn() => self::telegram()->sendMessage($botMsg), 'sendMessage', [
|
|
|
+ self::sendTelegramRequest(fn(Api $telegram) => $telegram->sendMessage($botMsg), 'sendMessage', [
|
|
|
'chat_id' => $botMsg['chat_id'],
|
|
|
'chunk_index' => $index,
|
|
|
'has_image' => false,
|
|
|
@@ -384,21 +462,21 @@ abstract class BaseService
|
|
|
$botMsg['protect_content'] = true;
|
|
|
|
|
|
$res[] = $botMsg;
|
|
|
- $response = self::sendTelegramRequest(fn() => self::telegram()->sendPhoto($botMsg), 'sendPhoto', [
|
|
|
+ $response = self::sendTelegramRequest(fn(Api $telegram) => $telegram->sendPhoto($botMsg), 'sendPhoto', [
|
|
|
'chat_id' => $botMsg['chat_id'],
|
|
|
'chunk_index' => $index,
|
|
|
'has_image' => true,
|
|
|
]);
|
|
|
} else {
|
|
|
$res[] = $botMsg;
|
|
|
- $response = self::sendTelegramRequest(fn() => self::telegram()->sendMessage($botMsg), 'sendMessage', [
|
|
|
+ $response = self::sendTelegramRequest(fn(Api $telegram) => $telegram->sendMessage($botMsg), 'sendMessage', [
|
|
|
'chat_id' => $botMsg['chat_id'],
|
|
|
'chunk_index' => $index,
|
|
|
'has_image' => false,
|
|
|
]);
|
|
|
}
|
|
|
if ($isTop === true) {
|
|
|
- self::sendTelegramRequest(fn() => self::telegram()->pinChatMessage([
|
|
|
+ self::sendTelegramRequest(fn(Api $telegram) => $telegram->pinChatMessage([
|
|
|
'chat_id' => "@{$bettingGroup}",
|
|
|
'message_id' => $response->get('message_id')
|
|
|
]), 'pinChatMessage', [
|
|
|
@@ -448,13 +526,13 @@ abstract class BaseService
|
|
|
$botMsg['photo'] = InputFile::create($image);
|
|
|
$botMsg['caption'] = $text;
|
|
|
$botMsg['protect_content'] = false; // 防止转发
|
|
|
- self::sendTelegramRequest(fn() => self::telegram()->sendPhoto($botMsg), 'sendPhoto', [
|
|
|
+ self::sendTelegramRequest(fn(Api $telegram) => $telegram->sendPhoto($botMsg), 'sendPhoto', [
|
|
|
'chat_id' => $chatId,
|
|
|
'has_image' => true,
|
|
|
]);
|
|
|
} else {
|
|
|
$botMsg['text'] = $text;
|
|
|
- self::sendTelegramRequest(fn() => self::telegram()->sendMessage($botMsg), 'sendMessage', [
|
|
|
+ self::sendTelegramRequest(fn(Api $telegram) => $telegram->sendMessage($botMsg), 'sendMessage', [
|
|
|
'chat_id' => $chatId,
|
|
|
'has_image' => false,
|
|
|
]);
|