adds dealsbot
This commit is contained in:
parent
2a05323f6f
commit
3a3dea453d
14 changed files with 1190 additions and 13 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.idea
|
||||
badger
|
||||
db
|
|
@ -22,4 +22,8 @@
|
|||
- TODO: add humblebundle
|
||||
- [msgbot](./cmd/msgbot/main.go) send messages which can be edited by other admins
|
||||
- needs env `disgo_token`
|
||||
- TODO: implement
|
||||
- TODO: implement
|
||||
|
||||
## TODO
|
||||
- check Intents https://gist.github.com/advaith1/e69bcc1cdd6d0087322734451f15aa2f
|
||||
- https://discordpy.readthedocs.io/en/stable/intents.html
|
14
cmd/dealsbot/api.go
Normal file
14
cmd/dealsbot/api.go
Normal 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
129
cmd/dealsbot/epic.go
Normal 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
135
cmd/dealsbot/gog.go
Normal 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
|
||||
}
|
137
cmd/dealsbot/humblebundle.go
Normal file
137
cmd/dealsbot/humblebundle.go
Normal 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
|
||||
}
|
|
@ -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
121
cmd/dealsbot/repository.go
Normal 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
146
cmd/dealsbot/steam.go
Normal 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
143
cmd/dealsbot/ubisoft.go
Normal 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
98
cmd/groupbot/main.go
Normal 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("")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
25
go.mod
25
go.mod
|
@ -3,16 +3,35 @@ module grow.rievo.dev/discordBots
|
|||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/disgoorg/disgo v0.15.0
|
||||
github.com/disgoorg/log v1.2.0
|
||||
github.com/dgraph-io/badger/v4 v4.0.1
|
||||
github.com/disgoorg/disgo v0.15.1-0.20230203231532-07092d420798
|
||||
github.com/disgoorg/log v1.2.1-0.20220709130509-d138f028c3b7
|
||||
github.com/disgoorg/snowflake/v2 v2.0.1
|
||||
github.com/k0kubun/pp/v3 v3.2.0
|
||||
golang.org/x/net v0.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/disgoorg/json v1.0.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
|
||||
github.com/golang/protobuf v1.3.1 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/flatbuffers v1.12.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/klauspost/compress v1.12.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b // indirect
|
||||
go.opencensus.io v0.22.5 // indirect
|
||||
golang.org/x/crypto v0.5.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874 // indirect
|
||||
golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/text v0.6.0 // indirect
|
||||
)
|
||||
|
|
126
go.sum
126
go.sum
|
@ -1,22 +1,136 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/disgoorg/disgo v0.15.0 h1:OTYlhyQ/frPtqY9dGRSaBpo7GJtTQj+8dah2ml+LPHM=
|
||||
github.com/disgoorg/disgo v0.15.0/go.mod h1:j7MCI6foUipYNozxwttr2hcaEQa3gNEbcwgLnyLUf6E=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger/v4 v4.0.1 h1:zwLYFc4sfxKdaRTvS6wlHsSuYWNUiWnYLU+TS+/nCDI=
|
||||
github.com/dgraph-io/badger/v4 v4.0.1/go.mod h1:edFJfgVfwYjg+grodpS7Yj2vohQMK3VL6eCaR6EpRJU=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/disgoorg/disgo v0.15.1-0.20230203231532-07092d420798 h1:BW5wOwP53TX8XgjrSSL6DiIzKijHpKBDl/jWAGYGT20=
|
||||
github.com/disgoorg/disgo v0.15.1-0.20230203231532-07092d420798/go.mod h1:j7MCI6foUipYNozxwttr2hcaEQa3gNEbcwgLnyLUf6E=
|
||||
github.com/disgoorg/json v1.0.0 h1:kDhSM661fgIuNoZF3BO5/odaR5NSq80AWb937DH+Pdo=
|
||||
github.com/disgoorg/json v1.0.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA=
|
||||
github.com/disgoorg/log v1.2.0 h1:sqlXnu/ZKAlIlHV9IO+dbMto7/hCQ474vlIdMWk8QKo=
|
||||
github.com/disgoorg/log v1.2.0/go.mod h1:3x1KDG6DI1CE2pDwi3qlwT3wlXpeHW/5rVay+1qDqOo=
|
||||
github.com/disgoorg/log v1.2.1-0.20220709130509-d138f028c3b7 h1:TDiZG14dzJgtdlt4sZjgLvtpA3Q0yxK9QMo1R1p2LCs=
|
||||
github.com/disgoorg/log v1.2.1-0.20220709130509-d138f028c3b7/go.mod h1:hhQWYTFTnIGzAuFPZyXJEi11IBm9wq+/TVZt/FEwX0o=
|
||||
github.com/disgoorg/snowflake/v2 v2.0.1 h1:CuUxGLwggUxEswZOmZ+mZ5i0xSumQdXW9tXW7uGqe+0=
|
||||
github.com/disgoorg/snowflake/v2 v2.0.1/go.mod h1:SPU9c2CNn5DSyb86QcKtdZgix9osEtKrHLW4rMhfLCs=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs=
|
||||
github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b h1:qYTY2tN72LhgDj2rtWG+LI6TXFl2ygFQQ4YezfVaGQE=
|
||||
github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874 h1:kWC3b7j6Fu09SnEBr7P4PuQyM0R6sqyH9R+EjIvT1nQ=
|
||||
golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9 h1:frX3nT9RkKybPnjyI+yvZh6ZucTZatCCEm9D47sZ2zo=
|
||||
golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
Loading…
Reference in a new issue