|
|
@@ -47,6 +47,7 @@ class TelegramChannelService
|
|
|
'id' => $legacy ? 'prohibited' : 'sometimes|required|integer|min:1',
|
|
|
'name' => 'nullable|string|max:255',
|
|
|
'chatId' => 'required|string|max:64',
|
|
|
+ 'bot_id' => 'nullable|integer|min:1|prohibits:bot_token',
|
|
|
'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',
|
|
|
@@ -60,21 +61,30 @@ class TelegramChannelService
|
|
|
$message = isset($fields['type']) ? self::message($input) : 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();
|
|
|
+ $row = $this->joinedChannels()->where(fn (Builder $query) => $query
|
|
|
+ ->where('channels.target', $target)->orWhere('channels.telegram_chat_id', $target))->first();
|
|
|
}
|
|
|
$newToken = $fields['bot_token'] ?? null;
|
|
|
- $bot = $this->validateBot($fields['bot_username'] ?? null, $newToken, $row);
|
|
|
+ $botId = array_key_exists('bot_id', $fields) ? $fields['bot_id'] : $row?->bot_id;
|
|
|
+ $selectedBot = $botId === null ? null : $this->findBot((int) $botId);
|
|
|
+ $bot = $newToken === null ? [] : $this->validateBot($fields['bot_username'] ?? null, $newToken);
|
|
|
+ if ($newToken === null && isset($fields['bot_username'])
|
|
|
+ && strcasecmp(ltrim($fields['bot_username'], '@'), $selectedBot->bot_username ?? '') !== 0) {
|
|
|
+ throw ValidationException::withMessages(['bot_username' => '请从机器人管理中选择机器人']);
|
|
|
+ }
|
|
|
+
|
|
|
$targetUnchanged = $row !== null && in_array($target, [$row->target, $row->telegram_chat_id], true);
|
|
|
- $identity = $targetUnchanged && $newToken === null
|
|
|
+ $bindingUnchanged = (int) $botId === (int) $row?->bot_id && $newToken === null;
|
|
|
+ $identity = $targetUnchanged && $bindingUnchanged
|
|
|
? ['target' => $row->target, 'telegram_chat_id' => $row->telegram_chat_id, 'title' => $row->name]
|
|
|
- : $this->resolve($target, $newToken ?? $this->storedToken($row));
|
|
|
+ : $this->resolve($target, $newToken ?? $this->storedToken($selectedBot));
|
|
|
|
|
|
- return DB::transaction(function () use ($fields, $identity, $message, $legacy, $row, $bot) {
|
|
|
+ return DB::transaction(function () use ($fields, $identity, $message, $legacy, $row, $bot, $selectedBot) {
|
|
|
$default = $this->settings()->lockForUpdate()->first();
|
|
|
$channels = DB::table('telegram_channels');
|
|
|
$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 || $existing->bot_token !== $row->bot_token)) {
|
|
|
+ || $existing->telegram_chat_id !== $row->telegram_chat_id || $existing->bot_id !== $row->bot_id)) {
|
|
|
throw ValidationException::withMessages(['id' => '频道已变更,请刷新后重试']);
|
|
|
}
|
|
|
|
|
|
@@ -86,7 +96,7 @@ class TelegramChannelService
|
|
|
})->get();
|
|
|
if ($legacy && $row === null && $match->count() === 1) {
|
|
|
$existing = $match->first();
|
|
|
- if ($existing->bot_token !== null && $bot === []) {
|
|
|
+ if ($existing->bot_id !== null && $bot === [] && !array_key_exists('bot_id', $fields)) {
|
|
|
throw ValidationException::withMessages(['chatId' => '请使用已保存的频道标识,或通过频道管理按 ID 编辑']);
|
|
|
}
|
|
|
}
|
|
|
@@ -94,6 +104,20 @@ class TelegramChannelService
|
|
|
throw ValidationException::withMessages(['chatId' => '该频道已存在']);
|
|
|
}
|
|
|
|
|
|
+ if ($selectedBot !== null && $this->findBot((int) $selectedBot->id)->bot_token !== $selectedBot->bot_token) {
|
|
|
+ throw ValidationException::withMessages(['bot_id' => '机器人配置已更新,请刷新后重试']);
|
|
|
+ }
|
|
|
+ $binding = $selectedBot?->id;
|
|
|
+ if ($bot !== []) {
|
|
|
+ $registered = DB::table('telegram_bots')->where('bot_username', $bot['bot_username'])->first();
|
|
|
+ if ($registered !== null && $this->storedToken($registered) !== $this->storedToken((object) $bot)) {
|
|
|
+ throw ValidationException::withMessages(['bot_token' => '请在机器人管理中更新已有机器人的 Token']);
|
|
|
+ }
|
|
|
+ $binding = $registered?->id ?? DB::table('telegram_bots')->insertGetId($bot + [
|
|
|
+ 'created_at' => now(), 'updated_at' => now(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
$enabled = (bool) ($fields['enabled'] ?? $existing?->enabled ?? true);
|
|
|
$values = [
|
|
|
'name' => $fields['name'] ?? $existing?->name ?? $identity['title'],
|
|
|
@@ -103,8 +127,9 @@ class TelegramChannelService
|
|
|
'message' => $message === null
|
|
|
? ($existing?->message ?? 'null')
|
|
|
: json_encode($message, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
|
|
|
+ 'bot_id' => $binding,
|
|
|
'updated_at' => now(),
|
|
|
- ] + $bot;
|
|
|
+ ];
|
|
|
if ($existing === null) {
|
|
|
$firstLegacy = $legacy && !$channels->exists();
|
|
|
$id = (int) $channels->insertGetId($values + ['created_at' => now()]);
|
|
|
@@ -246,15 +271,66 @@ class TelegramChannelService
|
|
|
return $telegram->post($image ? 'sendPhoto' : 'sendVideo', $params + [$field => $media])->getResult() ?: [];
|
|
|
}
|
|
|
|
|
|
- private function validateBot(?string $username, #[\SensitiveParameter] ?string $token, ?object $row): array
|
|
|
+ public function bots(): array
|
|
|
{
|
|
|
- if ($token === null) {
|
|
|
- if ($username !== null && strcasecmp(ltrim($username, '@'), $row->bot_username ?? '') !== 0) {
|
|
|
- throw ValidationException::withMessages(['bot_token' => '绑定或更换机器人时请填写 Bot Token']);
|
|
|
- }
|
|
|
- return [];
|
|
|
+ return DB::table('telegram_bots')->orderBy('bot_username')
|
|
|
+ ->get(['id', 'bot_username', 'bot_name'])->all();
|
|
|
+ }
|
|
|
+
|
|
|
+ public function saveBot(#[\SensitiveParameter] array $input): array
|
|
|
+ {
|
|
|
+ $fields = Validator::make($input, [
|
|
|
+ 'id' => 'sometimes|required|integer|min:1',
|
|
|
+ 'bot_username' => ['nullable', 'string', 'max:64', 'regex:/^@?[a-z][a-z0-9_]*$/iD'],
|
|
|
+ 'bot_token' => ['required', 'string', 'max:255', 'regex:/^[1-9][0-9]*:[a-z0-9_-]+$/iD'],
|
|
|
+ ])->validate();
|
|
|
+ $id = isset($fields['id']) ? (int) $fields['id'] : null;
|
|
|
+ $row = $id === null ? null : $this->findBot($id);
|
|
|
+ $bot = $this->validateBot($fields['bot_username'] ?? $row?->bot_username, $fields['bot_token']);
|
|
|
+ if ($row !== null && strcasecmp($row->bot_username, $bot['bot_username']) !== 0) {
|
|
|
+ throw ValidationException::withMessages(['bot_username' => '请使用此机器人的 Token,更换机器人请新增后再绑定']);
|
|
|
}
|
|
|
|
|
|
+ return DB::transaction(function () use ($id, $row, $bot) {
|
|
|
+ $this->settings()->lockForUpdate()->first();
|
|
|
+ $bots = DB::table('telegram_bots');
|
|
|
+ $duplicate = (clone $bots)->where('bot_username', $bot['bot_username'])->value('id');
|
|
|
+ if ($duplicate !== null && (int) $duplicate !== $id) {
|
|
|
+ throw ValidationException::withMessages(['bot_username' => '该机器人已存在']);
|
|
|
+ }
|
|
|
+ $values = $bot + ['updated_at' => now()];
|
|
|
+ if ($id === null) {
|
|
|
+ $id = (int) $bots->insertGetId($values + ['created_at' => now()]);
|
|
|
+ } else {
|
|
|
+ if ($this->findBot($id)->bot_token !== $row->bot_token) {
|
|
|
+ throw ValidationException::withMessages(['id' => '机器人已更新,请刷新后重试']);
|
|
|
+ }
|
|
|
+ $bots->where('id', $id)->update($values);
|
|
|
+ }
|
|
|
+ return ['id' => $id, 'bot_username' => $bot['bot_username'], 'bot_name' => $bot['bot_name']];
|
|
|
+ }, 3);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function deleteBot(int $id): void
|
|
|
+ {
|
|
|
+ DB::transaction(function () use ($id) {
|
|
|
+ $this->settings()->lockForUpdate()->first();
|
|
|
+ $this->findBot($id);
|
|
|
+ if (DB::table('telegram_channels')->where('bot_id', $id)->exists()) {
|
|
|
+ throw ValidationException::withMessages(['id' => '机器人仍绑定频道,请先在频道中解除绑定']);
|
|
|
+ }
|
|
|
+ DB::table('telegram_bots')->where('id', $id)->delete();
|
|
|
+ }, 3);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function findBot(int $id): object
|
|
|
+ {
|
|
|
+ return DB::table('telegram_bots')->where('id', $id)->first()
|
|
|
+ ?? throw ValidationException::withMessages(['bot_id' => '机器人不存在']);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function validateBot(?string $username, #[\SensitiveParameter] string $token): array
|
|
|
+ {
|
|
|
try {
|
|
|
$bot = $this->telegram($token)->getMe();
|
|
|
} catch (\Exception) {
|
|
|
@@ -270,7 +346,7 @@ class TelegramChannelService
|
|
|
}
|
|
|
|
|
|
return [
|
|
|
- 'bot_username' => $actualUsername,
|
|
|
+ 'bot_username' => strtolower($actualUsername),
|
|
|
'bot_name' => mb_substr((string) $bot->get('first_name'), 0, 255),
|
|
|
'bot_token' => Crypt::encryptString($token),
|
|
|
];
|
|
|
@@ -398,7 +474,8 @@ class TelegramChannelService
|
|
|
{
|
|
|
return DB::table('telegram_channels as channels')
|
|
|
->crossJoin('telegram_channel_settings as settings')
|
|
|
- ->select('channels.*', 'settings.default_channel_id')
|
|
|
+ ->leftJoin('telegram_bots as bots', 'bots.id', '=', 'channels.bot_id')
|
|
|
+ ->select('channels.id', 'channels.name', 'channels.target', 'channels.telegram_chat_id', 'channels.enabled', 'channels.message', 'channels.bot_id', 'settings.default_channel_id', 'bots.bot_username', 'bots.bot_name', 'bots.bot_token')
|
|
|
->where('settings.id', 1);
|
|
|
}
|
|
|
|
|
|
@@ -409,6 +486,7 @@ class TelegramChannelService
|
|
|
'name' => $row->name,
|
|
|
'chatId' => $row->target,
|
|
|
'telegram_chat_id' => $row->telegram_chat_id,
|
|
|
+ 'bot_id' => $row->bot_id === null ? null : (int) $row->bot_id,
|
|
|
'bot_username' => $row->bot_username,
|
|
|
'bot_name' => $row->bot_name,
|
|
|
'has_bot_token' => $row->bot_token !== null,
|