package bot // FIXME: aemoji does not update existing users import ( "fmt" "log" "regexp" "strings" "git.lalonde.me/matth/AltVRBot/pkg/discord/mux" "github.com/bwmarrin/discordgo" ) var ( commandFormats = map[string]string{ "auser": " [Discord Emoji]", "aemoji": " ", "msg": " ...", "accept": "(As reply) [Discord Emoji]", "deny": "(As reply)", } commandReplies = map[string]string{ "online_welcome": "Hello, AltVRBot online and here to serve!", "all_done": "All done!", "unauthorized_user": "Sorry, you are not authorized to do this!", "invalid_command": "Invalid command format!", "unknown_error": "I couldn't not process your request due to an unknown error!", "auser_unknown_friend": "I don't know about this user, are you sure they are one of my friends and the username is correct?", "auser_known_user": "I already know about this user!", "aemoji_uknow_user": "I don't know about this user, try to associate it first!", "msg_unknown_sender": "I don't know you! You must be associated first!", "msg_unknown_receipient": "I don't know about <@!%s>! They must be associated first!", "msg_all_done_truncated": "All done, however your message was too long and truncated!", "new_friendship_requested": "I just heard that %s would like to become friends, should I accept?", "accept_unknown_reference": "Unknown reference, are you sure you are replying to the right message? Perhaps the request was withdrawn!", } commandRepliesRude = map[string]string{ "online_welcome": "Ah fuck, back to work already? Well message me, maybe I'll help you anyway!", "all_done": "Alright alright, stop bugging me already, I got it done for you, you fuck!", "unauthorized_user": "Fartface, who the fuck do you think you are?!? You're not allowed to do that you lowlife!", "invalid_command": "You drunk or something?", "unknown_error": "_BOFH_", "auser_unknown_friend": "You dimwit, you should know I don't know who this idiot is. Do I really want them to be my friend? I don't know, but maybe they do!", "auser_known_user": "Fuckwad, I already know about this asshole!", "aemoji_uknow_user": "Who the fuck is that? Maybe try to tell me first you airhead!", "msg_unknown_sender": "Never heard of you chucklefuck! You even registered?", "msg_unknown_receipient": "I don't know about this chickenfucker named <@!%s>! They must be associated first!", "msg_all_done_truncated": "Ok ok I did it you shitstick, but you're a verbose fuck so I had to cut your message off a bit!", "new_friendship_requested": "Have you heard about this dipshit named %s, they think they're cool enough, ha! Should we let that numskull in?", "accept_unknown_reference": "Numnuts, I don't know what you're talking about! That user might have been too cool for you, or you're just fucking confused!", } ) func (b *Type) loadDiscordHandlers() { // XXX: Status [Discord Mention] reload the online users states and display b.dg.Router.Route("auser", "Associates an AltVR user to a discord user", b.handleAssociateUser) b.dg.Router.Route("aemoji", "Associates or updates an AltVR user to a discord guild emoji", b.handleAssociateEmoji) b.dg.Router.Route("accept", "Accept a pending friendship request", b.handleAcceptFriendship) b.dg.Router.Route("deny", "Deny a pending friendship request", b.handleDenyFriendship) // XXX: Remove b.dg.Router.Route("reload", "Reload the bot's data and configs", b.handleReload) b.dg.Router.Route("check", "Force a recheck of pending friendship requests and messsages", b.handleForceCheck) b.dg.Router.Route("msg", "Message an AltVR user", b.handleSendMessage) // XXX: Promote b.dg.Session.AddHandler(b.handleMessageReplies) } func (b *Type) handleAssociateUser(ds *discordgo.Session, dm *discordgo.Message, ctx *mux.Context) { //fmt.Printf("dm:\t%+v\nctv:\t%+v\n", dm, ctx) if !b.checkUserRole(dm.Author.ID, RoleModerator) && len(b.users) != 0 { b.replyPermissionDenied(ds, dm, ctx) return } c := strings.TrimSpace(ctx.Content) p := strings.Split(c, " ") if len(p) < 3 || len(p) > 4 { b.replyInvalidCommandFormat(ds, dm, ctx, "auser") return } // XXX discordID := strings.Trim(strings.Replace(p[2], "@!", "", -1), "<>") friend := b.avr.GetFriendByUsername(p[1]) if friend.Username == "" { b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("auser_unknown_friend"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) return } u, _ := b.getUserByDiscordID(discordID) if u.AltVRUserID != "" { b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("auser_known_user"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) return } e := UserEmoji{} if len(p) == 4 { s := strings.Split(strings.Trim(p[3], "<>"), ":") e.ID = s[2] e.Name = s[1] } role := RoleUser if len(b.users) == 0 { role = RoleAdmin } b.users = append(b.users, User{ AltVRUserID: friend.UserID, DiscordID: discordID, DiscordName: dm.Mentions[len(dm.Mentions)-1].Username, DiscordEmoji: e, Role: role, isRude: false, }) b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("all_done"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) if err := b.saveUserFile(); err != nil { log.Printf("Error while saving user file: %+v\n", err) } } func (b *Type) handleAssociateEmoji(ds *discordgo.Session, dm *discordgo.Message, ctx *mux.Context) { //fmt.Printf("dm:\t%+v\nctv:\t%+v\n", dm, ctx) if !b.checkUserRole(dm.Author.ID, RoleModerator) { b.replyPermissionDenied(ds, dm, ctx) return } c := strings.TrimSpace(ctx.Content) p := strings.Split(c, " ") if len(p) != 3 { b.replyInvalidCommandFormat(ds, dm, ctx, "aemoji") return } // XXX discordID := strings.Trim(strings.Replace(p[1], "@!", "", -1), "<>") u, err := b.getUserByDiscordID(discordID) if err != nil { b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("aemoji_unknown_user"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) return } e := UserEmoji{} if len(p) == 4 { s := strings.Split(strings.Trim(p[2], "<>"), ":") e.ID = s[2] e.Name = s[1] } u.DiscordEmoji = e b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("all_done"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) if err := b.saveUserFile(); err != nil { log.Printf("Error while saving user file: %+v\n", err) } } // XXX Don't allow messaging the bot func (b *Type) handleSendMessage(ds *discordgo.Session, dm *discordgo.Message, ctx *mux.Context) { //fmt.Printf("dm:\t%+v\nctv:\t%+v\n", dm, ctx) if dm.MessageReference != nil { return } u, err := b.getUserByDiscordID(dm.Author.ID) if err != nil { b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("msg_unknown_sender"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) return } um := b.getMentions(dm.Mentions) if len(um) != 1 { b.replyInvalidCommandFormat(ds, dm, ctx, "msg") return } discordID := um[0].ID uu, err := b.getUserByDiscordID(discordID) if err != nil { b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("msg_unknown_receipient", discordID), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) return } reg := regexp.MustCompile(fmt.Sprintf(" <@!?(%s)>", discordID)) c := strings.TrimSpace(ctx.Content) c = reg.ReplaceAllString(c, "") c = strings.TrimPrefix(c, "msg ") au := b.avr.GetFriend(u.AltVRUserID) c = au.GetDisplayName() + ": " + c cl := len([]rune(c)) c = truncateString(c, 140) if err := b.avr.PostNewConversation(uu.AltVRUserID, c); err != nil { log.Printf("Error while sending message: %+v\n", err) b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("unknown_error"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) } else { rm := "all_done" if cl > len([]rune(c)) { rm = "msg_all_done_truncated" } b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage(rm), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) } } func (b *Type) handleMessageReplies(ds *discordgo.Session, mc *discordgo.MessageCreate) { // Ignore all messages created by the Bot account itself if mc.Author.ID == ds.State.User.ID { return } if mc.MessageReference == nil { return } if _, ok := b.convos[mc.MessageReference.MessageID]; !ok { return } u, err := b.getUserByDiscordID(mc.Author.ID) if err != nil { b.dg.Session.ChannelMessageSendReply(mc.ChannelID, b.getReplyMessage("msg_unknown_sender"), &discordgo.MessageReference{ MessageID: mc.ID, ChannelID: mc.ChannelID, GuildID: mc.GuildID, }) return } au := b.avr.GetFriend(u.AltVRUserID) msg := au.GetDisplayName() + ": " + mc.Content cl := len([]rune(msg)) msg = truncateString(msg, 140) if err := b.avr.PostNewConversation(b.convos[mc.MessageReference.MessageID], msg); err != nil { log.Printf("Error while replying to message: %+v\n", err) b.dg.Session.ChannelMessageSendReply(mc.ChannelID, b.getReplyMessage("unknown_error"), &discordgo.MessageReference{ MessageID: mc.ID, ChannelID: mc.ChannelID, GuildID: mc.GuildID, }) } else { rm := "all_done" if cl > len([]rune(msg)) { rm = "msg_all_done_truncated" } b.dg.Session.ChannelMessageSendReply(mc.ChannelID, b.getReplyMessage(rm), &discordgo.MessageReference{ MessageID: mc.ID, ChannelID: mc.ChannelID, GuildID: mc.GuildID, }) } } func (b *Type) handleAcceptFriendship(ds *discordgo.Session, dm *discordgo.Message, ctx *mux.Context) { //fmt.Printf("dm:\t%+v\nctv:\t%+v\n", dm, ctx) if !b.checkUserRole(dm.Author.ID, RoleModerator) && len(b.users) != 0 { b.replyPermissionDenied(ds, dm, ctx) return } if dm.MessageReference == nil { b.replyInvalidCommandFormat(ds, dm, ctx, "accept") return } var req FriendshipRequestPending found := false for _, fr := range b.frPending { if fr.MessageID == dm.MessageReference.MessageID { found = true req = fr break } } if !found { b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("accept_unknown_reference"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) return } um := b.getMentions(dm.Mentions) if len(um) != 1 { b.replyInvalidCommandFormat(ds, dm, ctx, "accept") return } reg := regexp.MustCompile(fmt.Sprintf(" <@!?(%s)>", um[0].ID)) c := strings.TrimSpace(ctx.Content) c = reg.ReplaceAllString(c, "") p := strings.Split(c, " ") if len(p) > 3 { b.replyInvalidCommandFormat(ds, dm, ctx, "accept") return } e := UserEmoji{} if len(p) == 3 { s := strings.Split(strings.Trim(p[2], "<>"), ":") e.ID = s[2] e.Name = s[1] } role := RoleUser if len(b.users) == 0 { role = RoleAdmin } b.users = append(b.users, User{ AltVRUserID: req.Friendship.UserID, DiscordID: um[0].ID, DiscordName: dm.Mentions[len(dm.Mentions)-1].Username, DiscordEmoji: e, Role: role, isRude: false, }) b.avr.AcceptFriendshipRequest(req.Friendship.FriendshipID) for k, fr := range b.frPending { if fr.Friendship.FriendshipID == req.Friendship.FriendshipID { b.frPending = append(b.frPending[:k], b.frPending[k+1:]...) break } } b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("all_done"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) if err := b.saveUserFile(); err != nil { log.Printf("Error while saving user file: %+v\n", err) } b.avr.FetchFriend(req.Friendship.UserID) } func (b *Type) handleDenyFriendship(ds *discordgo.Session, dm *discordgo.Message, ctx *mux.Context) { //fmt.Printf("dm:\t%+v\nctv:\t%+v\n", dm, ctx) if !b.checkUserRole(dm.Author.ID, RoleModerator) { b.replyPermissionDenied(ds, dm, ctx) return } if dm.MessageReference == nil { b.replyInvalidCommandFormat(ds, dm, ctx, "deny") return } var req FriendshipRequestPending found := false for _, fr := range b.frPending { if fr.MessageID == dm.MessageReference.MessageID { found = true break } } if !found { b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("accept_unknown_reference"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) return } b.avr.DenyFriendshipRequest(req.Friendship.FriendshipID) for k, fr := range b.frPending { if fr.Friendship.FriendshipID == req.Friendship.FriendshipID { b.frPending = append(b.frPending[:k], b.frPending[k+1:]...) break } } b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("all_done"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) } func (b *Type) handleForceCheck(ds *discordgo.Session, dm *discordgo.Message, ctx *mux.Context) { //fmt.Printf("dm:\t%+v\nctv:\t%+v\n", dm, ctx) if !b.checkUserRole(dm.Author.ID, RoleModerator) { b.replyPermissionDenied(ds, dm, ctx) return } log.Println("Checking for incoming friendship request") fr, _ := b.avr.FetchPendingFriendshipRequests() if len(fr) > 0 { b.handleNewFriendshipRequests(fr) } log.Println("Checking for conversations") pm, _ := b.avr.FetchPendingConversations() if len(pm) > 0 { b.handleNewPendingConversation(pm) } } func (b *Type) handleReload(ds *discordgo.Session, dm *discordgo.Message, ctx *mux.Context) { //fmt.Printf("dm:\t%+v\nctv:\t%+v\n", dm, ctx) if !b.checkUserRole(dm.Author.ID, RoleAdmin) { b.replyPermissionDenied(ds, dm, ctx) return } log.Println("Reloading bot data") b.loadUserFile() err := b.avr.FetchMyUser() if err != nil { log.Printf("Error while reloading own user data: %+v\n", err) } b.dg.UpdateAvatar(b.avr.GetAvatarURL()) } func (b *Type) checkUserRole(discordID string, role Roles) bool { u, err := b.getUserByDiscordID(discordID) if err != nil { log.Printf("Error fetching bot user: %s\n", err) return false } return (u.Role >= role) } func (b *Type) replyPermissionDenied(ds *discordgo.Session, dm *discordgo.Message, ctx *mux.Context) { b.dg.Session.ChannelMessageSendReply(dm.ChannelID, b.getReplyMessage("unauthorized_user"), &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) } func (b *Type) replyInvalidCommandFormat(ds *discordgo.Session, dm *discordgo.Message, ctx *mux.Context, cmd string) { msg := b.getReplyMessage("invalid_command") if usage, ok := commandFormats[cmd]; ok { msg = msg + " " + usage } b.dg.Session.ChannelMessageSendReply(dm.ChannelID, msg, &discordgo.MessageReference{ MessageID: dm.ID, ChannelID: dm.ChannelID, GuildID: dm.GuildID, }) } func (b *Type) getReplyMessage(msgID string, params ...interface{}) string { if b.isRude { if val, ok := commandRepliesRude[msgID]; ok { if val == "_BOFH_" { val = getBOFHExcuse() } return fmt.Sprintf(val, params...) } } if val, ok := commandReplies[msgID]; ok { return fmt.Sprintf(val, params...) } return "Unknown message???" } func (b *Type) getMentions(ms []*discordgo.User) []*discordgo.User { var um []*discordgo.User for _, u := range ms { if u.ID != b.dg.User.ID { um = append(um, u) } } return um } func truncateString(s string, i int) string { runes := []rune(s) if len(runes) > i { return string(runes[:i]) } return s }