From bd1406d7c17cc90d7b503f093ead3cc7b525efe2 Mon Sep 17 00:00:00 2001 From: Justin Judd Date: Sun, 27 Jul 2025 20:48:40 -0700 Subject: [PATCH] Created new config type to use base feature type for easier per-server configs. --- featurename_string.go | 25 ++++++ main.go | 188 ++++++++++++++++++++++++++++++++++-------- schema.cue | 54 ++++++++---- 3 files changed, 215 insertions(+), 52 deletions(-) create mode 100644 featurename_string.go diff --git a/featurename_string.go b/featurename_string.go new file mode 100644 index 0000000..52975c6 --- /dev/null +++ b/featurename_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=FeatureName"; DO NOT EDIT. + +package main + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[UnknownFeature-0] + _ = x[VoiceChatAnnounceFeature-1] + _ = x[BirthdayAnnounceFeature-2] +} + +const _FeatureName_name = "UnknownFeatureVoiceChatAnnounceFeatureBirthdayAnnounceFeature" + +var _FeatureName_index = [...]uint8{0, 14, 38, 61} + +func (i FeatureName) String() string { + if i < 0 || i >= FeatureName(len(_FeatureName_index)-1) { + return "FeatureName(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _FeatureName_name[_FeatureName_index[i]:_FeatureName_index[i+1]] +} diff --git a/main.go b/main.go index d081ee6..21f6ee1 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,10 @@ +//go:generate stringer -type=FeatureName + package main import ( "database/sql" + "encoding/json" "flag" "fmt" "io" @@ -40,38 +43,121 @@ func init() { //go:embed schema.cue var schemaFile string +type FeatureName int + +const ( + UnknownFeature FeatureName = iota + VoiceChatAnnounceFeature + BirthdayAnnounceFeature +) + +type Feature interface { + Name() FeatureName +} + +func (c *Config) UnmarshalJSON(in []byte) error { + c2 := BaseConfig{} + c.featureMap = map[FeatureName]Feature{} + err := json.Unmarshal(in, &c2) + if err != nil { + return err + } + c.Server = c2.Server + for _, f := range c2.Features { + base := BaseFeature{} + err := json.Unmarshal(f, &base) + if err != nil { + fmt.Println(err.Error()) + continue + } + switch base.Feature { + case VoiceChatAnnounceFeature: + f2 := VoiceChatAnnounce{} + err := json.Unmarshal(f, &f2) + if err != nil { + fmt.Println(err.Error()) + continue + } + c.Features = append(c.Features, f2) + c.featureMap[VoiceChatAnnounceFeature] = f2 + case BirthdayAnnounceFeature: + f2 := BirthdayAnnounce{} + err := json.Unmarshal(f, &f2) + if err != nil { + fmt.Println(err.Error()) + continue + } + c.Features = append(c.Features, f2) + c.featureMap[BirthdayAnnounceFeature] = f2 + default: + fmt.Println("Unknown feature") + } + } + + return nil +} + +type BaseConfig struct { + Server string + Features []json.RawMessage +} + +type BaseFeature struct { + Feature FeatureName + Enabled bool +} + +type AccounceFeature struct { + BaseFeature `json:",inline"` + AnnounceChannel string +} + +type VoiceChatAnnounce struct { + AccounceFeature `json:",inline"` + JoinMessages []string + CleanUpDelay int +} + +func (VoiceChatAnnounce) Name() FeatureName { return VoiceChatAnnounceFeature } + +type BirthdayAnnounce struct { + AccounceFeature `json:",inline"` + Birthdays []Birthday +} + +func (BirthdayAnnounce) Name() FeatureName { return BirthdayAnnounceFeature } + type Config struct { - Server string - VoiceChatAnnounce struct { - Enable bool - AnnounceChannel string - JoinMessages []string - CleanUpDelay int - } - BirthdayAnnounce struct { - Enable bool - AnnounceChannel string - Birthdays []Birthday - } + Server string + Features []Feature + featureMap map[FeatureName]Feature } func (c Config) String() string { s := strings.Builder{} s.WriteString(fmt.Sprintf("Guild ID: %s\n", c.Server)) - if c.VoiceChatAnnounce.Enable { - s.WriteString("\tVoice Chat Accounce: ✅ \n") - s.WriteString(fmt.Sprintf("\t\tTo Channel: %s\n", c.VoiceChatAnnounce.AnnounceChannel)) - s.WriteString(fmt.Sprintf("\t\t %d Custom messages\n", len(c.VoiceChatAnnounce.JoinMessages))) - } else { - s.WriteString("\tVoice Chat Accounce: ❌ \n") - } - if c.BirthdayAnnounce.Enable { - s.WriteString("\tBirthday Accounce: ✅ \n") - s.WriteString(fmt.Sprintf("\t\tTo Channel: %s\n", c.BirthdayAnnounce.AnnounceChannel)) - s.WriteString(fmt.Sprintf("\t\t %d Birthdays to Announce\n", len(c.BirthdayAnnounce.Birthdays))) - } else { - s.WriteString("\tBirthday Accounce: ❌ \n") + for _, f := range c.Features { + switch f2 := f.(type) { + case VoiceChatAnnounce: + if f2.Enabled { + s.WriteString("\tVoice Chat Accounce: ✅ \n") + s.WriteString(fmt.Sprintf("\t\tTo Channel: %s\n", f2.AnnounceChannel)) + s.WriteString(fmt.Sprintf("\t\t %d Custom messages\n", len(f2.JoinMessages))) + } else { + s.WriteString("\tVoice Chat Accounce: ❌ \n") + } + + case BirthdayAnnounce: + if f2.Enabled { + s.WriteString("\tBirthday Accounce: ✅ \n") + s.WriteString(fmt.Sprintf("\t\tTo Channel: %s\n", f2.AnnounceChannel)) + s.WriteString(fmt.Sprintf("\t\t %d Birthdays to Announce\n", len(f2.Birthdays))) + } else { + s.WriteString("\tBirthday Accounce: ❌ \n") + } + } } + return s.String() } @@ -123,6 +209,10 @@ func NewServer(dbFile string) (*Server, error) { } func (s *Server) AddConfig(c Config) { + c.featureMap = map[FeatureName]Feature{} + for _, f := range c.Features { + c.featureMap[f.Name()] = f + } s.configs[c.Server] = c log.Printf("Added server: \n%s\n", c) } @@ -233,11 +323,25 @@ func (s VoiceState) String() string { // Handle for when someone joins a voice channel - send notification about them joining. func (s *Server) voiceStatus(ds *discordgo.Session, m *discordgo.VoiceStateUpdate) { - guild, _ := ds.Guild(m.VoiceState.GuildID) - server := s.configs[guild.ID] - if !server.VoiceChatAnnounce.Enable { + guild, err := ds.Guild(m.VoiceState.GuildID) + if err != nil { + log.Printf("Unable to get Guild info: %s", err.Error()) return } + server, serverConfigured := s.configs[guild.ID] + if !serverConfigured { + log.Printf("Server %q not configured", guild.Name) + return + } + + vc, ok := server.featureMap[VoiceChatAnnounceFeature] + if !ok { + log.Printf("Server %q not configured for voice chat announcements", guild.Name) + return + } + + config := vc.(VoiceChatAnnounce) + var state VoiceState switch { case len(m.ChannelID) == 0: @@ -277,12 +381,12 @@ func (s *Server) voiceStatus(ds *discordgo.Session, m *discordgo.VoiceStateUpdat return } - ch, err := ds.Channel(server.VoiceChatAnnounce.AnnounceChannel) + ch, err := ds.Channel(config.AnnounceChannel) if err != nil { log.Printf("Unable to get announce channel for Guild: %s", m.GuildID) return } - messages := append(joinMessages, server.VoiceChatAnnounce.JoinMessages...) + messages := append(joinMessages, config.JoinMessages...) msg := fmt.Sprintf(messages[rand.IntN(len(messages))], m.UserID, m.ChannelID) switch state { @@ -295,7 +399,7 @@ func (s *Server) voiceStatus(ds *discordgo.Session, m *discordgo.VoiceStateUpdat log.Printf("unable to send message: %s", err) } - time.AfterFunc(time.Second*time.Duration(server.VoiceChatAnnounce.CleanUpDelay), func() { + time.AfterFunc(time.Second*time.Duration(config.CleanUpDelay), func() { ds.ChannelMessageDelete(sent.ChannelID, sent.ID) }) } @@ -303,10 +407,15 @@ func (s *Server) voiceStatus(ds *discordgo.Session, m *discordgo.VoiceStateUpdat func (s *Server) setupBirthdayWatch(ds *discordgo.Session) error { birthdays := map[string][]Birthday{} // Use a list in case there are collisions (See birthday paradox) for _, guild := range s.configs { - if !guild.BirthdayAnnounce.Enable { + ba, ok := guild.featureMap[BirthdayAnnounceFeature] + if !ok { continue } - for _, b := range guild.BirthdayAnnounce.Birthdays { + config, ok := ba.(BirthdayAnnounce) + if !ok || !config.Enabled { + continue + } + for _, b := range config.Birthdays { b.server = guild.Server birthdays[b.Date] = append(birthdays[b.Date], b) } @@ -332,14 +441,21 @@ func (s *Server) setupBirthdayWatch(ds *discordgo.Session) error { if bd, ok := birthdays[dateString]; ok { for _, b := range bd { log.Printf("It is %q's birthday today (%s)", b.Member, b.Date) - if err := announceBirthday(ds, b.server, s.configs[b.server].BirthdayAnnounce.AnnounceChannel, b.Member); err != nil { + ba, ok := s.configs[b.server].featureMap[BirthdayAnnounceFeature] + if !ok { + continue + } + config, ok := ba.(BirthdayAnnounce) + if !ok || !config.Enabled { + continue + } + if err := announceBirthday(ds, config.AnnounceChannel, b.Member); err != nil { log.Printf("Error w/ announcing: %v", err) } } } } time.AfterFunc(initialDelay, func() { - log.Printf("Birthday watcher initiated") // Run now, but also set up a daily job birthdayMatcher(ds) @@ -352,7 +468,7 @@ func (s *Server) setupBirthdayWatch(ds *discordgo.Session) error { return nil } -func announceBirthday(ds *discordgo.Session, guildID, channelID, userId string) error { +func announceBirthday(ds *discordgo.Session, channelID, userId string) error { _, err := ds.ChannelMessageSend(channelID, fmt.Sprintf("Happy Birthday to <@%s>!!", userId)) if err != nil { return fmt.Errorf("unable to send message: %w", err) diff --git a/schema.cue b/schema.cue index ca1ba64..a9f0869 100644 --- a/schema.cue +++ b/schema.cue @@ -4,28 +4,50 @@ package main +#FeatureName: int // #enumFeatureName + +#enumFeatureName: + #VoiceChatAnnounceFeature | + #BirthdayAnnounceFeature + +#values_FeatureName: { + VoiceChatAnnounceFeature: #VoiceChatAnnounceFeature + BirthdayAnnounceFeature: #BirthdayAnnounceFeature +} + +#VoiceChatAnnounceFeature: #FeatureName & 1 +#BirthdayAnnounceFeature: #FeatureName & 2 + +#Feature: _ + +#BaseFeature: { + Feature!: #enumFeatureName + Enabled: bool | *true + ... +} + +#AccounceFeature: #BaseFeature & { + AnnounceChannel!: string +} + +#VoiceChatAnnounce: #AccounceFeature & { + Feature: #VoiceChatAnnounceFeature + JoinMessages: [...string] @go(,[]string) + CleanUpDelay: int | *100 +} + +#BirthdayAnnounce: #AccounceFeature &{ + Feature: #BirthdayAnnounceFeature + Birthdays: [...#Birthday] @go(,[]Birthday) +} + #Config: { Server: string - VoiceChatAnnounce?: { - Enable: bool | *false - AnnounceChannel: string | *"" - JoinMessages: [...string] @go(,[]string) - CleanUpDelay: int | *30 - } @go(,struct{Enable bool; AnnounceChannel string; JoinMessages []string; CleanUpDelay int}) - BirthdayAnnounce?: { - Enable: bool | *false - AnnounceChannel: string | *"" - Birthdays: [...#Birthday] @go(,[]Birthday) - } @go(,struct{Enable bool; AnnounceChannel string; Birthdays []Birthday}) + Features: [...#Feature] @go(,[]Feature) } #Birthday: { Name: string Member: string Date: string -} - -#Enabled: { - Enable: true - ... } \ No newline at end of file