├── .gitignore ├── LICENSE ├── README.md ├── actions └── moderation.go ├── cmd ├── bot_config.json └── main.go ├── commands ├── bot.go ├── commands.go ├── customroles.go ├── handlers.go ├── helpers.go ├── misc.go ├── moderation.go └── usercolor.go ├── dgo.patch ├── go.mod ├── go.sum ├── models ├── config.go └── embeds.go └── permissions └── permissions.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | main 3 | cmd/main 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Marc Egerton 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scuzzy Discord Bot 2 | 3 | Scuzzy is a simple Discord bot written in Golang. It uses [discordgo](https://github.com/bwmarrin/discordgo). 4 | 5 | ## Bot Configuration 6 | A sample bot configuration is provided in the `cmd` directory. 7 | 8 | ## License 9 | This project is licensed under the BSD-3-Clause. 10 | -------------------------------------------------------------------------------- /actions/moderation.go: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import "github.com/bwmarrin/discordgo" 4 | 5 | func KickUser(s *discordgo.Session, guild string, user string, reason string) error { 6 | err := s.GuildMemberDeleteWithReason(guild, user, reason) 7 | if err != nil { 8 | return err 9 | } 10 | 11 | return nil 12 | } 13 | 14 | func BanUser(s *discordgo.Session, guild string, user string, reason string) error { 15 | err := s.GuildBanCreateWithReason(guild, user, reason, 0) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /cmd/bot_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "command_key": ".", 3 | 4 | "guild_id": "506629366659153951", 5 | 6 | "status_text": ".help", 7 | "welcome_text": "Hi! Welcome to the Hak5 Discord Server. Please be sure to check out the #rules channel and say hello in #general! Remember to use the correct channels and grab a nickname color from #bots!", 8 | "rules_text": "The Hak5 community is a place where pentesters, students, coders, enthusiasts and all-around Hak5 fans come together to help each other, inspire one another and collectively share feedback with Hak5. It's a welcoming place! We just ask that you follow these simple rules:\n\n1. BE GOOD. BE NICE. BEHAVE\nThis isn't a place for trolling – it's a place to help an encourage each other, and to provide constructive feedback. Remember, nobody was born 1337. We all started somewhere.\n\n2. DON'T SPAM\nPlease keep your posts relevant to the topic, thread or board you're posting on. Don't post random junk, troll bait or off topic ramblings – that's what YouTube is for ;)\n\n3. VIEWS EXPRESSED ARE NOT THAT OF HAK5\nWe don't prescreen any information submitted by community members. We retain the right, but not the responsibility, to edit or remove posts which violate the community guidelines. Further, Hak5 does not provide formal product support on the community forums. Hak5 may provide general product or technical information, however any information provided is offered on an \"AS IS\" basis without warranties of any kind. This disclaimer is in addition to the disclaimers and limitation of liability set forth in the Terms of Service. Similarly, community contributions such as payloads come with absolutely no warranty. You are solely responsible for the outcome of their execution.\n\nNo advertising or solicitiaion and please keep chat both ethical & legal.\n\nPlease do not post any personal order information in chat.\nIf you have questions about an order of you've placed with the Hak5 Shop please contact support via the links provided on our website https://shop.hak5.org/", 9 | "admin_roles": ["Admin", "Moderator"], 10 | "join_role_ids": [], 11 | 12 | "command_restrictions": [ 13 | { "command": "color", "mode": "white", "channels": ["714366713512067103", "698519835532984470"] }, 14 | { "command": "colors", "mode": "white", "channels": ["714366713512067103", "698519835532984470"] } 15 | ], 16 | 17 | "color_roles": [ 18 | { "color": "red", "id": "697930042491142174" }, 19 | { "color": "blue", "id": "697930093766639699" }, 20 | { "color": "green", "id": "697930441939877970" }, 21 | { "color": "purple", "id": "697930578485444610" }, 22 | { "color": "yellow", "id": "697930630247350402" }, 23 | { "color": "pink", "id": "697965484313935966" } 24 | ], 25 | 26 | "custom_roles": [ 27 | { "name": "WiFi Pineapple Beta", "short_name": "pineapple", "id": "756469985030832158" } 28 | ], 29 | 30 | "ignored_users": [], 31 | 32 | "logging_channel": "714369335254188053" 33 | } 34 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "github.com/foxtrot/scuzzy/commands" 7 | "github.com/foxtrot/scuzzy/models" 8 | "github.com/foxtrot/scuzzy/permissions" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "os/signal" 13 | "syscall" 14 | "time" 15 | 16 | "github.com/bwmarrin/discordgo" 17 | ) 18 | 19 | // Core Bot Properties 20 | var ( 21 | Token string 22 | ConfigPath string 23 | Config models.Configuration 24 | ) 25 | 26 | func getConfig() error { 27 | cf, err := ioutil.ReadFile(ConfigPath) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | err = json.Unmarshal(cf, &Config) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func main() { 41 | // Parse and Check Flags 42 | flag.StringVar(&Token, "t", "", "Bot Token") 43 | flag.StringVar(&ConfigPath, "c", "", "Config Path") 44 | flag.Parse() 45 | 46 | if len(Token) == 0 { 47 | log.Fatal("[!] Error: No Auth Token supplied.") 48 | } 49 | if len(ConfigPath) == 0 { 50 | log.Fatal("[!] Error: No Config Path supplied.") 51 | } 52 | 53 | // Get Config 54 | err := getConfig() 55 | if err != nil { 56 | log.Fatal("[!] Error: " + err.Error()) 57 | } 58 | Config.ConfigPath = ConfigPath 59 | 60 | // Instantiate Bot 61 | bot, err := discordgo.New("Bot " + Token) 62 | if err != nil { 63 | log.Fatal("[!] Error: " + err.Error()) 64 | } 65 | 66 | // Enable Reconnect 67 | bot.ShouldReconnectOnError = true 68 | 69 | // Enable Message Caching (Last 1024 Events) 70 | bot.State.MaxMessageCount = 1024 71 | bot.State.TrackChannels = true 72 | bot.State.TrackMembers = true 73 | 74 | // Open Connection 75 | err = bot.Open() 76 | if err != nil { 77 | log.Fatal("[!] Error: " + err.Error()) 78 | } 79 | 80 | // Setup Auth 81 | g, err := bot.Guild(Config.GuildID) 82 | if err != nil { 83 | log.Fatal("[!] Error: " + err.Error()) 84 | } 85 | var p *permissions.Permissions 86 | p = permissions.New(&Config, g) 87 | 88 | Config.GuildName = g.Name 89 | 90 | // Setup Handlers 91 | c := commands.Commands{ 92 | Token: Token, 93 | Permissions: p, 94 | Config: &Config, 95 | } 96 | c.RegisterHandlers() 97 | 98 | // Add Handlers for Bot 99 | bot.AddHandler(c.ProcessMessage) 100 | 101 | log.Printf("[*] Bot Running.\n") 102 | 103 | // Set Bot Status 104 | go func() { 105 | usd := discordgo.UpdateStatusData{ 106 | IdleSince: nil, 107 | AFK: false, 108 | Status: "online", 109 | } 110 | err = bot.UpdateStatusComplex(usd) 111 | if err != nil { 112 | log.Fatal("[!] Error: " + err.Error()) 113 | } 114 | 115 | // For some reason the bot's status will regularly disappear... 116 | for range time.Tick(10 * time.Minute) { 117 | err := bot.UpdateStatusComplex(usd) 118 | if err != nil { 119 | log.Fatal("[!] Error: " + err.Error()) 120 | } 121 | } 122 | }() 123 | 124 | // Catch SIGINT 125 | sc := make(chan os.Signal, 1) 126 | signal.Notify(sc, syscall.SIGINT, syscall.SIGKILL) 127 | <-sc 128 | 129 | err = bot.Close() 130 | if err != nil { 131 | log.Fatal("[!] Error: " + err.Error()) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /commands/bot.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "github.com/bwmarrin/discordgo" 6 | "log" 7 | "os" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func (c *Commands) handleSetStatus(s *discordgo.Session, m *discordgo.MessageCreate) error { 13 | stSplit := strings.SplitN(m.Content, " ", 2) 14 | if len(stSplit) < 2 { 15 | return errors.New("You did not specify a status.") 16 | } 17 | 18 | st := stSplit[1] 19 | 20 | err := s.UpdateGameStatus(0, st) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | msg := c.CreateDefinedEmbed("Set Status", "Operation completed successfully.", "success", m.Author) 26 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, msg) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | return nil 32 | } 33 | 34 | func (c *Commands) handleDisconnect(s *discordgo.Session, m *discordgo.MessageCreate) error { 35 | msg := c.CreateDefinedEmbed("Disconnect", "Attempting Disconnect...", "", m.Author) 36 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, msg) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | err = s.Close() 42 | if err != nil { 43 | return err 44 | } 45 | 46 | os.Exit(0) 47 | 48 | return nil 49 | } 50 | 51 | func (c *Commands) handleReconnect(s *discordgo.Session, m *discordgo.MessageCreate) error { 52 | t := time.Now() 53 | 54 | err := s.Close() 55 | if err != nil { 56 | return err 57 | } 58 | 59 | err = s.Open() 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | 64 | msg := c.CreateDefinedEmbed("Reconnect", "Reconnected Successfully.\nTime: `"+time.Since(t).String()+"`.", "success", m.Author) 65 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, msg) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /commands/commands.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/bwmarrin/discordgo" 5 | "github.com/foxtrot/scuzzy/models" 6 | "github.com/foxtrot/scuzzy/permissions" 7 | ) 8 | 9 | type ScuzzyHandler func(session *discordgo.Session, m *discordgo.MessageCreate) error 10 | 11 | type ScuzzyCommand struct { 12 | Index int 13 | Name string 14 | Description string 15 | AdminOnly bool 16 | Handler ScuzzyHandler 17 | } 18 | 19 | type Commands struct { 20 | Token string 21 | Permissions *permissions.Permissions 22 | Config *models.Configuration 23 | ScuzzyCommands map[string]ScuzzyCommand 24 | ScuzzyCommandsByIndex map[int]ScuzzyCommand 25 | } 26 | -------------------------------------------------------------------------------- /commands/customroles.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | discordgo "github.com/bwmarrin/discordgo" 6 | "github.com/foxtrot/scuzzy/models" 7 | "strings" 8 | ) 9 | 10 | func (c *Commands) handleListCustomRoles(s *discordgo.Session, m *discordgo.MessageCreate) error { 11 | msgC := "You can choose from the following roles:\n\n" 12 | for _, v := range c.Config.CustomRoles { 13 | msgC += "<@&" + v.ID + "> (" + v.ShortName + ")\n" 14 | } 15 | msgC += "\n\n Use `" + c.Config.CommandKey + "joinrole ` to join a role.\n" 16 | msgC += "Example: `" + c.Config.CommandKey + "joinrole pineapple`.\n" 17 | 18 | msg := c.CreateDefinedEmbed("Joinable Roles", msgC, "", m.Author) 19 | 20 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, msg) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func (c *Commands) handleJoinCustomRole(s *discordgo.Session, m *discordgo.MessageCreate) error { 29 | var err error 30 | 31 | rUserID := m.Author.ID 32 | 33 | userInput := strings.Split(m.Content, " ") 34 | if len(userInput) < 2 { 35 | err = c.handleListCustomRoles(s, m) 36 | return err 37 | } 38 | 39 | desiredRole := userInput[1] 40 | desiredRole = strings.ToLower(desiredRole) 41 | desiredRoleID := "" 42 | 43 | for _, role := range c.Config.CustomRoles { 44 | if role.ShortName == desiredRole { 45 | desiredRoleID = role.ID 46 | break 47 | } 48 | } 49 | 50 | if len(desiredRoleID) == 0 { 51 | err = c.handleListCustomRoles(s, m) 52 | return err 53 | } 54 | 55 | err = s.GuildMemberRoleAdd(m.GuildID, rUserID, desiredRoleID) 56 | if err != nil { 57 | return err 58 | } else { 59 | msg := c.CreateDefinedEmbed("Join Role", "<@"+m.Author.ID+">: You have joined <@&"+desiredRoleID+">!", "success", m.Author) 60 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, msg) 61 | if err != nil { 62 | return err 63 | } 64 | } 65 | 66 | err = s.ChannelMessageDelete(m.ChannelID, m.ID) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func (c *Commands) handleLeaveCustomRole(s *discordgo.Session, m *discordgo.MessageCreate) error { 75 | var err error 76 | 77 | rUserID := m.Author.ID 78 | 79 | userInput := strings.Split(m.Content, " ") 80 | if len(userInput) < 2 { 81 | err = c.handleListCustomRoles(s, m) 82 | return err 83 | } 84 | 85 | desiredRole := userInput[1] 86 | desiredRole = strings.ToLower(desiredRole) 87 | desiredRoleID := "" 88 | 89 | for _, role := range c.Config.CustomRoles { 90 | if role.ShortName == desiredRole { 91 | desiredRoleID = role.ID 92 | break 93 | } 94 | } 95 | 96 | if len(desiredRoleID) == 0 { 97 | err = c.handleListCustomRoles(s, m) 98 | return err 99 | } 100 | 101 | err = s.GuildMemberRoleRemove(m.GuildID, rUserID, desiredRoleID) 102 | if err != nil { 103 | return err 104 | } else { 105 | msg := c.CreateDefinedEmbed("Leave Role", "<@"+m.Author.ID+">: You have left <@&"+desiredRoleID+">!", "success", m.Author) 106 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, msg) 107 | if err != nil { 108 | return err 109 | } 110 | } 111 | 112 | err = s.ChannelMessageDelete(m.ChannelID, m.ID) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | return nil 118 | } 119 | 120 | func (c *Commands) handleAddCustomRole(s *discordgo.Session, m *discordgo.MessageCreate) error { 121 | var err error 122 | 123 | userInput := strings.Split(m.Content, " ") 124 | if len(userInput) < 3 { 125 | return errors.New("Expected Arguments: short_name role_id") 126 | } 127 | 128 | shortName := userInput[1] 129 | shortName = strings.ToLower(shortName) 130 | roleID := userInput[2] 131 | 132 | customRole := models.CustomRole{ 133 | Name: "", 134 | ShortName: shortName, 135 | ID: roleID, 136 | } 137 | 138 | c.Config.CustomRoles = append(c.Config.CustomRoles, customRole) 139 | 140 | err = c.handleSaveConfig(s, m) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | return nil 146 | } 147 | -------------------------------------------------------------------------------- /commands/handlers.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/bwmarrin/discordgo" 10 | ) 11 | 12 | func (c *Commands) RegisterCommand(name string, description string, adminonly bool, handler ScuzzyHandler) { 13 | log.Printf("[*] Registering Command '%s'\n", name) 14 | co := ScuzzyCommand{ 15 | Index: len(c.ScuzzyCommands) + 1, 16 | Name: name, 17 | Description: description, 18 | AdminOnly: adminonly, 19 | Handler: handler, 20 | } 21 | c.ScuzzyCommands[name] = co 22 | c.ScuzzyCommandsByIndex[co.Index] = co 23 | } 24 | 25 | func (c *Commands) RegisterHandlers() { 26 | c.ScuzzyCommands = make(map[string]ScuzzyCommand) 27 | c.ScuzzyCommandsByIndex = make(map[int]ScuzzyCommand) 28 | 29 | // Misc Commands 30 | c.RegisterCommand("help", "Show Help Text", false, c.handleHelp) 31 | c.RegisterCommand("info", "Show Bot Info", false, c.handleInfo) 32 | c.RegisterCommand("md", "Show common Discord MarkDown formatting", false, c.handleMarkdownInfo) 33 | c.RegisterCommand("userinfo", "Display a users information", false, c.handleUserInfo) 34 | c.RegisterCommand("serverinfo", "Display the current servers information", false, c.handleServerInfo) 35 | c.RegisterCommand("no", "", false, c.handleCat) 36 | 37 | // User Settings 38 | c.RegisterCommand("colours", "Display available colour roles", false, c.handleUserColors) 39 | c.RegisterCommand("colors", "", false, c.handleUserColors) 40 | c.RegisterCommand("colour", "Set a colour role for yourself", false, c.handleUserColor) 41 | c.RegisterCommand("color", "", false, c.handleUserColor) 42 | c.RegisterCommand("listroles", "List user joinable roles", false, c.handleListCustomRoles) 43 | c.RegisterCommand("joinrole", "Join an available role for yourself", false, c.handleJoinCustomRole) 44 | c.RegisterCommand("leaverole", "Leave an available role", false, c.handleLeaveCustomRole) 45 | 46 | // Conversion Helpers 47 | c.RegisterCommand("ctof", "Convert Celsius to Farenheit", false, c.handleCtoF) 48 | c.RegisterCommand("ftoc", "Convert Farenheit to Celsius", false, c.handleFtoC) 49 | c.RegisterCommand("metofe", "Convert Meters to Feet", false, c.handleMetersToFeet) 50 | c.RegisterCommand("fetome", "Convert Feet to Meters", false, c.handleFeetToMeters) 51 | c.RegisterCommand("cmtoin", "Convert Centimeters to Inches", false, c.handleCentimeterToInch) 52 | c.RegisterCommand("intocm", "Convert Inches to Centimeters", false, c.handleInchToCentimeter) 53 | c.RegisterCommand("google4u", "Displays a letmegooglethat link", false, c.handleGoogle4U) 54 | 55 | // Admin Commands 56 | c.RegisterCommand("ping", "Ping Scuzzy", true, c.handlePing) 57 | c.RegisterCommand("rules", "Display the Server Rules", true, c.handleRules) 58 | c.RegisterCommand("status", "Set Bot Status", true, c.handleSetStatus) 59 | c.RegisterCommand("purge", "Purge Channel Messages", true, c.handlePurgeChannel) 60 | c.RegisterCommand("kick", "Kick a User", true, c.handleKickUser) 61 | c.RegisterCommand("ban", "Ban a User", true, c.handleBanUser) 62 | c.RegisterCommand("slow", "Set Channel Slow Mode", true, c.handleSetSlowmode) 63 | c.RegisterCommand("unslow", "Unset Channel Slow Mode", true, c.handleUnsetSlowmode) 64 | c.RegisterCommand("ignore", "Add a user to Scuzzy's ignore list", true, c.handleIgnoreUser) 65 | c.RegisterCommand("unignore", "Remove a user from Scuzzy's ignore list", true, c.handleUnIgnoreUser) 66 | c.RegisterCommand("setconfig", "Set Configuration", true, c.handleSetConfig) 67 | c.RegisterCommand("getconfig", "Print Configuration", true, c.handleGetConfig) 68 | c.RegisterCommand("saveconfig", "Save Configuration to Disk", true, c.handleSaveConfig) 69 | c.RegisterCommand("reloadconfig", "Reload Configuration", true, c.handleReloadConfig) 70 | c.RegisterCommand("addrole", "Add a joinable role", true, c.handleAddCustomRole) 71 | } 72 | 73 | func (c *Commands) ProcessCommand(s *discordgo.Session, m *discordgo.MessageCreate) error { 74 | cKey := c.Config.CommandKey 75 | cCmd := strings.Split(m.Content, " ")[0] 76 | 77 | // Ignore the bot itself 78 | if m.Author.ID == s.State.User.ID { 79 | return nil 80 | } 81 | 82 | // Ignore anything not starting with the command prefix 83 | if !strings.HasPrefix(cCmd, cKey) { 84 | return nil 85 | } 86 | 87 | // Ignore Direct Messages 88 | if m.Member == nil { 89 | return nil 90 | } 91 | 92 | // Ignore any users on the ignore list 93 | if c.Permissions.CheckIgnoredUser(m.Author) { 94 | log.Printf("[*] Ignoring command from ignored user.") 95 | return nil 96 | } 97 | 98 | cName := strings.Split(cCmd, cKey)[1] 99 | 100 | if cmd, ok := c.ScuzzyCommands[cName]; ok { 101 | if cmd.AdminOnly && !c.Permissions.CheckAdminRole(m.Member) { 102 | log.Printf("[*] User %s tried to run admin command %s\n", m.Author.Username, cName) 103 | return nil 104 | } 105 | 106 | log.Printf("[*] Running command %s (Requested by %s)\n", cName, m.Author.Username) 107 | 108 | err := cmd.Handler(s, m) 109 | if err != nil { 110 | log.Printf("[!] Command %s (Requested by %s) had error: '%s'\n", cName, m.Author.Username, err.Error()) 111 | 112 | eMsg := c.CreateDefinedEmbed("Error ("+cName+")", err.Error(), "error", m.Author) 113 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, eMsg) 114 | if err != nil { 115 | return err 116 | } 117 | } 118 | } 119 | 120 | return nil 121 | } 122 | 123 | func (c *Commands) ProcessMessageDelete(s *discordgo.Session, m *discordgo.MessageDelete) error { 124 | msgChannelID := m.ChannelID 125 | 126 | if m.BeforeDelete == nil { 127 | return errors.New("Couldn't get deleted message data.") 128 | } 129 | 130 | msgContent := m.BeforeDelete.Content 131 | msgAuthor := m.BeforeDelete.Author 132 | 133 | msg := "`Username` - " + msgAuthor.Username + "#" + msgAuthor.Discriminator + "\n" 134 | msg += "`User ID` - " + msgAuthor.ID + "\n" 135 | msg += "`Channel` - <#" + msgChannelID + ">\n" 136 | msg += "`Message` - " + msgContent + "\n" 137 | 138 | embed := c.CreateDefinedEmbed("Deleted Message", msg, "", msgAuthor) 139 | _, err := s.ChannelMessageSendEmbed(c.Config.LoggingChannel, embed) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | return nil 145 | } 146 | 147 | func (c *Commands) ProcessMessageDeleteBulk(s *discordgo.Session, m *discordgo.MessageDeleteBulk) error { 148 | msgChannelID := m.ChannelID 149 | 150 | msg := "`Channel` - <#" + msgChannelID + ">\n" 151 | msg += "Message IDs: \n" 152 | for k, v := range m.Messages { 153 | msg += strconv.Itoa(k) + ": `" + v + "`\n" 154 | } 155 | 156 | embed := c.CreateDefinedEmbed("Deleted Bulk Messages", msg, "", nil) 157 | _, err := s.ChannelMessageSendEmbed(c.Config.LoggingChannel, embed) 158 | if err != nil { 159 | return err 160 | } 161 | 162 | return nil 163 | } 164 | 165 | func (c *Commands) ProcessUserJoin(s *discordgo.Session, m *discordgo.GuildMemberAdd) error { 166 | userChannel, err := s.UserChannelCreate(m.User.ID) 167 | if err != nil { 168 | log.Print("[!] Error (User Join): " + err.Error()) 169 | return err 170 | } 171 | 172 | _, err = s.ChannelMessageSend(userChannel.ID, c.Config.WelcomeText) 173 | if err != nil { 174 | log.Print("[!] Error (User Join): " + err.Error()) 175 | return err 176 | } 177 | 178 | for _, roleID := range c.Config.JoinRoleIDs { 179 | err = s.GuildMemberRoleAdd(c.Config.GuildID, m.User.ID, roleID) 180 | if err != nil { 181 | log.Print("[!] Error (User Join)" + err.Error()) 182 | return err 183 | } 184 | } 185 | 186 | return nil 187 | } 188 | 189 | func (c *Commands) ProcessMessage(s *discordgo.Session, m interface{}) { 190 | switch m.(type) { 191 | case *discordgo.MessageCreate: 192 | // Pass Messages to the command processor 193 | err := c.ProcessCommand(s, m.(*discordgo.MessageCreate)) 194 | if err != nil { 195 | log.Println("[!] Error: " + err.Error()) 196 | } 197 | break 198 | case *discordgo.MessageDelete: 199 | // Log deleted messages to the logging channel. 200 | err := c.ProcessMessageDelete(s, m.(*discordgo.MessageDelete)) 201 | if err != nil { 202 | eMsg := c.CreateDefinedEmbed("Error (Message Deleted)", err.Error(), "error", nil) 203 | _, err = s.ChannelMessageSendEmbed(c.Config.LoggingChannel, eMsg) 204 | if err != nil { 205 | log.Println("[!] Error " + err.Error()) 206 | } 207 | } 208 | break 209 | case *discordgo.MessageDeleteBulk: 210 | err := c.ProcessMessageDeleteBulk(s, m.(*discordgo.MessageDeleteBulk)) 211 | if err != nil { 212 | eMsg := c.CreateDefinedEmbed("Error (Message Bulk Deleted)", err.Error(), "error", nil) 213 | _, err = s.ChannelMessageSendEmbed(c.Config.LoggingChannel, eMsg) 214 | if err != nil { 215 | log.Println("[!] Error " + err.Error()) 216 | } 217 | } 218 | break 219 | case *discordgo.GuildMemberAdd: 220 | // Handle new member (Welcome message, etc) 221 | err := c.ProcessUserJoin(s, m.(*discordgo.GuildMemberAdd)) 222 | if err != nil { 223 | log.Println("[!] Error (Guild Member Joined): " + err.Error()) 224 | } 225 | break 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /commands/helpers.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/bwmarrin/discordgo" 5 | "github.com/foxtrot/scuzzy/models" 6 | "time" 7 | ) 8 | 9 | func (c *Commands) CreateDefinedEmbed(title string, desc string, status string, user *discordgo.User) *discordgo.MessageEmbed { 10 | msgColor := 0x000000 11 | 12 | switch status { 13 | case "error": 14 | msgColor = 0xCC0000 15 | break 16 | case "success": 17 | msgColor = 0x00CC00 18 | break 19 | default: 20 | msgColor = 0xFFA500 21 | } 22 | 23 | ftrText := "" 24 | if user != nil { 25 | ftrText += "Requested by " + user.Username + "#" + user.Discriminator 26 | } 27 | 28 | ftr := discordgo.MessageEmbedFooter{ 29 | Text: ftrText, 30 | IconURL: "https://cdn.discordapp.com/avatars/514163441548656641/a4ede220fea0ad8872b86f3eebc45524.png?size=128", 31 | ProxyIconURL: "", 32 | } 33 | 34 | msg := discordgo.MessageEmbed{ 35 | URL: "", 36 | Type: "", 37 | Title: title, 38 | Description: desc, 39 | Timestamp: time.Now().Format(time.RFC3339), 40 | Color: msgColor, 41 | Footer: &ftr, 42 | Image: nil, 43 | Thumbnail: nil, 44 | Video: nil, 45 | Provider: nil, 46 | Author: nil, 47 | Fields: nil, 48 | } 49 | 50 | return &msg 51 | } 52 | 53 | func (c *Commands) CreateCustomEmbed(embedData *models.CustomEmbed) *discordgo.MessageEmbed { 54 | var typ discordgo.EmbedType 55 | var ftr discordgo.MessageEmbedFooter 56 | var img discordgo.MessageEmbedImage 57 | var thm discordgo.MessageEmbedThumbnail 58 | var prv discordgo.MessageEmbedProvider 59 | var atr discordgo.MessageEmbedAuthor 60 | 61 | typ = discordgo.EmbedType(embedData.Type) 62 | 63 | ftr.Text = embedData.FooterText 64 | ftr.IconURL = embedData.FooterImageURL 65 | 66 | img.URL = embedData.ImageURL 67 | img.Height = embedData.ImageH 68 | img.Width = embedData.ImageW 69 | 70 | thm.URL = embedData.ThumbnailURL 71 | thm.Height = embedData.ThumbnailH 72 | thm.Width = embedData.ThumbnailW 73 | 74 | prv.Name = embedData.ProviderText 75 | prv.URL = embedData.ProviderURL 76 | 77 | atr.Name = embedData.AuthorText 78 | atr.URL = embedData.AuthorURL 79 | atr.IconURL = embedData.AuthorImageURL 80 | 81 | msg := discordgo.MessageEmbed{ 82 | URL: embedData.URL, 83 | Type: typ, 84 | Title: embedData.Title, 85 | Description: embedData.Desc, 86 | Timestamp: "", 87 | Color: embedData.Color, 88 | Footer: &ftr, 89 | Image: &img, 90 | Thumbnail: &thm, 91 | Video: nil, 92 | Provider: &prv, 93 | Author: &atr, 94 | Fields: nil, 95 | } 96 | 97 | return &msg 98 | } 99 | -------------------------------------------------------------------------------- /commands/misc.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "net/url" 9 | "os" 10 | "reflect" 11 | "sort" 12 | "strconv" 13 | "strings" 14 | "time" 15 | 16 | "github.com/bwmarrin/discordgo" 17 | "github.com/foxtrot/scuzzy/models" 18 | ) 19 | 20 | func (c *Commands) handleSetConfig(s *discordgo.Session, m *discordgo.MessageCreate) error { 21 | configArgs := strings.Split(m.Content, " ") 22 | 23 | if len(configArgs) != 3 { 24 | return errors.New("Invalid arguments supplied. Usage: " + c.Config.CommandKey + "setconfig ") 25 | } 26 | 27 | configKey := configArgs[1] 28 | configVal := configArgs[2] 29 | 30 | rt := reflect.TypeOf(c.Config) 31 | for i := 0; i < rt.NumField(); i++ { 32 | x := rt.Field(i) 33 | tagVal := strings.Split(x.Tag.Get("json"), ",")[0] 34 | tagName := x.Name 35 | 36 | if tagVal == configKey { 37 | prop := reflect.ValueOf(&c.Config).Elem().FieldByName(tagName) 38 | 39 | switch prop.Interface().(type) { 40 | case string: 41 | prop.SetString(configVal) 42 | break 43 | case int: 44 | intVal, err := strconv.ParseInt(configVal, 10, 64) 45 | if err != nil { 46 | return err 47 | } 48 | prop.SetInt(intVal) 49 | break 50 | case float64: 51 | floatVal, err := strconv.ParseFloat(configVal, 64) 52 | if err != nil { 53 | return err 54 | } 55 | prop.SetFloat(floatVal) 56 | break 57 | case bool: 58 | boolVal, err := strconv.ParseBool(configVal) 59 | if err != nil { 60 | return err 61 | } 62 | prop.SetBool(boolVal) 63 | break 64 | default: 65 | return errors.New("Unsupported key value type") 66 | } 67 | 68 | msgE := c.CreateDefinedEmbed("Set Configuration", "Successfully set property '"+configKey+"'!", "success", m.Author) 69 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, msgE) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | return nil 75 | } 76 | } 77 | 78 | return errors.New("Unknown key specified") 79 | } 80 | 81 | func (c *Commands) handleGetConfig(s *discordgo.Session, m *discordgo.MessageCreate) error { 82 | //TODO: Handle printing of slices (check the Type, loop accordingly) 83 | 84 | configArgs := strings.Split(m.Content, " ") 85 | configKey := "all" 86 | if len(configArgs) == 2 { 87 | configKey = configArgs[1] 88 | } 89 | 90 | msg := "" 91 | 92 | rt := reflect.TypeOf(*c.Config) 93 | for i := 0; i < rt.NumField(); i++ { 94 | x := rt.Field(i) 95 | tagVal := strings.Split(x.Tag.Get("json"), ",")[0] 96 | tagName := x.Name 97 | prop := reflect.ValueOf(c.Config).Elem().FieldByName(tagName) 98 | 99 | if configKey == "all" { 100 | switch prop.Interface().(type) { 101 | case string: 102 | if len(prop.String()) > 256 { 103 | // Truncate large values. 104 | msg += "`" + tagName + "` - " + "Truncated...\n" 105 | } else { 106 | msg += "`" + tagName + "` - `" + prop.String() + "`\n" 107 | } 108 | break 109 | default: 110 | // Ignore non strings for now... 111 | msg += "`" + tagName + "` - Skipped Value\n" 112 | continue 113 | } 114 | } else { 115 | if tagVal == configKey { 116 | switch prop.Interface().(type) { 117 | case string: 118 | msg += "`" + tagName + "` - `" + prop.String() + "`\n" 119 | default: 120 | // Ignore non strings for now... 121 | msg += "`" + tagName + "` - Skipped Value\n" 122 | } 123 | 124 | eMsg := c.CreateDefinedEmbed("Get Configuration", msg, "success", m.Author) 125 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, eMsg) 126 | if err != nil { 127 | return err 128 | } 129 | 130 | return nil 131 | } 132 | } 133 | } 134 | 135 | if msg == "" { 136 | return errors.New("Unknown key specified") 137 | } 138 | 139 | eMsg := c.CreateDefinedEmbed("Get Configuration", msg, "success", m.Author) 140 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, eMsg) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | return nil 146 | } 147 | 148 | func (c *Commands) handleReloadConfig(s *discordgo.Session, m *discordgo.MessageCreate) error { 149 | fBuf, err := ioutil.ReadFile(c.Config.ConfigPath) 150 | if err != nil { 151 | return err 152 | } 153 | 154 | conf := &models.Configuration{} 155 | 156 | err = json.Unmarshal(fBuf, &conf) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | c.Config = conf 162 | c.Permissions.Config = conf 163 | 164 | eMsg := c.CreateDefinedEmbed("Reload Configuration", "Successfully reloaded configuration from disk", "success", m.Author) 165 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, eMsg) 166 | if err != nil { 167 | return err 168 | } 169 | 170 | return nil 171 | } 172 | 173 | func (c *Commands) handleSaveConfig(s *discordgo.Session, m *discordgo.MessageCreate) error { 174 | j, err := json.Marshal(c.Config) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | err = ioutil.WriteFile(c.Config.ConfigPath, j, os.ModePerm) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | eMsg := c.CreateDefinedEmbed("Save Configuration", "Saved runtime configuration successfully", "success", m.Author) 185 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, eMsg) 186 | if err != nil { 187 | return err 188 | } 189 | 190 | return nil 191 | } 192 | 193 | func (c *Commands) handleCat(s *discordgo.Session, m *discordgo.MessageCreate) error { 194 | _, err := s.ChannelMessageSend(m.ChannelID, "https://giphy.com/gifs/cat-cute-no-rCxogJBzaeZuU") 195 | if err != nil { 196 | return err 197 | } 198 | 199 | err = s.ChannelMessageDelete(m.ChannelID, m.ID) 200 | if err != nil { 201 | return err 202 | } 203 | 204 | return nil 205 | } 206 | 207 | func (c *Commands) handlePing(s *discordgo.Session, m *discordgo.MessageCreate) error { 208 | var r *discordgo.Message 209 | var err error 210 | 211 | msg := c.CreateDefinedEmbed("Ping", "Pong", "success", m.Author) 212 | r, err = s.ChannelMessageSendEmbed(m.ChannelID, msg) 213 | if err != nil { 214 | return err 215 | } 216 | 217 | time.Sleep(5 * time.Second) 218 | 219 | err = s.ChannelMessageDelete(m.ChannelID, r.ID) 220 | if err != nil { 221 | return err 222 | } 223 | err = s.ChannelMessageDelete(m.ChannelID, m.ID) 224 | if err != nil { 225 | return err 226 | } 227 | 228 | return nil 229 | } 230 | 231 | func (c *Commands) handleInfo(s *discordgo.Session, m *discordgo.MessageCreate) error { 232 | desc := "**Source**: https://github.com/foxtrot/scuzzy\n" 233 | desc += "**Language**: Go\n" 234 | desc += "**Commands**: See `" + c.Config.CommandKey + "help`\n\n\n" 235 | 236 | gm, err := s.GuildMember(c.Config.GuildID, s.State.User.ID) 237 | if err != nil { 238 | return err 239 | } 240 | 241 | d := models.CustomEmbed{ 242 | Title: "Scuzzy Information", 243 | Desc: desc, 244 | ImageURL: "", 245 | ImageH: 100, 246 | ImageW: 100, 247 | Color: 0xFFA500, 248 | URL: "", 249 | Type: "", 250 | Timestamp: "", 251 | FooterText: "Made with ❤ by Foxtrot", 252 | FooterImageURL: "https://cdn.discordapp.com/avatars/514163441548656641/a_ac5e022e77e62e7793711ebde8cdf4a1.gif", 253 | ThumbnailURL: gm.User.AvatarURL(""), 254 | ThumbnailH: 150, 255 | ThumbnailW: 150, 256 | ProviderURL: "", 257 | ProviderText: "", 258 | AuthorText: "", 259 | AuthorURL: "", 260 | AuthorImageURL: "", 261 | } 262 | 263 | msg := c.CreateCustomEmbed(&d) 264 | 265 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, msg) 266 | if err != nil { 267 | return err 268 | } 269 | 270 | return nil 271 | } 272 | 273 | func (c *Commands) handleHelp(s *discordgo.Session, m *discordgo.MessageCreate) error { 274 | keys := make([]int, 0, len(c.ScuzzyCommands)) 275 | for _, cmd := range c.ScuzzyCommands { 276 | keys = append(keys, cmd.Index) 277 | } 278 | sort.Ints(keys) 279 | 280 | for _, k := range keys { 281 | fmt.Println(k, c.ScuzzyCommandsByIndex[k]) 282 | } 283 | 284 | desc := "**Available Commands**\n" 285 | for _, k := range keys { 286 | command := c.ScuzzyCommandsByIndex[k] 287 | 288 | if !command.AdminOnly && command.Description != "" { 289 | desc += "`" + command.Name + "` - " + command.Description + "\n" 290 | } 291 | } 292 | 293 | if c.Permissions.CheckAdminRole(m.Member) { 294 | desc += "\n" 295 | desc += "**Admin Commands**\n" 296 | for _, k := range keys { 297 | command := c.ScuzzyCommandsByIndex[k] 298 | 299 | if command.AdminOnly { 300 | desc += "`" + command.Name + "` - " + command.Description + "\n" 301 | } 302 | } 303 | } 304 | 305 | desc += "\n\nAll commands are prefixed with `" + c.Config.CommandKey + "`\n" 306 | 307 | msg := c.CreateDefinedEmbed("Help", desc, "", m.Author) 308 | 309 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, msg) 310 | if err != nil { 311 | return err 312 | } 313 | err = s.ChannelMessageDelete(m.ChannelID, m.ID) 314 | if err != nil { 315 | return err 316 | } 317 | 318 | return nil 319 | } 320 | 321 | func (c *Commands) handleRules(s *discordgo.Session, m *discordgo.MessageCreate) error { 322 | msg := c.Config.RulesText 323 | embedTitle := "Rules (" + c.Config.GuildName + ")" 324 | embed := c.CreateDefinedEmbed(embedTitle, msg, "success", m.Author) 325 | 326 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, embed) 327 | if err != nil { 328 | return err 329 | } 330 | 331 | return nil 332 | } 333 | 334 | func (c *Commands) handleMarkdownInfo(s *discordgo.Session, m *discordgo.MessageCreate) error { 335 | cleanup := true 336 | args := strings.Split(m.Content, " ") 337 | 338 | if len(args) == 2 { 339 | if args[1] == "stay" && c.Permissions.CheckAdminRole(m.Member) { 340 | cleanup = false 341 | } 342 | } 343 | 344 | desc := "*Italic* text goes between `*single asterisks*`\n" 345 | desc += "**Bold** text goes between `**double asterisks**`\n" 346 | desc += "***Bold and Italic*** text goes between `***triple asterisks***`\n" 347 | desc += "__Underlined__ text goes between `__double underscore__`\n" 348 | desc += "~~Strikethrough~~ text goes between `~~double tilde~~`\n" 349 | desc += "||Spoilers|| go between `|| double pipe ||`\n\n" 350 | desc += "You can combine the above styles.\n\n" 351 | desc += "Inline Code Blocks start and end with a single ``​`​``\n" 352 | desc += "Multi line Code Blocks start and end with ``​```​``\n" 353 | desc += "Multi line Code Blocks can also specify a language with ``​```​language`` at the start\n\n" 354 | desc += "Single line quotes start with `>`\n" 355 | desc += "Multi line quotes start with `>>>`\n" 356 | 357 | msg := c.CreateDefinedEmbed("Discord Markdown", desc, "", m.Author) 358 | r, err := s.ChannelMessageSendEmbed(m.ChannelID, msg) 359 | if err != nil { 360 | return err 361 | } 362 | 363 | if cleanup { 364 | time.Sleep(15 * time.Second) 365 | 366 | err = s.ChannelMessageDelete(m.ChannelID, r.ID) 367 | if err != nil { 368 | return err 369 | } 370 | err = s.ChannelMessageDelete(m.ChannelID, m.ID) 371 | if err != nil { 372 | return err 373 | } 374 | } 375 | 376 | return nil 377 | } 378 | 379 | func (c *Commands) handleCtoF(s *discordgo.Session, m *discordgo.MessageCreate) error { 380 | inS := strings.Split(m.Content, " ") 381 | 382 | if len(inS) < 2 { 383 | return errors.New("You did not specify a temperature") 384 | } 385 | in := inS[1] 386 | 387 | inF, err := strconv.ParseFloat(in, 2) 388 | if err != nil { 389 | return errors.New("You did not specify a valid number") 390 | } 391 | 392 | cels := (inF * 9.0 / 5.0) + 32.0 393 | celsF := float64(cels) 394 | 395 | msg := fmt.Sprintf("`%.1f°c` is `%.1f°f`", inF, celsF) 396 | 397 | e := c.CreateDefinedEmbed("Celsius to Farenheit", msg, "", m.Author) 398 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, e) 399 | if err != nil { 400 | return err 401 | } 402 | 403 | return nil 404 | } 405 | 406 | func (c *Commands) handleFtoC(s *discordgo.Session, m *discordgo.MessageCreate) error { 407 | inS := strings.Split(m.Content, " ") 408 | 409 | if len(inS) < 2 { 410 | return errors.New("You did not specify a temperature") 411 | } 412 | in := inS[1] 413 | 414 | inF, err := strconv.ParseFloat(in, 2) 415 | if err != nil { 416 | return errors.New("You did not specify a valid number") 417 | } 418 | 419 | faren := (inF - 32) * 5 / 9 420 | farenF := float64(faren) 421 | 422 | msg := fmt.Sprintf("`%.1f°f` is `%.1f°c`", inF, farenF) 423 | 424 | e := c.CreateDefinedEmbed("Farenheit to Celsius", msg, "", m.Author) 425 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, e) 426 | if err != nil { 427 | return err 428 | } 429 | 430 | return nil 431 | } 432 | 433 | func (c *Commands) handleMetersToFeet(s *discordgo.Session, m *discordgo.MessageCreate) error { 434 | inS := strings.Split(m.Content, " ") 435 | 436 | if len(inS) < 2 { 437 | return errors.New("You did not specify a distance") 438 | } 439 | in := inS[1] 440 | 441 | inF, err := strconv.ParseFloat(in, 2) 442 | if err != nil { 443 | return errors.New("You did not specify a valid number") 444 | } 445 | 446 | meters := inF * 3.28 447 | metersF := float64(meters) 448 | 449 | msg := fmt.Sprintf("`%.1fm` is `%.1fft`", inF, metersF) 450 | 451 | e := c.CreateDefinedEmbed("Meters to Feet", msg, "", m.Author) 452 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, e) 453 | if err != nil { 454 | return err 455 | } 456 | 457 | return nil 458 | } 459 | 460 | func (c *Commands) handleFeetToMeters(s *discordgo.Session, m *discordgo.MessageCreate) error { 461 | inS := strings.Split(m.Content, " ") 462 | 463 | if len(inS) < 2 { 464 | return errors.New("You did not specify a distance") 465 | } 466 | in := inS[1] 467 | 468 | inF, err := strconv.ParseFloat(in, 2) 469 | if err != nil { 470 | return errors.New("You did not specify a valid number") 471 | } 472 | 473 | feet := inF / 3.28 474 | feetF := float64(feet) 475 | 476 | msg := fmt.Sprintf("`%.1fft` is `%.1fm`", inF, feetF) 477 | 478 | e := c.CreateDefinedEmbed("Feet to Meters", msg, "", m.Author) 479 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, e) 480 | if err != nil { 481 | return err 482 | } 483 | 484 | return nil 485 | } 486 | 487 | func (c *Commands) handleCentimeterToInch(s *discordgo.Session, m *discordgo.MessageCreate) error { 488 | inS := strings.Split(m.Content, " ") 489 | 490 | if len(inS) < 2 { 491 | return errors.New("You did not specify a distance") 492 | } 493 | in := inS[1] 494 | 495 | inF, err := strconv.ParseFloat(in, 2) 496 | if err != nil { 497 | return errors.New("You did not specify a valid number") 498 | } 499 | 500 | inch := inF / 2.54 501 | inchF := float64(inch) 502 | 503 | msg := fmt.Sprintf("`%.1fcm` is `%.1fin`", inF, inchF) 504 | 505 | e := c.CreateDefinedEmbed("Centimeter To Inch", msg, "", m.Author) 506 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, e) 507 | if err != nil { 508 | return err 509 | } 510 | 511 | return nil 512 | } 513 | 514 | func (c *Commands) handleInchToCentimeter(s *discordgo.Session, m *discordgo.MessageCreate) error { 515 | inS := strings.Split(m.Content, " ") 516 | 517 | if len(inS) < 2 { 518 | return errors.New("You did not specify a distance") 519 | } 520 | in := inS[1] 521 | 522 | inF, err := strconv.ParseFloat(in, 2) 523 | if err != nil { 524 | return errors.New("You did not specify a valid number") 525 | } 526 | 527 | cm := inF * 2.54 528 | cmF := float64(cm) 529 | 530 | msg := fmt.Sprintf("`%.1fin` is `%.1fcm`", inF, cmF) 531 | 532 | e := c.CreateDefinedEmbed("Inch to Centimeter", msg, "", m.Author) 533 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, e) 534 | if err != nil { 535 | return err 536 | } 537 | 538 | return nil 539 | } 540 | 541 | func (c *Commands) handleUserInfo(s *discordgo.Session, m *discordgo.MessageCreate) error { 542 | var ( 543 | mHandle *discordgo.Member 544 | requester *discordgo.Member 545 | err error 546 | ) 547 | 548 | userSplit := strings.Split(m.Content, " ") 549 | 550 | if len(userSplit) < 2 { 551 | mHandle, err = s.GuildMember(c.Config.GuildID, m.Author.ID) 552 | requester = mHandle 553 | if err != nil { 554 | return err 555 | } 556 | } else { 557 | idStr := strings.ReplaceAll(userSplit[1], "<@!", "") 558 | idStr = strings.ReplaceAll(idStr, "<@", "") 559 | idStr = strings.ReplaceAll(idStr, ">", "") 560 | mHandle, err = s.GuildMember(c.Config.GuildID, idStr) 561 | if err != nil { 562 | return err 563 | } 564 | requester, err = s.GuildMember(c.Config.GuildID, m.Author.ID) 565 | if err != nil { 566 | return err 567 | } 568 | } 569 | 570 | rUserID := mHandle.User.ID 571 | rUserNick := mHandle.Nick 572 | rUsername := mHandle.User.Username 573 | rUserDiscrim := mHandle.User.Discriminator 574 | rUserAvatar := mHandle.User.AvatarURL("4096") 575 | rJoinTime := mHandle.JoinedAt 576 | rRoles := mHandle.Roles 577 | 578 | if len(rUserNick) == 0 { 579 | rUserNick = "No Nickname" 580 | } 581 | 582 | rJoinTimeP, err := rJoinTime.Parse() 583 | if err != nil { 584 | return err 585 | } 586 | 587 | rRolesTidy := "" 588 | if len(rRoles) == 0 { 589 | rRolesTidy = "No Roles" 590 | } else { 591 | for _, role := range rRoles { 592 | rRolesTidy += "<@&" + role + "> " 593 | } 594 | } 595 | 596 | msg := "**User ID**: `" + rUserID + "`\n" 597 | msg += "**User Name**: `" + rUsername + "`\n" 598 | msg += "**User Nick**: `" + rUserNick + "`\n" 599 | msg += "**User Discrim**: `#" + rUserDiscrim + "`\n" 600 | msg += "**User Join**: `" + rJoinTimeP.String() + "`\n" 601 | msg += "**User Roles**: " + rRolesTidy + "\n" 602 | 603 | embedData := models.CustomEmbed{ 604 | URL: "", 605 | Title: "User Info (" + rUsername + ")", 606 | Desc: msg, 607 | Type: "", 608 | Timestamp: time.Now().Format(time.RFC3339), 609 | Color: 0xFFA500, 610 | FooterText: "Requested by " + requester.User.Username + "#" + requester.User.Discriminator, 611 | FooterImageURL: "", 612 | ImageURL: "", 613 | ImageH: 0, 614 | ImageW: 0, 615 | ThumbnailURL: rUserAvatar, 616 | ThumbnailH: 512, 617 | ThumbnailW: 512, 618 | ProviderURL: "", 619 | ProviderText: "", 620 | AuthorText: "", 621 | AuthorURL: "", 622 | AuthorImageURL: "", 623 | } 624 | 625 | embed := c.CreateCustomEmbed(&embedData) 626 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, embed) 627 | if err != nil { 628 | return err 629 | } 630 | 631 | return nil 632 | } 633 | 634 | func (c *Commands) handleServerInfo(s *discordgo.Session, m *discordgo.MessageCreate) error { 635 | g, err := s.Guild(c.Config.GuildID) 636 | if err != nil { 637 | return err 638 | } 639 | 640 | sID := c.Config.GuildID 641 | sName := c.Config.GuildName 642 | 643 | chans, _ := s.GuildChannels(c.Config.GuildID) 644 | sChannels := strconv.Itoa(len(chans)) 645 | sEmojis := strconv.Itoa(len(g.Emojis)) 646 | sRoles := strconv.Itoa(len(g.Roles)) 647 | sRegion := g.Region 648 | 649 | iID, _ := strconv.Atoi(c.Config.GuildID) 650 | createdMSecs := ((iID / 4194304) + 1420070400000) / 1000 651 | sCreatedAt := time.Unix(int64(createdMSecs), 0).Format(time.RFC1123) 652 | 653 | sIconURL := g.IconURL() 654 | 655 | user := m.Author 656 | 657 | desc := "**Server ID**: `" + sID + "`\n" 658 | desc += "**Server Name**: `" + sName + "`\n" 659 | desc += "**Server Channels**: `" + sChannels + "`\n" 660 | desc += "**Server Emojis**: `" + sEmojis + "`\n" 661 | desc += "**Server Roles**: `" + sRoles + "`\n" 662 | desc += "**Server Region**: `" + sRegion + "`\n" 663 | desc += "**Server Creation**: `" + sCreatedAt + "`\n" 664 | 665 | embedData := models.CustomEmbed{ 666 | URL: "", 667 | Title: "Server Info (" + sName + ")", 668 | Desc: desc, 669 | Type: "", 670 | Timestamp: time.Now().Format(time.RFC3339), 671 | Color: 0xFFA500, 672 | FooterText: "Requested by " + user.Username + "#" + user.Discriminator, 673 | FooterImageURL: "", 674 | ImageURL: "", 675 | ImageH: 0, 676 | ImageW: 0, 677 | ThumbnailURL: sIconURL, 678 | ThumbnailH: 256, 679 | ThumbnailW: 256, 680 | ProviderURL: "", 681 | ProviderText: "", 682 | AuthorText: "", 683 | AuthorURL: "", 684 | AuthorImageURL: "", 685 | } 686 | 687 | msg := c.CreateCustomEmbed(&embedData) 688 | 689 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, msg) 690 | if err != nil { 691 | return err 692 | } 693 | 694 | return nil 695 | } 696 | func (c *Commands) handleGoogle4U(s *discordgo.Session, m *discordgo.MessageCreate) error { 697 | args := strings.Split(m.Content, " ") 698 | 699 | if len(args) < 2 { 700 | return errors.New("You did not specify anything to google") 701 | } 702 | 703 | input := m.Content[strings.Index(m.Content, " "):len(m.Content)] 704 | 705 | desc := "https://letmegooglethat.com/?q=" + url.QueryEscape(input) 706 | 707 | msg := c.CreateDefinedEmbed("Google", desc, "", m.Author) 708 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, msg) 709 | if err != nil { 710 | return err 711 | } 712 | err = s.ChannelMessageDelete(m.ChannelID, m.ID) 713 | if err != nil { 714 | return err 715 | } 716 | return nil 717 | } 718 | -------------------------------------------------------------------------------- /commands/moderation.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "strings" 7 | "time" 8 | 9 | "github.com/bwmarrin/discordgo" 10 | "github.com/foxtrot/scuzzy/actions" 11 | ) 12 | 13 | func (c *Commands) handleSetSlowmode(s *discordgo.Session, m *discordgo.MessageCreate) error { 14 | slowmodeSplit := strings.Split(m.Content, " ") 15 | if len(slowmodeSplit) < 2 { 16 | return errors.New("You must supply at least an amount of time") 17 | } 18 | 19 | slowmodeTimeStr := slowmodeSplit[1] 20 | slowModeTime, err := strconv.Atoi(slowmodeTimeStr) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | if len(slowmodeSplit) == 3 { 26 | if slowmodeSplit[2] == "all" { 27 | channels, err := s.GuildChannels(c.Config.GuildID) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | for _, channel := range channels { 33 | currPos := channel.Position 34 | s.ChannelEditComplex(channel.ID, &discordgo.ChannelEdit{ 35 | Position: currPos, 36 | RateLimitPerUser: slowModeTime, 37 | }) 38 | } 39 | } 40 | } else { 41 | currChan, err := s.Channel(m.ChannelID) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | currPos := currChan.Position 47 | _, err = s.ChannelEditComplex(m.ChannelID, &discordgo.ChannelEdit{ 48 | Position: currPos, 49 | RateLimitPerUser: slowModeTime, 50 | }) 51 | if err != nil { 52 | return err 53 | } 54 | } 55 | 56 | msg := c.CreateDefinedEmbed("Slow Mode", "Successfully set Slow Mode to `"+slowmodeTimeStr+"`.", "success", m.Author) 57 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, msg) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | return nil 63 | } 64 | 65 | func (c *Commands) handleUnsetSlowmode(s *discordgo.Session, m *discordgo.MessageCreate) error { 66 | slowmodeSplit := strings.Split(m.Content, " ") 67 | 68 | secs := 0 69 | 70 | if len(slowmodeSplit) == 2 { 71 | if slowmodeSplit[1] == "all" { 72 | channels, err := s.GuildChannels(c.Config.GuildID) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | for _, channel := range channels { 78 | currPos := channel.Position 79 | s.ChannelEditComplex(channel.ID, &discordgo.ChannelEdit{ 80 | Position: currPos, 81 | RateLimitPerUser: secs, 82 | }) 83 | } 84 | } 85 | } else { 86 | currChan, err := s.Channel(m.ChannelID) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | currPos := currChan.Position 92 | _, err = s.ChannelEditComplex(m.ChannelID, &discordgo.ChannelEdit{ 93 | Position: currPos, 94 | RateLimitPerUser: secs, 95 | }) 96 | if err != nil { 97 | return err 98 | } 99 | } 100 | 101 | msg := c.CreateDefinedEmbed("Slow Mode", "Successfully unset Slow Mode", "success", m.Author) 102 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, msg) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | return nil 108 | } 109 | 110 | func (c *Commands) handlePurgeChannel(s *discordgo.Session, m *discordgo.MessageCreate) error { 111 | purgeSplit := strings.SplitN(m.Content, " ", 2) 112 | if len(purgeSplit) < 2 { 113 | return errors.New("No message count supplied") 114 | } 115 | 116 | msgCount, err := strconv.Atoi(purgeSplit[1]) 117 | if err != nil { 118 | return nil 119 | } 120 | 121 | if msgCount > 100 { 122 | return errors.New("You may only purge upto 100 messages at a time.") 123 | } 124 | 125 | chanMsgs, err := s.ChannelMessages(m.ChannelID, msgCount, "", "", "") 126 | if err != nil { 127 | return err 128 | } 129 | 130 | msg := c.CreateDefinedEmbed("Purge Channel", "Purging `"+purgeSplit[1]+"` messages.", "", m.Author) 131 | r, err := s.ChannelMessageSendEmbed(m.ChannelID, msg) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | var delMsgs []string 137 | for _, v := range chanMsgs { 138 | delMsgs = append(delMsgs, v.ID) 139 | } 140 | 141 | err = s.ChannelMessagesBulkDelete(m.ChannelID, delMsgs) 142 | if err != nil { 143 | return err 144 | } 145 | 146 | err = s.ChannelMessageDelete(m.ChannelID, r.ID) 147 | msg = c.CreateDefinedEmbed("Purge Channel", "Purged `"+purgeSplit[1]+"` messages!", "success", m.Author) 148 | msgS, err := s.ChannelMessageSendEmbed(m.ChannelID, msg) 149 | if err != nil { 150 | return err 151 | } 152 | 153 | time.Sleep(time.Second * 10) 154 | 155 | err = s.ChannelMessageDelete(m.ChannelID, msgS.ID) 156 | if err != nil { 157 | return err 158 | } 159 | 160 | return nil 161 | } 162 | 163 | func (c *Commands) handleKickUser(s *discordgo.Session, m *discordgo.MessageCreate) error { 164 | var ( 165 | mHandle *discordgo.Member 166 | kickReason string 167 | err error 168 | ) 169 | 170 | args := strings.Split(m.Content, " ") 171 | if len(args) < 2 { 172 | return errors.New("You must specify a user to kick.") 173 | } 174 | if len(args) == 3 { 175 | kickReason = strings.Join(args[2:], " ") 176 | } 177 | 178 | member := args[1] 179 | idStr := strings.ReplaceAll(member, "<@!", "") 180 | idStr = strings.ReplaceAll(idStr, "<@", "") 181 | idStr = strings.ReplaceAll(idStr, ">", "") 182 | mHandle, err = s.GuildMember(c.Config.GuildID, idStr) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | err = actions.KickUser(s, c.Config.GuildID, mHandle.User.ID, kickReason) 188 | if err != nil { 189 | return err 190 | } 191 | 192 | msg := "User `" + mHandle.User.Username + "#" + mHandle.User.Discriminator + "` was kicked.\n" 193 | if len(kickReason) > 0 { 194 | msg += "Reason: `" + kickReason + "`\n" 195 | } 196 | 197 | embed := c.CreateDefinedEmbed("Kick User", msg, "success", m.Author) 198 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, embed) 199 | if err != nil { 200 | return err 201 | } 202 | 203 | return nil 204 | } 205 | 206 | func (c *Commands) handleBanUser(s *discordgo.Session, m *discordgo.MessageCreate) error { 207 | var ( 208 | mHandle *discordgo.User 209 | banReason string 210 | err error 211 | ) 212 | 213 | args := strings.Split(m.Content, " ") 214 | if len(args) < 2 { 215 | return errors.New("You must specify a user to ban.") 216 | } 217 | if len(args) == 3 { 218 | banReason = strings.Join(args[2:], " ") 219 | } 220 | 221 | member := args[1] 222 | idStr := strings.ReplaceAll(member, "<@!", "") 223 | idStr = strings.ReplaceAll(idStr, "<@", "") 224 | idStr = strings.ReplaceAll(idStr, ">", "") 225 | mHandle, err = s.User(idStr) 226 | if err != nil { 227 | return err 228 | } 229 | 230 | err = actions.BanUser(s, c.Config.GuildID, mHandle.ID, banReason) 231 | if err != nil { 232 | return err 233 | } 234 | 235 | msg := "User `" + mHandle.Username + "#" + mHandle.Discriminator + "` was banned.\n" 236 | if len(banReason) > 0 { 237 | msg += "Reason: `" + banReason + "`\n" 238 | } 239 | 240 | embed := c.CreateDefinedEmbed("Ban User", msg, "success", m.Author) 241 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, embed) 242 | if err != nil { 243 | return err 244 | } 245 | 246 | return nil 247 | } 248 | 249 | func (c *Commands) handleIgnoreUser(s *discordgo.Session, m *discordgo.MessageCreate) error { 250 | ignArgs := strings.Split(m.Content, " ") 251 | if len(ignArgs) < 2 { 252 | return errors.New("You did not specify a user.") 253 | } 254 | 255 | member := ignArgs[1] 256 | idStr := strings.ReplaceAll(member, "<@!", "") 257 | idStr = strings.ReplaceAll(idStr, "<@", "") 258 | idStr = strings.ReplaceAll(idStr, ">", "") 259 | 260 | c.Config.IgnoredUsers = append(c.Config.IgnoredUsers, idStr) 261 | 262 | eMsg := c.CreateDefinedEmbed("Ignore User", "<@!"+idStr+"> is now being ignored.", "success", m.Author) 263 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, eMsg) 264 | if err != nil { 265 | return err 266 | } 267 | 268 | err = c.handleSaveConfig(s, m) 269 | if err != nil { 270 | return err 271 | } 272 | 273 | return nil 274 | } 275 | 276 | func (c *Commands) handleUnIgnoreUser(s *discordgo.Session, m *discordgo.MessageCreate) error { 277 | ignArgs := strings.Split(m.Content, " ") 278 | if len(ignArgs) < 2 { 279 | return errors.New("You did not specify a user.") 280 | } 281 | 282 | member := ignArgs[1] 283 | idStr := strings.ReplaceAll(member, "<@!", "") 284 | idStr = strings.ReplaceAll(idStr, "<@", "") 285 | idStr = strings.ReplaceAll(idStr, ">", "") 286 | 287 | for k, v := range c.Config.IgnoredUsers { 288 | if v == idStr { 289 | c.Config.IgnoredUsers[k] = c.Config.IgnoredUsers[len(c.Config.IgnoredUsers)-1] 290 | c.Config.IgnoredUsers = c.Config.IgnoredUsers[:len(c.Config.IgnoredUsers)-1] 291 | } 292 | } 293 | 294 | eMsg := c.CreateDefinedEmbed("Unignore User", "<@!"+idStr+"> is not being ignored.", "success", m.Author) 295 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, eMsg) 296 | if err != nil { 297 | return err 298 | } 299 | 300 | err = c.handleSaveConfig(s, m) 301 | if err != nil { 302 | return err 303 | } 304 | 305 | return nil 306 | } 307 | -------------------------------------------------------------------------------- /commands/usercolor.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "github.com/bwmarrin/discordgo" 6 | "strings" 7 | ) 8 | 9 | func (c *Commands) handleUserColors(s *discordgo.Session, m *discordgo.MessageCreate) error { 10 | if !c.Permissions.CheckCommandRestrictions(m) { 11 | return errors.New("This command is not allowed in this channel.") 12 | } 13 | 14 | msgC := "You can choose from the following colors:\n\n" 15 | for _, v := range c.Config.ColorRoles { 16 | msgC += "<@&" + v.ID + ">\n" 17 | } 18 | msgC += "\n\nUse `" + c.Config.CommandKey + "color ` to set.\n" 19 | msgC += "Example: `" + c.Config.CommandKey + "color red`.\n" 20 | 21 | msg := c.CreateDefinedEmbed("User Colors", msgC, "", m.Author) 22 | 23 | _, err := s.ChannelMessageSendEmbed(m.ChannelID, msg) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func (c *Commands) handleUserColor(s *discordgo.Session, m *discordgo.MessageCreate) error { 32 | var err error 33 | 34 | if !c.Permissions.CheckCommandRestrictions(m) { 35 | return errors.New("This command is not allowed in this channel.") 36 | } 37 | 38 | rUserID := m.Author.ID 39 | 40 | userInput := strings.Split(m.Content, " ") 41 | if len(userInput) < 2 { 42 | err = c.handleUserColors(s, m) 43 | return err 44 | } 45 | roleColorName := userInput[1] 46 | roleColorName = strings.ToLower(roleColorName) 47 | 48 | roleColorID := "" 49 | for _, role := range c.Config.ColorRoles { 50 | if role.Name == roleColorName { 51 | roleColorID = role.ID 52 | break 53 | } 54 | } 55 | if len(roleColorID) == 0 { 56 | err = c.handleUserColors(s, m) 57 | return err 58 | } 59 | 60 | for _, role := range c.Config.ColorRoles { 61 | // Attempt to remove all color roles regardless of if they have them or not. 62 | // Slow because of the REST requests... 63 | _ = s.GuildMemberRoleRemove(m.GuildID, rUserID, role.ID) 64 | } 65 | 66 | err = s.GuildMemberRoleAdd(m.GuildID, rUserID, roleColorID) 67 | if err != nil { 68 | return err 69 | } else { 70 | msg := c.CreateDefinedEmbed("User Color", "<@"+m.Author.ID+">: Your color has been set to <@&"+roleColorID+">!", "success", m.Author) 71 | _, err = s.ChannelMessageSendEmbed(m.ChannelID, msg) 72 | if err != nil { 73 | return err 74 | } 75 | } 76 | 77 | err = s.ChannelMessageDelete(m.ChannelID, m.ID) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /dgo.patch: -------------------------------------------------------------------------------- 1 | --- structs.go.old 2021-10-01 14:56:44.944208960 +0000 2 | +++ structs.go 2021-10-01 14:56:41.660150490 +0000 3 | @@ -310,12 +310,12 @@ 4 | Name string `json:"name,omitempty"` 5 | Topic string `json:"topic,omitempty"` 6 | NSFW bool `json:"nsfw,omitempty"` 7 | - Position int `json:"position"` 8 | + Position int `json:"position,omitempty"` 9 | Bitrate int `json:"bitrate,omitempty"` 10 | UserLimit int `json:"user_limit,omitempty"` 11 | PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"` 12 | ParentID string `json:"parent_id,omitempty"` 13 | - RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` 14 | + RateLimitPerUser *int `json:"rate_limit_per_user,omitempty"` 15 | } 16 | 17 | // A ChannelFollow holds data returned after following a news channel 18 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/foxtrot/scuzzy 2 | 3 | go 1.16 4 | 5 | require github.com/bwmarrin/discordgo v0.23.2 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4= 2 | github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= 3 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 4 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 5 | golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= 6 | golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 7 | -------------------------------------------------------------------------------- /models/config.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type ColorRole struct { 4 | Name string `json:"color"` 5 | ID string `json:"id"` 6 | } 7 | 8 | type CustomRole struct { 9 | Name string `json:"role_name"` 10 | ShortName string `json:"short_name"` 11 | ID string `json:"id"` 12 | } 13 | 14 | type CommandRestriction struct { 15 | Command string `json:"command"` 16 | Mode string `json:"mode"` 17 | Channels []string `json:"channels"` 18 | } 19 | 20 | type Configuration struct { 21 | CommandKey string `json:"command_key"` 22 | 23 | GuildID string `json:"guild_id"` 24 | GuildName string `json:"guild_name"` 25 | 26 | StatusText string `json:"status_text"` 27 | WelcomeText string `json:"welcome_text"` 28 | RulesText string `json:"rules_text"` 29 | 30 | AdminRoles []string `json:"admin_roles"` 31 | JoinRoleIDs []string `json:"join_role_ids"` 32 | 33 | CommandRestrictions []CommandRestriction `json:"command_restrictions"` 34 | 35 | ColorRoles []ColorRole `json:"color_roles"` 36 | CustomRoles []CustomRole `json:"custom_roles"` 37 | 38 | IgnoredUsers []string `json:"ignored_users"` 39 | 40 | LoggingChannel string `json:"logging_channel"` 41 | 42 | ConfigPath string 43 | 44 | FilterLanguage bool 45 | JoinFloodThreshold int 46 | UserMessageThreshold int 47 | MaxUserWarnings int 48 | MaxUserKicks int 49 | EnforceMode bool 50 | } 51 | -------------------------------------------------------------------------------- /models/embeds.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type CustomEmbed struct { 4 | URL string 5 | Title string 6 | Desc string 7 | Type string 8 | Timestamp string 9 | Color int 10 | 11 | FooterText string 12 | FooterImageURL string 13 | 14 | ImageURL string 15 | ImageH int 16 | ImageW int 17 | 18 | ThumbnailURL string 19 | ThumbnailH int 20 | ThumbnailW int 21 | 22 | ProviderURL string 23 | ProviderText string 24 | 25 | AuthorText string 26 | AuthorURL string 27 | AuthorImageURL string 28 | } 29 | -------------------------------------------------------------------------------- /permissions/permissions.go: -------------------------------------------------------------------------------- 1 | package permissions 2 | 3 | import ( 4 | "github.com/bwmarrin/discordgo" 5 | "github.com/foxtrot/scuzzy/models" 6 | "strings" 7 | ) 8 | 9 | type AdminRole struct { 10 | Name string 11 | ID string 12 | } 13 | 14 | type Permissions struct { 15 | AdminRoles []AdminRole 16 | CommandRestrictions []models.CommandRestriction 17 | 18 | Config *models.Configuration 19 | } 20 | 21 | func New(config *models.Configuration, guild *discordgo.Guild) *Permissions { 22 | var ars []AdminRole 23 | for _, gRole := range guild.Roles { 24 | for _, aRole := range config.AdminRoles { 25 | if aRole != gRole.Name { 26 | continue 27 | } 28 | 29 | ar := AdminRole{ 30 | Name: gRole.Name, 31 | ID: gRole.ID, 32 | } 33 | ars = append(ars, ar) 34 | } 35 | } 36 | 37 | var crs []models.CommandRestriction 38 | for _, cRes := range config.CommandRestrictions { 39 | cr := models.CommandRestriction{ 40 | Command: cRes.Command, 41 | Mode: cRes.Mode, 42 | Channels: cRes.Channels, 43 | } 44 | crs = append(crs, cr) 45 | } 46 | 47 | return &Permissions{ 48 | AdminRoles: ars, 49 | CommandRestrictions: crs, 50 | Config: config, 51 | } 52 | } 53 | 54 | func (p *Permissions) CheckAdminRole(m *discordgo.Member) bool { 55 | for _, aR := range p.AdminRoles { 56 | for _, mID := range m.Roles { 57 | if aR.ID == mID { 58 | return true 59 | } 60 | } 61 | } 62 | 63 | return false 64 | } 65 | 66 | func (p *Permissions) CheckIgnoredUser(m *discordgo.User) bool { 67 | for _, iU := range p.Config.IgnoredUsers { 68 | if iU == m.ID { 69 | return true 70 | } 71 | } 72 | 73 | return false 74 | } 75 | 76 | func (p *Permissions) CheckCommandRestrictions(m *discordgo.MessageCreate) bool { 77 | cName := strings.Split(m.Content, " ")[0] 78 | cName = strings.Replace(cName, p.Config.CommandKey, "", 1) 79 | cChanID := m.ChannelID 80 | 81 | for _, cR := range p.CommandRestrictions { 82 | if cName == cR.Command { 83 | for _, cID := range cR.Channels { 84 | if cID == cChanID && cR.Mode == "white" { 85 | return true 86 | } else if cID == cChanID && cR.Mode == "black" { 87 | return false 88 | } 89 | } 90 | 91 | if cR.Mode == "white" { 92 | return false 93 | } else { 94 | return true 95 | } 96 | } 97 | } 98 | 99 | return true 100 | } 101 | --------------------------------------------------------------------------------