option("force"); $overrideType = MessageType::tryFrom($this->option('type') ?? '') ?? null; $query = UserSubject::where("is_active", true) ->whereHas("subject", function ($q) { $q->where("is_active", true); }) ->with(["user", "subject"]); if ($this->option("user")) { $query->where("user_id", (int) $this->option("user")); } if ($this->option("subject")) { $query->where("subject_id", (int) $this->option("subject")); } $subscriptions = $query->get(); $sentCount = 0; foreach ($subscriptions as $subscription) { if ($force || $this->shouldSend($subscription)) { $this->sendInsight($subscription, $ai, $telegram, $overrideType); $sentCount++; } } $this->info("Sent {$sentCount} insights."); return 0; } /** * Determine if an insight should be sent based on the schedule. * Only called when --force is NOT used. */ private function shouldSend(UserSubject $subscription): bool { $user = $subscription->user; $timezone = $user->timezone ?? 'UTC'; $now = now()->setTimezone($timezone); // Check if today is a scheduled weekday if ($subscription->weekdays && is_array($subscription->weekdays) && count($subscription->weekdays) > 0) { $today = (string) $now->format('w'); $found = false; foreach ($subscription->weekdays as $day) { if ((string) $day === $today) { $found = true; break; } } if (!$found) { return false; // Not a scheduled day } } // Check if current hour:minute matches any preferred time if ($subscription->preferred_times && is_array($subscription->preferred_times) && count($subscription->preferred_times) > 0) { $currentHM = $now->format('H:i'); $matchFound = false; foreach ($subscription->preferred_times as $time) { $timeStr = is_string($time) ? substr($time, 0, 5) : '09:00'; // Allow 15-minute window each way (total 30 min window) $scheduled = \Carbon\Carbon::parse($timeStr, $timezone); $diff = abs($now->diffInMinutes($scheduled)); if ($diff <= 15) { $matchFound = true; break; } } if (!$matchFound) { return false; // Not the right time } } // Never sent before - send now (time and weekday already match) if (!$subscription->last_sent_at) { return true; } // Check frequency $lastSent = $subscription->last_sent_at->setTimezone($timezone); return match ($subscription->frequency) { "daily" => $lastSent->lt($now->copy()->subDay()), "weekly" => $lastSent->lt($now->copy()->subWeek()), "biweekly" => $lastSent->lt($now->copy()->subWeeks(2)), "monthly" => $lastSent->lt($now->copy()->subMonth()), default => true, }; } private function sendInsight(UserSubject $subscription, AIService $ai, TelegramService $telegram, ?MessageType $overrideType = null): void { $user = $subscription->user; $subject = $subscription->subject; $language = $subscription->insight_language ?? $user->locale ?? 'en'; $messageType = $overrideType ?? MessageType::AiGenerated; $prompt = match ($messageType) { MessageType::LanguageLesson => __('Teach me a practical language lesson about :subject with examples, practice sentences, pronunciation, grammar, and vocabulary.', ['subject' => $subject->name]), MessageType::GameSimulator => __('Give me a strategic tip for :subject with actionable strategies, commands, and practical steps.', ['subject' => $subject->name]), MessageType::TechnicalTeaching => __('Give me a technical teaching lesson about :subject covering components, techniques, repairs, schematics, and practical exercises.', ['subject' => $subject->name]), default => __('Generate an insightful learning tip about :subject. Include practical advice and key concepts.', ['subject' => $subject->name]), }; if ($subject->introduction) { $prompt = __('Context about :subject: :introduction', [ 'subject' => $subject->name, 'introduction' => $subject->introduction, ]) . "\n\n" . $prompt; } $result = $ai->generateInsight($subject->name, $prompt, ['language' => $language], $messageType); if (!$result["success"]) { $this->error("Failed to generate message for user {$user->id}: " . ($result["error"] ?? "Unknown error")); return; } DB::transaction(function () use ($user, $subject, $result, $subscription, $telegram, $messageType) { Insight::create([ "user_id" => $user->id, "subject_id" => $subject->id, "content" => $result["content"], "type" => $messageType->value, "input_tokens" => $result["input_tokens"], "output_tokens" => $result["output_tokens"], "sent_at" => now(), ]); TokenUsage::create([ "user_id" => $user->id, "input_tokens" => $result["input_tokens"], "output_tokens" => $result["output_tokens"], "total_tokens" => $result["input_tokens"] + $result["output_tokens"], "model" => config("services." . config('services.ai.default_provider', 'openrouter') . ".model"), "provider" => config('services.ai.default_provider', 'openrouter'), ]); $subscription->update(["last_sent_at" => now()]); // Send to Telegram if enabled if ($subscription->hasChannel('telegram')) { $telegramUser = $user->telegramUsers() ->where("is_active", true) ->first(); if ($telegramUser && $telegramUser->isFullyConfigured()) { $botTelegram = new TelegramService($telegramUser->bot_token); $plainText = MarkdownService::strip($result["content"]); $botTelegram->sendMessage( $telegramUser->chat_id, "📚 *{$subject->name}*\n\n{$plainText}" ); } } }); $this->info("Sent message to user {$user->id} for subject {$subject->id}"); } }