commit 26097ea9d187829003bc338b199cfcf40982800c Author: DutchEllie Date: Tue May 18 12:11:30 2021 +0200 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03b22a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +discordtoken.txt \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1c36771 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +# syntax=docker/dockerfile:1 +FROM golang:1.16.4 AS builder +WORKDIR /go/src/quenten.nl/pepebot/ + +COPY . ./ +RUN go mod download +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app ./discord + +FROM alpine:latest +RUN apk --no-cache add ca-certificates +WORKDIR /root/ +COPY --from=builder /go/src/quenten.nl/pepebot/app . +CMD ["./app"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a62c1e --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Substitute the environment variables in the docker-compose.yml file for your own diff --git a/discord/admin.go b/discord/admin.go new file mode 100644 index 0000000..196d7bd --- /dev/null +++ b/discord/admin.go @@ -0,0 +1,149 @@ +package main + +import "github.com/bwmarrin/discordgo" + +func (app *application) addWord(s *discordgo.Session, m *discordgo.MessageCreate, splitCommand []string) { + /* Check if admin */ + r, err := app.checkIfAdmin(s, m) + if err != nil { + app.errorLog.Print(err) + return + } + if !r { + return + } + /* [0] = trigger, [1] is addword, [2] is the word! */ + err = app.contextLength(splitCommand) + if err != nil { + s.ChannelMessageSend(m.ChannelID, "Please provide a word to add") + return + } + + _, err = app.badwords.InsertNewWord(splitCommand[2], m.GuildID) + if err != nil { + app.errorLog.Print(err) + s.ChannelMessageSend(m.ChannelID, err.Error()) + return + } + + app.updateAllBadWords() +} + +func (app *application) removeWord(s *discordgo.Session, m *discordgo.MessageCreate, splitCommand []string) { + /* Check if admin */ + r, err := app.checkIfAdmin(s, m) + if err != nil { + app.errorLog.Print(err) + return + } + if !r { + return + } + /* [0] = trigger, [1] is removeword, [2] is the word! */ + err = app.contextLength(splitCommand) + if err != nil { + s.ChannelMessageSend(m.ChannelID, "Please provide a word to remove") + return + } + + err = app.badwords.RemoveWord(splitCommand[2], m.GuildID) + if err != nil { + app.errorLog.Print(err) + s.ChannelMessageSend(m.ChannelID, err.Error()) + return + } + + app.updateAllBadWords() +} + +func (app *application) addAdmin(s *discordgo.Session, m *discordgo.MessageCreate, splitCommand []string) { + /* Check if admin */ + r, err := app.checkIfAdmin(s, m) + if err != nil { + app.errorLog.Print(err) + return + } + if !r { + return + } + /* [0] = trigger, [1] is addadmin, [2] is the id! */ + err = app.contextLength(splitCommand) + if err != nil { + s.ChannelMessageSend(m.ChannelID, "Please provide a role id") + return + } + + allRoles, err := s.GuildRoles(m.GuildID) + if err != nil { + app.unknownError(err, s, true, m.ChannelID) + return + } + + var found bool = false + var counter int = 0 + for i := 0; i < len(allRoles); i++ { + if allRoles[i].ID == splitCommand[2] { + found = true + counter = i + break + } + } + + if !found { + s.ChannelMessageSend(m.ChannelID, "This role id does not exist") + return + } + + _, err = app.adminroles.AddAdminRole(allRoles[counter].Name, allRoles[counter].ID, m.GuildID) + if err != nil { + app.unknownError(err, s, true, m.ChannelID) + return + } + +} + +func (app *application) removeAdmin(s *discordgo.Session, m *discordgo.MessageCreate, splitCommand []string) { + /* Check if admin */ + r, err := app.checkIfAdmin(s, m) + if err != nil { + app.errorLog.Print(err) + return + } + if !r { + return + } + /* [0] = trigger, [1] is removeadmin, [2] is the id! */ + err = app.contextLength(splitCommand) + if err != nil { + s.ChannelMessageSend(m.ChannelID, "Please provide a role id") + return + } + + allRoles, err := s.GuildRoles(m.GuildID) + if err != nil { + app.unknownError(err, s, true, m.ChannelID) + return + } + + var found bool = false + var counter int = 0 + for i := 0; i < len(allRoles); i++ { + if allRoles[i].ID == splitCommand[2] { + found = true + counter = i + break + } + } + + if !found { + s.ChannelMessageSend(m.ChannelID, "This role id does not exist") + return + } + + err = app.adminroles.RemoveAdminRole(allRoles[counter].Name, allRoles[counter].ID, m.GuildID) + if err != nil { + app.unknownError(err, s, true, m.ChannelID) + return + } + +} \ No newline at end of file diff --git a/discord/discord.go b/discord/discord.go new file mode 100644 index 0000000..406f30a --- /dev/null +++ b/discord/discord.go @@ -0,0 +1,55 @@ +package main + +import ( + "strings" + + "github.com/bwmarrin/discordgo" +) + +func (app *application) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { + if m.Author.Bot { + return + } + + /* Checking if the message starts with the trigger specified in application struct + if it does then we start the switch statement to trigger the appropriate function + if it does not then we check if it contains a triggerword from the database */ + if strings.HasPrefix(m.Content, app.trigger) { + splitCommand := strings.Split(strings.TrimSpace(m.Content), " ") + + /* If the whole message on it's own is just "!pepe" aka the triggerword, then send a pepe and stop */ + if strings.TrimSpace(m.Content) == app.trigger { + app.sendPepe(s, m) + return + } + /* This switches on the first word in the message + it could be anything starting with !pepe */ + if len(splitCommand) > 1 { + switch splitCommand[1] { + /* --- Funny commands --- */ + case "cringe": + app.sendCringe(s, m) + case "gif": + app.sendNigelGif(s, m) + /* --- Bot commands for words --- */ + /* --- Bot commands, but only admins --- */ + case "addword": + app.addWord(s, m, splitCommand) + case "removeword": + app.removeWord(s, m, splitCommand) + case "addadmin": + app.addAdmin(s, m, splitCommand) + case "removeadmin": + app.removeAdmin(s, m, splitCommand) + } + + } + } else { + /* If the trigger wasn't the prefix of the message, we need to check all the words for a trigger */ + app.findTrigger(s, m) + } + + + +} + diff --git a/discord/general.go b/discord/general.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/discord/general.go @@ -0,0 +1 @@ +package main diff --git a/discord/helper.go b/discord/helper.go new file mode 100644 index 0000000..abeeba0 --- /dev/null +++ b/discord/helper.go @@ -0,0 +1,64 @@ +package main + +import ( + "database/sql" + "fmt" + "io" + "os" + "runtime/debug" + + "github.com/bwmarrin/discordgo" + _ "github.com/go-sql-driver/mysql" +) + +/* -------- DB Helper functions -------- */ + +func openDB(dsn string) (*sql.DB, error){ + db, err := sql.Open("mysql", dsn) + if err != nil { + return nil, err + } + if orr := db.Ping(); orr != nil { + return nil, orr + } + + return db, nil +} + +func (app *application) updateAllBadWords() (error) { + var err error + app.allBadWords, err = app.badwords.AllWords() + if err != nil { + return err + } + + return nil +} + +/* -------- Error Helper functions -------- */ + +func (app *application) unknownError(err error, s *discordgo.Session, notifyDiscord bool, channelID string) { + trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack()) + app.errorLog.Output(2, trace) + + if notifyDiscord { + msg := fmt.Sprintf("An unknown error occured, error message attached below. Stack trace is in the server logs.\n%s", err.Error()) + s.ChannelMessageSend(channelID, msg) + } +} + +/* -------- Discord Helper functions -------- */ + +func (app *application) readAuthToken() (string, error) { + file, err := os.Open("./discordtoken.txt") + if err != nil { + return "", err + } + + token, err := io.ReadAll(file) + if err != nil { + return "", err + } + + return string(token), nil +} \ No newline at end of file diff --git a/discord/main.go b/discord/main.go new file mode 100644 index 0000000..012aff3 --- /dev/null +++ b/discord/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/signal" + "syscall" + + "github.com/bwmarrin/discordgo" + "quenten.nl/pepebot/models/mysql" +) + +/* Application struct contains the logging objects. +It also has many methods for the different functions of the bot. +These methods are mostly located in discord.go */ +type application struct { + errorLog *log.Logger + infoLog *log.Logger + badwords *mysql.BadwordModel + adminroles *mysql.AdminRolesModel + trigger string + allBadWords map[string][]string +} + +func main() { + dbUser := os.Getenv("DB_USER") + dbPass := os.Getenv("DB_PASS") + discordToken := os.Getenv("DISCORD_TOKEN") + dsn := fmt.Sprintf("%s:%s@tcp(db:3306)/badwords?parseTime=true", dbUser, dbPass) + infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime) + errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile) + + db, err := openDB(dsn) + if err != nil { + errorLog.Fatal(err) + } + + app := &application{ + infoLog: infoLog, + errorLog: errorLog, + badwords: &mysql.BadwordModel{DB: db}, + adminroles: &mysql.AdminRolesModel{DB: db}, + trigger: "!pepe", + } + + app.allBadWords, err = app.badwords.AllWords() + if err != nil { + app.errorLog.Fatal(err) + } + + /* token, err := app.readAuthToken() + if err != nil { + app.errorLog.Fatal(err) + } */ + + discord, err := discordgo.New("Bot " + discordToken) + if err != nil { + app.errorLog.Fatal(err) + } + + discord.AddHandler(app.messageCreate) + + discord.Identify.Intents = discordgo.IntentsGuildMessages + + err = discord.Open() + if err != nil { + app.errorLog.Fatal(err) + } + defer discord.Close() + + fmt.Println("Bot is now running. Press CTRL-C to exit.") + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) + <-sc + + // Cleanly close down the Discord session. + discord.Close() +} diff --git a/discord/simple.go b/discord/simple.go new file mode 100644 index 0000000..bacbe81 --- /dev/null +++ b/discord/simple.go @@ -0,0 +1,215 @@ +package main + +import ( + "errors" + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "regexp" + "strings" + "time" + + "github.com/bwmarrin/discordgo" +) + +func (app *application) sendPepe(s *discordgo.Session, m *discordgo.MessageCreate) { + resp, err := http.Get("http://bbwroller.com/random") + if err != nil { + app.errorLog.Print(err) + return + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + app.errorLog.Print(err) + return + } + rep, err := regexp.Compile("/static.*\\.jpg") + if err != nil { + app.errorLog.Print(err) + return + } + pepes := rep.FindAllString(string(body), 200) + if pepes == nil { + app.errorLog.Printf("No pepes were found\n") + return + } + randomIndex := rand.Intn(35) + url := "https://bbwroller.com" + url += pepes[randomIndex] + + _, err = s.ChannelMessageSend(m.ChannelID, url) + if err != nil { + app.errorLog.Print(err) + } +} + +func (app *application) sendCringe(s *discordgo.Session, m *discordgo.MessageCreate) { + _, err := s.ChannelMessageSend(m.ChannelID, "https://cdn.nicecock.eu/cringe.webm") + if err != nil { + app.errorLog.Print(err) + return + } +} + +func (app *application) sendNigelGif(s *discordgo.Session, m *discordgo.MessageCreate) { + msg := "<@77516941199159296> kun je die gif verwijderen van die pickup truck die naar de camera rijdt want bij mij zorg ie voor dat discord opnieuw opstart. ik weet niet of iemand anders dit heeft maar als iemand weet hoe dit komt en een andere oplossing weet hoor ik het graag." + _, err := s.ChannelMessageSend(m.ChannelID, msg) + if err != nil { + app.errorLog.Print(err) + return + } +} + +func (app *application) findTrigger(s *discordgo.Session, m *discordgo.MessageCreate) { + /* Finding for every word in the allBadWords map of string slices + Check if the message contains that word + if it doesn't continue, + if it does then get the word from the database, update the new time, format a message and send it */ + for i := 0; i < len(app.allBadWords[m.GuildID]); i++{ + if strings.Contains(strings.ToLower(m.Content), strings.ToLower(app.allBadWords[m.GuildID][i])) { + /* Found the bad word */ + word, err := app.badwords.GetWord(strings.ToLower(app.allBadWords[m.GuildID][i]), m.GuildID) + if err != nil { + app.errorLog.Print(err) + s.ChannelMessageSend(m.ChannelID, err.Error()) + } + format := formatTimeCheck(word.LastSaid) + _, err = app.badwords.UpdateLastSaid(word.Word, word.ServerID) + if err != nil { + app.errorLog.Print(err) + return + } + user := m.Author.Mention() + eyesEmoji := ":eyes:" + message := fmt.Sprintf("%s mentioned the forbidden word '%s'. They broke a streak of %s...\nYou better watch out, I am always watching %s", user, word.Word, format, eyesEmoji) + _ ,err = s.ChannelMessageSend(m.ChannelID, message) + if err != nil { + app.errorLog.Print(err) + return + } + break + } + } +} + +func formatTimeCheck(last time.Time) string{ + now := time.Now() + sinceLast := now.Sub(last) + var realSeconds uint64 = uint64(sinceLast.Seconds()) + var seconds, minutes, hours, days uint64 + realBackup := realSeconds + days = realSeconds / ( 24 * 3600 ) + realSeconds -= days * ( 24 * 3600 ) + hours = realSeconds / 3600 + realSeconds -= hours * 3600 + minutes = realSeconds / 60 + realSeconds -= minutes * 60 + seconds = realSeconds + if realBackup < 60{ + if seconds == 1{ + return fmt.Sprintf("%d second", seconds) + } + return fmt.Sprintf("%d seconds", seconds) + }else if realBackup > 60 && realBackup < 3600 { + if seconds == 1 && minutes == 1{ + return fmt.Sprintf("%d minute and %d second", minutes, seconds) + }else if minutes == 1 && seconds != 1{ + return fmt.Sprintf("%d minute and %d seconds", minutes, seconds) + }else if minutes != 1 && seconds == 1{ + return fmt.Sprintf("%d minutes and %d second", minutes, seconds) + } + return fmt.Sprintf("%d minutes and %d seconds", minutes, seconds) + }else if realBackup > 60 && realBackup < ( 24 * 3600 ){ + if hours == 1 && minutes == 1 && seconds == 1{ + return fmt.Sprintf("%d hour, %d minute and %d second", hours, minutes, seconds) + }else if hours == 1 && minutes == 1 && seconds != 1 { + return fmt.Sprintf("%d hour, %d minute and %d seconds", hours, minutes, seconds) + }else if hours == 1 && minutes != 1 && seconds == 1{ + return fmt.Sprintf("%d hour, %d minutes and %d second", hours, minutes, seconds) + }else if hours == 1 && minutes != 1 && seconds != 1{ + return fmt.Sprintf("%d hour, %d minutes and %d seconds", hours, minutes, seconds) + }else if hours != 1 && minutes == 1 && seconds == 1{ + return fmt.Sprintf("%d hours, %d minute and %d second", hours, minutes, seconds) + }else if hours != 1 && minutes == 1 && seconds != 1{ + return fmt.Sprintf("%d hours, %d minute and %d seconds", hours, minutes, seconds) + }else if hours != 1 && minutes != 1 && seconds == 1{ + return fmt.Sprintf("%d hours, %d minutes and %d second", hours, minutes, seconds) + }else if hours != 1 && minutes != 1 && seconds != 1{ + return fmt.Sprintf("%d hours, %d minutes and %d seconds", hours, minutes, seconds) + } + return fmt.Sprintf("%d hours, %d minutes and %d seconds", hours, minutes, seconds) + }else if realBackup > ( 24 * 3600 ){ + if days != 1 && hours != 1 && minutes != 1 && seconds != 1{ + return fmt.Sprintf("%d days, %d hours, %d minutes and %d seconds", days, hours, minutes, seconds) + }else if days != 1 && hours != 1 && minutes != 1 && seconds == 1{ + return fmt.Sprintf("%d days, %d hours, %d minutes and %d second", days, hours, minutes, seconds) + }else if days != 1 && hours != 1 && minutes == 1 && seconds != 1{ + return fmt.Sprintf("%d days, %d hours, %d minute and %d seconds", days, hours, minutes, seconds) + }else if days != 1 && hours != 1 && minutes == 1 && seconds == 1{ + return fmt.Sprintf("%d days, %d hours, %d minute and %d second", days, hours, minutes, seconds) + }else if days != 1 && hours == 1 && minutes != 1 && seconds != 1{ + return fmt.Sprintf("%d days, %d hour, %d minutes and %d seconds", days, hours, minutes, seconds) + }else if days != 1 && hours == 1 && minutes != 1 && seconds == 1{ + return fmt.Sprintf("%d days, %d hour, %d minutes and %d second", days, hours, minutes, seconds) + }else if days != 1 && hours == 1 && minutes == 1 && seconds != 1{ + return fmt.Sprintf("%d days, %d hour, %d minute and %d seconds", days, hours, minutes, seconds) + }else if days != 1 && hours == 1 && minutes == 1 && seconds == 1{ + return fmt.Sprintf("%d days, %d hour, %d minute and %d second", days, hours, minutes, seconds) + }else if days == 1 && hours != 1 && minutes != 1 && seconds != 1{ + return fmt.Sprintf("%d day, %d hours, %d minutes and %d seconds", days, hours, minutes, seconds) + }else if days == 1 && hours != 1 && minutes != 1 && seconds == 1{ + return fmt.Sprintf("%d day, %d hours, %d minutes and %d second", days, hours, minutes, seconds) + }else if days == 1 && hours != 1 && minutes == 1 && seconds != 1{ + return fmt.Sprintf("%d day, %d hours, %d minute and %d seconds", days, hours, minutes, seconds) + }else if days == 1 && hours != 1 && minutes == 1 && seconds == 1{ + return fmt.Sprintf("%d day, %d hours, %d minute and %d second", days, hours, minutes, seconds) + }else if days == 1 && hours == 1 && minutes != 1 && seconds != 1{ + return fmt.Sprintf("%d day, %d hour, %d minutes and %d seconds", days, hours, minutes, seconds) + }else if days == 1 && hours == 1 && minutes != 1 && seconds == 1{ + return fmt.Sprintf("%d day, %d hour, %d minutes and %d second", days, hours, minutes, seconds) + }else if days == 1 && hours == 1 && minutes == 1 && seconds != 1{ + return fmt.Sprintf("%d day, %d hour, %d minute and %d seconds", days, hours, minutes, seconds) + }else if days == 1 && hours == 1 && minutes == 1 && seconds == 1{ + return fmt.Sprintf("%d day, %d hour, %d minute and %d second", days, hours, minutes, seconds) + } + return fmt.Sprintf("%d days, %d hours, %d minutes and %d seconds", days, hours, minutes, seconds) + } + return "error" +} + +func (app *application) checkIfAdmin(s *discordgo.Session, m *discordgo.MessageCreate) (bool, error) { + authorMemberInfo, err := s.GuildMember(m.GuildID, m.Author.ID) + if err != nil { + return false, err + } + + roleIDs, err := app.adminroles.GetAdminRoleIDs() + if err != nil { + return false, err + } + + for i := 0; i < len(authorMemberInfo.Roles); i++ { + for j := 0; j < len(roleIDs); j++ { + if authorMemberInfo.Roles[i] == roleIDs[j] { + return true, nil + } + } + } + + app.infoLog.Printf("The user %s tried to perform an admin command without an admin role, purge them", m.Author) + _, err = s.ChannelMessageSend(m.ChannelID, "You aren't authorized to perform this function, this incident has been reported.") + if err != nil { + return false, err + } + + return false, nil +} + +func (app *application) contextLength(splitCommand []string) (error) { + if !(len(splitCommand) > 2) { + app.errorLog.Printf("The command's context was not enough.\n") + return errors.New("not enough context") + } + return nil +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..259c5db --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,27 @@ +version: "3.7" + +services: + app: + container_name: pepebot_server + build: . + restart: always + depends_on: + - db + environment: + - DB_USER= + - DB_PASS= + - DISCORD_TOKEN= + db: + container_name: pepebot_database + image: mysql:8.0 + restart: always + environment: + MYSQL_ROOT_PASSWORD: + MYSQL_DATABASE: + MYSQL_USER: + MYSQL_PASSWORD: + volumes: + - pepe_db_data:/var/lib/mysql + +volumes: + pepe_db_data: {} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0b98507 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module quenten.nl/pepebot + +go 1.16 + +require ( + github.com/bwmarrin/discordgo v0.23.2 + github.com/go-sql-driver/mysql v1.6.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5b4c2a6 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4= +github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/models/models.go b/models/models.go new file mode 100644 index 0000000..4d36f29 --- /dev/null +++ b/models/models.go @@ -0,0 +1,23 @@ +package models + +import ( + "errors" + "time" +) + + +var ErrNoRecord = errors.New("models: no matching record found") + +type Badword struct { + ID int + Word string + ServerID string + LastSaid time.Time +} + +type AdminRoles struct { + ID int + RoleName string + RoleID string + GuildID string +} \ No newline at end of file diff --git a/models/mysql/adminroles.go b/models/mysql/adminroles.go new file mode 100644 index 0000000..2696b9e --- /dev/null +++ b/models/mysql/adminroles.go @@ -0,0 +1,99 @@ +package mysql + +import ( + "database/sql" + "errors" + + "quenten.nl/pepebot/models" +) + + +type AdminRolesModel struct { + DB *sql.DB +} + +func (m *AdminRolesModel) GetAdmins() ([]*models.AdminRoles, error) { + stmt := `SELECT id, rolename, roleid, guildid FROM adminroles` + + rows, err := m.DB.Query(stmt) + if err != nil { + return nil, err + } + defer rows.Close() + + tmp := []*models.AdminRoles{} + + for rows.Next() { + t := &models.AdminRoles{} + err = rows.Scan(&t.ID, &t.RoleName, &t.RoleID, &t.GuildID) + if err != nil { + return nil, err + } + + tmp = append(tmp, t) + } + + if err = rows.Err(); err != nil{ + return nil, err + } + + return tmp, nil +} + +func (m *AdminRolesModel) GetAdminRoleIDs() ([]string, error) { + admins, err := m.GetAdmins() + if err != nil { + return nil, err + } + + roleIDs := make([]string, 0) + for i := 0; i < len(admins); i++ { + roleIDs = append(roleIDs, admins[i].RoleID) + } + + return roleIDs, nil +} + +func (m *AdminRolesModel) AddAdminRole(roleName string, roleID string, guildID string) (int, error) { + stmt := `INSERT INTO adminroles (rolename, roleid, guildid) VALUES (?, ?, ?)` + + result, err := m.DB.Exec(stmt, roleName, roleID, guildID) + if err != nil { + return 0, err + } + + id, err := result.LastInsertId() + if err != nil { + return 0, err + } + + return int(id), nil +} + +func (m *AdminRolesModel) RemoveAdminRole(roleName string, roleID string, guildID string) (error) { + stmt := `SELECT id FROM adminroles WHERE rolename = ? AND roleid = ? AND guildid = ?` + + row := m.DB.QueryRow(stmt, roleName, roleID, guildID) + + var id int + err := row.Scan(&id) + if err != nil { + if errors.Is(err, sql.ErrNoRows){ + return models.ErrNoRecord + }else{ + return err + } + } + + stmt = `DELETE FROM adminroles WHERE id = ?` + result, err := m.DB.Exec(stmt, id) + if err != nil { + return err + } + + if r, _ := result.RowsAffected(); r == 0 || r > 1 { + return errors.New("either zero or more than one rows were affected") + } + + return nil +} \ No newline at end of file diff --git a/models/mysql/badword.go b/models/mysql/badword.go new file mode 100644 index 0000000..feb9418 --- /dev/null +++ b/models/mysql/badword.go @@ -0,0 +1,147 @@ +package mysql + +import ( + "database/sql" + "errors" + "strconv" + + "quenten.nl/pepebot/models" +) + + +type BadwordModel struct { + DB *sql.DB +} + +func (m *BadwordModel) GetWord(word string, serverID string) (*models.Badword, error) { + stmt := `SELECT id, word, serverid, lastsaid FROM badwords + WHERE word = ? AND serverid = ?` + + row := m.DB.QueryRow(stmt, word, serverID) + + bw := &models.Badword{} + + err := row.Scan(&bw.ID, &bw.Word, &bw.ServerID, &bw.LastSaid) + if err != nil { + if errors.Is(err, sql.ErrNoRows){ + return nil, models.ErrNoRecord + }else{ + return nil, err + } + } + + return bw, nil +} + +func (m *BadwordModel) AllWords() (map[string][]string, error) { + stmt := `SELECT word, serverid FROM badwords` + + rows, err := m.DB.Query(stmt) + if err != nil { + return nil, err + } + defer rows.Close() + + type tmp struct{ + word string + serverid string + } + tmp2 := []*tmp{} + + for rows.Next() { + t := &tmp{} + err = rows.Scan(&t.word, &t.serverid) + if err != nil { + return nil, err + } + + tmp2 = append(tmp2, t) + } + + if err = rows.Err(); err != nil{ + return nil, err + } + + finaltmp := make(map[string][]string) + for i := 0; i < len(tmp2); i++ { + + finaltmp[tmp2[i].serverid] = append(finaltmp[tmp2[i].serverid], tmp2[i].word) + } + + return finaltmp, nil +} + +func (m *BadwordModel) InsertNewWord(word string, serverid string) (int, error) { + stmt := `INSERT INTO badwords (word, serverid, lastsaid) + VALUES (?, ?, UTC_TIMESTAMP())` + + id1, err := strconv.Atoi(serverid) + if err != nil { + return 0, err + } + result, err := m.DB.Exec(stmt, word, id1) + if err != nil { + return 0, err + } + + id, err := result.LastInsertId() + if err != nil { + return 0, err + } + + return int(id), nil +} + +func (m *BadwordModel) RemoveWord(word string, serverid string) (error) { + allwords, err := m.AllWords() + if err != nil { + return err + } + found := false + for i := 0; i < len(allwords[serverid]); i++ { + if allwords[serverid][i] == word { + found = true + break + } + } + if !found { + return errors.New("that word doesn't exist") + } + + stmt := `DELETE FROM badwords WHERE word = ? AND serverid = ?` + + result, err := m.DB.Exec(stmt, word, serverid) + if err != nil { + return err + } + + if r, _ := result.RowsAffected(); r == 0 || r > 1 { + return errors.New("an unknown error occured") + } + + return nil +} + +func (m *BadwordModel) UpdateLastSaid(word string, serverid string) (int, error) { + stmt := `SELECT id FROM badwords WHERE + word = ? AND serverid = ?` + + row := m.DB.QueryRow(stmt, word, serverid) + + id := 0 + row.Scan(&id) + + stmt = `UPDATE badwords SET lastsaid = UTC_TIMESTAMP() WHERE id = ?` + + result, err := m.DB.Exec(stmt, id) + if err != nil { + return 0, err + } + + id2, err := result.LastInsertId() + if err != nil { + return 0, nil + } + + return int(id2), nil +} \ No newline at end of file