diff --git a/app.go b/app.go index 37ea8a8..32553c9 100644 --- a/app.go +++ b/app.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + "encoding/json" "errors" "fmt" "io" @@ -15,6 +16,11 @@ import ( "git.sr.ht/~ashkeel/containers/sync" kv "git.sr.ht/~ashkeel/kilovolt/v12" + "github.com/nicklaw5/helix/v2" + "github.com/urfave/cli/v2" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/runtime" + "git.sr.ht/~ashkeel/strimertul/database" "git.sr.ht/~ashkeel/strimertul/docs" "git.sr.ht/~ashkeel/strimertul/loyalty" @@ -22,10 +28,6 @@ import ( "git.sr.ht/~ashkeel/strimertul/twitch" "git.sr.ht/~ashkeel/strimertul/twitch/client" "git.sr.ht/~ashkeel/strimertul/webserver" - "github.com/nicklaw5/helix/v2" - "github.com/urfave/cli/v2" - "github.com/wailsapp/wails/v2/pkg/options" - "github.com/wailsapp/wails/v2/pkg/runtime" ) // App struct @@ -155,7 +157,7 @@ func (a *App) initializeComponents() error { } // Initialize loyalty system - a.loyaltyManager, err = loyalty.NewManager(a.db) + a.loyaltyManager, err = loyalty.NewManager(a.db, a.twitchManager) if err != nil { return fmt.Errorf("could not initialize loyalty manager: %w", err) } diff --git a/cli.database.go b/cli.database.go index 384a7c4..a9268aa 100644 --- a/cli.database.go +++ b/cli.database.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "log/slog" "os" diff --git a/database/database_test.go b/database/database_test.go index 5e222fb..3b87982 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -1,13 +1,12 @@ package database import ( + "encoding/json" "errors" "testing" "time" kv "git.sr.ht/~ashkeel/kilovolt/v12" - - jsoniter "github.com/json-iterator/go" ) func TestLocalDBClientPutKey(t *testing.T) { @@ -60,7 +59,7 @@ func TestLocalDBClientPutJSON(t *testing.T) { } var testStored test - err = jsoniter.ConfigFastest.UnmarshalFromString(stored, &testStored) + err = json.Unmarshal([]byte(stored), &testStored) if err != nil { t.Fatal(err) } @@ -107,13 +106,13 @@ func TestLocalDBClientPutJSONBulk(t *testing.T) { } var testStored1 test - err = jsoniter.ConfigFastest.UnmarshalFromString(keys["test"], &testStored1) + err = json.Unmarshal([]byte(keys["test"]), &testStored1) if err != nil { t.Fatal(err) } var testStored2 test - err = jsoniter.ConfigFastest.UnmarshalFromString(keys["test2"], &testStored2) + err = json.Unmarshal([]byte(keys["test2"]), &testStored2) if err != nil { t.Fatal(err) } @@ -171,12 +170,12 @@ func TestLocalDBClientGetJSON(t *testing.T) { // Store a key directly in the store key := "test" - byt, err := jsoniter.ConfigFastest.MarshalToString(testStruct) + byt, err := json.Marshal(testStruct) if err != nil { t.Fatal(err) } - err = store.Set(key, byt) + err = store.Set(key, string(byt)) if err != nil { t.Fatal(err) } diff --git a/docs/cmd/docgen/main.go b/docs/cmd/docgen/main.go index b89936d..37a850f 100644 --- a/docs/cmd/docgen/main.go +++ b/docs/cmd/docgen/main.go @@ -1,14 +1,14 @@ package main import ( + "encoding/json" "os" "git.sr.ht/~ashkeel/strimertul/docs" - jsoniter "github.com/json-iterator/go" ) func main() { - enc := jsoniter.ConfigFastest.NewEncoder(os.Stdout) + enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") _ = enc.Encode(docs.Keys) } diff --git a/go.mod b/go.mod index 128f055..7a2e367 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/cockroachdb/pebble v1.1.0 github.com/gorilla/websocket v1.5.1 github.com/hashicorp/golang-lru/v2 v2.0.7 - github.com/json-iterator/go v1.1.12 github.com/nicklaw5/helix/v2 v2.28.1 github.com/samber/slog-multi v1.0.2 github.com/urfave/cli/v2 v2.27.1 @@ -59,8 +58,6 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.12.0 // indirect diff --git a/go.sum b/go.sum index 44659ba..b8ea564 100644 --- a/go.sum +++ b/go.sum @@ -193,7 +193,6 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -245,11 +244,9 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= diff --git a/logging.go b/logging.go index d266d8e..e7e56aa 100644 --- a/logging.go +++ b/logging.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "fmt" "log/slog" "math/rand" @@ -66,7 +67,7 @@ func (core *LogStorage) Handle(_ context.Context, record slog.Record) error { attributes[attrs.Key] = attrs.Value.Any() return true }) - attrJSON, _ := json.MarshalToString(attributes) + attrJSON, _ := json.Marshal(attributes) // Generate unique log ID id := fmt.Sprintf("%d-%d", time.Now().UnixNano(), rand.Int31()) @@ -76,7 +77,7 @@ func (core *LogStorage) Handle(_ context.Context, record slog.Record) error { Time: record.Time.Format(time.RFC3339), Level: record.Level.String(), Message: record.Message, - Data: attrJSON, + Data: string(attrJSON), } lastLogs.Push(logEntry) if lastLogs.Size() > LogHistory { diff --git a/twitch/chat/loyalty.go b/loyalty/chat.go similarity index 68% rename from twitch/chat/loyalty.go rename to loyalty/chat.go index e6634fb..15427f4 100644 --- a/twitch/chat/loyalty.go +++ b/loyalty/chat.go @@ -1,19 +1,19 @@ -package chat +package loyalty import ( "context" "errors" "fmt" + "log/slog" "strconv" "strings" "time" - "git.sr.ht/~ashkeel/strimertul/twitch" + "github.com/nicklaw5/helix/v2" "git.sr.ht/~ashkeel/containers/sync" - - "git.sr.ht/~ashkeel/strimertul/loyalty" - "github.com/nicklaw5/helix/v2" + "git.sr.ht/~ashkeel/strimertul/twitch" + "git.sr.ht/~ashkeel/strimertul/twitch/chat" ) const ( @@ -23,49 +23,49 @@ const ( commandContribute = "!contribute" ) -type loyaltyIntegration struct { +type twitchIntegration struct { ctx context.Context - manager *loyalty.Manager - module *Module + manager *Manager + module *chat.Module activeUsers *sync.Map[string, bool] } -func setupLoyaltyIntegration(ctx context.Context, mod *Module, manager *loyalty.Manager) *loyaltyIntegration { - li := &loyaltyIntegration{ +func setupTwitchIntegration(ctx context.Context, m *Manager, mod *chat.Module) *twitchIntegration { + li := &twitchIntegration{ ctx: ctx, - manager: manager, + manager: m, module: mod, activeUsers: sync.NewMap[string, bool](), } // Add loyalty-based commands - mod.commands.SetKey(commandRedeem, Command{ + mod.RegisterCommand(commandRedeem, chat.Command{ Description: "Redeem a reward with loyalty points", Usage: fmt.Sprintf("%s [request text]", commandRedeem), - AccessLevel: ALTEveryone, + AccessLevel: chat.ALTEveryone, Handler: li.cmdRedeemReward, Enabled: true, }) - mod.commands.SetKey(commandBalance, Command{ + mod.RegisterCommand(commandBalance, chat.Command{ Description: "See your current point balance", Usage: commandBalance, - AccessLevel: ALTEveryone, + AccessLevel: chat.ALTEveryone, Handler: li.cmdBalance, Enabled: true, }) - mod.commands.SetKey(commandGoals, Command{ + mod.RegisterCommand(commandGoals, chat.Command{ Description: "Check currently active community goals", Usage: commandGoals, - AccessLevel: ALTEveryone, + AccessLevel: chat.ALTEveryone, Handler: li.cmdGoalList, Enabled: true, }) - mod.commands.SetKey(commandContribute, Command{ + mod.RegisterCommand(commandContribute, chat.Command{ Description: "Contribute points to a community goal", Usage: fmt.Sprintf("%s []", commandContribute), - AccessLevel: ALTEveryone, + AccessLevel: chat.ALTEveryone, Handler: li.cmdContributeGoal, Enabled: true, }) @@ -87,9 +87,9 @@ func setupLoyaltyIntegration(ctx context.Context, mod *Module, manager *loyalty. // If stream is confirmed offline, don't give points away! var streamInfos []helix.Stream - err := mod.db.GetJSON(twitch.StreamInfoKey, &streamInfos) + err := m.db.GetJSON(twitch.StreamInfoKey, &streamInfos) if err != nil { - mod.logger.Error("Error retrieving stream info", "error", err) + slog.Error("Error retrieving stream info", "error", err) continue } if len(streamInfos) < 1 { @@ -97,32 +97,7 @@ func setupLoyaltyIntegration(ctx context.Context, mod *Module, manager *loyalty. } // Get user list - cursor := "" - var users []string - for { - userClient, err := twitch.GetUserClient(mod.db, false) - if err != nil { - mod.logger.Error("Could not get user api client for list of chatters", "error", err) - return - } - res, err := userClient.GetChannelChatChatters(&helix.GetChatChattersParams{ - BroadcasterID: mod.user.ID, - ModeratorID: mod.user.ID, - First: "1000", - After: cursor, - }) - if err != nil { - mod.logger.Error("Could not retrieve list of chatters", "error", err) - return - } - for _, user := range res.Data.Chatters { - users = append(users, user.UserLogin) - } - cursor = res.Data.Pagination.Cursor - if cursor == "" { - break - } - } + users := mod.GetChatters() // Iterate for each user in the list pointsToGive := make(map[string]int64) @@ -148,48 +123,48 @@ func setupLoyaltyIntegration(ctx context.Context, mod *Module, manager *loyalty. if len(users) > 0 { err := li.manager.GivePoints(pointsToGive) if err != nil { - mod.logger.Error("Error awarding loyalty points to user", "error", err) + slog.Error("Error awarding loyalty points to user", "error", err) } } } }() - mod.logger.Info("Loyalty system integration with Twitch is ready") + slog.Info("Loyalty system integration with Twitch is ready") return li } -func (li *loyaltyIntegration) Close() { - li.module.commands.DeleteKey(commandRedeem) - li.module.commands.DeleteKey(commandBalance) - li.module.commands.DeleteKey(commandGoals) - li.module.commands.DeleteKey(commandContribute) +func (li *twitchIntegration) Close() { + li.module.UnregisterCommand(commandRedeem) + li.module.UnregisterCommand(commandBalance) + li.module.UnregisterCommand(commandGoals) + li.module.UnregisterCommand(commandContribute) } -func (li *loyaltyIntegration) HandleMessage(message helix.EventSubChannelChatMessageEvent) { +func (li *twitchIntegration) HandleMessage(message helix.EventSubChannelChatMessageEvent) { li.activeUsers.SetKey(message.ChatterUserLogin, true) } -func (li *loyaltyIntegration) IsActive(user string) bool { +func (li *twitchIntegration) IsActive(user string) bool { active, ok := li.activeUsers.GetKey(user) return ok && active } -func (li *loyaltyIntegration) ResetActivity() { +func (li *twitchIntegration) ResetActivity() { li.activeUsers = sync.NewMap[string, bool]() } -func (li *loyaltyIntegration) cmdBalance(message helix.EventSubChannelChatMessageEvent) { +func (li *twitchIntegration) cmdBalance(message helix.EventSubChannelChatMessageEvent) { // Get user balance balance := li.manager.GetPoints(message.ChatterUserLogin) - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: fmt.Sprintf("You have %d %s!", balance, li.manager.Config.Get().Currency), ReplyTo: message.MessageID, }) } -func (li *loyaltyIntegration) cmdRedeemReward(message helix.EventSubChannelChatMessageEvent) { +func (li *twitchIntegration) cmdRedeemReward(message helix.EventSubChannelChatMessageEvent) { parts := strings.Fields(message.Message.Text) if len(parts) < 2 { return @@ -213,7 +188,7 @@ func (li *loyaltyIntegration) cmdRedeemReward(message helix.EventSubChannelChatM // Check if user can afford the reward if balance-reward.Price < 0 { - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: fmt.Sprintf("I'm sorry but you cannot afford this (have %d %s, need %d)", balance, config.Currency, reward.Price), ReplyTo: message.MessageID, }) @@ -226,36 +201,36 @@ func (li *loyaltyIntegration) cmdRedeemReward(message helix.EventSubChannelChatM } // Perform redeem - if err := li.manager.PerformRedeem(loyalty.Redeem{ + if err := li.manager.PerformRedeem(Redeem{ Username: message.ChatterUserLogin, DisplayName: message.ChatterUserName, When: time.Now(), Reward: reward, RequestText: text, }); err != nil { - if errors.Is(err, loyalty.ErrRedeemInCooldown) { + if errors.Is(err, ErrRedeemInCooldown) { nextAvailable := li.manager.GetRewardCooldown(reward.ID) - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: fmt.Sprintf("That reward is in cooldown (available in %s)", time.Until(nextAvailable).Truncate(time.Second)), ReplyTo: message.MessageID, }) return } - li.module.logger.Error("Error while performing redeem", "error", err) + slog.Error("Error while performing redeem", "error", err) return } - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: fmt.Sprintf("HolidayPresent %s has redeemed %s! (new balance: %d %s)", message.ChatterUserName, reward.Name, li.manager.GetPoints(message.ChatterUserLogin), config.Currency), }) } -func (li *loyaltyIntegration) cmdGoalList(message helix.EventSubChannelChatMessageEvent) { +func (li *twitchIntegration) cmdGoalList(message helix.EventSubChannelChatMessageEvent) { goals := li.manager.Goals.Get() if len(goals) < 1 { - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: "There are no active community goals right now :(!", ReplyTo: message.MessageID, }) @@ -269,12 +244,12 @@ func (li *loyaltyIntegration) cmdGoalList(message helix.EventSubChannelChatMessa msg += fmt.Sprintf("%s (%d/%d %s) [id: %s] | ", goal.Name, goal.Contributed, goal.TotalGoal, li.manager.Config.Get().Currency, goal.ID) } msg += " Contribute with " - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: msg, }) } -func (li *loyaltyIntegration) cmdContributeGoal(message helix.EventSubChannelChatMessageEvent) { +func (li *twitchIntegration) cmdContributeGoal(message helix.EventSubChannelChatMessageEvent) { goals := li.manager.Goals.Get() // Set defaults if user doesn't provide them @@ -297,12 +272,12 @@ func (li *loyaltyIntegration) cmdContributeGoal(message helix.EventSubChannelCha // Do we not have any goal we can contribute to? Hooray I guess? if goalIndex < 0 { if hasGoals { - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: "All active community goals have been reached already! NewRecord", ReplyTo: message.MessageID, }) } else { - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: "There are no active community goals right now :(!", ReplyTo: message.MessageID, }) @@ -316,7 +291,7 @@ func (li *loyaltyIntegration) cmdContributeGoal(message helix.EventSubChannelCha newPoints, err := strconv.ParseInt(parts[1], 10, 64) if err == nil { if newPoints <= 0 { - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: "Nice try SoBayed", ReplyTo: message.MessageID, }) @@ -340,7 +315,7 @@ func (li *loyaltyIntegration) cmdContributeGoal(message helix.EventSubChannelCha } // Invalid goal ID provided if !found { - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: "I couldn't find that goal ID :(", ReplyTo: message.MessageID, }) @@ -354,7 +329,7 @@ func (li *loyaltyIntegration) cmdContributeGoal(message helix.EventSubChannelCha // Check if goal was reached already if selectedGoal.Contributed >= selectedGoal.TotalGoal { - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: "This goal was already reached! ヾ(•ω•`)o", ReplyTo: message.MessageID, }) @@ -364,11 +339,11 @@ func (li *loyaltyIntegration) cmdContributeGoal(message helix.EventSubChannelCha // Add points to goal points, err := li.manager.PerformContribution(selectedGoal, message.ChatterUserLogin, points) if err != nil { - li.module.logger.Error("Error while contributing to goal", "error", err) + slog.Error("Error while contributing to goal", "error", err) return } if points == 0 { - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: "Sorry but you're broke", ReplyTo: message.MessageID, }) @@ -378,13 +353,13 @@ func (li *loyaltyIntegration) cmdContributeGoal(message helix.EventSubChannelCha selectedGoal = li.manager.Goals.Get()[goalIndex] config := li.manager.Config.Get() newRemaining := selectedGoal.TotalGoal - selectedGoal.Contributed - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: fmt.Sprintf("NewRecord %s contributed %d %s to \"%s\"!! Only %d %s left!", message.ChatterUserName, points, config.Currency, selectedGoal.Name, newRemaining, config.Currency), }) // Check if goal was reached! if newRemaining <= 0 { - li.module.WriteMessage(WriteMessageRequest{ + li.module.WriteMessage(chat.WriteMessageRequest{ Message: fmt.Sprintf("FallWinning The community goal \"%s\" was reached! FallWinning", selectedGoal.Name), Announce: true, }) diff --git a/loyalty/manager.go b/loyalty/manager.go index aa6e23d..63c1eae 100644 --- a/loyalty/manager.go +++ b/loyalty/manager.go @@ -2,6 +2,7 @@ package loyalty import ( "context" + "encoding/json" "errors" "fmt" "log/slog" @@ -10,12 +11,10 @@ import ( "git.sr.ht/~ashkeel/containers/sync" "git.sr.ht/~ashkeel/strimertul/database" + twitchclient "git.sr.ht/~ashkeel/strimertul/twitch/client" "git.sr.ht/~ashkeel/strimertul/utils" - jsoniter "github.com/json-iterator/go" ) -var json = jsoniter.ConfigFastest - var ( ErrRedeemNotFound = errors.New("redeem not found") ErrRedeemInCooldown = errors.New("redeem is on cooldown") @@ -36,9 +35,11 @@ type Manager struct { cancelFn context.CancelFunc cancelSub database.CancelFunc restartTwitchHandler chan struct{} + twitchManager *twitchclient.Manager + twitchIntegration *twitchIntegration } -func NewManager(db database.Database) (*Manager, error) { +func NewManager(db database.Database, twitchManager *twitchclient.Manager) (*Manager, error) { ctx, cancelFn := context.WithCancel(context.Background()) loyalty := &Manager{ Config: sync.NewRWSync(Config{Enabled: false}), @@ -53,6 +54,7 @@ func NewManager(db database.Database) (*Manager, error) { ctx: ctx, cancelFn: cancelFn, restartTwitchHandler: make(chan struct{}), + twitchManager: twitchManager, } // Get data from DB var config Config @@ -103,7 +105,7 @@ func NewManager(db database.Database) (*Manager, error) { for k, v := range points { var entry PointsEntry - err := json.UnmarshalFromString(v, &entry) + err := json.Unmarshal([]byte(v), &entry) if err != nil { return nil, err } @@ -119,10 +121,20 @@ func NewManager(db database.Database) (*Manager, error) { loyalty.SetBanList(config.BanList) + // Start twitch handler + if twitchManager.Client() != nil { + loyalty.twitchIntegration = setupTwitchIntegration(ctx, loyalty, twitchManager.Client().Chat) + } + return loyalty, nil } func (m *Manager) Close() error { + // Disable twitch integration + if m.twitchIntegration != nil { + m.twitchIntegration.Close() + } + // Stop subscription if m.cancelSub != nil { m.cancelSub() @@ -152,13 +164,13 @@ func (m *Manager) update(key, value string) { err = utils.LoadJSONToWrapped[[]Redeem](value, m.Queue) case CreateRedeemRPC: var redeem Redeem - err = json.UnmarshalFromString(value, &redeem) + err = json.Unmarshal([]byte(value), &redeem) if err == nil { err = m.AddRedeem(redeem) } case RemoveRedeemRPC: var redeem Redeem - err = json.UnmarshalFromString(value, &redeem) + err = json.Unmarshal([]byte(value), &redeem) if err == nil { err = m.RemoveRedeem(redeem) } @@ -168,7 +180,7 @@ func (m *Manager) update(key, value string) { // User point changed case strings.HasPrefix(key, PointsPrefix): var entry PointsEntry - err = json.UnmarshalFromString(value, &entry) + err = json.Unmarshal([]byte(value), &entry) user := key[len(PointsPrefix):] m.points.SetKey(user, entry) } diff --git a/main.go b/main.go index 7fba642..277f60f 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,6 @@ import ( "git.sr.ht/~ashkeel/strimertul/utils" "github.com/apenwarr/fixconsole" - jsoniter "github.com/json-iterator/go" "github.com/urfave/cli/v2" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" @@ -20,8 +19,6 @@ import ( "github.com/wailsapp/wails/v2/pkg/runtime" ) -var json = jsoniter.ConfigFastest - const devVersionMarker = "v0.0.0-UNKNOWN" var appVersion = devVersionMarker diff --git a/twitch/alerts/config.go b/twitch/alerts/config.go index c9f099a..04c3382 100644 --- a/twitch/alerts/config.go +++ b/twitch/alerts/config.go @@ -1,7 +1,8 @@ package alerts import ( - jsoniter "github.com/json-iterator/go" + "encoding/json" + "github.com/nicklaw5/helix/v2" ) @@ -10,7 +11,7 @@ const ConfigKey = "twitch/alerts/config" type eventSubNotification struct { Subscription helix.EventSubSubscription `json:"subscription"` Challenge string `json:"challenge"` - Event jsoniter.RawMessage `json:"event" desc:"Event payload, as JSON object"` + Event json.RawMessage `json:"event" desc:"Event payload, as JSON object"` } type subscriptionVariation struct { diff --git a/twitch/chat/module.go b/twitch/chat/module.go index 2875354..c6787de 100644 --- a/twitch/chat/module.go +++ b/twitch/chat/module.go @@ -2,23 +2,23 @@ package chat import ( "context" + "encoding/json" "errors" "log/slog" "strings" textTemplate "text/template" "time" + "github.com/nicklaw5/helix/v2" + "git.sr.ht/~ashkeel/containers/sync" "git.sr.ht/~ashkeel/strimertul/database" + "git.sr.ht/~ashkeel/strimertul/twitch" "git.sr.ht/~ashkeel/strimertul/twitch/eventsub" "git.sr.ht/~ashkeel/strimertul/twitch/template" "git.sr.ht/~ashkeel/strimertul/utils" - jsoniter "github.com/json-iterator/go" - "github.com/nicklaw5/helix/v2" ) -var json = jsoniter.ConfigFastest - type Module struct { Config Config @@ -97,8 +97,8 @@ func (mod *Module) onChatMessage(newValue string) { var chatMessage struct { Event helix.EventSubChannelChatMessageEvent `json:"event"` } - if err := json.UnmarshalFromString(newValue, &chatMessage); err != nil { - mod.logger.Error("Failed to decode incoming chat message", "error", err) + if err := json.Unmarshal([]byte(newValue), &chatMessage); err != nil { + mod.logger.Error("Failed to decode incoming chat message", slog.String("error", err.Error())) return } @@ -222,3 +222,39 @@ func (mod *Module) updateTemplates() error { func (mod *Module) WriteMessage(request WriteMessageRequest) { WriteMessage(mod.db, mod.logger, request) } + +func (mod *Module) RegisterCommand(name string, command Command) { + mod.commands.SetKey(name, command) +} + +func (mod *Module) UnregisterCommand(name string) { + mod.commands.DeleteKey(name) +} + +func (mod *Module) GetChatters() (users []string) { + cursor := "" + for { + userClient, err := twitch.GetUserClient(mod.db, false) + if err != nil { + slog.Error("Could not get user api client for list of chatters", "error", err) + return + } + res, err := userClient.GetChannelChatChatters(&helix.GetChatChattersParams{ + BroadcasterID: mod.user.ID, + ModeratorID: mod.user.ID, + First: "1000", + After: cursor, + }) + if err != nil { + mod.logger.Error("Could not retrieve list of chatters", "error", err) + return + } + for _, user := range res.Data.Chatters { + users = append(users, user.UserLogin) + } + cursor = res.Data.Pagination.Cursor + if cursor == "" { + return + } + } +} diff --git a/twitch/eventsub/client.go b/twitch/eventsub/client.go index 1715c62..a2f4578 100644 --- a/twitch/eventsub/client.go +++ b/twitch/eventsub/client.go @@ -2,6 +2,7 @@ package eventsub import ( "context" + "encoding/json" "fmt" "log/slog" "time" @@ -10,12 +11,9 @@ import ( "git.sr.ht/~ashkeel/strimertul/utils" "github.com/gorilla/websocket" lru "github.com/hashicorp/golang-lru/v2" - jsoniter "github.com/json-iterator/go" "github.com/nicklaw5/helix/v2" ) -var json = jsoniter.ConfigFastest - const websocketEndpoint = "wss://eventsub.wss.twitch.tv/ws" type Client struct { @@ -259,8 +257,8 @@ func topicCondition(topic string, id string) helix.EventSubCondition { } type WebsocketMessage struct { - Metadata Metadata `json:"metadata"` - Payload jsoniter.RawMessage `json:"payload"` + Metadata Metadata `json:"metadata"` + Payload json.RawMessage `json:"payload"` } type WelcomeMessagePayload struct { @@ -275,7 +273,7 @@ type WelcomeMessagePayload struct { type NotificationMessagePayload struct { Subscription helix.EventSubSubscription `json:"subscription"` - Event jsoniter.RawMessage `json:"event"` + Event json.RawMessage `json:"event"` Date time.Time `json:"date,omitempty"` } diff --git a/twitch/timers/module.go b/twitch/timers/module.go index 338eacf..18bcfd8 100644 --- a/twitch/timers/module.go +++ b/twitch/timers/module.go @@ -2,6 +2,7 @@ package timers import ( "context" + "encoding/json" "log/slog" "math/rand" "time" @@ -9,11 +10,8 @@ import ( "git.sr.ht/~ashkeel/containers/sync" "git.sr.ht/~ashkeel/strimertul/database" "git.sr.ht/~ashkeel/strimertul/twitch/chat" - jsoniter "github.com/json-iterator/go" ) -var json = jsoniter.ConfigFastest - const AverageMessageWindow = 5 type Module struct { @@ -56,7 +54,7 @@ func Setup(ctx context.Context, db database.Database, logger *slog.Logger) *Modu } if err := db.SubscribeKeyContext(ctx, ConfigKey, func(value string) { - if err := json.UnmarshalFromString(value, &mod.Config); err != nil { + if err := json.Unmarshal([]byte(value), &mod.Config); err != nil { logger.Warn("Error reloading timer config", "error", err) return } diff --git a/utils/json_test.go b/utils/json_test.go index d0774c3..a13a97c 100644 --- a/utils/json_test.go +++ b/utils/json_test.go @@ -1,10 +1,10 @@ package utils import ( + "encoding/json" "testing" "git.sr.ht/~ashkeel/containers/sync" - jsoniter "github.com/json-iterator/go" ) func TestLoadJSONToWrapped(t *testing.T) { @@ -17,7 +17,7 @@ func TestLoadJSONToWrapped(t *testing.T) { } // Encode test object to JSON - testStr, err := jsoniter.ConfigFastest.MarshalToString(testObj) + testByt, err := json.Marshal(testObj) if err != nil { t.Fatal(err) } @@ -26,7 +26,7 @@ func TestLoadJSONToWrapped(t *testing.T) { wrapped := sync.NewSync[test](test{}) // Load JSON to wrapped - err = LoadJSONToWrapped[test](testStr, wrapped) + err = LoadJSONToWrapped[test](string(testByt), wrapped) if err != nil { t.Fatal(err) }