option('limit')); $defaultStabileId = (int) ($this->option('stabile-id') ?: 0); $createTicket = (bool) $this->option('create-ticket'); $filterAdminIds = collect((array) $this->option('admin-id')) ->map(fn($v) => (int) $v) ->filter(fn(int $v) => $v > 0) ->values() ->all(); $query = Amministratore::query(); if ($filterAdminIds !== []) { $query->whereIn('id', $filterAdminIds); } $amministratori = $query->get(); if ($amministratori->isEmpty()) { $this->warn('Nessun amministratore trovato per i filtri richiesti.'); return self::SUCCESS; } foreach ($amministratori as $amministratore) { $google = Arr::get($amministratore->impostazioni ?? [], 'google', []); $oauth = Arr::get($google, 'oauth', []); if (! (bool) Arr::get($oauth, 'connected', false)) { $this->line("Admin {$amministratore->id}: Google non collegato, salto."); continue; } $token = $this->resolveAccessToken($amministratore, $google, $oauth); if ($token === null) { $this->warn("Admin {$amministratore->id}: token non disponibile, salto."); continue; } $imported = 0; $skipped = 0; $tickets = 0; $nextPageToken = null; $creatorUserId = (int) ($amministratore->user_id ?? 0) > 0 ? (int) $amministratore->user_id : null; while ($imported < $limit) { $response = Http::withToken($token) ->acceptJson() ->get('https://keep.googleapis.com/v1/notes', [ 'pageSize' => min(50, $limit), 'pageToken' => $nextPageToken, ]); if ($response->status() === 401) { $token = $this->resolveAccessToken($amministratore, $google, $oauth, true) ?? $token; $response = Http::withToken($token) ->acceptJson() ->get('https://keep.googleapis.com/v1/notes', [ 'pageSize' => min(50, $limit), 'pageToken' => $nextPageToken, ]); } if (! $response->successful()) { $err = $this->extractGoogleError($response); $this->warn("Admin {$amministratore->id}: errore Keep API ({$response->status()}) {$err}"); break; } $notes = $response->json('notes'); if (! is_array($notes) || count($notes) === 0) { break; } foreach ($notes as $note) { if ($imported >= $limit) { break; } $resourceId = (string) (Arr::get($note, 'name') ?: Arr::get($note, 'id') ?: Arr::get($note, 'resourceName') ?: ''); if ($resourceId === '') { $skipped++; continue; } $already = ChiamataPostIt::query() ->where('origine', 'google_keep') ->where('origine_id', $resourceId) ->exists(); if ($already) { $skipped++; continue; } $title = trim((string) (Arr::get($note, 'title') ?: Arr::get($note, 'textContent', ''))); $body = trim((string) (Arr::get($note, 'body.text') ?: Arr::get($note, 'textContent') ?: Arr::get($note, 'body') ?: '')); if ($title === '' && $body === '') { $skipped++; continue; } $fullText = trim($title . "\n" . $body); $telefono = $this->extractPhone($fullText); $direzione = $this->extractDirection($fullText); $priorita = $this->extractPriority($fullText); $chiamataIl = Arr::get($note, 'createTime') ?: Arr::get($note, 'createdTime'); $chiamataIl = is_string($chiamataIl) ? Carbon::parse($chiamataIl) : now(); $postIt = ChiamataPostIt::query()->create([ 'telefono' => $telefono, 'nome_chiamante' => null, 'direzione' => $direzione, 'esito' => 'importata da Google Keep', 'oggetto' => $title !== '' ? $title : 'Nota Keep', 'nota' => $body !== '' ? $body : $title, 'priorita' => $priorita, 'stato' => 'post_it', 'creato_da_user_id' => $creatorUserId, 'chiamata_il' => $chiamataIl, 'origine' => 'google_keep', 'origine_id' => $resourceId, ]); if ($createTicket) { $stabileId = $this->extractStabileId($fullText) ?: $defaultStabileId; if ($stabileId > 0 && Stabile::query()->whereKey($stabileId)->exists()) { $ticket = Ticket::query()->create([ 'stabile_id' => $stabileId, 'aperto_da_user_id' => (int) ($amministratore->user_id ?: 1), 'titolo' => $title !== '' ? $title : 'Nota Keep importata', 'descrizione' => ($body !== '' ? $body : $title) . "\n\nOrigine: Google Keep ({$resourceId})", 'data_apertura' => now(), 'stato' => 'Aperto', 'priorita' => $priorita, ]); $postIt->ticket_id = $ticket->id; $postIt->stato = 'ticket'; $postIt->save(); $tickets++; } } $imported++; } $nextPageToken = $response->json('nextPageToken'); if (! is_string($nextPageToken) || $nextPageToken === '') { break; } } $this->info("Admin {$amministratore->id}: importate {$imported}, saltate {$skipped}, ticket creati {$tickets}."); } return self::SUCCESS; } private function extractStabileId(string $text): int { if (preg_match('/stabile\s*[:#-]?\s*(\d+)/i', $text, $m) === 1) { return (int) ($m[1] ?? 0); } return 0; } private function extractPhone(string $text): ?string { if (preg_match('/(\+?\d[\d\s\-\(\)]{7,}\d)/', $text, $m) !== 1) { return null; } return PhoneNumber::toE164Italy((string) ($m[1] ?? '')); } private function extractDirection(string $text): string { $t = strtolower($text); if (str_contains($t, '#uscita') || str_contains($t, 'in uscita')) { return 'in_uscita'; } if (str_contains($t, '#persa') || str_contains($t, 'chiamata persa')) { return 'persa'; } return 'in_arrivo'; } private function extractPriority(string $text): string { $t = strtolower($text); if (str_contains($t, '#urgente') || str_contains($t, 'urgente')) { return 'Urgente'; } if (str_contains($t, '#alta') || str_contains($t, 'alta priorita')) { return 'Alta'; } if (str_contains($t, '#bassa') || str_contains($t, 'bassa priorita')) { return 'Bassa'; } return 'Media'; } private function extractGoogleError($response): string { $msg = (string) (Arr::get($response->json(), 'error.message') ?: Arr::get($response->json(), 'error.status') ?: ''); return trim($msg); } private function resolveAccessToken(Amministratore $amministratore, array $google, array $oauth, bool $forceRefresh = false): ?string { $accessToken = trim((string) Arr::get($oauth, 'access_token', '')); $refreshToken = trim((string) Arr::get($oauth, 'refresh_token', '')); if (! $forceRefresh && $accessToken !== '') { return $accessToken; } if ($refreshToken === '') { return null; } $settingsClientId = trim((string) Arr::get($google, 'client_id', '')); $settingsClientSecret = trim((string) Arr::get($google, 'client_secret', '')); $configClientId = trim((string) config('services.google.client_id')); $configClientSecret = trim((string) config('services.google.client_secret')); $credentialPairs = []; if ($settingsClientId !== '' && $settingsClientSecret !== '') { $credentialPairs[] = [$settingsClientId, $settingsClientSecret]; } if ( $configClientId !== '' && $configClientSecret !== '' && ($configClientId !== $settingsClientId || $configClientSecret !== $settingsClientSecret) ) { $credentialPairs[] = [$configClientId, $configClientSecret]; } if ($credentialPairs === []) { return null; } foreach ($credentialPairs as [$clientId, $clientSecret]) { $response = Http::asForm()->post('https://oauth2.googleapis.com/token', [ 'client_id' => $clientId, 'client_secret' => $clientSecret, 'refresh_token' => $refreshToken, 'grant_type' => 'refresh_token', ]); if (! $response->successful()) { continue; } $payload = $response->json(); $newAccessToken = trim((string) Arr::get($payload, 'access_token', '')); if ($newAccessToken === '') { continue; } $impostazioni = $amministratore->impostazioni ?? []; $impostazioniGoogle = Arr::get($impostazioni, 'google', []); $impostazioniOauth = Arr::get($impostazioniGoogle, 'oauth', []); $impostazioniOauth['access_token'] = $newAccessToken; $impostazioniOauth['expires_in'] = (int) Arr::get($payload, 'expires_in', 3600); $impostazioniOauth['refreshed_at'] = Carbon::now()->toDateTimeString(); if (isset($payload['refresh_token']) && is_string($payload['refresh_token']) && $payload['refresh_token'] !== '') { $impostazioniOauth['refresh_token'] = $payload['refresh_token']; } $impostazioniGoogle['oauth'] = $impostazioniOauth; $impostazioni['google'] = $impostazioniGoogle; $amministratore->impostazioni = $impostazioni; $amministratore->save(); return $newAccessToken; } return null; } }