Created new config type to use base feature type for easier per-server configs.

This commit is contained in:
Justin Judd 2025-07-27 20:48:40 -07:00
parent 59da01b0e5
commit bd1406d7c1
3 changed files with 215 additions and 52 deletions

25
featurename_string.go Normal file
View File

@ -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]]
}

188
main.go
View File

@ -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)

View File

@ -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
...
}