From 28d78bdb8c9d9e46ef8a263448a86c28e429c036 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 00:08:40 -0800 Subject: [PATCH 01/13] Reorganize and update Dockerfile --- Dockerfile | 19 ++- events/member_events.go | 135 ++++++++++++++++++ events/message_events.go | 97 +++++++++++++ exts/P_interpreter.go | 84 ++++++++++++ exts/fun.go | 84 ++++++++++++ exts/guild.go | 290 +++++++++++++++++++++++++++++++++++++++ exts/init.go | 234 +++++++++++++++++++++++++++++++ exts/tags.go | 137 ++++++++++++++++++ exts/tasks.go | 56 ++++++++ exts/user_management.go | 256 ++++++++++++++++++++++++++++++++++ exts/utils.go | 169 +++++++++++++++++++++++ go.mod | 4 +- goff.go | 136 ++++++++++++++++++ utils/database.go | 154 +++++++++++++++++++++ utils/date_strings.go | 64 +++++++++ utils/email.go | 132 ++++++++++++++++++ utils/logging.go | 34 +++++ utils/postfixes.go | 77 +++++++++++ utils/puzzles.go | 93 +++++++++++++ utils/rpn.go | 156 +++++++++++++++++++++ utils/rpnParser.go | 95 +++++++++++++ utils/snowflake.go | 32 +++++ utils/tasks.go | 132 ++++++++++++++++++ utils/types.go | 10 ++ 24 files changed, 2672 insertions(+), 8 deletions(-) create mode 100644 events/member_events.go create mode 100644 events/message_events.go create mode 100644 exts/P_interpreter.go create mode 100644 exts/fun.go create mode 100644 exts/guild.go create mode 100644 exts/init.go create mode 100644 exts/tags.go create mode 100644 exts/tasks.go create mode 100644 exts/user_management.go create mode 100644 exts/utils.go create mode 100644 goff.go create mode 100644 utils/database.go create mode 100644 utils/date_strings.go create mode 100644 utils/email.go create mode 100644 utils/logging.go create mode 100644 utils/postfixes.go create mode 100644 utils/puzzles.go create mode 100644 utils/rpn.go create mode 100644 utils/rpnParser.go create mode 100644 utils/snowflake.go create mode 100644 utils/tasks.go create mode 100644 utils/types.go diff --git a/Dockerfile b/Dockerfile index ac585ff..e5c5618 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,20 @@ -FROM golang:1.14-alpine +FROM golang:1.14-alpine as dev WORKDIR /go/src/Goff COPY ./go.mod . +COPY ./go.sum . -RUN apk add --no-cache git - -RUN go get -d -v ./... +RUN go mod download COPY . . -RUN go install -v ./... +RUN go install github.com/dustinpianalto/goff + +CMD [ "go", "run", "goff.go"] + +from alpine + +WORKDIR /bin + +COPY --from=dev /go/bin/goff ./goff -ENTRYPOINT /go/bin/goff +CMD [ "goff" ] diff --git a/events/member_events.go b/events/member_events.go new file mode 100644 index 0000000..ebc0755 --- /dev/null +++ b/events/member_events.go @@ -0,0 +1,135 @@ +package events + +import ( + "fmt" + "log" + "strconv" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/dustinpianalto/goff/utils" +) + +func OnGuildMemberAddLogging(s *discordgo.Session, member *discordgo.GuildMemberAdd) { + defer func() { + if r := recover(); r != nil { + log.Println("Recovered from panic in OnGuildMemberAddLogging", r) + } + }() + var channelID string + row := utils.Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", member.GuildID) + err := row.Scan(&channelID) + if err != nil || channelID == "" { + return + } + guild, err := s.State.Guild(member.GuildID) + if err != nil { + log.Println(err) + return + } + + var title string + if member.User.Bot { + title = "Bot Joined" + } else { + title = "Member Joined" + } + + thumb := &discordgo.MessageEmbedThumbnail{ + URL: member.User.AvatarURL(""), + } + + int64ID, _ := strconv.ParseInt(member.User.ID, 10, 64) + snow := utils.ParseSnowflake(int64ID) + + field := &discordgo.MessageEmbedField{ + Name: "User was created:", + Value: utils.ParseDateString(snow.CreationTime), + Inline: false, + } + + joinTime, _ := member.JoinedAt.Parse() + + embed := &discordgo.MessageEmbed{ + Title: title, + Description: fmt.Sprintf("%v (%v) Has Joined the Server", member.User.Mention(), member.User.ID), + Color: 0x0cc56a, + Thumbnail: thumb, + Footer: &discordgo.MessageEmbedFooter{ + Text: fmt.Sprintf("Current Member Count: %v", guild.MemberCount), + IconURL: guild.IconURL(), + }, + Timestamp: joinTime.Format(time.RFC3339), + Fields: []*discordgo.MessageEmbedField{field}, + } + s.ChannelMessageSendEmbed(channelID, embed) +} + +func OnGuildMemberRemoveLogging(s *discordgo.Session, member *discordgo.GuildMemberRemove) { + defer func() { + if r := recover(); r != nil { + log.Println("Recovered from panic in OnGuildMemberAddLogging", r) + } + }() + timeNow := time.Now() + var channelID string + row := utils.Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", member.GuildID) + err := row.Scan(&channelID) + if err != nil || channelID == "" { + return + } + guild, err := s.State.Guild(member.GuildID) + if err != nil { + log.Println(err) + return + } + + var title string + if member.User.Bot { + title = "Bot Left" + } else { + title = "Member Left" + } + + thumb := &discordgo.MessageEmbedThumbnail{ + URL: member.User.AvatarURL(""), + } + + desc := "" + al, err := s.GuildAuditLog(member.GuildID, "", "", 20, 1) + if err != nil { + log.Println(err) + } else { + for _, log := range al.AuditLogEntries { + if log.TargetID == member.User.ID { + int64ID, _ := strconv.ParseInt(log.ID, 10, 64) + logSnow := utils.ParseSnowflake(int64ID) + if timeNow.Sub(logSnow.CreationTime).Seconds() <= 10 { + user, err := s.User(log.UserID) + if err == nil { + desc = fmt.Sprintf("%v (%v) was Kicked by: %v\nReason: %v", member.User.String(), member.User.ID, user.String(), log.Reason) + } else { + desc = fmt.Sprintf("%v (%v) was Kicked by: %v\nReason: %v", member.User.String(), member.User.ID, log.UserID, log.Reason) + } + break + } + } + } + } + if desc == "" { + desc = fmt.Sprintf("%v (%v) Has Left the Server", member.User.String(), member.User.ID) + } + + embed := &discordgo.MessageEmbed{ + Title: title, + Description: desc, + Color: 0xff9431, + Thumbnail: thumb, + Footer: &discordgo.MessageEmbedFooter{ + Text: fmt.Sprintf("Current Member Count: %v", guild.MemberCount), + IconURL: guild.IconURL(), + }, + Timestamp: timeNow.Format(time.RFC3339), + } + s.ChannelMessageSendEmbed(channelID, embed) +} diff --git a/events/message_events.go b/events/message_events.go new file mode 100644 index 0000000..5167a82 --- /dev/null +++ b/events/message_events.go @@ -0,0 +1,97 @@ +package events + +import ( + "fmt" + "log" + + "github.com/bwmarrin/discordgo" + "github.com/dustinpianalto/goff/utils" +) + +func OnMessageUpdate(session *discordgo.Session, m *discordgo.MessageUpdate) { + defer func() { + if r := recover(); r != nil { + log.Println("Recovered from panic in OnMessageUpdate", r) + } + }() + msg := m.BeforeUpdate + if msg.Author.Bot { + return + } + var channelID string + row := utils.Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", msg.GuildID) + err := row.Scan(&channelID) + if err != nil || channelID == "" { + return + } + channel, err := session.State.Channel(msg.ChannelID) + if err != nil { + log.Println(err) + return + } + embed := &discordgo.MessageEmbed{ + Title: fmt.Sprintf("Message Edited: %v", msg.ID), + Description: fmt.Sprintf("**Before:** %v\n**After:** %v\nIn Channel: %v", msg.Content, m.Content, channel.Mention()), + Color: session.State.UserColor(msg.Author.ID, channelID), + Footer: &discordgo.MessageEmbedFooter{ + Text: fmt.Sprintf("Author: %v", msg.Author.String()), + IconURL: msg.Author.AvatarURL(""), + }, + } + session.ChannelMessageSendEmbed(channelID, embed) +} + +func OnMessageDelete(session *discordgo.Session, m *discordgo.MessageDelete) { + defer func() { + if r := recover(); r != nil { + log.Println("Recovered from panic in OnMessageDelete", r) + } + }() + msg := m.BeforeDelete + if msg == nil { + log.Printf("Message Deleted but the original message was not in my cache so we are ignoring it.\nMessage ID: %v\nGuild ID: %v\nChannel ID: %v\n", m.ID, m.GuildID, m.ChannelID) + return + } + if msg.Author.Bot { + return + } + var channelID string + row := utils.Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", msg.GuildID) + err := row.Scan(&channelID) + if err != nil || channelID == "" { + return + } + channel, err := session.State.Channel(msg.ChannelID) + if err != nil { + log.Println(err) + return + } + desc := "" + al, err := session.GuildAuditLog(msg.GuildID, "", "", 72, 1) + if err != nil { + log.Println(err) + } else { + for _, log := range al.AuditLogEntries { + if log.TargetID == msg.Author.ID && log.Options.ChannelID == msg.ChannelID { + user, err := session.User(log.UserID) + if err == nil { + desc = fmt.Sprintf("**Content:** %v\nIn Channel: %v\nDeleted By: %v", msg.Content, channel.Mention(), user.Mention()) + } + break + } + } + } + if desc == "" { + desc = fmt.Sprintf("**Content:** %v\nIn Channel: %v", msg.Content, channel.Mention()) + } + embed := &discordgo.MessageEmbed{ + Title: fmt.Sprintf("Message Deleted: %v", msg.ID), + Description: desc, + Color: session.State.UserColor(msg.Author.ID, channelID), + Footer: &discordgo.MessageEmbedFooter{ + Text: fmt.Sprintf("Author: %v", msg.Author.String()), + IconURL: msg.Author.AvatarURL(""), + }, + } + session.ChannelMessageSendEmbed(channelID, embed) +} diff --git a/exts/P_interpreter.go b/exts/P_interpreter.go new file mode 100644 index 0000000..57f4b77 --- /dev/null +++ b/exts/P_interpreter.go @@ -0,0 +1,84 @@ +package exts + +import ( + "errors" + "fmt" + "github.com/dustinpianalto/disgoman" + "strings" +) + +func pCommand(ctx disgoman.Context, args []string) { + input := strings.Join(args, "") + const LENGTH = 1999 + var mem [LENGTH]byte + pointer := 0 + l := 0 + for i := 0; i < len(input); i++ { + if input[i] == 'L' { + if pointer == 0 { + pointer = LENGTH - 1 + } else { + pointer-- + } + } else if input[i] == 'R' { + if pointer == LENGTH-1 { + pointer = 0 + } else { + pointer++ + } + } else if input[i] == '+' { + mem[pointer]++ + } else if input[i] == '-' { + mem[pointer]-- + } else if input[i] == '(' { + if mem[pointer] == 0 { + i++ + for l > 0 || input[i] != ')' { + if input[i] == '(' { + l++ + } + if input[i] == ')' { + l-- + } + i++ + } + } + } else if input[i] == ')' { + if mem[pointer] != 0 { + i-- + for l > 0 || input[i] != '(' { + if input[i] == ')' { + l++ + } + if input[i] == '(' { + l-- + } + i-- + } + } + } else { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: fmt.Sprintf("Invalid Character: %v", input[i]), + Error: errors.New("invalid character"), + } + return + } + } + var out []byte + for _, i := range mem { + if i != 0 { + out = append(out, i) + } + } + _, err := ctx.Send(string(out)) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Couldn't send results", + Error: err, + } + return + } + return +} diff --git a/exts/fun.go b/exts/fun.go new file mode 100644 index 0000000..5084128 --- /dev/null +++ b/exts/fun.go @@ -0,0 +1,84 @@ +package exts + +import ( + "fmt" + "strconv" + "strings" + + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/rpnparse" +) + +func interleave(ctx disgoman.Context, args []string) { + if len(args) == 2 { + x, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + return + } + y, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return + } + var z = int64(0) + for i := 0; i < 64; i++ { + x_masked_i := x & (1 << i) + y_masked_i := y & (1 << i) + + z |= x_masked_i << i + z |= y_masked_i << (i + 1) + } + ctx.Send(fmt.Sprintf("%v", z)) + } +} + +func deinterleave(ctx disgoman.Context, args []string) { + if len(args) == 1 { + z, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + return + } + var x = int64(0) + var y = int64(0) + i := 0 + for z > 0 { + x |= (z & 1) << i + z >>= 1 + y |= (z & 1) << i + z >>= 1 + i++ + } + ctx.Send(fmt.Sprintf("(%v, %v)", x, y)) + } +} + +func generateRPNCommand(ctx disgoman.Context, args []string) { + rpn, err := rpnparse.GenerateRPN(args) + if err != nil { + ctx.Send(err.Error()) + return + } + ctx.Send(rpn) +} + +func parseRPNCommand(ctx disgoman.Context, args []string) { + res, err := rpnparse.ParseRPN(args) + if err != nil { + ctx.Send(err.Error()) + return + } + ctx.Send(fmt.Sprintf("The result is: %v", res)) +} + +func solveCommand(ctx disgoman.Context, args []string) { + rpn, err := rpnparse.GenerateRPN(args) + if err != nil { + ctx.Send(err.Error()) + return + } + res, err := rpnparse.ParseRPN(strings.Split(rpn, " ")) + if err != nil { + ctx.Send(err.Error()) + return + } + ctx.Send(fmt.Sprintf("The result is: %v", res)) +} diff --git a/exts/guild.go b/exts/guild.go new file mode 100644 index 0000000..b5da4bc --- /dev/null +++ b/exts/guild.go @@ -0,0 +1,290 @@ +package exts + +import ( + "fmt" + "strings" + + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/goff/utils" +) + +// Guild management commands + +func loggingChannel(ctx disgoman.Context, args []string) { + var idString string + if len(args) > 0 { + idString = args[0] + if strings.HasPrefix(idString, "<#") && strings.HasSuffix(idString, ">") { + idString = idString[2 : len(idString)-1] + } + } else { + idString = "" + } + fmt.Println(idString) + if idString == "" { + _, err := utils.Database.Exec("UPDATE guilds SET logging_channel='' WHERE id=$1;", ctx.Guild.ID) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error Updating Database", + Error: err, + } + return + } + _, _ = ctx.Send("Logging Channel Updated.") + return + } + channel, err := ctx.Session.State.Channel(idString) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Can't find that channel.", + Error: err, + } + return + } + if channel.GuildID != ctx.Guild.ID { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "The channel passed is not in this guild.", + Error: err, + } + return + } + _, err = utils.Database.Exec("UPDATE guilds SET logging_channel=$1 WHERE id=$2;", idString, ctx.Guild.ID) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error Updating Database", + Error: err, + } + return + } + _, _ = ctx.Send("Logging Channel Updated.") +} + +func getLoggingChannel(ctx disgoman.Context, _ []string) { + var channelID string + row := utils.Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", ctx.Guild.ID) + err := row.Scan(&channelID) + if err != nil { + fmt.Println(err) + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error getting data from the database.", + Error: err, + } + return + } + if channelID == "" { + _, _ = ctx.Send("The logging channel is not set.") + return + } + channel, err := ctx.Session.State.GuildChannel(ctx.Guild.ID, channelID) + if err != nil { + fmt.Println(err) + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "I got the channel ID but it does not appear to be a valid channel in this guild.", + Error: err, + } + return + } + _, _ = ctx.Send(fmt.Sprintf("The logging channel is currently %s", channel.Mention())) + return +} + +func welcomeChannel(ctx disgoman.Context, args []string) { + var idString string + if len(args) > 0 { + idString = args[0] + if strings.HasPrefix(idString, "<#") && strings.HasSuffix(idString, ">") { + idString = idString[2 : len(idString)-1] + } + } else { + idString = "" + } + fmt.Println(idString) + if idString == "" { + _, err := utils.Database.Exec("UPDATE guilds SET welcome_channel='' WHERE id=$1;", ctx.Guild.ID) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error Updating Database", + Error: err, + } + return + } + _, _ = ctx.Send("Welcomer Disabled.") + return + } + channel, err := ctx.Session.State.Channel(idString) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Can't find that channel.", + Error: err, + } + return + } + if channel.GuildID != ctx.Guild.ID { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "The channel passed is not in this guild.", + Error: err, + } + return + } + _, err = utils.Database.Exec("UPDATE guilds SET welcome_channel=$1 WHERE id=$2;", idString, ctx.Guild.ID) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error Updating Database", + Error: err, + } + return + } + _, _ = ctx.Send("Welcome Channel Updated.") + return +} + +func getWelcomeChannel(ctx disgoman.Context, _ []string) { + var channelID string + row := utils.Database.QueryRow("SELECT welcome_channel FROM guilds where id=$1", ctx.Guild.ID) + err := row.Scan(&channelID) + if err != nil { + fmt.Println(err) + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error getting data from the database.", + Error: err, + } + return + } + if channelID == "" { + _, _ = ctx.Send("The welcomer is disabled.") + return + } + channel, err := ctx.Session.State.GuildChannel(ctx.Guild.ID, channelID) + if err != nil { + fmt.Println(err) + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "I got the channel ID but it does not appear to be a valid channel in this guild.", + Error: err, + } + return + } + _, _ = ctx.Send(fmt.Sprintf("The welcome channel is currently %s", channel.Mention())) +} + +func addGuildCommand(ctx disgoman.Context, args []string) { + var guildID string + row := utils.Database.QueryRow("SELECT id FROM guilds where id=$1", ctx.Guild.ID) + err := row.Scan(&guildID) + if err == nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "This guild is already in my database", + Error: err, + } + return + } + + _, err = utils.Database.Query("INSERT INTO guilds (id) VALUES ($1)", ctx.Guild.ID) + if err != nil { + fmt.Println(err) + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "There was a problem inserting this guild into the database", + Error: err, + } + return + } + _, _ = ctx.Send("This guild has been added.") + +} + +func puzzleChannel(ctx disgoman.Context, args []string) { + var idString string + if len(args) > 0 { + idString = args[0] + if strings.HasPrefix(idString, "<#") && strings.HasSuffix(idString, ">") { + idString = idString[2 : len(idString)-1] + } + } else { + idString = "" + } + fmt.Println(idString) + if idString == "" { + _, err := utils.Database.Exec("UPDATE guilds SET puzzle_channel='' WHERE id=$1;", ctx.Guild.ID) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error Updating Database", + Error: err, + } + return + } + _, _ = ctx.Send("Puzzle Channel Updated.") + return + } + channel, err := ctx.Session.State.Channel(idString) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Can't find that channel.", + Error: err, + } + return + } + if channel.GuildID != ctx.Guild.ID { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "The channel passed is not in this guild.", + Error: err, + } + return + } + _, err = utils.Database.Exec("UPDATE guilds SET puzzle_channel=$1 WHERE id=$2;", idString, ctx.Guild.ID) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error Updating Database", + Error: err, + } + return + } + _, _ = ctx.Send("Puzzle Channel Updated.") +} + +func getPuzzleChannel(ctx disgoman.Context, _ []string) { + var channelID string + row := utils.Database.QueryRow("SELECT puzzle_channel FROM guilds where id=$1", ctx.Guild.ID) + err := row.Scan(&channelID) + if err != nil { + fmt.Println(err) + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error getting data from the database.", + Error: err, + } + return + } + if channelID == "" { + _, _ = ctx.Send("The puzzle channel is not set.") + return + } + channel, err := ctx.Session.State.GuildChannel(ctx.Guild.ID, channelID) + if err != nil { + fmt.Println(err) + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "I got the channel ID but it does not appear to be a valid channel in this guild.", + Error: err, + } + return + } + _, _ = ctx.Send(fmt.Sprintf("The puzzle channel is currently %s", channel.Mention())) + return +} diff --git a/exts/init.go b/exts/init.go new file mode 100644 index 0000000..94d70ee --- /dev/null +++ b/exts/init.go @@ -0,0 +1,234 @@ +package exts + +import ( + "github.com/dustinpianalto/disgoman" +) + +func AddCommandHandlers(h *disgoman.CommandManager) { + // Arguments: + // name - command name - string + // desc - command description - string + // owneronly - only allow owners to run - bool + // hidden - hide command from non-owners - bool + // perms - permissisions required - anpan.Permission (int) + // type - command type, sets where the command is available + // run - function to run - func(anpan.Context, []string) / CommandRunFunc + _ = h.AddCommand(&disgoman.Command{ + Name: "ping", + Aliases: nil, + Description: "Check the bot's ping", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: pingCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "say", + Aliases: nil, + Description: "Repeat a message", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + SanitizeEveryone: true, + Invoke: sayCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "user", + Aliases: nil, + Description: "Get user info", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: userCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "git", + Aliases: nil, + Description: "Show my github link", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: gitCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "tag", + Aliases: nil, + Description: "Get a tag", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: tagCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "addtag", + Aliases: nil, + Description: "Add a tag", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + SanitizeEveryone: true, + Invoke: addTagCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "invite", + Aliases: nil, + Description: "Get the invite link for this bot or others", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: inviteCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "P", + Aliases: nil, + Description: "Interpret a P\" program and return the results", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: pCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "set-logging-channel", + Aliases: []string{"slc"}, + Description: "Set the channel logging messages will be sent to.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: loggingChannel, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "get-logging-channel", + Aliases: []string{"glc"}, + Description: "Gets the channel logging messages will be sent to.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: getLoggingChannel, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "set-welcome-channel", + Aliases: []string{"swc"}, + Description: "Set the channel welcome messages will be sent to.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: welcomeChannel, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "get-welcome-channel", + Aliases: []string{"gwc"}, + Description: "Gets the channel welcome messages will be sent to.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: getWelcomeChannel, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "kick", + Aliases: nil, + Description: "Kicks the given user with the given reason", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionKickMembers, + Invoke: kickUserCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "addGuild", + Aliases: nil, + Description: "Adds the current guild to the database", + OwnerOnly: true, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: addGuildCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "ban", + Aliases: []string{"ban-no-delete"}, + Description: "Bans the given user with the given reason", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionBanMembers, + Invoke: banUserCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "unban", + Aliases: nil, + Description: "Unbans the given user", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionBanMembers, + Invoke: unbanUserCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "remind", + Aliases: nil, + Description: "Remind me at a later time", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: addReminderCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "encode", + Aliases: []string{"e"}, + Description: "Encode 2 numbers", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: interleave, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "decode", + Aliases: []string{"d"}, + Description: "Decode 1 number into 2", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: deinterleave, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "set-puzzle-channel", + Aliases: []string{"spc"}, + Description: "Set the channel puzzle messages will be sent to.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: puzzleChannel, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "get-puzzle-channel", + Aliases: []string{"gpc"}, + Description: "Gets the channel puzzle messages will be sent to.", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: disgoman.PermissionManageServer, + Invoke: getPuzzleChannel, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "RPN", + Aliases: []string{"rpn"}, + Description: "Convert infix to rpn", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: generateRPNCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "ParseRPN", + Aliases: []string{"PRPN", "prpn"}, + Description: "Parse RPN string and return the result", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: parseRPNCommand, + }) + _ = h.AddCommand(&disgoman.Command{ + Name: "solve", + Aliases: []string{"math", "infix"}, + Description: "Solve infix equation and return the result", + OwnerOnly: false, + Hidden: false, + RequiredPermissions: 0, + Invoke: solveCommand, + }) +} diff --git a/exts/tags.go b/exts/tags.go new file mode 100644 index 0000000..6839a7e --- /dev/null +++ b/exts/tags.go @@ -0,0 +1,137 @@ +package exts + +import ( + "errors" + "fmt" + "log" + "strings" + + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/goff/utils" + "github.com/kballard/go-shellquote" +) + +func addTagCommand(ctx disgoman.Context, input []string) { + if len(input) >= 1 { + args, err := shellquote.Split(strings.Join(input, " ")) + if err != nil { + if strings.Contains(err.Error(), "Unterminated") { + args = strings.SplitN(strings.Join(args, " "), " ", 2) + } else { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "", + Error: err, + } + return + } + } + queryString := `SELECT tags.id, tags.tag, tags.content from tags + WHERE tags.guild_id = $1 + AND tags.tag = $2;` + row := utils.Database.QueryRow(queryString, ctx.Guild.ID, args[0]) + var dest string + if err := row.Scan(&dest); err != nil { + tag := args[0] + if tag == "" { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "That is not a valid tag name", + Error: err, + } + return + } + if len(args) <= 1 { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "I got a name but no value", + Error: err, + } + return + } + value := args[1] + if value == "" { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "You have to include a content for the tag", + Error: err, + } + return + } + queryString = `INSERT INTO tags (tag, content, creator, guild_id) VALUES ($1, $2, $3, $4);` + _, err := utils.Database.Exec(queryString, tag, value, ctx.Message.Author.ID, ctx.Guild.ID) + if err != nil { + ctx.Send(err.Error()) + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "", + Error: err, + } + return + } + ctx.Send(fmt.Sprintf("Tag %v added successfully.", tag)) + return + } else { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "That tag already exists", + Error: err, + } + return + } + } else { + ctx.Send("You need to tell me what tag you want to add...") + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "You need to tell me what tag you want to add...", + Error: errors.New("nothing to do"), + } + return + } +} + +func tagCommand(ctx disgoman.Context, args []string) { + if len(args) >= 1 { + tagString := strings.Join(args, " ") + queryString := `SELECT tags.id, tags.tag, tags.content from tags + WHERE tags.guild_id = $1;` + rows, err := utils.Database.Query(queryString, ctx.Guild.ID) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "", + Error: err, + } + return + } else { + for rows.Next() { + var ( + id int + tag string + content string + ) + if err := rows.Scan(&id, &tag, &content); err != nil { + log.Fatal(err) + } + if tagString == tag { + ctx.Send(content) + return + } + } + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: fmt.Sprintf("Tag %v not found", args[0]), + Error: err, + } + return + } + } else { + ctx.Send("I need a tag to fetch...") + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "I need a tag to fetch...", + Error: errors.New("nothing to do"), + } + return + } +} diff --git a/exts/tasks.go b/exts/tasks.go new file mode 100644 index 0000000..fa54327 --- /dev/null +++ b/exts/tasks.go @@ -0,0 +1,56 @@ +package exts + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/goff/utils" + "github.com/olebedev/when" + "github.com/olebedev/when/rules/common" + "github.com/olebedev/when/rules/en" +) + +func addReminderCommand(ctx disgoman.Context, args []string) { + w := when.New(nil) + w.Add(en.All...) + w.Add(common.All...) + + text := strings.Join(args, " ") + r, err := w.Parse(text, time.Now()) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error parsing time", + Error: err, + } + return + } + if r == nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "You need to include a valid time", + Error: errors.New("no time found"), + } + return + } + content := strings.Replace(text, r.Text+" ", "", 1) + query := "INSERT INTO tasks (type, content, guild_id, channel_id, user_id, trigger_time) " + + "VALUES ('Reminder', $1, $2, $3, $4, $5)" + _, err = utils.Database.Exec(query, content, ctx.Guild.ID, ctx.Channel.ID, ctx.User.ID, r.Time) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error adding task to database", + Error: err, + } + return + } + _ = ctx.Session.MessageReactionAdd(ctx.Channel.ID, ctx.Message.ID, "✅") + _, _ = ctx.Session.ChannelMessageSend( + ctx.Channel.ID, + fmt.Sprintf("I will remind you at %v, with `%v`", r.Time.Format(time.RFC1123), content), + ) +} diff --git a/exts/user_management.go b/exts/user_management.go new file mode 100644 index 0000000..08c5bc3 --- /dev/null +++ b/exts/user_management.go @@ -0,0 +1,256 @@ +package exts + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/goff/utils" +) + +func kickUserCommand(ctx disgoman.Context, args []string) { + var member *discordgo.Member + var err error + if len(ctx.Message.Mentions) > 0 { + member, err = ctx.Session.GuildMember(ctx.Guild.ID, ctx.Message.Mentions[0].ID) + } else if len(args) >= 1 { + idString := args[0] + if strings.HasPrefix(idString, "<@!") && strings.HasSuffix(idString, ">") { + idString = idString[3 : len(idString)-1] + } + member, err = ctx.Session.GuildMember(ctx.Guild.ID, idString) + } else { + err = errors.New("that is not a valid id") + } + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Couldn't get that member", + Error: err, + } + return + } + + if higher, _ := disgoman.HasHigherRole(ctx.Session, ctx.Guild.ID, ctx.Message.Author.ID, member.User.ID); !higher { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "You must have a higher role than the person you are trying to kick", + Error: errors.New("need higher role"), + } + return + } + + if higher, _ := disgoman.HasHigherRole(ctx.Session, ctx.Guild.ID, ctx.Session.State.User.ID, member.User.ID); !higher { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "I don't have a high enough role to kick that person", + Error: errors.New("need higher role"), + } + return + } + + var reason string + if len(args) > 1 { + reason = strings.Join(args[1:], " ") + } else { + reason = "No Reason Given" + } + auditReason := fmt.Sprintf("%v#%v: %v", ctx.User.Username, ctx.User.Discriminator, reason) + err = ctx.Session.GuildMemberDeleteWithReason(ctx.Guild.ID, member.User.ID, auditReason) + + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: fmt.Sprintf("Something went wrong kicking %v", member.User.Username), + Error: err, + } + return + } + + event := &utils.LogEvent{ + Embed: discordgo.MessageEmbed{ + Title: "User Kicked", + Description: fmt.Sprintf( + "User %v#%v was kicked by %v.\nReason: %v", + member.User.Username, + member.User.Discriminator, + ctx.Message.Author.Username, + reason), + Timestamp: time.Now().Format("2006-01-02 15:04:05"), + Color: 0xff8c00, + }, + GuildID: ctx.Guild.ID, + Session: ctx.Session, + } + utils.LoggingChannel <- event + _, _ = ctx.Send(fmt.Sprintf("User %v#%v has been kicked.", member.User.Username, member.User.Discriminator)) +} + +func banUserCommand(ctx disgoman.Context, args []string) { + var user *discordgo.User + var err error + if len(ctx.Message.Mentions) > 0 { + user, err = ctx.Session.User(ctx.Message.Mentions[0].ID) + } else if len(args) >= 1 { + idString := args[0] + if strings.HasPrefix(idString, "<@!") && strings.HasSuffix(idString, ">") { + idString = idString[3 : len(idString)-1] + } + user, err = ctx.Session.User(idString) + } else { + err = errors.New("that is not a valid id") + } + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Couldn't get that user", + Error: err, + } + return + } + + if higher, err := disgoman.HasHigherRole(ctx.Session, ctx.Guild.ID, ctx.Message.Author.ID, user.ID); err != nil { + if err.Error() == "can't find caller member" { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Who are you?", + Error: err, + } + return + } + } else if !higher { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "You must have a higher role than the person you are trying to ban", + Error: errors.New("need higher role"), + } + return + } + + if higher, err := disgoman.HasHigherRole(ctx.Session, ctx.Guild.ID, ctx.Session.State.User.ID, user.ID); err != nil { + if err.Error() == "can't find caller member" { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Who am I?", + Error: err, + } + return + } + } else if !higher { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "I don't have a high enough role to ban that person", + Error: errors.New("need higher role"), + } + return + } + + var reason string + if len(args) > 1 { + reason = strings.Join(args[1:], " ") + } else { + reason = "No Reason Given" + } + auditReason := fmt.Sprintf("%v#%v: %v", ctx.User.Username, ctx.User.Discriminator, reason) + days := 7 + if ctx.Invoked == "ban-no-delete" { + days = 0 + } + err = ctx.Session.GuildBanCreateWithReason(ctx.Guild.ID, user.ID, auditReason, days) + + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: fmt.Sprintf("Something went wrong banning %v", user.Username), + Error: err, + } + return + } + + event := &utils.LogEvent{ + Embed: discordgo.MessageEmbed{ + Title: "User Banned", + Description: fmt.Sprintf( + "User %v#%v was banned by %v.\nReason: %v", + user.Username, + user.Discriminator, + ctx.Message.Author.Username, + reason), + Timestamp: time.Now().Format("2006-01-02 15:04:05"), + Color: 0xff0000, + }, + GuildID: ctx.Guild.ID, + Session: ctx.Session, + } + utils.LoggingChannel <- event + _, _ = ctx.Send(fmt.Sprintf("User %v#%v has been banned.", user.Username, user.Discriminator)) +} + +func unbanUserCommand(ctx disgoman.Context, args []string) { + var user *discordgo.User + var err error + if len(ctx.Message.Mentions) > 0 { + user, err = ctx.Session.User(ctx.Message.Mentions[0].ID) + } else if len(args) >= 1 { + idString := args[0] + if strings.HasPrefix(idString, "<@!") && strings.HasSuffix(idString, ">") { + idString = idString[3 : len(idString)-1] + } + user, err = ctx.Session.User(idString) + } else { + err = errors.New("that is not a valid id") + } + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Couldn't get that user", + Error: err, + } + return + } + + bans, err := ctx.Session.GuildBans(ctx.Guild.ID) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Error processing current bans", + Error: err, + } + return + } + for _, ban := range bans { + if ban.User.ID == user.ID { + err = ctx.Session.GuildBanDelete(ctx.Guild.ID, user.ID) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: fmt.Sprintf("Something went wrong unbanning %v", user.Username), + Error: err, + } + return + } + event := &utils.LogEvent{ + Embed: discordgo.MessageEmbed{ + Title: "User Banned", + Description: fmt.Sprintf( + "User %v#%v was unbanned by %v.\nOrignal Ban Reason: %v", + user.Username, + user.Discriminator, + ctx.Message.Author.Username, + ban.Reason), + Timestamp: time.Now().Format("2006-01-02 15:04:05"), + Color: 0x00ff00, + }, + GuildID: ctx.Guild.ID, + Session: ctx.Session, + } + utils.LoggingChannel <- event + _, _ = ctx.Send(fmt.Sprintf("User %v#%v has been unbanned.", user.Username, user.Discriminator)) + return + } + } + _, _ = ctx.Send(fmt.Sprintf("%v#%v is not banned in this guild.", user.Username, user.Discriminator)) +} diff --git a/exts/utils.go b/exts/utils.go new file mode 100644 index 0000000..4a0e3f6 --- /dev/null +++ b/exts/utils.go @@ -0,0 +1,169 @@ +package exts + +import ( + "fmt" + "sort" + "strconv" + "strings" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/goff/utils" +) + +func pingCommand(ctx disgoman.Context, _ []string) { + timeBefore := time.Now() + msg, _ := ctx.Send("Pong!") + took := time.Now().Sub(timeBefore) + _, err := ctx.Session.ChannelMessageEdit(ctx.Message.ChannelID, msg.ID, fmt.Sprintf("Pong!\nPing Took **%s**", took.String())) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Ping Failed", + Error: err, + } + } +} + +func inviteCommand(ctx disgoman.Context, args []string) { + var ids []string + if len(args) == 0 { + ids = []string{ctx.Session.State.User.ID} + } else { + for _, id := range args { + ids = append(ids, id) + } + } + for _, id := range ids { + url := fmt.Sprintf("", id) + _, err := ctx.Send(url) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Couldn't send the invite link.", + Error: err, + } + } + } +} + +func gitCommand(ctx disgoman.Context, _ []string) { + embed := &discordgo.MessageEmbed{ + Title: "Hi there, My code is on Github", + Color: 0, + URL: "https://github.com/dustinpianalto/Goff", + } + _, err := ctx.Session.ChannelMessageSendEmbed(ctx.Channel.ID, embed) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Git failed", + Error: err, + } + } +} + +func sayCommand(ctx disgoman.Context, args []string) { + resp := strings.Join(args, " ") + resp = strings.ReplaceAll(resp, "@everyone", "@\ufff0everyone") + resp = strings.ReplaceAll(resp, "@here", "@\ufff0here") + _, err := ctx.Session.ChannelMessageSend(ctx.Message.ChannelID, resp) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Say Failed", + Error: err, + } + } +} + +func userCommand(ctx disgoman.Context, args []string) { + var member *discordgo.Member + if len(args) == 0 { + member, _ = ctx.Session.GuildMember(ctx.Guild.ID, ctx.Message.Author.ID) + } else { + var err error + if len(ctx.Message.Mentions) > 0 { + member, err = ctx.Session.GuildMember(ctx.Guild.ID, ctx.Message.Mentions[0].ID) + } else { + member, err = ctx.Session.GuildMember(ctx.Guild.ID, args[0]) + } + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Couldn't get that member", + Error: err, + } + return + } + } + thumb := &discordgo.MessageEmbedThumbnail{ + URL: member.User.AvatarURL(""), + } + + var botString string + if member.User.Bot { + botString = "BOT" + } else { + botString = "" + } + + var roles []*discordgo.Role + for _, roleID := range member.Roles { + role, _ := ctx.Session.State.Role(ctx.Guild.ID, roleID) + roles = append(roles, role) + } + sort.Slice(roles, func(i, j int) bool { return roles[i].Position > roles[j].Position }) + var roleMentions []string + for _, role := range roles { + roleMentions = append(roleMentions, role.Mention()) + } + var rolesString string + if len(roleMentions) > 0 { + rolesString = strings.Join(roleMentions, " ") + } else { + rolesString = "None" + } + + rolesField := &discordgo.MessageEmbedField{ + Name: "Roles:", + Value: rolesString, + Inline: false, + } + + guildJoinTime, _ := member.JoinedAt.Parse() + guildJoinedField := &discordgo.MessageEmbedField{ + Name: "Joined Guild:", + Value: utils.ParseDateString(guildJoinTime), + Inline: false, + } + + int64ID, _ := strconv.ParseInt(member.User.ID, 10, 64) + s := utils.ParseSnowflake(int64ID) + discordJoinedField := &discordgo.MessageEmbedField{ + Name: "Joined Discord:", + Value: utils.ParseDateString(s.CreationTime), + Inline: false, + } + + embed := &discordgo.MessageEmbed{ + Title: fmt.Sprintf("%v#%v %v", member.User.Username, member.User.Discriminator, botString), + Description: fmt.Sprintf("**%v** (%v)", member.Nick, member.User.ID), + Color: ctx.Session.State.UserColor(member.User.ID, ctx.Channel.ID), + Thumbnail: thumb, + Fields: []*discordgo.MessageEmbedField{ + guildJoinedField, + discordJoinedField, + rolesField, + }, + } + _, err := ctx.Session.ChannelMessageSendEmbed(ctx.Channel.ID, embed) + if err != nil { + ctx.ErrorChannel <- disgoman.CommandError{ + Context: ctx, + Message: "Couldn't send the user embed", + Error: err, + } + } +} diff --git a/go.mod b/go.mod index 29d07d6..92010e8 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ -module djpianalto.com/goff +module github.com/dustinpianalto/goff go 1.14 require ( github.com/bwmarrin/discordgo v0.20.3-0.20200525154655-ca64123b05de github.com/dustinpianalto/disgoman v0.0.10 + github.com/dustinpianalto/rpnparse v1.0.1 github.com/emersion/go-imap v1.0.5 github.com/emersion/go-message v0.12.0 - github.com/dustinpianalto/rpnparse v1.0.1 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/lib/pq v1.3.0 github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254 diff --git a/goff.go b/goff.go new file mode 100644 index 0000000..08a4124 --- /dev/null +++ b/goff.go @@ -0,0 +1,136 @@ +package main + +import ( + "fmt" + "log" + + "github.com/dustinpianalto/disgoman" + "github.com/dustinpianalto/goff/events" + "github.com/dustinpianalto/goff/exts" + "github.com/dustinpianalto/goff/utils" + + //"github.com/MikeModder/anpan" + "os" + "os/signal" + "syscall" + + "github.com/bwmarrin/discordgo" +) + +var ( + Token string +) + +//func init() { +// flag.StringVar(&Token, "t", "", "Bot Token") +// flag.Parse() +//} + +func main() { + Token = os.Getenv("DISCORDGO_TOKEN") + dg, err := discordgo.New("Bot " + Token) + if err != nil { + fmt.Println("There was an error when creating the Discord Session, ", err) + return + } + dg.State.MaxMessageCount = 100 + + utils.ConnectDatabase(os.Getenv("DATABASE_URL")) + utils.InitializeDatabase() + //utils.LoadTestData() + + //prefixes := []string{ + // "Go.", + //} + owners := []string{ + "351794468870946827", + } + + // Arguments are: + // prefixes - []string + // owner ids - []string + // ignore bots - bool + // check perms - bool + handler := disgoman.CommandManager{ + Prefixes: getPrefixes, + Owners: owners, + StatusManager: disgoman.GetDefaultStatusManager(), + ErrorChannel: make(chan disgoman.CommandError, 10), + Commands: make(map[string]*disgoman.Command), + IgnoreBots: true, + CheckPermissions: false, + } + + // Add Command Handlers + exts.AddCommandHandlers(&handler) + + //if _, ok := handler.Commands["help"]; !ok { + // handler.AddDefaultHelpCommand() + //} + + dg.AddHandler(handler.OnMessage) + dg.AddHandler(handler.StatusManager.OnReady) + dg.AddHandler(events.OnMessageUpdate) + dg.AddHandler(events.OnMessageDelete) + dg.AddHandler(events.OnGuildMemberAddLogging) + dg.AddHandler(events.OnGuildMemberRemoveLogging) + + err = dg.Open() + if err != nil { + fmt.Println("There was an error opening the connection, ", err) + return + } + + // Start the Error handler in a goroutine + go ErrorHandler(handler.ErrorChannel) + + // Start the Logging handler in a goroutine + go utils.LoggingHandler(utils.LoggingChannel) + + // Start the task handler in a goroutine + go utils.ProcessTasks(dg, 1) + + go utils.RecieveEmail(dg) + + fmt.Println("The Bot is now running.") + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) + <-sc + + fmt.Println("Shutting Down...") + err = dg.Close() + if err != nil { + fmt.Println(err) + } +} + +func getPrefixes(guildID string) []string { + queryString := "Select prefix from prefixes p, x_guilds_prefixes xgp where xgp.guild_id = $1 and xgp.prefix_id = p.id" + rows, err := utils.Database.Query(queryString, guildID) + if err != nil { + log.Println(err) + return []string{"Go.", "go."} + } + var prefixes []string + for rows.Next() { + var prefix string + err = rows.Scan(&prefix) + if err != nil { + log.Println(err) + return []string{"Go.", "go."} + } + prefixes = append(prefixes, prefix) + } + return prefixes +} + +func ErrorHandler(ErrorChan chan disgoman.CommandError) { + for ce := range ErrorChan { + msg := ce.Message + if msg == "" { + msg = ce.Error.Error() + } + _, _ = ce.Context.Send(msg) + fmt.Println(ce.Error) + } +} diff --git a/utils/database.go b/utils/database.go new file mode 100644 index 0000000..88d0aed --- /dev/null +++ b/utils/database.go @@ -0,0 +1,154 @@ +package utils + +import ( + "database/sql" + "fmt" + "log" + + _ "github.com/lib/pq" +) + +var ( + Database *sql.DB +) + +func ConnectDatabase(dbConnString string) { + db, err := sql.Open("postgres", dbConnString) + if err != nil { + panic(fmt.Sprintf("Can't connect to the database. %v", err)) + } else { + fmt.Println("Database Connected.") + } + Database = db +} + +func InitializeDatabase() { + _, err := Database.Query("CREATE TABLE IF NOT EXISTS users(" + + "id varchar(30) primary key," + + "banned bool not null default false," + + "logging bool not null default true," + + "steam_id varchar(30) NOT NULL DEFAULT ''," + + "is_active bool not null default true," + + "is_staff bool not null default false," + + "is_admin bool not null default false" + + ")") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("CREATE TABLE IF NOT EXISTS guilds(" + + "id varchar(30) primary key," + + "welcome_message varchar(1000) NOT NULL DEFAULT ''," + + "goodbye_message varchar(1000) NOT NULL DEFAULT ''," + + "logging_channel varchar(30) NOT NULL DEFAULT ''," + + "welcome_channel varchar(30) NOT NULL DEFAULT ''" + + ")") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("CREATE TABLE IF NOT EXISTS prefixes(" + + "id serial primary key," + + "prefix varchar(10) not null unique default 'Go.'" + + ")") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("CREATE TABLE IF NOT EXISTS tags(" + + "id serial primary key," + + "tag varchar(100) not null unique," + + "content varchar(1000) not null," + + "creator varchar(30) not null references users(id)," + + "creation_time timestamp not null default NOW()," + + "guild_id varchar(30) not null references guilds(id)" + + ")") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("CREATE TABLE IF NOT EXISTS x_users_guilds(" + + "guild_id varchar(30) not null references guilds(id)," + + "user_id varchar(30) not null references users(id)" + + ")") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("CREATE TABLE IF NOT EXISTS x_guilds_prefixes(" + + "guild_id varchar(30) not null references guilds(id)," + + "prefix_id int not null references prefixes(id)" + + ")") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("CREATE TABLE IF NOT EXISTS tasks(" + + "id serial primary key," + + "type varchar(10) not null," + + "content text not null," + + "guild_id varchar(30) not null references guilds(id)," + + "channel_id varchar(30) not null," + + "user_id varchar(30) not null," + + "creation_time timestamp not null default NOW()," + + "trigger_time timestamp not null," + + "completed bool not null default false," + + "processing bool default false)") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query(`CREATE TABLE IF NOT EXISTS postfixes( + id serial primary key, + name varchar(100) not null, + time timestamp not null default NOW())`) + if err != nil { + log.Println(err) + } + _, err = Database.Exec(`CREATE TABLE IF NOT EXISTS puzzles( + id serial primary key, + text text not null, + time timestamp not null + )`) + if err != nil { + log.Println(err) + } + _, err = Database.Exec(`CREATE TABLE IF NOT EXISTS x_guilds_puzzles( + id serial primary key, + guild_id varchar(30) not null references guilds(id), + puzzle_id int not null references puzzles(id), + message_id varchar(30) not null + )`) + RunPostfixes() +} + +func LoadTestData() { + _, err := Database.Query("INSERT INTO users (id, banned, logging, steam_id, is_active, is_staff, is_admin) values " + + "('351794468870946827', false, true, '76561198024193239', true, true, true)," + + "('692908139506434065', false, true, '', true, false, false)," + + "('396588996706304010', false, true, '', true, true, false)") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("INSERT INTO guilds (id, welcome_message, goodbye_message) VALUES " + + "('265828729970753537', 'Hey there is someone new here.', 'Well fine then... Just leave without saying goodbye')") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("INSERT INTO prefixes (prefix) VALUES ('Godev.'), ('godev.'), ('godev,')") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("INSERT INTO x_users_guilds (guild_id, user_id) VALUES " + + "('265828729970753537', '351794468870946827')," + + "('265828729970753537', '692908139506434065')," + + "('265828729970753537', '396588996706304010')") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("INSERT INTO x_guilds_prefixes (guild_id, prefix_id) VALUES " + + "('265828729970753537', 1)," + + "('265828729970753537', 2)," + + "('265828729970753537', 3)") + if err != nil { + fmt.Println(err) + } + _, err = Database.Query("INSERT INTO tags (tag, content, creator, guild_id) VALUES " + + "('test', 'This is a test of the tag system', '351794468870946827', '265828729970753537')") + if err != nil { + fmt.Println(err) + } +} diff --git a/utils/date_strings.go b/utils/date_strings.go new file mode 100644 index 0000000..510288d --- /dev/null +++ b/utils/date_strings.go @@ -0,0 +1,64 @@ +package utils + +import ( + "fmt" + "time" +) + +func ParseDateString(inTime time.Time) string { + d := time.Now().Sub(inTime) + s := int64(d.Seconds()) + days := s / 86400 + s = s - (days * 86400) + hours := s / 3600 + s = s - (hours * 3600) + minutes := s / 60 + seconds := s - (minutes * 60) + dateString := "" + if days != 0 { + dateString += fmt.Sprintf("%v days ", days) + } + if hours != 0 { + dateString += fmt.Sprintf("%v hours ", hours) + } + if minutes != 0 { + dateString += fmt.Sprintf("%v minutes ", minutes) + } + if seconds != 0 { + dateString += fmt.Sprintf("%v seconds ", seconds) + } + if dateString != "" { + dateString += " ago." + } else { + dateString = "Now" + } + stamp := inTime.Format("2006-01-02 15:04:05") + return fmt.Sprintf("%v\n%v", dateString, stamp) +} + +func ParseDurationString(inDur time.Duration) string { + s := int64(inDur.Seconds()) + days := s / 86400 + s = s - (days * 86400) + hours := s / 3600 + s = s - (hours * 3600) + minutes := s / 60 + seconds := s - (minutes * 60) + durString := "" + if days != 0 { + durString += fmt.Sprintf("%v days ", days) + } + if hours != 0 { + durString += fmt.Sprintf("%v hours ", hours) + } + if minutes != 0 { + durString += fmt.Sprintf("%v minutes ", minutes) + } + if seconds != 0 { + durString += fmt.Sprintf("%v seconds ", seconds) + } + if durString == "" { + durString = "0 seconds" + } + return fmt.Sprintf("%v", durString) +} diff --git a/utils/email.go b/utils/email.go new file mode 100644 index 0000000..305ff6a --- /dev/null +++ b/utils/email.go @@ -0,0 +1,132 @@ +package utils + +import ( + "io" + "log" + "os" + "strings" + "sync" + "time" + + "github.com/bwmarrin/discordgo" + imap "github.com/emersion/go-imap" + "github.com/emersion/go-imap/client" + "github.com/emersion/go-message/mail" +) + +const () + +var ( + emailUsername = os.Getenv("GOFF_EMAIL_USERNAME") + emailPassword = os.Getenv("GOFF_EMAIL_PASSWORD") + puzzleAddress = mail.Address{ + Name: "Daily Coding Problem", + Address: "founders@dailycodingproblem.com", + } +) + +var EmailClient client.Client + +func RecieveEmail(dg *discordgo.Session) { + for { + log.Println("Connecting to Email server.") + + EmailClient, err := client.DialTLS("mail.djpianalto.com:993", nil) + if err != nil { + log.Println(err) + return + } + if err = EmailClient.Login(emailUsername, emailPassword); err != nil { + log.Println(err) + return + } + log.Println("Connected to Email server.") + + mbox, err := EmailClient.Select("INBOX", false) + if err != nil { + log.Println(err) + return + } + + if mbox.Messages == 0 { + log.Println("No Messages in Mailbox") + } + + criteria := imap.NewSearchCriteria() + criteria.WithoutFlags = []string{"\\Seen"} + uids, err := EmailClient.Search(criteria) + if err != nil { + log.Println(err) + } + if len(uids) > 0 { + seqset := new(imap.SeqSet) + seqset.AddNum(uids...) + section := &imap.BodySectionName{} + items := []imap.FetchItem{section.FetchItem()} + messages := make(chan *imap.Message, 10) + go func() { + if err = EmailClient.Fetch(seqset, items, messages); err != nil { + log.Println(err) + return + } + }() + + var wg sync.WaitGroup + + for msg := range messages { + if msg == nil { + log.Println("No New Messages") + continue + } + r := msg.GetBody(section) + if r == nil { + log.Println("Server didn't send a message body") + continue + } + wg.Add(1) + go processEmail(r, dg, &wg) + } + wg.Wait() + } + + EmailClient.Logout() + time.Sleep(300 * time.Second) + } +} + +func processEmail(r io.Reader, dg *discordgo.Session, wg *sync.WaitGroup) { + defer wg.Done() + mr, err := mail.CreateReader(r) + if err != nil { + log.Println(err) + return + } + header := mr.Header + from, err := header.AddressList("From") + if err != nil { + log.Println(err) + return + } + subject, err := header.Subject() + if err != nil { + log.Println(err) + return + } + log.Println(from) + log.Println(subject) + if addressIn(from, puzzleAddress) && + strings.Contains(subject, "Daily Coding Problem:") { + log.Println("Processing Puzzle") + ProcessPuzzleEmail(mr, dg) + } + +} + +func addressIn(s []*mail.Address, a mail.Address) bool { + for _, item := range s { + if item.String() == a.String() { + return true + } + } + return false +} diff --git a/utils/logging.go b/utils/logging.go new file mode 100644 index 0000000..c3415c4 --- /dev/null +++ b/utils/logging.go @@ -0,0 +1,34 @@ +package utils + +import ( + "fmt" + "github.com/bwmarrin/discordgo" +) + +var LoggingChannel = make(chan *LogEvent, 10) + +type LogEvent struct { + // Embed with log message + Embed discordgo.MessageEmbed + // Guild to log event in + GuildID string + // Discordgo Session. Needed for sending messages + Session *discordgo.Session +} + +func LoggingHandler(lc chan *LogEvent) { + for event := range lc { + var channelID string + row := Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", event.GuildID) + err := row.Scan(&channelID) + if err != nil { + fmt.Println(err) + return + } + if channelID == "" { + return + } + + _, _ = event.Session.ChannelMessageSendEmbed(channelID, &event.Embed) + } +} diff --git a/utils/postfixes.go b/utils/postfixes.go new file mode 100644 index 0000000..12d297a --- /dev/null +++ b/utils/postfixes.go @@ -0,0 +1,77 @@ +package utils + +import "log" + +type postfix struct { + Name string + Invoke func(bool) error +} + +var postfixes = []postfix{ + postfix{ + Name: "1_Update_Guild_for_Puzzle", + Invoke: updateGuildForPuzzle, + }, + postfix{ + Name: "1_Update_X_Guild_Prefixes_to_add_ID", + Invoke: updateXGuildPrefixesToAddID, + }, +} + +func RunPostfixes() { + for _, postfix := range postfixes { + queryString := "SELECT * from postfixes where name = $1" + rows, err := Database.Query(queryString, postfix.Name) + if err != nil { + log.Println(err) + continue + } + if rows.Next() { + continue + } else { + err := postfix.Invoke(false) + if err != nil { + continue + } + _, err = Database.Exec("INSERT INTO postfixes (name) VALUES ($1)", postfix.Name) + if err != nil { + log.Println(err) + continue + } + } + } +} + +func updateGuildForPuzzle(revert bool) error { + var queryString string + if !revert { + queryString = `ALTER TABLE guilds + ADD COLUMN puzzle_channel varchar(30) not null default ''` + } else { + queryString = `ALTER TABLE guilds + DROP COLUMN puzzleChat` + } + _, err := Database.Exec(queryString) + if err != nil { + log.Println(err) + return err + } + return nil +} + +func updateXGuildPrefixesToAddID(revert bool) error { + var queryString string + if !revert { + queryString = `ALTER TABLE x_guilds_prefixes + ADD COLUMN id serial primary key` + } else { + queryString = `ALTER TABLE x_guilds_prefixes + DROP COLUMN id` + } + _, err := Database.Exec(queryString) + if err != nil { + log.Println(err) + return err + } + return nil +} diff --git a/utils/puzzles.go b/utils/puzzles.go new file mode 100644 index 0000000..6edd5ad --- /dev/null +++ b/utils/puzzles.go @@ -0,0 +1,93 @@ +package utils + +import ( + "io" + "io/ioutil" + "log" + "strings" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/emersion/go-message/mail" +) + +func ProcessPuzzleEmail(mr *mail.Reader, dg *discordgo.Session) { + var body []byte + for { + p, err := mr.NextPart() + if err == io.EOF { + break + } else if err != nil { + log.Println(err) + break + } + + switch h := p.Header.(type) { + case *mail.InlineHeader: + // This is the message's text (can be plain-text or HTML) + if t, _, _ := h.ContentType(); t == "text/plain" { + body, _ = ioutil.ReadAll(p.Body) + break + } + } + } + if len(body) > 0 { + s := string(body) + puzzle := strings.Split(s, "----------")[0] + date, err := mr.Header.Date() + if err != nil { + log.Println(err) + return + } + e := discordgo.MessageEmbed{ + Title: "Daily Coding Problem", + URL: "https://dailycodingproblem.com/", + Description: "```" + puzzle + "```", + Timestamp: date.Format(time.RFC3339), + Footer: &discordgo.MessageEmbedFooter{ + Text: "Daily Coding Problem", + }, + } + var guilds []Guild + queryString := `SELECT id, puzzle_channel from guilds` + rows, err := Database.Query(queryString) + if err != nil { + log.Println(err) + } + for rows.Next() { + var guild Guild + err := rows.Scan(&guild.ID, &guild.PuzzleChannel) + if err != nil { + log.Println(err) + continue + } + guilds = append(guilds, guild) + } + var puzzleID int64 + queryString = "INSERT INTO puzzles (text, time) VALUES ($1, $2) RETURNING id" + err = Database.QueryRow(queryString, puzzle, date).Scan(&puzzleID) + if err != nil { + log.Println(err) + return + } + for _, g := range guilds { + if g.PuzzleChannel == "" { + continue + } + msg := discordgo.MessageSend{ + Embed: &e, + } + m, err := dg.ChannelMessageSendComplex(g.PuzzleChannel, &msg) + if err != nil { + log.Println(err) + } + queryString = "INSERT INTO x_guilds_puzzles (guild_id, puzzle_id, message_id) VALUES ($1, $2, $3)" + _, err = Database.Exec(queryString, g.ID, puzzleID, m.ID) + if err != nil { + log.Println(err) + continue + } + } + } + +} diff --git a/utils/rpn.go b/utils/rpn.go new file mode 100644 index 0000000..db39f5d --- /dev/null +++ b/utils/rpn.go @@ -0,0 +1,156 @@ +package utils + +import ( + "fmt" + "strconv" + "strings" +) + +type Operator struct { + Token string + Precedence int + Association string +} + +func (o Operator) HasHigherPrecedence(t Operator) bool { + return o.Precedence < t.Precedence // lower number is higher precedence +} + +func (o Operator) HasEqualPrecedence(t Operator) bool { + return o.Precedence == t.Precedence +} + +func (o Operator) IsLeftAssociative() bool { + return o.Association == "left" +} + +var operators = map[string]Operator{ + "+": Operator{ + Token: "+", + Precedence: 4, + Association: "left", + }, + "-": Operator{ + Token: "-", + Precedence: 4, + Association: "left", + }, + "*": Operator{ + Token: "*", + Precedence: 3, + Association: "left", + }, + "/": Operator{ + Token: "/", + Precedence: 3, + Association: "left", + }, + "%": Operator{ + Token: "%", + Precedence: 3, + Association: "left", + }, + "(": Operator{ + Token: "(", + Precedence: 1, + Association: "left", + }, + ")": Operator{ + Token: ")", + Precedence: 1, + Association: "left", + }, +} + +type Stack []Operator + +func (s *Stack) IsEmpty() bool { + return len(*s) == 0 +} + +func (s *Stack) Push(op Operator) { + *s = append(*s, op) +} + +func (s *Stack) Pop() (Operator, bool) { + if s.IsEmpty() { + return Operator{}, false + } + index := len(*s) - 1 + element := (*s)[index] + *s = (*s)[:index] + return element, true +} + +func (s *Stack) Top() Operator { + if s.IsEmpty() { + return Operator{} + } + return (*s)[len(*s)-1] +} + +func GenerateRPN(tokens []string) (string, error) { + output := "" + s := Stack{} + for _, token := range tokens { + err := processToken(token, &s, &output) + if err != nil { + return "", err + } + } + for !s.IsEmpty() { + ele, _ := s.Pop() + output += " " + ele.Token + } + + return strings.TrimSpace(output), nil +} + +func processToken(t string, s *Stack, o *string) error { + if _, err := strconv.Atoi(t); err == nil { + *o += " " + t + return nil + } else if op, ok := operators[t]; ok { + if op.Token == "(" { + s.Push(op) + } else if op.Token == ")" { + if s.IsEmpty() { + return fmt.Errorf("mismatched parentheses") + } + for s.Top().Token != "(" { + if ele, ok := s.Pop(); ok { + *o += " " + ele.Token + } else { + return fmt.Errorf("mismatched parentheses") + } + if s.IsEmpty() { + break + } + } + s.Pop() // Pop and discard the ( + } else if !s.IsEmpty() { + for { + if (s.Top().HasHigherPrecedence(op) || + (s.Top().HasEqualPrecedence(op) && + op.IsLeftAssociative())) && + s.Top().Token != "(" { + if ele, ok := s.Pop(); ok { + *o += " " + ele.Token + if s.IsEmpty() { + break + } + continue + } else { + break + } + } + break + } + s.Push(op) + } else { + s.Push(op) + } + return nil + } + return fmt.Errorf("invalid character %s", t) +} diff --git a/utils/rpnParser.go b/utils/rpnParser.go new file mode 100644 index 0000000..7f9dff9 --- /dev/null +++ b/utils/rpnParser.go @@ -0,0 +1,95 @@ +package utils + +import ( + "errors" + "fmt" + "math" + "strconv" +) + +type FStack []float64 + +func (s *FStack) IsEmpty() bool { + return len(*s) == 0 +} + +func (s *FStack) Push(op float64) { + *s = append(*s, op) +} + +func (s *FStack) Pop() (float64, bool) { + if s.IsEmpty() { + return 0, false + } + index := len(*s) - 1 + element := (*s)[index] + *s = (*s)[:index] + return element, true +} + +func (s *FStack) PopTwo() (float64, float64, bool) { + if s.IsEmpty() || len(*s) < 2 { + return 0, 0, false + } + index := len(*s) - 1 + b := (*s)[index] + a := (*s)[index-1] + *s = (*s)[:index-1] + return a, b, true + +} + +func (s *FStack) Top() float64 { + if s.IsEmpty() { + return 0 + } + return (*s)[len(*s)-1] +} + +func ParseRPN(args []string) (float64, error) { + s := FStack{} + for _, token := range args { + switch token { + case "+": + if a, b, ok := s.PopTwo(); ok { + s.Push(a + b) + } else { + return 0, fmt.Errorf("not enough operands on stack for +: %v", s) + } + case "-": + if a, b, ok := s.PopTwo(); ok { + s.Push(a - b) + } else { + return 0, fmt.Errorf("not enough operands on stack for -: %v", s) + } + case "*": + if a, b, ok := s.PopTwo(); ok { + s.Push(a * b) + } else { + return 0, fmt.Errorf("not enough operands on stack for *: %v", s) + } + case "/": + if a, b, ok := s.PopTwo(); ok { + s.Push(a / b) + } else { + return 0, fmt.Errorf("not enough operands on stack for /: %v", s) + } + case "%": + if a, b, ok := s.PopTwo(); ok { + s.Push(math.Mod(a, b)) + } else { + return 0, fmt.Errorf("not enough operands on stack for %: %v", s) + } + default: + f, err := strconv.ParseFloat(token, 64) + if err != nil { + return 0, err + } + s.Push(f) + } + } + if res, ok := s.Pop(); ok { + return res, nil + } + return 0, errors.New("no result") +} diff --git a/utils/snowflake.go b/utils/snowflake.go new file mode 100644 index 0000000..f26978c --- /dev/null +++ b/utils/snowflake.go @@ -0,0 +1,32 @@ +package utils + +import "time" + +type Snowflake struct { + CreationTime time.Time + WorkerID int8 + ProcessID int8 + Increment int16 +} + +func ParseSnowflake(s int64) Snowflake { + const ( + DISCORD_EPOCH = 1420070400000 + TIME_BITS_LOC = 22 + WORKER_ID_LOC = 17 + WORKER_ID_MASK = 0x3E0000 + PROCESS_ID_LOC = 12 + PROCESS_ID_MASK = 0x1F000 + INCREMENT_MASK = 0xFFF + ) + creationTime := time.Unix(((s>>TIME_BITS_LOC)+DISCORD_EPOCH)/1000.0, 0) + workerID := (s & WORKER_ID_MASK) >> WORKER_ID_LOC + processID := (s & PROCESS_ID_MASK) >> PROCESS_ID_LOC + increment := s & INCREMENT_MASK + return Snowflake{ + CreationTime: creationTime, + WorkerID: int8(workerID), + ProcessID: int8(processID), + Increment: int16(increment), + } +} diff --git a/utils/tasks.go b/utils/tasks.go new file mode 100644 index 0000000..3af8494 --- /dev/null +++ b/utils/tasks.go @@ -0,0 +1,132 @@ +package utils + +import ( + "fmt" + "github.com/bwmarrin/discordgo" + "log" + "time" +) + +type Task struct { + ID int64 + Type string + Content string + GuildID string + ChannelID string + UserID string + CreationTime time.Time + TriggerTime time.Time +} + +func processTask(task *Task, s *discordgo.Session) { + query := "SELECT completed, processing from tasks where id = $1" + res, err := Database.Query(query, task.ID) + if err != nil { + log.Println(err) + return + } + var completed bool + var processing bool + res.Next() + err = res.Scan(&completed, &processing) + if err != nil { + log.Println(err) + return + } + if completed || processing { + return + } + closeQuery := "Update tasks set completed = true where id = $1" + processQuery := "UPDATE tasks SET processing = true WHERE id = $1" + defer Database.Exec(closeQuery, task.ID) + _, err = Database.Exec(processQuery, task.ID) + if err != nil { + log.Println(err) + return + } + log.Println(fmt.Sprintf("Processing task %v", task.ID)) + guild, err := s.Guild(task.GuildID) + if err != nil { + log.Print(fmt.Sprintf("Can't find guild with ID %v. Canceling task %v.", task.GuildID, task.ID)) + return + } + channel, err := s.Channel(task.ChannelID) + if err != nil { + log.Print(fmt.Sprintf("Can't find channel with ID %v. Canceling task %v.", task.ChannelID, task.ID)) + return + } + if channel.GuildID != guild.ID { + log.Print(fmt.Sprintf("The channel %v is not in guild %v. Canceling task %v.", channel.Name, guild.Name, task.ID)) + return + } + member, err := s.GuildMember(guild.ID, task.UserID) + if err != nil { + log.Print(fmt.Sprintf("Can't find user with ID %v in guild %v. Canceling task %v.", task.UserID, guild.Name, task.ID)) + return + } + if task.Type == "Reminder" { + color := s.State.UserColor(member.User.ID, channel.ID) + e := discordgo.MessageEmbed{ + Title: "REMINDER", + Description: task.Content, + Timestamp: task.CreationTime.Format(time.RFC3339), + Color: color, + Footer: &discordgo.MessageEmbedFooter{ + Text: "Created: ", + }, + } + msg := discordgo.MessageSend{ + Content: member.Mention(), + Embed: &e, + } + _, err = s.ChannelMessageSendComplex(channel.ID, &msg) + if err != nil { + log.Println(err) + } + } + processQuery = "UPDATE tasks SET processing = false WHERE id = $1" + _, err = Database.Exec(processQuery, task.ID) + if err != nil { + log.Println(err) + } + +} + +func getTasksToRun() []Task { + query := "SELECT id, type, content, guild_id, channel_id, user_id, creation_time, trigger_time " + + "from tasks where completed is false and processing is false and trigger_time < $1" + res, err := Database.Query(query, time.Now()) + if err != nil { + log.Println(err) + } + var tasks []Task + for res.Next() { + var t Task + err = res.Scan(&t.ID, &t.Type, &t.Content, &t.GuildID, &t.ChannelID, &t.UserID, &t.CreationTime, &t.TriggerTime) + if err != nil { + log.Println(err) + } + for _, task := range tasks { + if task.ID == t.ID { + continue + } + } + tasks = append(tasks, t) + } + + return tasks +} + +func ProcessTasks(s *discordgo.Session, interval int) { + for { + time.Sleep(time.Duration(interval * 1e9)) + + tasks := getTasksToRun() + + if len(tasks) > 0 { + for _, t := range tasks { + go processTask(&t, s) + } + } + } +} diff --git a/utils/types.go b/utils/types.go new file mode 100644 index 0000000..0fd92b9 --- /dev/null +++ b/utils/types.go @@ -0,0 +1,10 @@ +package utils + +type Guild struct { + ID string + WelcomeMessage string + GoodbyeMessage string + LoggingChannel string + WelcomeChannel string + PuzzleChannel string +} From fcddca22260108c2e3c2af4b4b0d68bc5c86ec85 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 00:10:11 -0800 Subject: [PATCH 02/13] Delete old organization --- djpianalto.com/goff/events/member_events.go | 134 --------- djpianalto.com/goff/events/message_events.go | 97 ------- djpianalto.com/goff/exts/P_interpreter.go | 84 ------ djpianalto.com/goff/exts/fun.go | 84 ------ djpianalto.com/goff/exts/guild.go | 290 ------------------- djpianalto.com/goff/exts/init.go | 233 --------------- djpianalto.com/goff/exts/tags.go | 137 --------- djpianalto.com/goff/exts/tasks.go | 55 ---- djpianalto.com/goff/exts/user_management.go | 255 ---------------- djpianalto.com/goff/exts/utils.go | 168 ----------- djpianalto.com/goff/goff.go | 136 --------- djpianalto.com/goff/utils/database.go | 154 ---------- djpianalto.com/goff/utils/date_strings.go | 64 ---- djpianalto.com/goff/utils/email.go | 132 --------- djpianalto.com/goff/utils/logging.go | 34 --- djpianalto.com/goff/utils/postfixes.go | 77 ----- djpianalto.com/goff/utils/puzzles.go | 93 ------ djpianalto.com/goff/utils/rpn.go | 156 ---------- djpianalto.com/goff/utils/rpnParser.go | 95 ------ djpianalto.com/goff/utils/snowflake.go | 32 -- djpianalto.com/goff/utils/tasks.go | 132 --------- djpianalto.com/goff/utils/types.go | 10 - 22 files changed, 2652 deletions(-) delete mode 100644 djpianalto.com/goff/events/member_events.go delete mode 100644 djpianalto.com/goff/events/message_events.go delete mode 100644 djpianalto.com/goff/exts/P_interpreter.go delete mode 100644 djpianalto.com/goff/exts/fun.go delete mode 100644 djpianalto.com/goff/exts/guild.go delete mode 100644 djpianalto.com/goff/exts/init.go delete mode 100644 djpianalto.com/goff/exts/tags.go delete mode 100644 djpianalto.com/goff/exts/tasks.go delete mode 100644 djpianalto.com/goff/exts/user_management.go delete mode 100644 djpianalto.com/goff/exts/utils.go delete mode 100644 djpianalto.com/goff/goff.go delete mode 100644 djpianalto.com/goff/utils/database.go delete mode 100644 djpianalto.com/goff/utils/date_strings.go delete mode 100644 djpianalto.com/goff/utils/email.go delete mode 100644 djpianalto.com/goff/utils/logging.go delete mode 100644 djpianalto.com/goff/utils/postfixes.go delete mode 100644 djpianalto.com/goff/utils/puzzles.go delete mode 100644 djpianalto.com/goff/utils/rpn.go delete mode 100644 djpianalto.com/goff/utils/rpnParser.go delete mode 100644 djpianalto.com/goff/utils/snowflake.go delete mode 100644 djpianalto.com/goff/utils/tasks.go delete mode 100644 djpianalto.com/goff/utils/types.go diff --git a/djpianalto.com/goff/events/member_events.go b/djpianalto.com/goff/events/member_events.go deleted file mode 100644 index c3fc370..0000000 --- a/djpianalto.com/goff/events/member_events.go +++ /dev/null @@ -1,134 +0,0 @@ -package events - -import ( - "djpianalto.com/goff/djpianalto.com/goff/utils" - "fmt" - "github.com/bwmarrin/discordgo" - "log" - "strconv" - "time" -) - -func OnGuildMemberAddLogging(s *discordgo.Session, member *discordgo.GuildMemberAdd) { - defer func() { - if r := recover(); r != nil { - log.Println("Recovered from panic in OnGuildMemberAddLogging", r) - } - }() - var channelID string - row := utils.Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", member.GuildID) - err := row.Scan(&channelID) - if err != nil || channelID == "" { - return - } - guild, err := s.State.Guild(member.GuildID) - if err != nil { - log.Println(err) - return - } - - var title string - if member.User.Bot { - title = "Bot Joined" - } else { - title = "Member Joined" - } - - thumb := &discordgo.MessageEmbedThumbnail{ - URL: member.User.AvatarURL(""), - } - - int64ID, _ := strconv.ParseInt(member.User.ID, 10, 64) - snow := utils.ParseSnowflake(int64ID) - - field := &discordgo.MessageEmbedField{ - Name: "User was created:", - Value: utils.ParseDateString(snow.CreationTime), - Inline: false, - } - - joinTime, _ := member.JoinedAt.Parse() - - embed := &discordgo.MessageEmbed{ - Title: title, - Description: fmt.Sprintf("%v (%v) Has Joined the Server", member.User.Mention(), member.User.ID), - Color: 0x0cc56a, - Thumbnail: thumb, - Footer: &discordgo.MessageEmbedFooter{ - Text: fmt.Sprintf("Current Member Count: %v", guild.MemberCount), - IconURL: guild.IconURL(), - }, - Timestamp: joinTime.Format(time.RFC3339), - Fields: []*discordgo.MessageEmbedField{field}, - } - s.ChannelMessageSendEmbed(channelID, embed) -} - -func OnGuildMemberRemoveLogging(s *discordgo.Session, member *discordgo.GuildMemberRemove) { - defer func() { - if r := recover(); r != nil { - log.Println("Recovered from panic in OnGuildMemberAddLogging", r) - } - }() - timeNow := time.Now() - var channelID string - row := utils.Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", member.GuildID) - err := row.Scan(&channelID) - if err != nil || channelID == "" { - return - } - guild, err := s.State.Guild(member.GuildID) - if err != nil { - log.Println(err) - return - } - - var title string - if member.User.Bot { - title = "Bot Left" - } else { - title = "Member Left" - } - - thumb := &discordgo.MessageEmbedThumbnail{ - URL: member.User.AvatarURL(""), - } - - desc := "" - al, err := s.GuildAuditLog(member.GuildID, "", "", 20, 1) - if err != nil { - log.Println(err) - } else { - for _, log := range al.AuditLogEntries { - if log.TargetID == member.User.ID { - int64ID, _ := strconv.ParseInt(log.ID, 10, 64) - logSnow := utils.ParseSnowflake(int64ID) - if timeNow.Sub(logSnow.CreationTime).Seconds() <= 10 { - user, err := s.User(log.UserID) - if err == nil { - desc = fmt.Sprintf("%v (%v) was Kicked by: %v\nReason: %v", member.User.String(), member.User.ID, user.String(), log.Reason) - } else { - desc = fmt.Sprintf("%v (%v) was Kicked by: %v\nReason: %v", member.User.String(), member.User.ID, log.UserID, log.Reason) - } - break - } - } - } - } - if desc == "" { - desc = fmt.Sprintf("%v (%v) Has Left the Server", member.User.String(), member.User.ID) - } - - embed := &discordgo.MessageEmbed{ - Title: title, - Description: desc, - Color: 0xff9431, - Thumbnail: thumb, - Footer: &discordgo.MessageEmbedFooter{ - Text: fmt.Sprintf("Current Member Count: %v", guild.MemberCount), - IconURL: guild.IconURL(), - }, - Timestamp: timeNow.Format(time.RFC3339), - } - s.ChannelMessageSendEmbed(channelID, embed) -} diff --git a/djpianalto.com/goff/events/message_events.go b/djpianalto.com/goff/events/message_events.go deleted file mode 100644 index 96d3abb..0000000 --- a/djpianalto.com/goff/events/message_events.go +++ /dev/null @@ -1,97 +0,0 @@ -package events - -import ( - "fmt" - "log" - - "djpianalto.com/goff/djpianalto.com/goff/utils" - "github.com/bwmarrin/discordgo" -) - -func OnMessageUpdate(session *discordgo.Session, m *discordgo.MessageUpdate) { - defer func() { - if r := recover(); r != nil { - log.Println("Recovered from panic in OnMessageUpdate", r) - } - }() - msg := m.BeforeUpdate - if msg.Author.Bot { - return - } - var channelID string - row := utils.Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", msg.GuildID) - err := row.Scan(&channelID) - if err != nil || channelID == "" { - return - } - channel, err := session.State.Channel(msg.ChannelID) - if err != nil { - log.Println(err) - return - } - embed := &discordgo.MessageEmbed{ - Title: fmt.Sprintf("Message Edited: %v", msg.ID), - Description: fmt.Sprintf("**Before:** %v\n**After:** %v\nIn Channel: %v", msg.Content, m.Content, channel.Mention()), - Color: session.State.UserColor(msg.Author.ID, channelID), - Footer: &discordgo.MessageEmbedFooter{ - Text: fmt.Sprintf("Author: %v", msg.Author.String()), - IconURL: msg.Author.AvatarURL(""), - }, - } - session.ChannelMessageSendEmbed(channelID, embed) -} - -func OnMessageDelete(session *discordgo.Session, m *discordgo.MessageDelete) { - defer func() { - if r := recover(); r != nil { - log.Println("Recovered from panic in OnMessageDelete", r) - } - }() - msg := m.BeforeDelete - if msg == nil { - log.Printf("Message Deleted but the original message was not in my cache so we are ignoring it.\nMessage ID: %v\nGuild ID: %v\nChannel ID: %v\n", m.ID, m.GuildID, m.ChannelID) - return - } - if msg.Author.Bot { - return - } - var channelID string - row := utils.Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", msg.GuildID) - err := row.Scan(&channelID) - if err != nil || channelID == "" { - return - } - channel, err := session.State.Channel(msg.ChannelID) - if err != nil { - log.Println(err) - return - } - desc := "" - al, err := session.GuildAuditLog(msg.GuildID, "", "", 72, 1) - if err != nil { - log.Println(err) - } else { - for _, log := range al.AuditLogEntries { - if log.TargetID == msg.Author.ID && log.Options.ChannelID == msg.ChannelID { - user, err := session.User(log.UserID) - if err == nil { - desc = fmt.Sprintf("**Content:** %v\nIn Channel: %v\nDeleted By: %v", msg.Content, channel.Mention(), user.Mention()) - } - break - } - } - } - if desc == "" { - desc = fmt.Sprintf("**Content:** %v\nIn Channel: %v", msg.Content, channel.Mention()) - } - embed := &discordgo.MessageEmbed{ - Title: fmt.Sprintf("Message Deleted: %v", msg.ID), - Description: desc, - Color: session.State.UserColor(msg.Author.ID, channelID), - Footer: &discordgo.MessageEmbedFooter{ - Text: fmt.Sprintf("Author: %v", msg.Author.String()), - IconURL: msg.Author.AvatarURL(""), - }, - } - session.ChannelMessageSendEmbed(channelID, embed) -} diff --git a/djpianalto.com/goff/exts/P_interpreter.go b/djpianalto.com/goff/exts/P_interpreter.go deleted file mode 100644 index 57f4b77..0000000 --- a/djpianalto.com/goff/exts/P_interpreter.go +++ /dev/null @@ -1,84 +0,0 @@ -package exts - -import ( - "errors" - "fmt" - "github.com/dustinpianalto/disgoman" - "strings" -) - -func pCommand(ctx disgoman.Context, args []string) { - input := strings.Join(args, "") - const LENGTH = 1999 - var mem [LENGTH]byte - pointer := 0 - l := 0 - for i := 0; i < len(input); i++ { - if input[i] == 'L' { - if pointer == 0 { - pointer = LENGTH - 1 - } else { - pointer-- - } - } else if input[i] == 'R' { - if pointer == LENGTH-1 { - pointer = 0 - } else { - pointer++ - } - } else if input[i] == '+' { - mem[pointer]++ - } else if input[i] == '-' { - mem[pointer]-- - } else if input[i] == '(' { - if mem[pointer] == 0 { - i++ - for l > 0 || input[i] != ')' { - if input[i] == '(' { - l++ - } - if input[i] == ')' { - l-- - } - i++ - } - } - } else if input[i] == ')' { - if mem[pointer] != 0 { - i-- - for l > 0 || input[i] != '(' { - if input[i] == ')' { - l++ - } - if input[i] == '(' { - l-- - } - i-- - } - } - } else { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: fmt.Sprintf("Invalid Character: %v", input[i]), - Error: errors.New("invalid character"), - } - return - } - } - var out []byte - for _, i := range mem { - if i != 0 { - out = append(out, i) - } - } - _, err := ctx.Send(string(out)) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Couldn't send results", - Error: err, - } - return - } - return -} diff --git a/djpianalto.com/goff/exts/fun.go b/djpianalto.com/goff/exts/fun.go deleted file mode 100644 index 5084128..0000000 --- a/djpianalto.com/goff/exts/fun.go +++ /dev/null @@ -1,84 +0,0 @@ -package exts - -import ( - "fmt" - "strconv" - "strings" - - "github.com/dustinpianalto/disgoman" - "github.com/dustinpianalto/rpnparse" -) - -func interleave(ctx disgoman.Context, args []string) { - if len(args) == 2 { - x, err := strconv.ParseInt(args[0], 10, 64) - if err != nil { - return - } - y, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return - } - var z = int64(0) - for i := 0; i < 64; i++ { - x_masked_i := x & (1 << i) - y_masked_i := y & (1 << i) - - z |= x_masked_i << i - z |= y_masked_i << (i + 1) - } - ctx.Send(fmt.Sprintf("%v", z)) - } -} - -func deinterleave(ctx disgoman.Context, args []string) { - if len(args) == 1 { - z, err := strconv.ParseInt(args[0], 10, 64) - if err != nil { - return - } - var x = int64(0) - var y = int64(0) - i := 0 - for z > 0 { - x |= (z & 1) << i - z >>= 1 - y |= (z & 1) << i - z >>= 1 - i++ - } - ctx.Send(fmt.Sprintf("(%v, %v)", x, y)) - } -} - -func generateRPNCommand(ctx disgoman.Context, args []string) { - rpn, err := rpnparse.GenerateRPN(args) - if err != nil { - ctx.Send(err.Error()) - return - } - ctx.Send(rpn) -} - -func parseRPNCommand(ctx disgoman.Context, args []string) { - res, err := rpnparse.ParseRPN(args) - if err != nil { - ctx.Send(err.Error()) - return - } - ctx.Send(fmt.Sprintf("The result is: %v", res)) -} - -func solveCommand(ctx disgoman.Context, args []string) { - rpn, err := rpnparse.GenerateRPN(args) - if err != nil { - ctx.Send(err.Error()) - return - } - res, err := rpnparse.ParseRPN(strings.Split(rpn, " ")) - if err != nil { - ctx.Send(err.Error()) - return - } - ctx.Send(fmt.Sprintf("The result is: %v", res)) -} diff --git a/djpianalto.com/goff/exts/guild.go b/djpianalto.com/goff/exts/guild.go deleted file mode 100644 index 960934b..0000000 --- a/djpianalto.com/goff/exts/guild.go +++ /dev/null @@ -1,290 +0,0 @@ -package exts - -import ( - "fmt" - "strings" - - "djpianalto.com/goff/djpianalto.com/goff/utils" - "github.com/dustinpianalto/disgoman" -) - -// Guild management commands - -func loggingChannel(ctx disgoman.Context, args []string) { - var idString string - if len(args) > 0 { - idString = args[0] - if strings.HasPrefix(idString, "<#") && strings.HasSuffix(idString, ">") { - idString = idString[2 : len(idString)-1] - } - } else { - idString = "" - } - fmt.Println(idString) - if idString == "" { - _, err := utils.Database.Exec("UPDATE guilds SET logging_channel='' WHERE id=$1;", ctx.Guild.ID) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error Updating Database", - Error: err, - } - return - } - _, _ = ctx.Send("Logging Channel Updated.") - return - } - channel, err := ctx.Session.State.Channel(idString) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Can't find that channel.", - Error: err, - } - return - } - if channel.GuildID != ctx.Guild.ID { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "The channel passed is not in this guild.", - Error: err, - } - return - } - _, err = utils.Database.Exec("UPDATE guilds SET logging_channel=$1 WHERE id=$2;", idString, ctx.Guild.ID) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error Updating Database", - Error: err, - } - return - } - _, _ = ctx.Send("Logging Channel Updated.") -} - -func getLoggingChannel(ctx disgoman.Context, _ []string) { - var channelID string - row := utils.Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", ctx.Guild.ID) - err := row.Scan(&channelID) - if err != nil { - fmt.Println(err) - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error getting data from the database.", - Error: err, - } - return - } - if channelID == "" { - _, _ = ctx.Send("The logging channel is not set.") - return - } - channel, err := ctx.Session.State.GuildChannel(ctx.Guild.ID, channelID) - if err != nil { - fmt.Println(err) - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "I got the channel ID but it does not appear to be a valid channel in this guild.", - Error: err, - } - return - } - _, _ = ctx.Send(fmt.Sprintf("The logging channel is currently %s", channel.Mention())) - return -} - -func welcomeChannel(ctx disgoman.Context, args []string) { - var idString string - if len(args) > 0 { - idString = args[0] - if strings.HasPrefix(idString, "<#") && strings.HasSuffix(idString, ">") { - idString = idString[2 : len(idString)-1] - } - } else { - idString = "" - } - fmt.Println(idString) - if idString == "" { - _, err := utils.Database.Exec("UPDATE guilds SET welcome_channel='' WHERE id=$1;", ctx.Guild.ID) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error Updating Database", - Error: err, - } - return - } - _, _ = ctx.Send("Welcomer Disabled.") - return - } - channel, err := ctx.Session.State.Channel(idString) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Can't find that channel.", - Error: err, - } - return - } - if channel.GuildID != ctx.Guild.ID { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "The channel passed is not in this guild.", - Error: err, - } - return - } - _, err = utils.Database.Exec("UPDATE guilds SET welcome_channel=$1 WHERE id=$2;", idString, ctx.Guild.ID) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error Updating Database", - Error: err, - } - return - } - _, _ = ctx.Send("Welcome Channel Updated.") - return -} - -func getWelcomeChannel(ctx disgoman.Context, _ []string) { - var channelID string - row := utils.Database.QueryRow("SELECT welcome_channel FROM guilds where id=$1", ctx.Guild.ID) - err := row.Scan(&channelID) - if err != nil { - fmt.Println(err) - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error getting data from the database.", - Error: err, - } - return - } - if channelID == "" { - _, _ = ctx.Send("The welcomer is disabled.") - return - } - channel, err := ctx.Session.State.GuildChannel(ctx.Guild.ID, channelID) - if err != nil { - fmt.Println(err) - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "I got the channel ID but it does not appear to be a valid channel in this guild.", - Error: err, - } - return - } - _, _ = ctx.Send(fmt.Sprintf("The welcome channel is currently %s", channel.Mention())) -} - -func addGuildCommand(ctx disgoman.Context, args []string) { - var guildID string - row := utils.Database.QueryRow("SELECT id FROM guilds where id=$1", ctx.Guild.ID) - err := row.Scan(&guildID) - if err == nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "This guild is already in my database", - Error: err, - } - return - } - - _, err = utils.Database.Query("INSERT INTO guilds (id) VALUES ($1)", ctx.Guild.ID) - if err != nil { - fmt.Println(err) - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "There was a problem inserting this guild into the database", - Error: err, - } - return - } - _, _ = ctx.Send("This guild has been added.") - -} - -func puzzleChannel(ctx disgoman.Context, args []string) { - var idString string - if len(args) > 0 { - idString = args[0] - if strings.HasPrefix(idString, "<#") && strings.HasSuffix(idString, ">") { - idString = idString[2 : len(idString)-1] - } - } else { - idString = "" - } - fmt.Println(idString) - if idString == "" { - _, err := utils.Database.Exec("UPDATE guilds SET puzzle_channel='' WHERE id=$1;", ctx.Guild.ID) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error Updating Database", - Error: err, - } - return - } - _, _ = ctx.Send("Puzzle Channel Updated.") - return - } - channel, err := ctx.Session.State.Channel(idString) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Can't find that channel.", - Error: err, - } - return - } - if channel.GuildID != ctx.Guild.ID { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "The channel passed is not in this guild.", - Error: err, - } - return - } - _, err = utils.Database.Exec("UPDATE guilds SET puzzle_channel=$1 WHERE id=$2;", idString, ctx.Guild.ID) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error Updating Database", - Error: err, - } - return - } - _, _ = ctx.Send("Puzzle Channel Updated.") -} - -func getPuzzleChannel(ctx disgoman.Context, _ []string) { - var channelID string - row := utils.Database.QueryRow("SELECT puzzle_channel FROM guilds where id=$1", ctx.Guild.ID) - err := row.Scan(&channelID) - if err != nil { - fmt.Println(err) - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error getting data from the database.", - Error: err, - } - return - } - if channelID == "" { - _, _ = ctx.Send("The puzzle channel is not set.") - return - } - channel, err := ctx.Session.State.GuildChannel(ctx.Guild.ID, channelID) - if err != nil { - fmt.Println(err) - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "I got the channel ID but it does not appear to be a valid channel in this guild.", - Error: err, - } - return - } - _, _ = ctx.Send(fmt.Sprintf("The puzzle channel is currently %s", channel.Mention())) - return -} diff --git a/djpianalto.com/goff/exts/init.go b/djpianalto.com/goff/exts/init.go deleted file mode 100644 index 316b3b7..0000000 --- a/djpianalto.com/goff/exts/init.go +++ /dev/null @@ -1,233 +0,0 @@ -package exts - -import ( - "github.com/dustinpianalto/disgoman" -) - -func AddCommandHandlers(h *disgoman.CommandManager) { - // Arguments: - // name - command name - string - // desc - command description - string - // owneronly - only allow owners to run - bool - // hidden - hide command from non-owners - bool - // perms - permissisions required - anpan.Permission (int) - // type - command type, sets where the command is available - // run - function to run - func(anpan.Context, []string) / CommandRunFunc - _ = h.AddCommand(&disgoman.Command{ - Name: "ping", - Aliases: nil, - Description: "Check the bot's ping", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: pingCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "say", - Aliases: nil, - Description: "Repeat a message", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - SanitizeEveryone: true, - Invoke: sayCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "user", - Aliases: nil, - Description: "Get user info", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: userCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "git", - Aliases: nil, - Description: "Show my github link", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: gitCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "tag", - Aliases: nil, - Description: "Get a tag", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: tagCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "addtag", - Aliases: nil, - Description: "Add a tag", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - SanitizeEveryone: true, - Invoke: addTagCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "invite", - Aliases: nil, - Description: "Get the invite link for this bot or others", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: inviteCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "P", - Aliases: nil, - Description: "Interpret a P\" program and return the results", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: pCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "set-logging-channel", - Aliases: []string{"slc"}, - Description: "Set the channel logging messages will be sent to.", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: disgoman.PermissionManageServer, - Invoke: loggingChannel, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "get-logging-channel", - Aliases: []string{"glc"}, - Description: "Gets the channel logging messages will be sent to.", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: disgoman.PermissionManageServer, - Invoke: getLoggingChannel, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "set-welcome-channel", - Aliases: []string{"swc"}, - Description: "Set the channel welcome messages will be sent to.", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: disgoman.PermissionManageServer, - Invoke: welcomeChannel, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "get-welcome-channel", - Aliases: []string{"gwc"}, - Description: "Gets the channel welcome messages will be sent to.", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: disgoman.PermissionManageServer, - Invoke: getWelcomeChannel, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "kick", - Aliases: nil, - Description: "Kicks the given user with the given reason", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: disgoman.PermissionKickMembers, - Invoke: kickUserCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "addGuild", - Aliases: nil, - Description: "Adds the current guild to the database", - OwnerOnly: true, - Hidden: false, - RequiredPermissions: disgoman.PermissionManageServer, - Invoke: addGuildCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "ban", - Aliases: []string{"ban-no-delete"}, - Description: "Bans the given user with the given reason", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: disgoman.PermissionBanMembers, - Invoke: banUserCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "unban", - Aliases: nil, - Description: "Unbans the given user", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: disgoman.PermissionBanMembers, - Invoke: unbanUserCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "remind", - Aliases: nil, - Description: "Remind me at a later time", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: addReminderCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "encode", - Aliases: []string{"e"}, - Description: "Encode 2 numbers", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: interleave, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "decode", - Aliases: []string{"d"}, - Description: "Decode 1 number into 2", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: deinterleave, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "set-puzzle-channel", - Aliases: []string{"spc"}, - Description: "Set the channel puzzle messages will be sent to.", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: disgoman.PermissionManageServer, - Invoke: puzzleChannel, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "get-puzzle-channel", - Aliases: []string{"gpc"}, - Description: "Gets the channel puzzle messages will be sent to.", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: disgoman.PermissionManageServer, - Invoke: getPuzzleChannel, - }) - Name: "RPN", - Aliases: []string{"rpn"}, - Description: "Convert infix to rpn", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: generateRPNCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "ParseRPN", - Aliases: []string{"PRPN", "prpn"}, - Description: "Parse RPN string and return the result", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: parseRPNCommand, - }) - _ = h.AddCommand(&disgoman.Command{ - Name: "solve", - Aliases: []string{"math", "infix"}, - Description: "Solve infix equation and return the result", - OwnerOnly: false, - Hidden: false, - RequiredPermissions: 0, - Invoke: solveCommand, - }) -} diff --git a/djpianalto.com/goff/exts/tags.go b/djpianalto.com/goff/exts/tags.go deleted file mode 100644 index 4a55771..0000000 --- a/djpianalto.com/goff/exts/tags.go +++ /dev/null @@ -1,137 +0,0 @@ -package exts - -import ( - "errors" - "fmt" - "log" - "strings" - - "djpianalto.com/goff/djpianalto.com/goff/utils" - "github.com/dustinpianalto/disgoman" - "github.com/kballard/go-shellquote" -) - -func addTagCommand(ctx disgoman.Context, input []string) { - if len(input) >= 1 { - args, err := shellquote.Split(strings.Join(input, " ")) - if err != nil { - if strings.Contains(err.Error(), "Unterminated") { - args = strings.SplitN(strings.Join(args, " "), " ", 2) - } else { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "", - Error: err, - } - return - } - } - queryString := `SELECT tags.id, tags.tag, tags.content from tags - WHERE tags.guild_id = $1 - AND tags.tag = $2;` - row := utils.Database.QueryRow(queryString, ctx.Guild.ID, args[0]) - var dest string - if err := row.Scan(&dest); err != nil { - tag := args[0] - if tag == "" { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "That is not a valid tag name", - Error: err, - } - return - } - if len(args) <= 1 { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "I got a name but no value", - Error: err, - } - return - } - value := args[1] - if value == "" { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "You have to include a content for the tag", - Error: err, - } - return - } - queryString = `INSERT INTO tags (tag, content, creator, guild_id) VALUES ($1, $2, $3, $4);` - _, err := utils.Database.Exec(queryString, tag, value, ctx.Message.Author.ID, ctx.Guild.ID) - if err != nil { - ctx.Send(err.Error()) - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "", - Error: err, - } - return - } - ctx.Send(fmt.Sprintf("Tag %v added successfully.", tag)) - return - } else { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "That tag already exists", - Error: err, - } - return - } - } else { - ctx.Send("You need to tell me what tag you want to add...") - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "You need to tell me what tag you want to add...", - Error: errors.New("nothing to do"), - } - return - } -} - -func tagCommand(ctx disgoman.Context, args []string) { - if len(args) >= 1 { - tagString := strings.Join(args, " ") - queryString := `SELECT tags.id, tags.tag, tags.content from tags - WHERE tags.guild_id = $1;` - rows, err := utils.Database.Query(queryString, ctx.Guild.ID) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "", - Error: err, - } - return - } else { - for rows.Next() { - var ( - id int - tag string - content string - ) - if err := rows.Scan(&id, &tag, &content); err != nil { - log.Fatal(err) - } - if tagString == tag { - ctx.Send(content) - return - } - } - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: fmt.Sprintf("Tag %v not found", args[0]), - Error: err, - } - return - } - } else { - ctx.Send("I need a tag to fetch...") - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "I need a tag to fetch...", - Error: errors.New("nothing to do"), - } - return - } -} diff --git a/djpianalto.com/goff/exts/tasks.go b/djpianalto.com/goff/exts/tasks.go deleted file mode 100644 index 6239f1d..0000000 --- a/djpianalto.com/goff/exts/tasks.go +++ /dev/null @@ -1,55 +0,0 @@ -package exts - -import ( - "djpianalto.com/goff/djpianalto.com/goff/utils" - "errors" - "fmt" - "github.com/dustinpianalto/disgoman" - "github.com/olebedev/when" - "github.com/olebedev/when/rules/common" - "github.com/olebedev/when/rules/en" - "strings" - "time" -) - -func addReminderCommand(ctx disgoman.Context, args []string) { - w := when.New(nil) - w.Add(en.All...) - w.Add(common.All...) - - text := strings.Join(args, " ") - r, err := w.Parse(text, time.Now()) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error parsing time", - Error: err, - } - return - } - if r == nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "You need to include a valid time", - Error: errors.New("no time found"), - } - return - } - content := strings.Replace(text, r.Text+" ", "", 1) - query := "INSERT INTO tasks (type, content, guild_id, channel_id, user_id, trigger_time) " + - "VALUES ('Reminder', $1, $2, $3, $4, $5)" - _, err = utils.Database.Exec(query, content, ctx.Guild.ID, ctx.Channel.ID, ctx.User.ID, r.Time) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error adding task to database", - Error: err, - } - return - } - _ = ctx.Session.MessageReactionAdd(ctx.Channel.ID, ctx.Message.ID, "✅") - _, _ = ctx.Session.ChannelMessageSend( - ctx.Channel.ID, - fmt.Sprintf("I will remind you at %v, with `%v`", r.Time.Format(time.RFC1123), content), - ) -} diff --git a/djpianalto.com/goff/exts/user_management.go b/djpianalto.com/goff/exts/user_management.go deleted file mode 100644 index f5a5594..0000000 --- a/djpianalto.com/goff/exts/user_management.go +++ /dev/null @@ -1,255 +0,0 @@ -package exts - -import ( - "djpianalto.com/goff/djpianalto.com/goff/utils" - "errors" - "fmt" - "github.com/bwmarrin/discordgo" - "github.com/dustinpianalto/disgoman" - "strings" - "time" -) - -func kickUserCommand(ctx disgoman.Context, args []string) { - var member *discordgo.Member - var err error - if len(ctx.Message.Mentions) > 0 { - member, err = ctx.Session.GuildMember(ctx.Guild.ID, ctx.Message.Mentions[0].ID) - } else if len(args) >= 1 { - idString := args[0] - if strings.HasPrefix(idString, "<@!") && strings.HasSuffix(idString, ">") { - idString = idString[3 : len(idString)-1] - } - member, err = ctx.Session.GuildMember(ctx.Guild.ID, idString) - } else { - err = errors.New("that is not a valid id") - } - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Couldn't get that member", - Error: err, - } - return - } - - if higher, _ := disgoman.HasHigherRole(ctx.Session, ctx.Guild.ID, ctx.Message.Author.ID, member.User.ID); !higher { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "You must have a higher role than the person you are trying to kick", - Error: errors.New("need higher role"), - } - return - } - - if higher, _ := disgoman.HasHigherRole(ctx.Session, ctx.Guild.ID, ctx.Session.State.User.ID, member.User.ID); !higher { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "I don't have a high enough role to kick that person", - Error: errors.New("need higher role"), - } - return - } - - var reason string - if len(args) > 1 { - reason = strings.Join(args[1:], " ") - } else { - reason = "No Reason Given" - } - auditReason := fmt.Sprintf("%v#%v: %v", ctx.User.Username, ctx.User.Discriminator, reason) - err = ctx.Session.GuildMemberDeleteWithReason(ctx.Guild.ID, member.User.ID, auditReason) - - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: fmt.Sprintf("Something went wrong kicking %v", member.User.Username), - Error: err, - } - return - } - - event := &utils.LogEvent{ - Embed: discordgo.MessageEmbed{ - Title: "User Kicked", - Description: fmt.Sprintf( - "User %v#%v was kicked by %v.\nReason: %v", - member.User.Username, - member.User.Discriminator, - ctx.Message.Author.Username, - reason), - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - Color: 0xff8c00, - }, - GuildID: ctx.Guild.ID, - Session: ctx.Session, - } - utils.LoggingChannel <- event - _, _ = ctx.Send(fmt.Sprintf("User %v#%v has been kicked.", member.User.Username, member.User.Discriminator)) -} - -func banUserCommand(ctx disgoman.Context, args []string) { - var user *discordgo.User - var err error - if len(ctx.Message.Mentions) > 0 { - user, err = ctx.Session.User(ctx.Message.Mentions[0].ID) - } else if len(args) >= 1 { - idString := args[0] - if strings.HasPrefix(idString, "<@!") && strings.HasSuffix(idString, ">") { - idString = idString[3 : len(idString)-1] - } - user, err = ctx.Session.User(idString) - } else { - err = errors.New("that is not a valid id") - } - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Couldn't get that user", - Error: err, - } - return - } - - if higher, err := disgoman.HasHigherRole(ctx.Session, ctx.Guild.ID, ctx.Message.Author.ID, user.ID); err != nil { - if err.Error() == "can't find caller member" { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Who are you?", - Error: err, - } - return - } - } else if !higher { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "You must have a higher role than the person you are trying to ban", - Error: errors.New("need higher role"), - } - return - } - - if higher, err := disgoman.HasHigherRole(ctx.Session, ctx.Guild.ID, ctx.Session.State.User.ID, user.ID); err != nil { - if err.Error() == "can't find caller member" { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Who am I?", - Error: err, - } - return - } - } else if !higher { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "I don't have a high enough role to ban that person", - Error: errors.New("need higher role"), - } - return - } - - var reason string - if len(args) > 1 { - reason = strings.Join(args[1:], " ") - } else { - reason = "No Reason Given" - } - auditReason := fmt.Sprintf("%v#%v: %v", ctx.User.Username, ctx.User.Discriminator, reason) - days := 7 - if ctx.Invoked == "ban-no-delete" { - days = 0 - } - err = ctx.Session.GuildBanCreateWithReason(ctx.Guild.ID, user.ID, auditReason, days) - - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: fmt.Sprintf("Something went wrong banning %v", user.Username), - Error: err, - } - return - } - - event := &utils.LogEvent{ - Embed: discordgo.MessageEmbed{ - Title: "User Banned", - Description: fmt.Sprintf( - "User %v#%v was banned by %v.\nReason: %v", - user.Username, - user.Discriminator, - ctx.Message.Author.Username, - reason), - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - Color: 0xff0000, - }, - GuildID: ctx.Guild.ID, - Session: ctx.Session, - } - utils.LoggingChannel <- event - _, _ = ctx.Send(fmt.Sprintf("User %v#%v has been banned.", user.Username, user.Discriminator)) -} - -func unbanUserCommand(ctx disgoman.Context, args []string) { - var user *discordgo.User - var err error - if len(ctx.Message.Mentions) > 0 { - user, err = ctx.Session.User(ctx.Message.Mentions[0].ID) - } else if len(args) >= 1 { - idString := args[0] - if strings.HasPrefix(idString, "<@!") && strings.HasSuffix(idString, ">") { - idString = idString[3 : len(idString)-1] - } - user, err = ctx.Session.User(idString) - } else { - err = errors.New("that is not a valid id") - } - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Couldn't get that user", - Error: err, - } - return - } - - bans, err := ctx.Session.GuildBans(ctx.Guild.ID) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Error processing current bans", - Error: err, - } - return - } - for _, ban := range bans { - if ban.User.ID == user.ID { - err = ctx.Session.GuildBanDelete(ctx.Guild.ID, user.ID) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: fmt.Sprintf("Something went wrong unbanning %v", user.Username), - Error: err, - } - return - } - event := &utils.LogEvent{ - Embed: discordgo.MessageEmbed{ - Title: "User Banned", - Description: fmt.Sprintf( - "User %v#%v was unbanned by %v.\nOrignal Ban Reason: %v", - user.Username, - user.Discriminator, - ctx.Message.Author.Username, - ban.Reason), - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - Color: 0x00ff00, - }, - GuildID: ctx.Guild.ID, - Session: ctx.Session, - } - utils.LoggingChannel <- event - _, _ = ctx.Send(fmt.Sprintf("User %v#%v has been unbanned.", user.Username, user.Discriminator)) - return - } - } - _, _ = ctx.Send(fmt.Sprintf("%v#%v is not banned in this guild.", user.Username, user.Discriminator)) -} diff --git a/djpianalto.com/goff/exts/utils.go b/djpianalto.com/goff/exts/utils.go deleted file mode 100644 index b8a2056..0000000 --- a/djpianalto.com/goff/exts/utils.go +++ /dev/null @@ -1,168 +0,0 @@ -package exts - -import ( - "djpianalto.com/goff/djpianalto.com/goff/utils" - "fmt" - "github.com/bwmarrin/discordgo" - "github.com/dustinpianalto/disgoman" - "sort" - "strconv" - "strings" - "time" -) - -func pingCommand(ctx disgoman.Context, _ []string) { - timeBefore := time.Now() - msg, _ := ctx.Send("Pong!") - took := time.Now().Sub(timeBefore) - _, err := ctx.Session.ChannelMessageEdit(ctx.Message.ChannelID, msg.ID, fmt.Sprintf("Pong!\nPing Took **%s**", took.String())) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Ping Failed", - Error: err, - } - } -} - -func inviteCommand(ctx disgoman.Context, args []string) { - var ids []string - if len(args) == 0 { - ids = []string{ctx.Session.State.User.ID} - } else { - for _, id := range args { - ids = append(ids, id) - } - } - for _, id := range ids { - url := fmt.Sprintf("", id) - _, err := ctx.Send(url) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Couldn't send the invite link.", - Error: err, - } - } - } -} - -func gitCommand(ctx disgoman.Context, _ []string) { - embed := &discordgo.MessageEmbed{ - Title: "Hi there, My code is on Github", - Color: 0, - URL: "https://github.com/dustinpianalto/Goff", - } - _, err := ctx.Session.ChannelMessageSendEmbed(ctx.Channel.ID, embed) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Git failed", - Error: err, - } - } -} - -func sayCommand(ctx disgoman.Context, args []string) { - resp := strings.Join(args, " ") - resp = strings.ReplaceAll(resp, "@everyone", "@\ufff0everyone") - resp = strings.ReplaceAll(resp, "@here", "@\ufff0here") - _, err := ctx.Session.ChannelMessageSend(ctx.Message.ChannelID, resp) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Say Failed", - Error: err, - } - } -} - -func userCommand(ctx disgoman.Context, args []string) { - var member *discordgo.Member - if len(args) == 0 { - member, _ = ctx.Session.GuildMember(ctx.Guild.ID, ctx.Message.Author.ID) - } else { - var err error - if len(ctx.Message.Mentions) > 0 { - member, err = ctx.Session.GuildMember(ctx.Guild.ID, ctx.Message.Mentions[0].ID) - } else { - member, err = ctx.Session.GuildMember(ctx.Guild.ID, args[0]) - } - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Couldn't get that member", - Error: err, - } - return - } - } - thumb := &discordgo.MessageEmbedThumbnail{ - URL: member.User.AvatarURL(""), - } - - var botString string - if member.User.Bot { - botString = "BOT" - } else { - botString = "" - } - - var roles []*discordgo.Role - for _, roleID := range member.Roles { - role, _ := ctx.Session.State.Role(ctx.Guild.ID, roleID) - roles = append(roles, role) - } - sort.Slice(roles, func(i, j int) bool { return roles[i].Position > roles[j].Position }) - var roleMentions []string - for _, role := range roles { - roleMentions = append(roleMentions, role.Mention()) - } - var rolesString string - if len(roleMentions) > 0 { - rolesString = strings.Join(roleMentions, " ") - } else { - rolesString = "None" - } - - rolesField := &discordgo.MessageEmbedField{ - Name: "Roles:", - Value: rolesString, - Inline: false, - } - - guildJoinTime, _ := member.JoinedAt.Parse() - guildJoinedField := &discordgo.MessageEmbedField{ - Name: "Joined Guild:", - Value: utils.ParseDateString(guildJoinTime), - Inline: false, - } - - int64ID, _ := strconv.ParseInt(member.User.ID, 10, 64) - s := utils.ParseSnowflake(int64ID) - discordJoinedField := &discordgo.MessageEmbedField{ - Name: "Joined Discord:", - Value: utils.ParseDateString(s.CreationTime), - Inline: false, - } - - embed := &discordgo.MessageEmbed{ - Title: fmt.Sprintf("%v#%v %v", member.User.Username, member.User.Discriminator, botString), - Description: fmt.Sprintf("**%v** (%v)", member.Nick, member.User.ID), - Color: ctx.Session.State.UserColor(member.User.ID, ctx.Channel.ID), - Thumbnail: thumb, - Fields: []*discordgo.MessageEmbedField{ - guildJoinedField, - discordJoinedField, - rolesField, - }, - } - _, err := ctx.Session.ChannelMessageSendEmbed(ctx.Channel.ID, embed) - if err != nil { - ctx.ErrorChannel <- disgoman.CommandError{ - Context: ctx, - Message: "Couldn't send the user embed", - Error: err, - } - } -} diff --git a/djpianalto.com/goff/goff.go b/djpianalto.com/goff/goff.go deleted file mode 100644 index 4bf65cc..0000000 --- a/djpianalto.com/goff/goff.go +++ /dev/null @@ -1,136 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "djpianalto.com/goff/djpianalto.com/goff/events" - "djpianalto.com/goff/djpianalto.com/goff/exts" - "djpianalto.com/goff/djpianalto.com/goff/utils" - "github.com/dustinpianalto/disgoman" - - //"github.com/MikeModder/anpan" - "os" - "os/signal" - "syscall" - - "github.com/bwmarrin/discordgo" -) - -var ( - Token string -) - -//func init() { -// flag.StringVar(&Token, "t", "", "Bot Token") -// flag.Parse() -//} - -func main() { - Token = os.Getenv("DISCORDGO_TOKEN") - dg, err := discordgo.New("Bot " + Token) - if err != nil { - fmt.Println("There was an error when creating the Discord Session, ", err) - return - } - dg.State.MaxMessageCount = 100 - - utils.ConnectDatabase(os.Getenv("DATABASE_URL")) - utils.InitializeDatabase() - //utils.LoadTestData() - - //prefixes := []string{ - // "Go.", - //} - owners := []string{ - "351794468870946827", - } - - // Arguments are: - // prefixes - []string - // owner ids - []string - // ignore bots - bool - // check perms - bool - handler := disgoman.CommandManager{ - Prefixes: getPrefixes, - Owners: owners, - StatusManager: disgoman.GetDefaultStatusManager(), - ErrorChannel: make(chan disgoman.CommandError, 10), - Commands: make(map[string]*disgoman.Command), - IgnoreBots: true, - CheckPermissions: false, - } - - // Add Command Handlers - exts.AddCommandHandlers(&handler) - - //if _, ok := handler.Commands["help"]; !ok { - // handler.AddDefaultHelpCommand() - //} - - dg.AddHandler(handler.OnMessage) - dg.AddHandler(handler.StatusManager.OnReady) - dg.AddHandler(events.OnMessageUpdate) - dg.AddHandler(events.OnMessageDelete) - dg.AddHandler(events.OnGuildMemberAddLogging) - dg.AddHandler(events.OnGuildMemberRemoveLogging) - - err = dg.Open() - if err != nil { - fmt.Println("There was an error opening the connection, ", err) - return - } - - // Start the Error handler in a goroutine - go ErrorHandler(handler.ErrorChannel) - - // Start the Logging handler in a goroutine - go utils.LoggingHandler(utils.LoggingChannel) - - // Start the task handler in a goroutine - go utils.ProcessTasks(dg, 1) - - go utils.RecieveEmail(dg) - - fmt.Println("The Bot is now running.") - sc := make(chan os.Signal, 1) - signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) - <-sc - - fmt.Println("Shutting Down...") - err = dg.Close() - if err != nil { - fmt.Println(err) - } -} - -func getPrefixes(guildID string) []string { - queryString := "Select prefix from prefixes p, x_guilds_prefixes xgp where xgp.guild_id = $1 and xgp.prefix_id = p.id" - rows, err := utils.Database.Query(queryString, guildID) - if err != nil { - log.Println(err) - return []string{"Go.", "go."} - } - var prefixes []string - for rows.Next() { - var prefix string - err = rows.Scan(&prefix) - if err != nil { - log.Println(err) - return []string{"Go.", "go."} - } - prefixes = append(prefixes, prefix) - } - return prefixes -} - -func ErrorHandler(ErrorChan chan disgoman.CommandError) { - for ce := range ErrorChan { - msg := ce.Message - if msg == "" { - msg = ce.Error.Error() - } - _, _ = ce.Context.Send(msg) - fmt.Println(ce.Error) - } -} diff --git a/djpianalto.com/goff/utils/database.go b/djpianalto.com/goff/utils/database.go deleted file mode 100644 index 88d0aed..0000000 --- a/djpianalto.com/goff/utils/database.go +++ /dev/null @@ -1,154 +0,0 @@ -package utils - -import ( - "database/sql" - "fmt" - "log" - - _ "github.com/lib/pq" -) - -var ( - Database *sql.DB -) - -func ConnectDatabase(dbConnString string) { - db, err := sql.Open("postgres", dbConnString) - if err != nil { - panic(fmt.Sprintf("Can't connect to the database. %v", err)) - } else { - fmt.Println("Database Connected.") - } - Database = db -} - -func InitializeDatabase() { - _, err := Database.Query("CREATE TABLE IF NOT EXISTS users(" + - "id varchar(30) primary key," + - "banned bool not null default false," + - "logging bool not null default true," + - "steam_id varchar(30) NOT NULL DEFAULT ''," + - "is_active bool not null default true," + - "is_staff bool not null default false," + - "is_admin bool not null default false" + - ")") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("CREATE TABLE IF NOT EXISTS guilds(" + - "id varchar(30) primary key," + - "welcome_message varchar(1000) NOT NULL DEFAULT ''," + - "goodbye_message varchar(1000) NOT NULL DEFAULT ''," + - "logging_channel varchar(30) NOT NULL DEFAULT ''," + - "welcome_channel varchar(30) NOT NULL DEFAULT ''" + - ")") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("CREATE TABLE IF NOT EXISTS prefixes(" + - "id serial primary key," + - "prefix varchar(10) not null unique default 'Go.'" + - ")") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("CREATE TABLE IF NOT EXISTS tags(" + - "id serial primary key," + - "tag varchar(100) not null unique," + - "content varchar(1000) not null," + - "creator varchar(30) not null references users(id)," + - "creation_time timestamp not null default NOW()," + - "guild_id varchar(30) not null references guilds(id)" + - ")") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("CREATE TABLE IF NOT EXISTS x_users_guilds(" + - "guild_id varchar(30) not null references guilds(id)," + - "user_id varchar(30) not null references users(id)" + - ")") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("CREATE TABLE IF NOT EXISTS x_guilds_prefixes(" + - "guild_id varchar(30) not null references guilds(id)," + - "prefix_id int not null references prefixes(id)" + - ")") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("CREATE TABLE IF NOT EXISTS tasks(" + - "id serial primary key," + - "type varchar(10) not null," + - "content text not null," + - "guild_id varchar(30) not null references guilds(id)," + - "channel_id varchar(30) not null," + - "user_id varchar(30) not null," + - "creation_time timestamp not null default NOW()," + - "trigger_time timestamp not null," + - "completed bool not null default false," + - "processing bool default false)") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query(`CREATE TABLE IF NOT EXISTS postfixes( - id serial primary key, - name varchar(100) not null, - time timestamp not null default NOW())`) - if err != nil { - log.Println(err) - } - _, err = Database.Exec(`CREATE TABLE IF NOT EXISTS puzzles( - id serial primary key, - text text not null, - time timestamp not null - )`) - if err != nil { - log.Println(err) - } - _, err = Database.Exec(`CREATE TABLE IF NOT EXISTS x_guilds_puzzles( - id serial primary key, - guild_id varchar(30) not null references guilds(id), - puzzle_id int not null references puzzles(id), - message_id varchar(30) not null - )`) - RunPostfixes() -} - -func LoadTestData() { - _, err := Database.Query("INSERT INTO users (id, banned, logging, steam_id, is_active, is_staff, is_admin) values " + - "('351794468870946827', false, true, '76561198024193239', true, true, true)," + - "('692908139506434065', false, true, '', true, false, false)," + - "('396588996706304010', false, true, '', true, true, false)") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("INSERT INTO guilds (id, welcome_message, goodbye_message) VALUES " + - "('265828729970753537', 'Hey there is someone new here.', 'Well fine then... Just leave without saying goodbye')") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("INSERT INTO prefixes (prefix) VALUES ('Godev.'), ('godev.'), ('godev,')") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("INSERT INTO x_users_guilds (guild_id, user_id) VALUES " + - "('265828729970753537', '351794468870946827')," + - "('265828729970753537', '692908139506434065')," + - "('265828729970753537', '396588996706304010')") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("INSERT INTO x_guilds_prefixes (guild_id, prefix_id) VALUES " + - "('265828729970753537', 1)," + - "('265828729970753537', 2)," + - "('265828729970753537', 3)") - if err != nil { - fmt.Println(err) - } - _, err = Database.Query("INSERT INTO tags (tag, content, creator, guild_id) VALUES " + - "('test', 'This is a test of the tag system', '351794468870946827', '265828729970753537')") - if err != nil { - fmt.Println(err) - } -} diff --git a/djpianalto.com/goff/utils/date_strings.go b/djpianalto.com/goff/utils/date_strings.go deleted file mode 100644 index 510288d..0000000 --- a/djpianalto.com/goff/utils/date_strings.go +++ /dev/null @@ -1,64 +0,0 @@ -package utils - -import ( - "fmt" - "time" -) - -func ParseDateString(inTime time.Time) string { - d := time.Now().Sub(inTime) - s := int64(d.Seconds()) - days := s / 86400 - s = s - (days * 86400) - hours := s / 3600 - s = s - (hours * 3600) - minutes := s / 60 - seconds := s - (minutes * 60) - dateString := "" - if days != 0 { - dateString += fmt.Sprintf("%v days ", days) - } - if hours != 0 { - dateString += fmt.Sprintf("%v hours ", hours) - } - if minutes != 0 { - dateString += fmt.Sprintf("%v minutes ", minutes) - } - if seconds != 0 { - dateString += fmt.Sprintf("%v seconds ", seconds) - } - if dateString != "" { - dateString += " ago." - } else { - dateString = "Now" - } - stamp := inTime.Format("2006-01-02 15:04:05") - return fmt.Sprintf("%v\n%v", dateString, stamp) -} - -func ParseDurationString(inDur time.Duration) string { - s := int64(inDur.Seconds()) - days := s / 86400 - s = s - (days * 86400) - hours := s / 3600 - s = s - (hours * 3600) - minutes := s / 60 - seconds := s - (minutes * 60) - durString := "" - if days != 0 { - durString += fmt.Sprintf("%v days ", days) - } - if hours != 0 { - durString += fmt.Sprintf("%v hours ", hours) - } - if minutes != 0 { - durString += fmt.Sprintf("%v minutes ", minutes) - } - if seconds != 0 { - durString += fmt.Sprintf("%v seconds ", seconds) - } - if durString == "" { - durString = "0 seconds" - } - return fmt.Sprintf("%v", durString) -} diff --git a/djpianalto.com/goff/utils/email.go b/djpianalto.com/goff/utils/email.go deleted file mode 100644 index 305ff6a..0000000 --- a/djpianalto.com/goff/utils/email.go +++ /dev/null @@ -1,132 +0,0 @@ -package utils - -import ( - "io" - "log" - "os" - "strings" - "sync" - "time" - - "github.com/bwmarrin/discordgo" - imap "github.com/emersion/go-imap" - "github.com/emersion/go-imap/client" - "github.com/emersion/go-message/mail" -) - -const () - -var ( - emailUsername = os.Getenv("GOFF_EMAIL_USERNAME") - emailPassword = os.Getenv("GOFF_EMAIL_PASSWORD") - puzzleAddress = mail.Address{ - Name: "Daily Coding Problem", - Address: "founders@dailycodingproblem.com", - } -) - -var EmailClient client.Client - -func RecieveEmail(dg *discordgo.Session) { - for { - log.Println("Connecting to Email server.") - - EmailClient, err := client.DialTLS("mail.djpianalto.com:993", nil) - if err != nil { - log.Println(err) - return - } - if err = EmailClient.Login(emailUsername, emailPassword); err != nil { - log.Println(err) - return - } - log.Println("Connected to Email server.") - - mbox, err := EmailClient.Select("INBOX", false) - if err != nil { - log.Println(err) - return - } - - if mbox.Messages == 0 { - log.Println("No Messages in Mailbox") - } - - criteria := imap.NewSearchCriteria() - criteria.WithoutFlags = []string{"\\Seen"} - uids, err := EmailClient.Search(criteria) - if err != nil { - log.Println(err) - } - if len(uids) > 0 { - seqset := new(imap.SeqSet) - seqset.AddNum(uids...) - section := &imap.BodySectionName{} - items := []imap.FetchItem{section.FetchItem()} - messages := make(chan *imap.Message, 10) - go func() { - if err = EmailClient.Fetch(seqset, items, messages); err != nil { - log.Println(err) - return - } - }() - - var wg sync.WaitGroup - - for msg := range messages { - if msg == nil { - log.Println("No New Messages") - continue - } - r := msg.GetBody(section) - if r == nil { - log.Println("Server didn't send a message body") - continue - } - wg.Add(1) - go processEmail(r, dg, &wg) - } - wg.Wait() - } - - EmailClient.Logout() - time.Sleep(300 * time.Second) - } -} - -func processEmail(r io.Reader, dg *discordgo.Session, wg *sync.WaitGroup) { - defer wg.Done() - mr, err := mail.CreateReader(r) - if err != nil { - log.Println(err) - return - } - header := mr.Header - from, err := header.AddressList("From") - if err != nil { - log.Println(err) - return - } - subject, err := header.Subject() - if err != nil { - log.Println(err) - return - } - log.Println(from) - log.Println(subject) - if addressIn(from, puzzleAddress) && - strings.Contains(subject, "Daily Coding Problem:") { - log.Println("Processing Puzzle") - ProcessPuzzleEmail(mr, dg) - } - -} - -func addressIn(s []*mail.Address, a mail.Address) bool { - for _, item := range s { - if item.String() == a.String() { - return true - } - } - return false -} diff --git a/djpianalto.com/goff/utils/logging.go b/djpianalto.com/goff/utils/logging.go deleted file mode 100644 index c3415c4..0000000 --- a/djpianalto.com/goff/utils/logging.go +++ /dev/null @@ -1,34 +0,0 @@ -package utils - -import ( - "fmt" - "github.com/bwmarrin/discordgo" -) - -var LoggingChannel = make(chan *LogEvent, 10) - -type LogEvent struct { - // Embed with log message - Embed discordgo.MessageEmbed - // Guild to log event in - GuildID string - // Discordgo Session. Needed for sending messages - Session *discordgo.Session -} - -func LoggingHandler(lc chan *LogEvent) { - for event := range lc { - var channelID string - row := Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", event.GuildID) - err := row.Scan(&channelID) - if err != nil { - fmt.Println(err) - return - } - if channelID == "" { - return - } - - _, _ = event.Session.ChannelMessageSendEmbed(channelID, &event.Embed) - } -} diff --git a/djpianalto.com/goff/utils/postfixes.go b/djpianalto.com/goff/utils/postfixes.go deleted file mode 100644 index 12d297a..0000000 --- a/djpianalto.com/goff/utils/postfixes.go +++ /dev/null @@ -1,77 +0,0 @@ -package utils - -import "log" - -type postfix struct { - Name string - Invoke func(bool) error -} - -var postfixes = []postfix{ - postfix{ - Name: "1_Update_Guild_for_Puzzle", - Invoke: updateGuildForPuzzle, - }, - postfix{ - Name: "1_Update_X_Guild_Prefixes_to_add_ID", - Invoke: updateXGuildPrefixesToAddID, - }, -} - -func RunPostfixes() { - for _, postfix := range postfixes { - queryString := "SELECT * from postfixes where name = $1" - rows, err := Database.Query(queryString, postfix.Name) - if err != nil { - log.Println(err) - continue - } - if rows.Next() { - continue - } else { - err := postfix.Invoke(false) - if err != nil { - continue - } - _, err = Database.Exec("INSERT INTO postfixes (name) VALUES ($1)", postfix.Name) - if err != nil { - log.Println(err) - continue - } - } - } -} - -func updateGuildForPuzzle(revert bool) error { - var queryString string - if !revert { - queryString = `ALTER TABLE guilds - ADD COLUMN puzzle_channel varchar(30) not null default ''` - } else { - queryString = `ALTER TABLE guilds - DROP COLUMN puzzleChat` - } - _, err := Database.Exec(queryString) - if err != nil { - log.Println(err) - return err - } - return nil -} - -func updateXGuildPrefixesToAddID(revert bool) error { - var queryString string - if !revert { - queryString = `ALTER TABLE x_guilds_prefixes - ADD COLUMN id serial primary key` - } else { - queryString = `ALTER TABLE x_guilds_prefixes - DROP COLUMN id` - } - _, err := Database.Exec(queryString) - if err != nil { - log.Println(err) - return err - } - return nil -} diff --git a/djpianalto.com/goff/utils/puzzles.go b/djpianalto.com/goff/utils/puzzles.go deleted file mode 100644 index 6edd5ad..0000000 --- a/djpianalto.com/goff/utils/puzzles.go +++ /dev/null @@ -1,93 +0,0 @@ -package utils - -import ( - "io" - "io/ioutil" - "log" - "strings" - "time" - - "github.com/bwmarrin/discordgo" - "github.com/emersion/go-message/mail" -) - -func ProcessPuzzleEmail(mr *mail.Reader, dg *discordgo.Session) { - var body []byte - for { - p, err := mr.NextPart() - if err == io.EOF { - break - } else if err != nil { - log.Println(err) - break - } - - switch h := p.Header.(type) { - case *mail.InlineHeader: - // This is the message's text (can be plain-text or HTML) - if t, _, _ := h.ContentType(); t == "text/plain" { - body, _ = ioutil.ReadAll(p.Body) - break - } - } - } - if len(body) > 0 { - s := string(body) - puzzle := strings.Split(s, "----------")[0] - date, err := mr.Header.Date() - if err != nil { - log.Println(err) - return - } - e := discordgo.MessageEmbed{ - Title: "Daily Coding Problem", - URL: "https://dailycodingproblem.com/", - Description: "```" + puzzle + "```", - Timestamp: date.Format(time.RFC3339), - Footer: &discordgo.MessageEmbedFooter{ - Text: "Daily Coding Problem", - }, - } - var guilds []Guild - queryString := `SELECT id, puzzle_channel from guilds` - rows, err := Database.Query(queryString) - if err != nil { - log.Println(err) - } - for rows.Next() { - var guild Guild - err := rows.Scan(&guild.ID, &guild.PuzzleChannel) - if err != nil { - log.Println(err) - continue - } - guilds = append(guilds, guild) - } - var puzzleID int64 - queryString = "INSERT INTO puzzles (text, time) VALUES ($1, $2) RETURNING id" - err = Database.QueryRow(queryString, puzzle, date).Scan(&puzzleID) - if err != nil { - log.Println(err) - return - } - for _, g := range guilds { - if g.PuzzleChannel == "" { - continue - } - msg := discordgo.MessageSend{ - Embed: &e, - } - m, err := dg.ChannelMessageSendComplex(g.PuzzleChannel, &msg) - if err != nil { - log.Println(err) - } - queryString = "INSERT INTO x_guilds_puzzles (guild_id, puzzle_id, message_id) VALUES ($1, $2, $3)" - _, err = Database.Exec(queryString, g.ID, puzzleID, m.ID) - if err != nil { - log.Println(err) - continue - } - } - } - -} diff --git a/djpianalto.com/goff/utils/rpn.go b/djpianalto.com/goff/utils/rpn.go deleted file mode 100644 index db39f5d..0000000 --- a/djpianalto.com/goff/utils/rpn.go +++ /dev/null @@ -1,156 +0,0 @@ -package utils - -import ( - "fmt" - "strconv" - "strings" -) - -type Operator struct { - Token string - Precedence int - Association string -} - -func (o Operator) HasHigherPrecedence(t Operator) bool { - return o.Precedence < t.Precedence // lower number is higher precedence -} - -func (o Operator) HasEqualPrecedence(t Operator) bool { - return o.Precedence == t.Precedence -} - -func (o Operator) IsLeftAssociative() bool { - return o.Association == "left" -} - -var operators = map[string]Operator{ - "+": Operator{ - Token: "+", - Precedence: 4, - Association: "left", - }, - "-": Operator{ - Token: "-", - Precedence: 4, - Association: "left", - }, - "*": Operator{ - Token: "*", - Precedence: 3, - Association: "left", - }, - "/": Operator{ - Token: "/", - Precedence: 3, - Association: "left", - }, - "%": Operator{ - Token: "%", - Precedence: 3, - Association: "left", - }, - "(": Operator{ - Token: "(", - Precedence: 1, - Association: "left", - }, - ")": Operator{ - Token: ")", - Precedence: 1, - Association: "left", - }, -} - -type Stack []Operator - -func (s *Stack) IsEmpty() bool { - return len(*s) == 0 -} - -func (s *Stack) Push(op Operator) { - *s = append(*s, op) -} - -func (s *Stack) Pop() (Operator, bool) { - if s.IsEmpty() { - return Operator{}, false - } - index := len(*s) - 1 - element := (*s)[index] - *s = (*s)[:index] - return element, true -} - -func (s *Stack) Top() Operator { - if s.IsEmpty() { - return Operator{} - } - return (*s)[len(*s)-1] -} - -func GenerateRPN(tokens []string) (string, error) { - output := "" - s := Stack{} - for _, token := range tokens { - err := processToken(token, &s, &output) - if err != nil { - return "", err - } - } - for !s.IsEmpty() { - ele, _ := s.Pop() - output += " " + ele.Token - } - - return strings.TrimSpace(output), nil -} - -func processToken(t string, s *Stack, o *string) error { - if _, err := strconv.Atoi(t); err == nil { - *o += " " + t - return nil - } else if op, ok := operators[t]; ok { - if op.Token == "(" { - s.Push(op) - } else if op.Token == ")" { - if s.IsEmpty() { - return fmt.Errorf("mismatched parentheses") - } - for s.Top().Token != "(" { - if ele, ok := s.Pop(); ok { - *o += " " + ele.Token - } else { - return fmt.Errorf("mismatched parentheses") - } - if s.IsEmpty() { - break - } - } - s.Pop() // Pop and discard the ( - } else if !s.IsEmpty() { - for { - if (s.Top().HasHigherPrecedence(op) || - (s.Top().HasEqualPrecedence(op) && - op.IsLeftAssociative())) && - s.Top().Token != "(" { - if ele, ok := s.Pop(); ok { - *o += " " + ele.Token - if s.IsEmpty() { - break - } - continue - } else { - break - } - } - break - } - s.Push(op) - } else { - s.Push(op) - } - return nil - } - return fmt.Errorf("invalid character %s", t) -} diff --git a/djpianalto.com/goff/utils/rpnParser.go b/djpianalto.com/goff/utils/rpnParser.go deleted file mode 100644 index 7f9dff9..0000000 --- a/djpianalto.com/goff/utils/rpnParser.go +++ /dev/null @@ -1,95 +0,0 @@ -package utils - -import ( - "errors" - "fmt" - "math" - "strconv" -) - -type FStack []float64 - -func (s *FStack) IsEmpty() bool { - return len(*s) == 0 -} - -func (s *FStack) Push(op float64) { - *s = append(*s, op) -} - -func (s *FStack) Pop() (float64, bool) { - if s.IsEmpty() { - return 0, false - } - index := len(*s) - 1 - element := (*s)[index] - *s = (*s)[:index] - return element, true -} - -func (s *FStack) PopTwo() (float64, float64, bool) { - if s.IsEmpty() || len(*s) < 2 { - return 0, 0, false - } - index := len(*s) - 1 - b := (*s)[index] - a := (*s)[index-1] - *s = (*s)[:index-1] - return a, b, true - -} - -func (s *FStack) Top() float64 { - if s.IsEmpty() { - return 0 - } - return (*s)[len(*s)-1] -} - -func ParseRPN(args []string) (float64, error) { - s := FStack{} - for _, token := range args { - switch token { - case "+": - if a, b, ok := s.PopTwo(); ok { - s.Push(a + b) - } else { - return 0, fmt.Errorf("not enough operands on stack for +: %v", s) - } - case "-": - if a, b, ok := s.PopTwo(); ok { - s.Push(a - b) - } else { - return 0, fmt.Errorf("not enough operands on stack for -: %v", s) - } - case "*": - if a, b, ok := s.PopTwo(); ok { - s.Push(a * b) - } else { - return 0, fmt.Errorf("not enough operands on stack for *: %v", s) - } - case "/": - if a, b, ok := s.PopTwo(); ok { - s.Push(a / b) - } else { - return 0, fmt.Errorf("not enough operands on stack for /: %v", s) - } - case "%": - if a, b, ok := s.PopTwo(); ok { - s.Push(math.Mod(a, b)) - } else { - return 0, fmt.Errorf("not enough operands on stack for %: %v", s) - } - default: - f, err := strconv.ParseFloat(token, 64) - if err != nil { - return 0, err - } - s.Push(f) - } - } - if res, ok := s.Pop(); ok { - return res, nil - } - return 0, errors.New("no result") -} diff --git a/djpianalto.com/goff/utils/snowflake.go b/djpianalto.com/goff/utils/snowflake.go deleted file mode 100644 index f26978c..0000000 --- a/djpianalto.com/goff/utils/snowflake.go +++ /dev/null @@ -1,32 +0,0 @@ -package utils - -import "time" - -type Snowflake struct { - CreationTime time.Time - WorkerID int8 - ProcessID int8 - Increment int16 -} - -func ParseSnowflake(s int64) Snowflake { - const ( - DISCORD_EPOCH = 1420070400000 - TIME_BITS_LOC = 22 - WORKER_ID_LOC = 17 - WORKER_ID_MASK = 0x3E0000 - PROCESS_ID_LOC = 12 - PROCESS_ID_MASK = 0x1F000 - INCREMENT_MASK = 0xFFF - ) - creationTime := time.Unix(((s>>TIME_BITS_LOC)+DISCORD_EPOCH)/1000.0, 0) - workerID := (s & WORKER_ID_MASK) >> WORKER_ID_LOC - processID := (s & PROCESS_ID_MASK) >> PROCESS_ID_LOC - increment := s & INCREMENT_MASK - return Snowflake{ - CreationTime: creationTime, - WorkerID: int8(workerID), - ProcessID: int8(processID), - Increment: int16(increment), - } -} diff --git a/djpianalto.com/goff/utils/tasks.go b/djpianalto.com/goff/utils/tasks.go deleted file mode 100644 index 3af8494..0000000 --- a/djpianalto.com/goff/utils/tasks.go +++ /dev/null @@ -1,132 +0,0 @@ -package utils - -import ( - "fmt" - "github.com/bwmarrin/discordgo" - "log" - "time" -) - -type Task struct { - ID int64 - Type string - Content string - GuildID string - ChannelID string - UserID string - CreationTime time.Time - TriggerTime time.Time -} - -func processTask(task *Task, s *discordgo.Session) { - query := "SELECT completed, processing from tasks where id = $1" - res, err := Database.Query(query, task.ID) - if err != nil { - log.Println(err) - return - } - var completed bool - var processing bool - res.Next() - err = res.Scan(&completed, &processing) - if err != nil { - log.Println(err) - return - } - if completed || processing { - return - } - closeQuery := "Update tasks set completed = true where id = $1" - processQuery := "UPDATE tasks SET processing = true WHERE id = $1" - defer Database.Exec(closeQuery, task.ID) - _, err = Database.Exec(processQuery, task.ID) - if err != nil { - log.Println(err) - return - } - log.Println(fmt.Sprintf("Processing task %v", task.ID)) - guild, err := s.Guild(task.GuildID) - if err != nil { - log.Print(fmt.Sprintf("Can't find guild with ID %v. Canceling task %v.", task.GuildID, task.ID)) - return - } - channel, err := s.Channel(task.ChannelID) - if err != nil { - log.Print(fmt.Sprintf("Can't find channel with ID %v. Canceling task %v.", task.ChannelID, task.ID)) - return - } - if channel.GuildID != guild.ID { - log.Print(fmt.Sprintf("The channel %v is not in guild %v. Canceling task %v.", channel.Name, guild.Name, task.ID)) - return - } - member, err := s.GuildMember(guild.ID, task.UserID) - if err != nil { - log.Print(fmt.Sprintf("Can't find user with ID %v in guild %v. Canceling task %v.", task.UserID, guild.Name, task.ID)) - return - } - if task.Type == "Reminder" { - color := s.State.UserColor(member.User.ID, channel.ID) - e := discordgo.MessageEmbed{ - Title: "REMINDER", - Description: task.Content, - Timestamp: task.CreationTime.Format(time.RFC3339), - Color: color, - Footer: &discordgo.MessageEmbedFooter{ - Text: "Created: ", - }, - } - msg := discordgo.MessageSend{ - Content: member.Mention(), - Embed: &e, - } - _, err = s.ChannelMessageSendComplex(channel.ID, &msg) - if err != nil { - log.Println(err) - } - } - processQuery = "UPDATE tasks SET processing = false WHERE id = $1" - _, err = Database.Exec(processQuery, task.ID) - if err != nil { - log.Println(err) - } - -} - -func getTasksToRun() []Task { - query := "SELECT id, type, content, guild_id, channel_id, user_id, creation_time, trigger_time " + - "from tasks where completed is false and processing is false and trigger_time < $1" - res, err := Database.Query(query, time.Now()) - if err != nil { - log.Println(err) - } - var tasks []Task - for res.Next() { - var t Task - err = res.Scan(&t.ID, &t.Type, &t.Content, &t.GuildID, &t.ChannelID, &t.UserID, &t.CreationTime, &t.TriggerTime) - if err != nil { - log.Println(err) - } - for _, task := range tasks { - if task.ID == t.ID { - continue - } - } - tasks = append(tasks, t) - } - - return tasks -} - -func ProcessTasks(s *discordgo.Session, interval int) { - for { - time.Sleep(time.Duration(interval * 1e9)) - - tasks := getTasksToRun() - - if len(tasks) > 0 { - for _, t := range tasks { - go processTask(&t, s) - } - } - } -} diff --git a/djpianalto.com/goff/utils/types.go b/djpianalto.com/goff/utils/types.go deleted file mode 100644 index 0fd92b9..0000000 --- a/djpianalto.com/goff/utils/types.go +++ /dev/null @@ -1,10 +0,0 @@ -package utils - -type Guild struct { - ID string - WelcomeMessage string - GoodbyeMessage string - LoggingChannel string - WelcomeChannel string - PuzzleChannel string -} From 96b63a4e834c1fb13229038af4216d6b2a64241e Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 15:43:45 -0800 Subject: [PATCH 03/13] Add action to build and push to ECR --- .github/workflows/main.yml | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..8bc71d0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,46 @@ +name: CI + +# Controls when the action will run. Triggers the workflow on push to master or development +# with a tag like v1.0.0 or v1.0.0-dev +on: + push: + branches: + - master + - development + tags: + - v[]+.[]+.[-0-9a-fA-F]+ + +jobs: + build: + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + with: + repositories: goff + + - name: Get Version + id: get_version + uses: battila7/get-version-action@v2.0.0 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: goff + IMAGE_TAG: ${{ steps.get_version.outputs.version-without-v }} + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG From feab8e661cf6a212f4c643f6363336e5c3626678 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 15:45:28 -0800 Subject: [PATCH 04/13] Fix globs --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8bc71d0..9646a90 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: - master - development tags: - - v[]+.[]+.[-0-9a-fA-F]+ + - v[0-9]+.[0-9]+.[-0-9a-fA-F]+ jobs: build: From 187f9816ca8abb139039fabcb475ce75be44536d Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 16:00:59 -0800 Subject: [PATCH 05/13] Fix globs --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9646a90..4dc0b05 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: - master - development tags: - - v[0-9]+.[0-9]+.[-0-9a-fA-F]+ + - v[0-9]+.[0-9]+.[\-0-9a-zA-Z]+ jobs: build: From 5411730a968bfef03d992c8098e4d58c7e5be174 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 16:03:01 -0800 Subject: [PATCH 06/13] Fix globs --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4dc0b05..dc02198 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,8 @@ on: - master - development tags: - - v[0-9]+.[0-9]+.[\-0-9a-zA-Z]+ + - v[0-9]+.[0-9]+.[\-0-9]+ + - v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z]+ jobs: build: From bec52ad5a5e12a7b10415620058b7c5f376094c5 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 16:03:44 -0800 Subject: [PATCH 07/13] Fix globs --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dc02198..c483f32 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: - master - development tags: - - v[0-9]+.[0-9]+.[\-0-9]+ + - v[0-9]+.[0-9]+.[0-9]+ - v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z]+ jobs: From f4ec8a7a493ebfb1020ed9405620d2aa6718d617 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 16:19:35 -0800 Subject: [PATCH 08/13] Fix globs --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c483f32..b6e361e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,6 +13,7 @@ on: jobs: build: + if: github.event.base_ref == 'refs/heads/master' || github.event.base_ref == 'refs/heads/development' runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job From ce1bbbc445add2c0e0a144477f603f7344fe64b4 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 16:46:05 -0800 Subject: [PATCH 09/13] Check branch name --- .github/workflows/main.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b6e361e..6220930 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,16 +4,13 @@ name: CI # with a tag like v1.0.0 or v1.0.0-dev on: push: - branches: - - master - - development tags: - v[0-9]+.[0-9]+.[0-9]+ - v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z]+ jobs: build: - if: github.event.base_ref == 'refs/heads/master' || github.event.base_ref == 'refs/heads/development' + if: ${GITHUB_REF##*/} == 'refs/heads/master' || ${GITHUB_REF##*/} == 'refs/heads/development' runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job From d1194252363d17401fd460370dd83acd59033255 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 16:51:00 -0800 Subject: [PATCH 10/13] revert to previous method of getting branch name --- .github/workflows/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6220930..8f82432 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ on: jobs: build: - if: ${GITHUB_REF##*/} == 'refs/heads/master' || ${GITHUB_REF##*/} == 'refs/heads/development' + if: github.event.base_ref == 'refs/heads/master' || github.event.base_ref == 'refs/heads/development' runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job @@ -28,8 +28,6 @@ jobs: - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - with: - repositories: goff - name: Get Version id: get_version From 3169953f3fb3d2f22fae32566b80595f44a357aa Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 17:07:37 -0800 Subject: [PATCH 11/13] Test github.ref to get branch name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8f82432..e5c5d50 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ on: jobs: build: - if: github.event.base_ref == 'refs/heads/master' || github.event.base_ref == 'refs/heads/development' + if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/development' runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job From 562ec6acafb864234b14804cac389db3ef8ecfd6 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 17:09:13 -0800 Subject: [PATCH 12/13] Test github.ref to get branch name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e5c5d50..ace0641 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ on: jobs: build: - if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/development' + if: endsWith(github.ref, 'master') || endsWith(github.ref, 'development') runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job From 87961c50818e2b35561ddc80bbccf8a92e9db2d6 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Sun, 30 Aug 2020 17:57:19 -0800 Subject: [PATCH 13/13] Remove branch check and also push latest tag --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ace0641..d877232 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,6 @@ on: jobs: build: - if: endsWith(github.ref, 'master') || endsWith(github.ref, 'development') runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job @@ -41,3 +40,5 @@ jobs: run: | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest + docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest