You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

519 lines
16 KiB

package bot
// FIXME: aemoji does not update existing users
// XXX: Help should only show commands available to a user
import (
"fmt"
"log"
"regexp"
"strings"
"git.lalonde.me/matth/AltVRBot/pkg/discord/mux"
"github.com/bwmarrin/discordgo"
)
var (
commandFormats = map[string]string{
"auser": "<Altspace VR Username> <Discord Mention> [Discord Emoji]",
"aemoji": "<Discord Mention> <Discord Emoji>",
"msg": "<Discord Mention> ...",
"accept": "(As reply) <Discord Mention> [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
}