package alerts import ( "math/rand" "time" "github.com/nicklaw5/helix/v2" "git.sr.ht/~ashkeel/strimertul/twitch/chat" ) type subMixedEvent struct { UserID string UserLogin string UserName string BroadcasterUserID string BroadcasterUserLogin string BroadcasterUserName string Tier string IsGift bool CumulativeMonths int StreakMonths int DurationMonths int Message helix.EventSubMessage } // Subscriptions are handled with a slight delay as info come from different events and must be aggregated func (m *Module) addMixedEvent(event any) { switch sub := event.(type) { case helix.EventSubChannelSubscribeEvent: m.pendingMux.Lock() defer m.pendingMux.Unlock() if ev, ok := m.pendingSubs[sub.UserID]; ok { // Already pending, add extra data ev.IsGift = sub.IsGift m.pendingSubs[sub.UserID] = ev return } m.pendingSubs[sub.UserID] = subMixedEvent{ UserID: sub.UserID, UserLogin: sub.UserLogin, UserName: sub.UserName, BroadcasterUserID: sub.BroadcasterUserID, BroadcasterUserLogin: sub.BroadcasterUserLogin, BroadcasterUserName: sub.BroadcasterUserName, Tier: sub.Tier, IsGift: sub.IsGift, } go func() { // Wait a bit to make sure we aggregate all events time.Sleep(time.Second * 3) m.processPendingSub(sub.UserID) }() case helix.EventSubChannelSubscriptionMessageEvent: m.pendingMux.Lock() defer m.pendingMux.Unlock() if ev, ok := m.pendingSubs[sub.UserID]; ok { // Already pending, add extra data ev.StreakMonths = sub.StreakMonths ev.DurationMonths = sub.DurationMonths ev.CumulativeMonths = sub.CumulativeMonths ev.Message = sub.Message return } m.pendingSubs[sub.UserID] = subMixedEvent{ UserID: sub.UserID, UserLogin: sub.UserLogin, UserName: sub.UserName, BroadcasterUserID: sub.BroadcasterUserID, BroadcasterUserLogin: sub.BroadcasterUserLogin, BroadcasterUserName: sub.BroadcasterUserName, Tier: sub.Tier, StreakMonths: sub.StreakMonths, DurationMonths: sub.DurationMonths, CumulativeMonths: sub.CumulativeMonths, Message: sub.Message, } go func() { // Wait a bit to make sure we aggregate all events time.Sleep(time.Second * 3) m.processPendingSub(sub.UserID) }() } } func (m *Module) processPendingSub(user string) { m.pendingMux.Lock() defer m.pendingMux.Unlock() sub, ok := m.pendingSubs[user] defer delete(m.pendingSubs, user) if !ok { // Somehow it's gone? Return early return } // One last check in case config changed if !m.Config.Subscription.Enabled { return } // Assign random message messageID := rand.Intn(len(m.Config.Subscription.Messages)) tpl, ok := m.templates[templateTypeSubscription][m.Config.Subscription.Messages[messageID]] // If template is broken, write it as is (soft fail, plus we raise attention I guess?) if !ok { // Broken template! chat.WriteMessage(m.db, m.logger, chat.WriteMessageRequest{ Message: m.Config.Subscription.Messages[messageID], Announce: m.Config.Subscription.Announce, }) return } // Check for variations, either by streak or gifted if sub.IsGift { variation := getBestValidVariation(m.Config.Subscription.Variations, func(variation subscriptionVariation) int { if variation.IsGifted != nil && *variation.IsGifted { return 1 } return 0 }) tpl = m.replaceWithVariation(tpl, templateTypeSubscription, variation.Messages) } else if sub.DurationMonths > 0 { // Get variation with the highest minimum streak that's met variation := getBestValidVariation(m.Config.Subscription.Variations, func(variation subscriptionVariation) int { if variation.MinStreak != nil && sub.DurationMonths >= *variation.MinStreak { return sub.DurationMonths } return 0 }) tpl = m.replaceWithVariation(tpl, templateTypeSubscription, variation.Messages) } m.writeTemplate(tpl, sub, m.Config.Subscription.Announce) }