|
@@ -3,6 +3,7 @@
|
|
|
namespace App\Services;
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
use Illuminate\Database\Query\Builder;
|
|
use Illuminate\Database\Query\Builder;
|
|
|
|
|
+use Illuminate\Support\Facades\Crypt;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Support\Facades\Validator;
|
|
@@ -33,13 +34,15 @@ class TelegramChannelService
|
|
|
return $this->present($row, (int) $row->default_channel_id === $id, true);
|
|
return $this->present($row, (int) $row->default_channel_id === $id, true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public function save(array $input, bool $legacy = false): array
|
|
|
|
|
|
|
+ public function save(#[\SensitiveParameter] array $input, bool $legacy = false): array
|
|
|
{
|
|
{
|
|
|
$hasMessage = array_intersect(['type', 'text', 'image', 'video', 'button'], array_keys($input)) !== [];
|
|
$hasMessage = array_intersect(['type', 'text', 'image', 'video', 'button'], array_keys($input)) !== [];
|
|
|
$fields = Validator::make($input, [
|
|
$fields = Validator::make($input, [
|
|
|
'id' => $legacy ? 'prohibited' : 'sometimes|required|integer|min:1',
|
|
'id' => $legacy ? 'prohibited' : 'sometimes|required|integer|min:1',
|
|
|
'name' => 'sometimes|required|string|max:255',
|
|
'name' => 'sometimes|required|string|max:255',
|
|
|
'chatId' => 'required|string|max:64',
|
|
'chatId' => 'required|string|max:64',
|
|
|
|
|
+ 'bot_username' => ['nullable', 'string', 'max:64', 'regex:/^@?[a-z][a-z0-9_]*$/iD'],
|
|
|
|
|
+ 'bot_token' => ['nullable', 'string', 'max:255', 'regex:/^[1-9][0-9]*:[a-z0-9_-]+$/iD'],
|
|
|
'enabled' => 'sometimes|required|boolean',
|
|
'enabled' => 'sometimes|required|boolean',
|
|
|
'type' => [($legacy || $hasMessage) ? 'required' : 'sometimes', 'string', 'in:text,image,video'],
|
|
'type' => [($legacy || $hasMessage) ? 'required' : 'sometimes', 'string', 'in:text,image,video'],
|
|
|
])->validate();
|
|
])->validate();
|
|
@@ -49,17 +52,23 @@ class TelegramChannelService
|
|
|
throw ValidationException::withMessages(['chatId' => '请输入频道用户名或负数频道 ID']);
|
|
throw ValidationException::withMessages(['chatId' => '请输入频道用户名或负数频道 ID']);
|
|
|
}
|
|
}
|
|
|
$message = isset($fields['type']) ? self::message($input) : null;
|
|
$message = isset($fields['type']) ? self::message($input) : null;
|
|
|
- $row = !$legacy && isset($fields['id']) ? $this->find((int) $fields['id']) : null;
|
|
|
|
|
|
|
+ $row = isset($fields['id']) && !$legacy ? $this->find((int) $fields['id']) : null;
|
|
|
|
|
+ if ($legacy) {
|
|
|
|
|
+ $row = DB::table('telegram_channels')->where('target', $target)->orWhere('telegram_chat_id', $target)->first();
|
|
|
|
|
+ }
|
|
|
|
|
+ $newToken = $fields['bot_token'] ?? null;
|
|
|
|
|
+ $bot = $this->validateBot($fields['bot_username'] ?? null, $newToken, $row);
|
|
|
$targetUnchanged = $row !== null && in_array($target, [$row->target, $row->telegram_chat_id], true);
|
|
$targetUnchanged = $row !== null && in_array($target, [$row->target, $row->telegram_chat_id], true);
|
|
|
- $identity = $targetUnchanged
|
|
|
|
|
|
|
+ $identity = $targetUnchanged && $newToken === null
|
|
|
? ['target' => $row->target, 'telegram_chat_id' => $row->telegram_chat_id, 'title' => $row->name]
|
|
? ['target' => $row->target, 'telegram_chat_id' => $row->telegram_chat_id, 'title' => $row->name]
|
|
|
- : $this->resolve($target);
|
|
|
|
|
|
|
+ : $this->resolve($target, $newToken ?? $this->storedToken($row));
|
|
|
|
|
|
|
|
- return DB::transaction(function () use ($fields, $identity, $message, $legacy, $row) {
|
|
|
|
|
|
|
+ return DB::transaction(function () use ($fields, $identity, $message, $legacy, $row, $bot) {
|
|
|
$default = $this->settings()->lockForUpdate()->first();
|
|
$default = $this->settings()->lockForUpdate()->first();
|
|
|
$channels = DB::table('telegram_channels');
|
|
$channels = DB::table('telegram_channels');
|
|
|
$existing = $row === null ? null : (clone $channels)->where('id', $row->id)->first();
|
|
$existing = $row === null ? null : (clone $channels)->where('id', $row->id)->first();
|
|
|
- if ($row !== null && ($existing === null || $existing->target !== $row->target || $existing->telegram_chat_id !== $row->telegram_chat_id)) {
|
|
|
|
|
|
|
+ if ($row !== null && ($existing === null || $existing->target !== $row->target
|
|
|
|
|
+ || $existing->telegram_chat_id !== $row->telegram_chat_id || $existing->bot_token !== $row->bot_token)) {
|
|
|
throw ValidationException::withMessages(['id' => '频道已变更,请刷新后重试']);
|
|
throw ValidationException::withMessages(['id' => '频道已变更,请刷新后重试']);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -69,8 +78,11 @@ class TelegramChannelService
|
|
|
$query->orWhere('telegram_chat_id', $identity['telegram_chat_id']);
|
|
$query->orWhere('telegram_chat_id', $identity['telegram_chat_id']);
|
|
|
}
|
|
}
|
|
|
})->get();
|
|
})->get();
|
|
|
- if ($legacy && $match->count() === 1) {
|
|
|
|
|
|
|
+ if ($legacy && $row === null && $match->count() === 1) {
|
|
|
$existing = $match->first();
|
|
$existing = $match->first();
|
|
|
|
|
+ if ($existing->bot_token !== null && $bot === []) {
|
|
|
|
|
+ throw ValidationException::withMessages(['chatId' => '请使用已保存的频道标识,或通过频道管理按 ID 编辑']);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
if ($match->contains(fn ($channel) => (int) $channel->id !== (int) $existing?->id)) {
|
|
if ($match->contains(fn ($channel) => (int) $channel->id !== (int) $existing?->id)) {
|
|
|
throw ValidationException::withMessages(['chatId' => '该频道已存在']);
|
|
throw ValidationException::withMessages(['chatId' => '该频道已存在']);
|
|
@@ -86,7 +98,7 @@ class TelegramChannelService
|
|
|
? ($existing?->message ?? 'null')
|
|
? ($existing?->message ?? 'null')
|
|
|
: json_encode($message, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
|
|
: json_encode($message, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
|
|
|
'updated_at' => now(),
|
|
'updated_at' => now(),
|
|
|
- ];
|
|
|
|
|
|
|
+ ] + $bot;
|
|
|
if ($existing === null) {
|
|
if ($existing === null) {
|
|
|
$firstLegacy = $legacy && !$channels->exists();
|
|
$firstLegacy = $legacy && !$channels->exists();
|
|
|
$id = (int) $channels->insertGetId($values + ['created_at' => now()]);
|
|
$id = (int) $channels->insertGetId($values + ['created_at' => now()]);
|
|
@@ -142,8 +154,10 @@ class TelegramChannelService
|
|
|
return $row === null ? null : ($row->telegram_chat_id ?? $row->target);
|
|
return $row === null ? null : ($row->telegram_chat_id ?? $row->target);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public function publish(array $channel, bool $pin): array
|
|
|
|
|
|
|
+ public function publish(int $id, bool $pin): array
|
|
|
{
|
|
{
|
|
|
|
|
+ $row = $this->find($id);
|
|
|
|
|
+ $channel = $this->present($row, (int) $row->default_channel_id === $id, true);
|
|
|
if (!$channel['enabled']) {
|
|
if (!$channel['enabled']) {
|
|
|
throw ValidationException::withMessages(['id' => '请先启用频道']);
|
|
throw ValidationException::withMessages(['id' => '请先启用频道']);
|
|
|
}
|
|
}
|
|
@@ -158,7 +172,7 @@ class TelegramChannelService
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- $telegram = app(Api::class);
|
|
|
|
|
|
|
+ $telegram = $this->telegram($this->storedToken($row));
|
|
|
$response = $this->send($telegram, $channel, $params);
|
|
$response = $this->send($telegram, $channel, $params);
|
|
|
} catch (TelegramResponseException $exception) {
|
|
} catch (TelegramResponseException $exception) {
|
|
|
if (($exception->getHttpStatusCode() ?? 500) < 500) {
|
|
if (($exception->getHttpStatusCode() ?? 500) < 500) {
|
|
@@ -226,10 +240,50 @@ class TelegramChannelService
|
|
|
return $telegram->post($image ? 'sendPhoto' : 'sendVideo', $params + [$field => $media])->getResult() ?: [];
|
|
return $telegram->post($image ? 'sendPhoto' : 'sendVideo', $params + [$field => $media])->getResult() ?: [];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private function resolve(string $target): array
|
|
|
|
|
|
|
+ private function validateBot(?string $username, #[\SensitiveParameter] ?string $token, ?object $row): array
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($token === null) {
|
|
|
|
|
+ if ($username !== null && strcasecmp(ltrim($username, '@'), $row->bot_username ?? '') !== 0) {
|
|
|
|
|
+ throw ValidationException::withMessages(['bot_token' => '绑定或更换机器人时请填写 Bot Token']);
|
|
|
|
|
+ }
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ $bot = $this->telegram($token)->getMe();
|
|
|
|
|
+ } catch (\Exception) {
|
|
|
|
|
+ throw ValidationException::withMessages(['bot_token' => '无法验证 Bot Token,请检查 Token 或 Telegram 连接']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $actualUsername = $bot->get('username');
|
|
|
|
|
+ if (!$bot->get('is_bot') || !is_string($actualUsername) || $actualUsername === '') {
|
|
|
|
|
+ throw ValidationException::withMessages(['bot_token' => 'Bot Token 未返回有效的机器人账号']);
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($username !== null && strcasecmp(ltrim($username, '@'), $actualUsername) !== 0) {
|
|
|
|
|
+ throw ValidationException::withMessages(['bot_username' => '机器人用户名和 Bot Token 不匹配']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'bot_username' => $actualUsername,
|
|
|
|
|
+ 'bot_name' => mb_substr((string) $bot->get('first_name'), 0, 255),
|
|
|
|
|
+ 'bot_token' => Crypt::encryptString($token),
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function storedToken(?object $row): ?string
|
|
|
|
|
+ {
|
|
|
|
|
+ return $row?->bot_token === null ? null : Crypt::decryptString($row->bot_token);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected function telegram(#[\SensitiveParameter] ?string $token): Api
|
|
|
|
|
+ {
|
|
|
|
|
+ return $token === null ? app(Api::class) : new Api($token);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function resolve(string $target, #[\SensitiveParameter] ?string $token): array
|
|
|
{
|
|
{
|
|
|
try {
|
|
try {
|
|
|
- $chat = app(Api::class)->getChat(['chat_id' => $target]);
|
|
|
|
|
|
|
+ $chat = $this->telegram($token)->getChat(['chat_id' => $target]);
|
|
|
} catch (\Exception) {
|
|
} catch (\Exception) {
|
|
|
throw ValidationException::withMessages(['chatId' => '无法读取频道,请检查频道标识及机器人是否已加入']);
|
|
throw ValidationException::withMessages(['chatId' => '无法读取频道,请检查频道标识及机器人是否已加入']);
|
|
|
}
|
|
}
|
|
@@ -349,6 +403,9 @@ class TelegramChannelService
|
|
|
'name' => $row->name,
|
|
'name' => $row->name,
|
|
|
'chatId' => $row->target,
|
|
'chatId' => $row->target,
|
|
|
'telegram_chat_id' => $row->telegram_chat_id,
|
|
'telegram_chat_id' => $row->telegram_chat_id,
|
|
|
|
|
+ 'bot_username' => $row->bot_username,
|
|
|
|
|
+ 'bot_name' => $row->bot_name,
|
|
|
|
|
+ 'has_bot_token' => $row->bot_token !== null,
|
|
|
'enabled' => (bool) $row->enabled,
|
|
'enabled' => (bool) $row->enabled,
|
|
|
'is_default' => $default,
|
|
'is_default' => $default,
|
|
|
];
|
|
];
|