package main
import (
"context"
"database/sql"
"embed"
"log"
"log/slog"
"os"
"os/signal"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/compat_oai/openai"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/pgx/v5/stdlib"
"github.com/openai/openai-go/option"
"github.com/pressly/goose/v3"
"mimi/internal/bot"
ghscraper "mimi/internal/provider/github/scraper"
tgscraper "mimi/internal/provider/telegram/scraper"
)
var prompts embed.FS
var embedMigrations embed.FS
const (
logseqGraphEnv = "LOGSEQ_GRAPH_PATH"
telegramBotTokenEnv = "TELEGRAM_BOT_API_TOKEN"
openrouterApiKeyEnv = "OPENROUTER_API_KEY"
openrouterApiUrlEnv = "OPENROUTER_API_URL"
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
var missingEnvVars []string
tgBotToken := os.Getenv(telegramBotTokenEnv)
if tgBotToken == "" {
missingEnvVars = append(missingEnvVars, telegramBotTokenEnv)
}
logseqPath := os.Getenv(logseqGraphEnv)
if logseqPath == "" {
missingEnvVars = append(missingEnvVars, logseqGraphEnv)
}
openrouterApiKey := os.Getenv(openrouterApiKeyEnv)
if openrouterApiKey == "" {
missingEnvVars = append(missingEnvVars, openrouterApiKeyEnv)
}
openrouterBaseURL := os.Getenv(openrouterApiUrlEnv)
if openrouterBaseURL == "" {
missingEnvVars = append(missingEnvVars, openrouterApiUrlEnv)
}
if len(missingEnvVars) > 0 {
log.Fatalf("env variables %#v are missing", missingEnvVars)
}
oai := &openai.OpenAI{
APIKey: openrouterApiKey,
Opts: []option.RequestOption{
option.WithBaseURL(openrouterBaseURL),
},
}
g := genkit.Init(ctx,
genkit.WithPlugins(oai),
genkit.WithPromptFS(prompts),
genkit.WithPromptDir("prompts"),
genkit.WithDefaultModel("openai/anthropic/claude-sonnet-4.5"),
)
oai.DefineModel("google/gemini-2.5-flash", ai.ModelOptions{
Label: "Gemini 2.5 Flash Preview 04-17",
Versions: []string{"google/gemini-2.5-flash"},
Supports: &ai.ModelSupports{
Multiturn: true,
Tools: true,
ToolChoice: true,
SystemRole: true,
Media: true,
Constrained: ai.ConstrainedSupportNone,
},
Stage: ai.ModelStageStable,
})
oai.DefineModel("perplexity/sonar-pro", ai.ModelOptions{
Label: "Perplexity Sonar Pro",
Versions: []string{},
Supports: &ai.ModelSupports{
Multiturn: true,
Tools: true,
ToolChoice: true,
SystemRole: true,
Media: true,
Constrained: ai.ConstrainedSupportNone,
},
Stage: ai.ModelStageStable,
})
pool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatalf("failed to connect to postgres with: %s", err)
}
RunMigrations(pool)
go func() {
err := tgscraper.Run(ctx, pool, g)
if err != nil {
log.Fatalf("Telegram scraper exited with %s", err)
} else {
slog.Info("Telegram scraper exited without an error")
}
}()
go func() {
err := ghscraper.Run(ctx, pool)
if err != nil {
log.Fatalf("GitHub scraper exited with %s", err)
} else {
slog.Info("GitHub scraper exited without an error")
}
}()
go func() {
err := bot.Start(ctx, tgBotToken, logseqPath, g)
if err != nil {
log.Fatalf("Telegram bot exited with %s", err)
} else {
slog.Info("Telegram bot exited without an error")
}
}()
<-ctx.Done()
}
func RunMigrations(pool *pgxpool.Pool) {
config := pool.Config().ConnConfig
connStr := stdlib.RegisterConnConfig(config)
db, err := sql.Open("pgx", connStr)
if err != nil {
log.Fatalf("failed to open database for migrations: %v", err)
}
defer db.Close()
goose.SetBaseFS(embedMigrations)
if err := goose.SetDialect("postgres"); err != nil {
log.Fatalf("failed to set goose dialect: %v", err)
}
if err := goose.Up(db, "sql/migrations"); err != nil {
log.Fatalf("failed to run migrations: %v", err)
}
slog.Info("Migrations completed successfully")
}