package main import ( "context" "errors" "fmt" "github.com/dgraph-io/badger/v4" "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/rest" "github.com/disgoorg/disgo/webhook" "github.com/disgoorg/snowflake/v2" "grow.rievo.dev/discordBots/cmd/dealsbot/api" "grow.rievo.dev/discordBots/cmd/dealsbot/repository" "log/slog" "os" "os/signal" "reflect" "syscall" "time" ) var ( webhookID = snowflake.GetEnv("webhook_id") webhookToken = os.Getenv("webhook_token") ) var logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)) var release string func main() { // query different sources store to db // try to incorporate operagx apiUrl: // - https://gx-proxy.operacdn.com/content/free-games?_limit=300&_sort=order%3AASC // send messages to discord // ideas: // - https://github.com/TheLovinator1/discord-free-game-notifier // - https://gg.deals/news/free-gog-games/ // - origin // - check ubisoft works logger.Info("starting dealsbot...", slog.String("disgo version", disgo.Version)) client := webhook.New(webhookID, webhookToken) defer client.Close(context.TODO()) repo := repository.InitDb(logger) defer repo.Close() ticker := time.NewTicker(10 * time.Minute) tickerGC := time.NewTicker(15 * time.Minute) quit := make(chan struct{}) go func() { for { select { case <-ticker.C: var apis []api.Api apis = append(apis, api.NewUbsioftApi(logger), api.NewEpicApi(logger), api.NewSteamApi(logger), api.NewGogFrontApi(logger), api.NewGogApi(logger), api.NewHumbleBundleApi(logger)) for _, a := range apis { err := a.Load() if err != nil { logger.Error("failed loading api", slog.Any("error", err)) } } var deals []api.Deal for _, a := range apis { apiDeals := a.Get() deals = append(deals, apiDeals...) } for _, deal := range deals { retrievedDeal, _ := repo.GetValue(deal.Id) if deal.Id == retrievedDeal.Id { logger.Debug("deal is already published", slog.String("deal", deal.Id)) } else if reflect.DeepEqual(deal, retrievedDeal) { logger.Error("deal is published but not equal", slog.String("deal", deal.Id)) } else { logger.Info("deal is new and will be published", slog.String("deal", deal.Id)) go sendWebhook(client, deal) err := repo.SetValue(deal) if err != nil { logger.Error("failed saving deal", slog.Any("error", err)) } } } case <-tickerGC.C: err := repo.RunGC() if err != nil && !errors.Is(err, badger.ErrNoRewrite) { logger.Error("GC failed", slog.Any("error", err)) } else { logger.Debug("GC successful") } case <-quit: ticker.Stop() tickerGC.Stop() return } } }() logger.Info("dealsbot is now running. Press CTRL-C to exit.", slog.String("version", release)) s := make(chan os.Signal, 1) signal.Notify(s, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) <-s } func sendWebhook(client webhook.Client, deal api.Deal) { content := "currently free:" message := discord.NewWebhookMessageCreateBuilder() if deal.Image == "" { message.SetContent(fmt.Sprintf("%v %v\n", content, deal.Url)) } else { message.SetContent(content).SetEmbeds( discord.NewEmbedBuilder(). SetTitle(deal.Title). SetURL(deal.Url). SetImage(deal.Image). Build()) } if _, err := client.CreateMessage(message.Build(), rest.WithDelay(2*time.Second), ); err != nil { logger.Error("sending message failed", slog.Any("error", err)) } }