adds dealsbot

This commit is contained in:
Seraphim Strub 2023-03-04 12:54:08 +01:00
parent 2a05323f6f
commit 3a3dea453d
14 changed files with 1190 additions and 13 deletions

14
cmd/dealsbot/api.go Normal file
View file

@ -0,0 +1,14 @@
package main
type Api interface {
load() error
get() []Deal
}
type DealsMap map[string]Deal
type Deal struct {
Id string
Title string
Url string
}

129
cmd/dealsbot/epic.go Normal file
View file

@ -0,0 +1,129 @@
package main
import (
"encoding/json"
"fmt"
"github.com/disgoorg/log"
"io"
"net/http"
"time"
)
type EpicStruct struct {
url string
idPrefix string
deals DealsMap
}
func newEpicApi() EpicStruct {
return EpicStruct{
url: "https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions",
idPrefix: "epic-",
deals: make(map[string]Deal),
}
}
type epicApiBody struct {
Data struct {
Catalog struct {
SearchStore struct {
Elements []struct {
Title string `json:"title"`
Id string `json:"id"`
Description string `json:"description"`
OfferType string `json:"offerType"`
IsCodeRedemptionOnly bool `json:"isCodeRedemptionOnly"`
ProductSlug string `json:"productSlug"`
OfferMappings []struct {
PageSlug string `json:"pageSlug"`
PageType string `json:"pageType"`
} `json:"offerMappings"`
Price struct {
TotalPrice struct {
DiscountPrice int `json:"discountPrice"`
OriginalPrice int `json:"originalPrice"`
Discount int `json:"discount"`
CurrencyCode string `json:"currencyCode"`
} `json:"totalPrice"`
} `json:"price"`
Promotions *struct {
PromotionalOffers []struct {
PromotionalOffers []struct {
StartDate time.Time `json:"startDate"`
EndDate time.Time `json:"endDate"`
DiscountSetting struct {
DiscountType string `json:"discountType"`
DiscountPercentage int `json:"discountPercentage"`
} `json:"discountSetting"`
} `json:"promotionalOffers"`
} `json:"promotionalOffers"`
} `json:"promotions"`
} `json:"elements"`
Paging struct {
Count int `json:"count"`
Total int `json:"total"`
} `json:"paging"`
} `json:"searchStore"`
} `json:"Catalog"`
} `json:"data"`
}
func (e EpicStruct) load() error {
client := &http.Client{}
req, err := http.NewRequest("GET", e.url, nil)
if err != nil {
return err
}
res, err := client.Do(req)
if err != nil {
return err
}
body, err := io.ReadAll(res.Body)
if err != nil {
return err
}
var data epicApiBody
err = json.Unmarshal(body, &data)
if err != nil {
return err
}
for _, element := range data.Data.Catalog.SearchStore.Elements {
if element.Promotions == nil ||
len(element.Promotions.PromotionalOffers) == 0 ||
len(element.Promotions.PromotionalOffers[0].PromotionalOffers) == 0 ||
element.Promotions.PromotionalOffers[0].PromotionalOffers[0].DiscountSetting.DiscountPercentage != 0 {
// no deal
continue
}
productSlug := element.ProductSlug
if len(element.OfferMappings) != 0 && productSlug == "" {
productSlug = element.OfferMappings[0].PageSlug
}
if productSlug == "" {
log.Error(fmt.Sprintf("product slug not found for: %v", element.Title))
continue
}
id := fmt.Sprintf("%v%v", e.idPrefix, element.Id)
title := element.Title
url := fmt.Sprintf("https://store.epicgames.com/en-US/p/%v", productSlug)
e.deals[id] = Deal{
Id: id,
Title: title,
Url: url,
}
}
return nil
}
func (e EpicStruct) get() []Deal {
var deals []Deal
for _, deal := range e.deals {
deals = append(deals, deal)
}
return deals
}

135
cmd/dealsbot/gog.go Normal file
View file

@ -0,0 +1,135 @@
package main
import (
"fmt"
"golang.org/x/net/html"
"net/http"
"regexp"
)
type GogStruct struct {
url string
baseUrl string
idPrefix string
deals DealsMap
}
func newGogApi() GogStruct {
return GogStruct{
url: "https://www.gog.com/en",
baseUrl: "https://www.gog.com/en/game/",
idPrefix: "gog-",
deals: make(map[string]Deal),
}
}
func (e GogStruct) load() error {
client := &http.Client{}
// might have to add a cookie at a later time but currently works without
// "Cookie", "gog_lc=GB_GBP_en-US" or "Accept-Language", "en"
reqStore, err := http.NewRequest("GET", e.url, nil)
if err != nil {
return err
}
resStore, err := client.Do(reqStore)
if err != nil {
return err
}
bodyStore := html.NewTokenizer(resStore.Body)
regexAppid, err := regexp.Compile(`/en/game/([-\w]+)`)
if err != nil {
return err
}
var appIDs []string
func() {
for {
tt := bodyStore.Next()
switch {
case tt == html.ErrorToken:
// file end or error
return
case tt == html.StartTagToken:
t := bodyStore.Token()
if t.Data != "a" {
continue
}
for _, a := range t.Attr {
if !(a.Key == "id" && a.Val == "giveaway") {
continue
}
for _, attr := range t.Attr {
if attr.Key != "ng-href" {
continue
}
appID := regexAppid.FindStringSubmatch(attr.Val)
if len(appID) < 1 {
continue
}
appIDs = append(appIDs, appID[1])
}
}
}
}
}()
for _, appID := range appIDs {
reqGame, err := http.NewRequest("GET", fmt.Sprintf("%v%v", e.baseUrl, appID), nil)
if err != nil {
return err
}
resGame, err := client.Do(reqGame)
if err != nil {
return err
}
bodyGame := html.NewTokenizer(resGame.Body)
func() {
for {
tt := bodyGame.Next()
switch {
case tt == html.ErrorToken:
// file end or error
return
case tt == html.StartTagToken:
t := bodyGame.Token()
if t.Data != "h1" {
continue
}
for _, a := range t.Attr {
if !(a.Key == "class" && a.Val == "productcard-basics__title") {
}
if tt = bodyGame.Next(); tt != html.TextToken {
continue
}
id := fmt.Sprintf("%v%v", e.idPrefix, appID)
title := bodyGame.Token().Data
url := fmt.Sprintf("%v%v", e.baseUrl, appID)
e.deals[id] = Deal{
Id: id,
Title: title,
Url: url,
}
}
}
}
}()
}
return nil
}
func (e GogStruct) get() []Deal {
var deals []Deal
for _, deal := range e.deals {
deals = append(deals, deal)
}
return deals
}

View file

@ -0,0 +1,137 @@
package main
import (
"encoding/json"
"fmt"
"golang.org/x/net/html"
"net/http"
"regexp"
)
type HumbleBundleStruct struct {
url string
baseUrl string
idPrefix string
deals DealsMap
}
func newHumbleBundleApi() HumbleBundleStruct {
return HumbleBundleStruct{
url: "https://www.humblebundle.com/",
baseUrl: "https://www.humblebundle.com/store/",
idPrefix: "humblebundle-",
deals: make(map[string]Deal),
}
}
type humblebundleJsonBody struct {
Mosaic []struct {
Products []struct {
ProductUrl string `json:"product_url,omitempty"`
Highlights []string `json:"highlights,omitempty"`
TileName string `json:"tile_name,omitempty"`
ProductTitle *string `json:"product_title,omitempty"`
Type string `json:"type"`
Category string `json:"category,omitempty"`
EndDateDatetime string `json:"end_date|datetime,omitempty"`
OperatingSystems []string `json:"operating_systems,omitempty"`
Platforms []string `json:"platforms,omitempty"`
} `json:"products"`
} `json:"mosaic"`
}
func (e HumbleBundleStruct) load() error {
client := &http.Client{}
// might have to add a cookie at a later time but currently works without
// "Cookie", "gog_lc=GB_GBP_en-US" or "Accept-Language", "en"
reqStore, err := http.NewRequest("GET", e.url, nil)
if err != nil {
return err
}
resStore, err := client.Do(reqStore)
if err != nil {
return err
}
bodyStore := html.NewTokenizer(resStore.Body)
var data humblebundleJsonBody
err = func() error {
for {
tt := bodyStore.Next()
switch {
case tt == html.ErrorToken:
// file end or error
return nil
case tt == html.StartTagToken:
t := bodyStore.Token()
if t.Data != "script" {
continue
}
for _, a := range t.Attr {
if !(a.Key == "id" && a.Val == "webpack-json-data") {
continue
}
if tt = bodyStore.Next(); tt != html.TextToken {
continue
}
err = json.Unmarshal([]byte(bodyStore.Token().Data), &data)
if err != nil {
return err
}
}
}
}
}()
if err != nil {
return err
}
regexAppid, err := regexp.Compile(`/store/([-\w]+)`)
if err != nil {
return err
}
for _, products := range data.Mosaic {
for _, product := range products.Products {
if !contains(product.Highlights, "FREE WHILE SUPPLIES LAST") {
continue
}
appID := regexAppid.FindStringSubmatch(product.ProductUrl)
if len(appID) < 1 {
continue
}
id := fmt.Sprintf("%v%v", e.idPrefix, appID[1])
title := product.TileName
url := fmt.Sprintf("%v%v", e.baseUrl, appID[1])
e.deals[id] = Deal{
Id: id,
Title: title,
Url: url,
}
}
}
return nil
}
func contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}
func (e HumbleBundleStruct) get() []Deal {
var deals []Deal
for _, deal := range e.deals {
deals = append(deals, deal)
}
return deals
}

View file

@ -1,9 +1,121 @@
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/log"
"github.com/disgoorg/snowflake/v2"
"os"
"os/signal"
"reflect"
"syscall"
"time"
)
var (
webhookID = snowflake.GetEnv("webhook_id")
webhookToken = os.Getenv("webhook_token")
)
func main() {
// translate this to go: https://dev.rievo.net/sst/feed-python
// query different sources store to db
// try to incorporate operagx api
// 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/games/free-games/
// - https://gg.deals/news/free-gog-games/
// - origin
// - check ubisoft works
log.SetLevel(log.LevelDebug)
log.Info("starting dealsbot...")
log.Info("disgo version: ", disgo.Version)
client := webhook.New(webhookID, webhookToken)
defer client.Close(context.TODO())
repo := InitDb()
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
apis = append(apis, newUbsioftApi(), newEpicApi(), newSteamApi(), newGogApi(), newHumbleBundleApi())
for _, api := range apis {
err := api.load()
if err != nil {
log.Error(err)
}
}
var deals []Deal
for _, api := range apis {
apiDeals := api.get()
deals = append(deals, apiDeals...)
}
for _, deal := range deals {
retrievedDeal, _ := repo.GetValue(deal.Id)
if deal.Id == retrievedDeal.Id {
log.Debugf("%v is already published", deal.Id)
} else if reflect.DeepEqual(deal, retrievedDeal) {
log.Errorf("%v is published but not equal", deal.Id)
} else {
log.Infof("%v is new and will be published", deal.Id)
go sendWebhook(client, deal)
err := repo.SetValue(deal)
if err != nil {
log.Error(err)
}
}
}
case <-tickerGC.C:
err := repo.RunGC()
if err != nil && !errors.Is(err, badger.ErrNoRewrite) {
log.Errorf("error with GC: %v", err)
} else {
log.Debug("GC successful")
}
case <-quit:
ticker.Stop()
tickerGC.Stop()
return
}
}
}()
log.Infof("dealsbot is now running. Press CTRL-C to exit.")
s := make(chan os.Signal, 1)
signal.Notify(s, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
<-s
}
func sendWebhook(client webhook.Client, deal Deal) {
var status string
status = fmt.Sprintf("currently free: %v\n", deal.Url)
if _, err := client.CreateMessage(discord.NewWebhookMessageCreateBuilder().
SetContent(status).Build(),
rest.WithDelay(2*time.Second),
); err != nil {
log.Errorf("error sending message %v", err.Error())
}
}

121
cmd/dealsbot/repository.go Normal file
View file

@ -0,0 +1,121 @@
package main
import (
"encoding/json"
"github.com/dgraph-io/badger/v4"
"github.com/disgoorg/log"
)
type Repository interface {
GetAll() ([]Deal, error)
GetValue(dealId string) Deal
SetValue(deal Deal) error
DeleteValue(dealId string) error
Close() error
}
type DealRepository struct {
db *badger.DB
}
func InitDb() *DealRepository {
opts := badger.DefaultOptions("./db")
opts.Logger = nil
db, err := badger.Open(opts)
if err != nil {
log.Fatal(err)
}
return &DealRepository{db}
}
func (d *DealRepository) Close() error {
return d.db.Close()
}
func (d *DealRepository) RunGC() error {
return d.db.RunValueLogGC(0.7)
}
func (d *DealRepository) GetAll() ([]Deal, error) {
var deals []Deal
err := d.db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchSize = 10
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
err := item.Value(func(val []byte) error {
retrievedDeal := Deal{}
err := json.Unmarshal(val, &retrievedDeal)
deals = append(deals, retrievedDeal)
return err
})
if err != nil {
return err
}
}
return nil
})
return deals, err
}
func (d *DealRepository) GetValue(dealId string) (Deal, error) {
retrievedDeal := Deal{}
err := d.db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte(dealId))
if err != nil {
return err
}
err = item.Value(func(val []byte) error {
err = json.Unmarshal(val, &retrievedDeal)
return err
})
return err
})
if err != nil {
return Deal{}, err
}
return retrievedDeal, nil
}
func (d *DealRepository) SetValue(deal Deal) error {
jsonBytes, err := json.Marshal(deal)
if err != nil {
return err
}
err = d.db.Update(func(txn *badger.Txn) error {
err := txn.Set([]byte(deal.Id), jsonBytes)
return err
})
if err != nil {
return err
}
return nil
}
func (d *DealRepository) DeleteValue(dealId string) error {
err := d.db.Update(func(txn *badger.Txn) error {
err := txn.Delete([]byte(dealId))
return err
})
return err
}
func (d *DealRepository) DeleteAll() error {
err := d.db.Update(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchSize = 10
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
err := txn.Delete(item.Key())
if err != nil {
return err
}
}
return nil
})
return err
}

146
cmd/dealsbot/steam.go Normal file
View file

@ -0,0 +1,146 @@
package main
import (
"encoding/json"
"fmt"
"golang.org/x/net/html"
"io"
"net/http"
"regexp"
)
type SteamStruct struct {
url string
baseUrl string
apiUrl string
idPrefix string
deals DealsMap
}
func newSteamApi() SteamStruct {
return SteamStruct{
url: "https://store.steampowered.com/search/results?force_infinite=1&maxprice=free&specials=1",
baseUrl: "https://store.steampowered.com/app/",
apiUrl: "https://store.steampowered.com/api/appdetails?appids=",
idPrefix: "steam-",
deals: make(map[string]Deal),
}
}
type steamApiBodyGame struct {
Success bool `json:"success"`
Data struct {
Type string `json:"type"`
Name string `json:"name"`
IsFree bool `json:"is_free"`
PriceOverview struct {
Currency string `json:"currency"`
Initial int `json:"initial"`
Final int `json:"final"`
DiscountPercent int `json:"discount_percent"`
InitialFormatted string `json:"initial_formatted"`
FinalFormatted string `json:"final_formatted"`
} `json:"price_overview"`
Platforms struct {
Windows bool `json:"windows"`
Mac bool `json:"mac"`
Linux bool `json:"linux"`
} `json:"platforms"`
} `json:"data"`
}
type steamApiBody map[string]steamApiBodyGame
func (e SteamStruct) load() error {
client := &http.Client{}
reqStore, err := http.NewRequest("GET", e.url, nil)
if err != nil {
return err
}
resStore, err := client.Do(reqStore)
if err != nil {
return err
}
bodyStore := html.NewTokenizer(resStore.Body)
// could also search over each #search_resultsRows element instead of regex
regexAppid, err := regexp.Compile(`https://store\.steampowered\.com/app/(\d+)`)
if err != nil {
return err
}
var appIDs []string
func() {
for {
tt := bodyStore.Next()
switch {
case tt == html.ErrorToken:
// file end or error
return
case tt == html.StartTagToken:
t := bodyStore.Token()
if t.Data != "a" {
continue
}
for _, a := range t.Attr {
if a.Key != "href" {
continue
}
appID := regexAppid.FindStringSubmatch(a.Val)
if len(appID) < 1 {
continue
}
appIDs = append(appIDs, appID[1])
}
}
}
}()
for _, appID := range appIDs {
reqApi, err := http.NewRequest("GET", fmt.Sprintf("%v%v", e.apiUrl, appID), nil)
if err != nil {
return err
}
resApi, err := client.Do(reqApi)
if err != nil {
return err
}
bodyApi, err := io.ReadAll(resApi.Body)
if err != nil {
return err
}
var data steamApiBody
err = json.Unmarshal(bodyApi, &data)
if err != nil {
return err
}
if game, ok := data[appID]; ok {
if game.Data.Type != "game" {
continue
}
id := fmt.Sprintf("%v%v", e.idPrefix, appID)
title := game.Data.Name
url := fmt.Sprintf("%v%v", e.baseUrl, appID)
e.deals[id] = Deal{
Id: id,
Title: title,
Url: url,
}
}
}
return nil
}
func (e SteamStruct) get() []Deal {
var deals []Deal
for _, deal := range e.deals {
deals = append(deals, deal)
}
return deals
}

143
cmd/dealsbot/ubisoft.go Normal file
View file

@ -0,0 +1,143 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"regexp"
)
type UbisoftStruct struct {
url string
idPrefix string
headers map[string]string
deals DealsMap
}
func newUbsioftApi() UbisoftStruct {
ubisoft := UbisoftStruct{
url: "https://free.ubisoft.com/configuration.js",
idPrefix: "ubisoft-",
headers: make(map[string]string),
deals: make(map[string]Deal),
}
ubisoft.headers["referer"] = "https://free.ubisoft.com/"
ubisoft.headers["origin"] = "https://free.ubisoft.com"
ubisoft.headers["ubi-localecode"] = "en-US"
ubisoft.headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0"
return ubisoft
}
type ubisoftApiBody struct {
News []struct {
Type string `json:"type"`
Title string `json:"title"`
Links []struct {
Param string `json:"param"`
} `json:"links"`
} `json:"news"`
}
func (e UbisoftStruct) load() error {
appId, prodUrl, err := func() (string, string, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", e.url, nil)
if err != nil {
return "", "", err
}
for key, value := range e.headers {
req.Header.Set(key, value)
}
res, err := client.Do(req)
if err != nil {
return "", "", err
}
body, err := io.ReadAll(res.Body)
regexAppId, err := regexp.Compile(`appId:\s*'(.+)'`)
if err != nil {
return "", "", err
}
regexProd, err := regexp.Compile(`prod:\s*'(.+)'`)
if err != nil {
return "", "", err
}
appId := regexAppId.FindSubmatch(body)
prodUrl := regexProd.FindSubmatch(body)
if len(appId) < 1 || len(prodUrl) < 1 {
return "", "", errors.New("appid or prod url not found")
}
return string(appId[1]), string(prodUrl[1]), nil
}()
if err != nil {
return err
}
client := &http.Client{}
req, err := http.NewRequest("GET", prodUrl, nil)
if err != nil {
return err
}
for key, value := range e.headers {
req.Header.Set(key, value)
}
req.Header.Set("ubi-appid", appId)
res, err := client.Do(req)
if err != nil {
return err
}
body, err := io.ReadAll(res.Body)
var data ubisoftApiBody
err = json.Unmarshal(body, &data)
if err != nil {
return err
}
for _, news := range data.News {
if news.Type != "freegame" {
continue
}
title := news.Title
if len(news.Links) != 1 {
return errors.New(fmt.Sprintf("lenght of links for %v is more or less then 1", news.Title))
}
regexName, err := regexp.Compile(`https://register.ubisoft.com/([^/]*)/?`)
idFromUrl := regexName.FindStringSubmatch(news.Links[0].Param)
if err != nil {
return err
}
if len(idFromUrl) < 1 {
return errors.New("could not parse url")
}
id := fmt.Sprintf("%v%v", e.idPrefix, idFromUrl[1])
url := news.Links[0].Param
e.deals[id] = Deal{
Id: id,
Title: title,
Url: url,
}
}
return nil
}
func (e UbisoftStruct) get() []Deal {
var deals []Deal
for _, deal := range e.deals {
deals = append(deals, deal)
}
return deals
}

98
cmd/groupbot/main.go Normal file
View file

@ -0,0 +1,98 @@
package main
import (
"context"
"fmt"
"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/cache"
"github.com/disgoorg/disgo/gateway"
"github.com/disgoorg/log"
"github.com/disgoorg/snowflake/v2"
"os"
)
var (
token = os.Getenv("disgo_token")
registerGuildID = snowflake.GetEnv("disgo_guild_id")
)
func main() {
log.SetLevel(log.LevelDebug)
log.Info("starting groupbot...")
log.Info("disgo version: ", disgo.Version)
// permissions:
// intent:
client, err := disgo.New(token,
bot.WithGatewayConfigOpts(
gateway.WithIntents(gateway.IntentsNone),
),
bot.WithCacheConfigOpts(
cache.WithCaches(
cache.FlagsNone,
),
),
)
if err != nil {
log.Fatal("error while building disgo instance: ", err)
return
}
defer client.Close(context.TODO())
var groups Groups
for i := 0; i < 30; i++ {
groups = append(groups, Group{
name: fmt.Sprintf("g%v", i),
group: "",
emoji: "",
})
}
createGroupMessage(groups)
if err = client.OpenGateway(context.TODO()); err != nil {
log.Fatal("error while connecting to gateway: ", err)
}
log.Infof("groupbot is now running. Press CTRL-C to exit.")
//s := make(chan os.Signal, 1)
//signal.Notify(s, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
//<-s
}
type Group struct {
name string
group string
emoji string
}
type Groups []Group
// chunk
// source https://github.com/golang/go/issues/53987
func chunk[T any](arrIn []T, size int) (arrOut [][]T) {
for i := 0; i < len(arrIn); i += size {
end := i + size
if end > len(arrIn) {
end = len(arrIn)
}
arrOut = append(arrOut, arrIn[i:end])
}
return arrOut
}
func createGroupMessage(groups Groups) {
groupsMessages := chunk(chunk(groups, 5), 5)
for _, messageGroups := range groupsMessages {
fmt.Println("--- new message ---")
for _, rowGroups := range messageGroups {
fmt.Printf("new row: ")
for _, group := range rowGroups {
fmt.Printf("%v;", group.name)
}
fmt.Println("")
}
}
}

View file

@ -21,7 +21,7 @@ var (
)
func main() {
log.SetLevel(log.LevelInfo)
log.SetLevel(log.LevelDebug)
log.Info("starting tempbot...")
log.Info("disgo version: ", disgo.Version)
@ -61,13 +61,15 @@ func main() {
// delete messages older then x min in channel
ticker := time.NewTicker(5 * time.Minute)
ticker := time.NewTicker(1 * time.Minute)
quit := make(chan struct{})
go func() {
client.Logger().Debug("does it even run")
for {
select {
case <-ticker.C:
messages, err := client.Rest().GetMessages(channelTempID, 0, 0, 0, 100)
client.Logger().Info(len(messages))
if err != nil {
client.Logger().Error("error getting messages: ", err)
}