feat: bot

This commit is contained in:
hashbro
2026-08-15 23:05:51 +08:00
parent 9049fdccad
commit 376b442880
26 changed files with 944 additions and 352 deletions
+136 -37
View File
@@ -13,6 +13,7 @@ use PHPUnit\Framework\Attributes\Test;
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\Telegram\Properties\ChatMemberStatus;
use SergiX44\Nutgram\Telegram\Properties\ChatType;
use SergiX44\Nutgram\Telegram\Properties\UpdateType;
use SergiX44\Nutgram\Testing\FakeNutgram;
use Tests\TestCase;
@@ -386,14 +387,12 @@ class TelegramBotTest extends TestCase
'password' => 'secret12',
'status' => 1,
'chat_id' => (string) self::AGENT_CHAT,
'bot_token' => '111:AAA',
]);
$agentB = User::query()->create([
'username' => 'agent_b',
'password' => 'secret12',
'status' => 1,
'chat_id' => '-100111',
'bot_token' => '222:BBB',
]);
$chA = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
@@ -418,8 +417,6 @@ class TelegramBotTest extends TestCase
'created_at' => now(),
]);
app(TelegramBotContext::class)->setAgent($agentA);
/** @var FakeNutgram $bot */
$bot = app(Nutgram::class);
$bot->willReceivePartial([
@@ -471,7 +468,6 @@ class TelegramBotTest extends TestCase
'password' => 'secret12',
'status' => 1,
'chat_id' => (string) self::AGENT_CHAT,
'bot_token' => '111:AAA',
]);
Channel::query()->create([
'channel_id' => 'cccccccccccccccccccccccccccccccc',
@@ -526,18 +522,17 @@ class TelegramBotTest extends TestCase
$history2 = $bot2->getRequestHistory();
$transferMsg = FakeNutgram::getActualData(array_values($history2[array_key_last($history2)])[0]);
$this->assertStringContainsString('仅限官方', $transferMsg['text'] ?? '');
$this->assertStringContainsString('无权操作该地址', $transferMsg['text'] ?? '');
}
#[Test]
public function agent_help_hides_transfer(): void
public function agent_help_lists_transfer_for_own_devices(): void
{
$agent = User::query()->create([
'username' => 'agent_help',
'password' => 'secret12',
'status' => 1,
'chat_id' => (string) self::AGENT_CHAT,
'bot_token' => '111:AAA',
]);
app(TelegramBotContext::class)->setAgent($agent);
@@ -557,46 +552,150 @@ class TelegramBotTest extends TestCase
$history = $bot->getRequestHistory();
$msg = FakeNutgram::getActualData(array_values($history[array_key_last($history)])[0]);
$this->assertStringContainsString('/data', $msg['text'] ?? '');
$this->assertStringContainsString('不支持 /transfer', $msg['text'] ?? '');
$this->assertStringNotContainsString('/transfer <fromAddress>', $msg['text'] ?? '');
$this->assertStringContainsString('/transfer <fromAddress>', $msg['text'] ?? '');
$this->assertStringNotContainsString('仅可查看/转出本代理渠道设备', $msg['text'] ?? '');
}
#[Test]
public function agent_webhook_requires_secret_and_config(): void
public function agent_can_transfer_own_device_address(): void
{
$bare = User::query()->create([
'username' => 'agent_hook_bare',
'password' => 'secret12',
'status' => 1,
]);
$this->postJson('/hooks/telegram/agent/'.$bare->id, ['update_id' => 1])
->assertNotFound();
$agent = User::query()->create([
'username' => 'agent_hook',
'username' => 'agent_xfer',
'password' => 'secret12',
'status' => 1,
'bot_token' => '111:AAA',
'chat_id' => (string) self::AGENT_CHAT,
]);
$this->assertTrue($agent->fresh()->hasTelegramBot());
$channelId = 'cccccccccccccccccccccccccccccccc';
Channel::query()->create([
'channel_id' => $channelId,
'user_id' => $agent->id,
'status' => 1,
]);
$device = \App\Models\Device::query()->create([
'device_id' => 'dev-agent-xfer',
'channel_id' => $channelId,
]);
\App\Models\WalletAddress::query()->create([
'device_id' => $device->id,
'address' => 'TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH',
'chain_type' => 'TRON',
'source' => 'imToken',
]);
$this->postJson('/hooks/telegram/agent/'.$agent->id, ['update_id' => 1], [
'X-Telegram-Bot-Api-Secret-Token' => 'wrong',
])->assertForbidden();
$mock = Mockery::mock(TransferService::class);
$mock->shouldReceive('handle')
->once()
->andReturn([
'ok' => true,
'txid' => 'agentbeef',
'from' => 'TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH',
'to' => 'TPayoutAddressxxxxxxxxxxxxxxxxxxxxxx',
'amount' => '1',
'asset' => 'USDT',
]);
$this->app->instance(TransferService::class, $mock);
$secret = TelegramBotContext::webhookSecretFor($agent->fresh());
$this->call(
'POST',
'/hooks/telegram/agent/'.$agent->id,
[],
[],
[],
[
'CONTENT_TYPE' => 'application/json',
'HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN' => $secret,
/** @var FakeNutgram $bot */
$bot = app(Nutgram::class);
$bot->willReceivePartial([
'status' => ChatMemberStatus::ADMINISTRATOR->value,
'user' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'AgentAdmin'],
'can_manage_chat' => true,
]);
$bot->hearMessage([
'text' => '/transfer TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH USDT 1',
'chat' => ['id' => self::AGENT_CHAT, 'type' => ChatType::SUPERGROUP->value],
'from' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'AgentAdmin'],
])->reply();
$history = $bot->getRequestHistory();
$last = FakeNutgram::getActualData(array_values($history[array_key_last($history)])[0]);
$this->assertStringContainsString('agentbeef', $last['text'] ?? '');
}
#[Test]
public function agent_webhook_route_is_removed(): void
{
$this->postJson('/hooks/telegram/agent/1', ['update_id' => 1])
->assertNotFound();
}
#[Test]
public function joining_a_group_announces_chat_id(): void
{
$chatId = -1005556667778;
/** @var FakeNutgram $bot */
$bot = app(Nutgram::class);
$bot->hearUpdateType(UpdateType::MY_CHAT_MEMBER, [
'chat' => [
'id' => $chatId,
'type' => ChatType::SUPERGROUP->value,
'title' => '代理测试群',
],
json_encode(['update_id' => 1])
)->assertNoContent();
'from' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'Admin'],
'date' => time(),
'old_chat_member' => [
'status' => ChatMemberStatus::LEFT->value,
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
],
'new_chat_member' => [
'status' => ChatMemberStatus::MEMBER->value,
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
],
])->reply();
$bot->assertCalled('sendMessage', 1);
$history = $bot->getRequestHistory();
$last = FakeNutgram::getActualData(array_values($history[array_key_last($history)])[0]);
$this->assertStringContainsString((string) $chatId, $last['text'] ?? '');
$this->assertStringContainsString('Chat ID', $last['text'] ?? '');
$this->assertStringContainsString('代理测试群', $last['text'] ?? '');
$copy = (string) data_get($last, 'reply_markup.inline_keyboard.0.0.copy_text.text', '');
$this->assertSame((string) $chatId, $copy);
}
#[Test]
public function promoting_or_leaving_does_not_announce_chat_id(): void
{
/** @var FakeNutgram $bot */
$bot = app(Nutgram::class);
$bot->hearUpdateType(UpdateType::MY_CHAT_MEMBER, [
'chat' => ['id' => -1001, 'type' => ChatType::SUPERGROUP->value, 'title' => 'x'],
'from' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'Admin'],
'date' => time(),
'old_chat_member' => [
'status' => ChatMemberStatus::MEMBER->value,
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
],
'new_chat_member' => [
'status' => ChatMemberStatus::ADMINISTRATOR->value,
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
],
])->reply();
$bot->assertCalled('sendMessage', 0);
$this->app->forgetInstance(Nutgram::class);
$this->app->forgetInstance('nutgram');
$this->app->forgetInstance('telegram');
$this->app->forgetInstance(FakeNutgram::class);
/** @var FakeNutgram $bot2 */
$bot2 = app(Nutgram::class);
$bot2->hearUpdateType(UpdateType::MY_CHAT_MEMBER, [
'chat' => ['id' => -1001, 'type' => ChatType::SUPERGROUP->value, 'title' => 'x'],
'from' => ['id' => self::USER_ID, 'is_bot' => false, 'first_name' => 'Admin'],
'date' => time(),
'old_chat_member' => [
'status' => ChatMemberStatus::MEMBER->value,
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
],
'new_chat_member' => [
'status' => ChatMemberStatus::LEFT->value,
'user' => ['id' => 1, 'is_bot' => true, 'first_name' => 'Bot'],
],
])->reply();
$bot2->assertCalled('sendMessage', 0);
}
}