package rolereactions import ( "io/ioutil" "log" "git.annabunch.es/annabunches/urizen/lib/util" "github.com/bwmarrin/discordgo" "gopkg.in/yaml.v2" ) type roleReactionConfig struct { Channel string Message string MessageText string `yaml:"message_text"` RoleMap map[string]emojiData `yaml:"role_map"` } type emojiData struct { CustomEmojiID string `yaml:"custom_emoji_id"` RoleID string `yaml:"role_id"` } var config []roleReactionConfig // Init initializes the module, installing the handlers into the provided // discord session and setting up the role mapping via the config file. func Init(session *discordgo.Session, configFile string) { // TODO: should config be added/stored dynamically? config = make([]roleReactionConfig, 0) util.ParseFile(configFile, initConfig) changed := initMessages(session) if changed { writeConfig(configFile) } session.AddHandler(handleReactAdd) session.AddHandler(handleReactRemove) } func initConfig(data []byte) int { err := yaml.Unmarshal(data, &config) if err != nil { log.Panicf("[RoleReactions] Failed to parse role reaction config: %v", err) } return 1 } func writeConfig(filename string) { data, err := yaml.Marshal(&config) if err != nil { log.Printf("[RoleReactions] Failed to convert config back to yaml. Couldn't save config. This will probably break something: %v", err) return } err = ioutil.WriteFile(filename, data, 0644) if err != nil { log.Printf("[RoleReactions] Failed to save changes to config. This will probably break something on future runs: %v", err) return } log.Printf("[RoleReactions] Wrote new config.") } // Iterate over the message IDs, first removing all reactions from the // bot, then adding the available reactions. // This will log an error if a message ID is not found, but program // execution will continue. func initMessages(session *discordgo.Session) bool { changed := false for i, data := range config { // if message ID is empty, create a new message and flag that we need to write // out the new config if data.Message == "" { msgID, err := postMessage(session, data) if err != nil { log.Printf("[RoleReactions] Couldn't post message to channel '%s'. This config entry will be ignored.", data.Channel) continue } config[i].Message = msgID data.Message = msgID // we have to set this one so that the rest of this loop iteration works as expected changed = true } // delete any self-made reactions from previous runs. We iterate over all of them // in case the options have changed deleteOwnReactions(session, data.Message, data.Channel) // Add the new reacts for name, eData := range data.RoleMap { emojiString := name if eData.CustomEmojiID != "" { emojiString += ":" + eData.CustomEmojiID } err := session.MessageReactionAdd(data.Channel, data.Message, emojiString) if err != nil { log.Printf("[RoleReactions] Couldn't add reaction '%s' on message '%s': %v", emojiString, data.Message, err) continue } } log.Printf("[RoleReactions] listening to message %s", data.Message) log.Printf("[RoleReactions] role map: %v", data.RoleMap) } return changed } func postMessage(session *discordgo.Session, data roleReactionConfig) (string, error) { log.Printf("Posting a new message to channel '%s'", data.Channel) msg, err := session.ChannelMessageSend(data.Channel, data.MessageText) if err != nil { return "", err } return msg.ID, nil } func deleteOwnReactions(session *discordgo.Session, message string, channel string) { // Clean up the old reacts msg, err := session.ChannelMessage(channel, message) if err != nil { log.Printf("[RoleReactions] Error retrieving message to clean reacts: %v", err) return } for _, react := range msg.Reactions { if react.Me != true { continue } session.MessageReactionRemove(channel, message, react.Emoji.Name, "@me") } } func handleReactAdd(s *discordgo.Session, m *discordgo.MessageReactionAdd) { for _, target := range config { if m.MessageID != target.Message { continue } eData, ok := target.RoleMap[m.Emoji.Name] if !ok { log.Printf("[RoleReactions] Unmapped react added to message '%s': %s", target.Message, m.Emoji.Name) return } // add the role to the user s.GuildMemberRoleAdd(m.GuildID, m.UserID, eData.RoleID) log.Printf("[RoleReactions] Added role '%s' to user '%s'", m.UserID, eData.RoleID) return } } func handleReactRemove(s *discordgo.Session, m *discordgo.MessageReactionRemove) { for _, target := range config { if m.MessageID != target.Message { continue } eData, ok := target.RoleMap[m.Emoji.Name] if !ok { return } s.GuildMemberRoleRemove(m.GuildID, m.UserID, eData.RoleID) log.Printf("[RoleReactions] Removed role '%s' from user '%s'", m.UserID, eData.RoleID) return } }