From a54a1074087a1efa1fa048dc3a9825ef657b66e9 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Fri, 1 Jan 2021 13:24:14 -0500 Subject: [PATCH] Initial Commit --- .gitignore | 2 + config.json.bak | 1 + discord.go | 337 ++++++++++++++++++++++++++++++++++++++++++++++++ email.go | 25 ++++ main.go | 83 ++++++++++++ minecraft.go | 216 +++++++++++++++++++++++++++++++ types.go | 78 +++++++++++ userdb.go | 153 ++++++++++++++++++++++ verification.go | 1 + 9 files changed, 896 insertions(+) create mode 100644 .gitignore create mode 100755 config.json.bak create mode 100644 discord.go create mode 100644 email.go create mode 100644 main.go create mode 100644 minecraft.go create mode 100644 types.go create mode 100644 userdb.go create mode 100644 verification.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3eb48e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +minedall +config.json diff --git a/config.json.bak b/config.json.bak new file mode 100755 index 0000000..c89b1f6 --- /dev/null +++ b/config.json.bak @@ -0,0 +1 @@ +{"GuildID":"694345617438408755","MonitorChan":"694349670452822076","VerifiedChan":"694349742762754135","AdminChan":"694349831136739379","MonitorRole":"694349539997515818","VerifiedRole":"694349466249199656","AdminRole":"694349424557686895","StartTime":"2020-05-11T16:08:32.331953504-04:00","DiscordToken":"Njk0MzQ2NDYzOTg3MzY3OTk3.XoKSrA.cFeXpq2uohRKN-emquwZpoUPO5w","MyEmail":"olmstea1@sunypoly.edu","EmailPass":"qgoyybrjjkfywllo","SmtpServer":"smtp.gmail.com:587","AuthServer":"smtp.gmail.com","DbFile":"minedall.db","WbFinished":true} \ No newline at end of file diff --git a/discord.go b/discord.go new file mode 100644 index 0000000..7b31abb --- /dev/null +++ b/discord.go @@ -0,0 +1,337 @@ +package main + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/google/uuid" +) + +var session *discordgo.Session +var pendingV []Verification +var rebootTS time.Time + +const alpha = "abcdefghijklmnopqrstuvwxyz1234567890" + +func configureDiscord() { + dg, err := discordgo.New("Bot " + config.DiscordToken) + if err != nil { + log.LogErrorType(err) + log.LogPanic("Unable to create bot using token.") + } + + dg.AddHandler(ready) + dg.AddHandler(readReaction) + dg.AddHandler(messageCreate) + dg.AddHandler(guildMemberAdd) + session = dg + + err = dg.Open() + if err != nil { + log.LogErrorType(err) + log.LogPanic("Unable to open websocket") + } + log.LogInfo("Discord link is now online.") +} +func discordExit() { + log.LogCritical("Closing Discord (closing Session)") + session.Close() +} + +func sendDiscordMessage(msg string) { + session.ChannelMessageSend(config.VerifiedChan, fixMentions(msg)) +} +func sendAdminDiscordMessage(msg string) { + session.ChannelMessageSend(config.AdminChan, msg) +} + +func ready(s *discordgo.Session, m *discordgo.Ready) { + s.UpdateStatus(0, "Minecraft") +} + +func banUser(s *discordgo.Session, u discordgo.User) { + s.GuildBanCreate(config.GuildID, u.ID, 0) +} + +func readReaction(s *discordgo.Session, m *discordgo.MessageReactionAdd) { + defer log.PanicSafe() + if m.Emoji.Name != "⚠️" && m.Emoji.Name != "❗" { + return + } + log.LogDebug(fmt.Sprintf("Report emoji: ```%+v```", *m)) + msg, err := s.ChannelMessage(m.ChannelID, m.MessageID) + log.LogDebug(fmt.Sprintf("Message retrieved: %+v", *msg)) + if err != nil { + s.ChannelMessageSend(config.AdminChan, "A message was reported that I could not detect.") + return + } + reporter, _ := s.GuildMember(config.GuildID, m.UserID) + violator, _ := s.GuildMember(config.GuildID, msg.Author.ID) + s.ChannelMessageSend(config.AdminChan, fmt.Sprintf("A message was reported! %+v reported %+v's message: %+v\n```%+v```", reporter.Nick, violator.Nick, msg.Content, *msg)) + var r Report + var violatorUser User + violatorUser.DiscordID = m.UserID + violatorUser.Retrieve() + var reporterUser User + reporterUser.DiscordID = msg.Author.ID + reporterUser.Retrieve() + reporterUser.Write() // Update last seen + r.Time = time.Now() + r.Reporter = reporterUser + r.Abuser = violatorUser + r.Msg = msg.Content + r.Write() + +} +func guildMemberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) { + defer log.PanicSafe() + s.GuildMemberRoleAdd(config.GuildID, m.User.ID, config.MonitorRole) + st, _ := s.UserChannelCreate(m.User.ID) // Get the ID of PM channel + id := uuid.New() // Generate new UUID4 + var v Verification // Set up new Verification + v.DiscordID = m.User.ID // ID is what really matters, username is garbage + v.Created = time.Now() // Setup the created time for reasons + v.Code = id.String() // Generate Random UUID + pendingV = append(pendingV, v) + s.ChannelMessageSend(st.ID, "Hello! You have not yet verified with me. Type !sitnet [your sitnet ID here] to verify who you are. You will get an email explaining what to do next. https://web.cs.sunyit.edu/polymc") + +} + +func fixMentions(s string) string { + defer log.PanicSafe() + var ret string + if !strings.Contains(s, "@") { + return s + } + parts := strings.Split(s, " ") + for _, part := range parts { + if strings.HasPrefix(part, "@") { + var u User + u.Email = strings.Replace(part, "@", "", -1) + u.Retrieve() + ret += fmt.Sprintf("<@!%+v>", u.DiscordID) + } else { + ret += part + } + ret += " " + + } + + return ret +} + +func setupVerifications(s *discordgo.Session, m *discordgo.MessageCreate) { + defer log.PanicSafe() + st, _ := s.UserChannelCreate(m.Author.ID) // Get the ID of PM channel + id := uuid.New() // Generate new UUID4 + var v Verification // Set up new Verification + v.DiscordID = m.Author.ID // ID is what really matters, username is garbage + v.Created = time.Now() // Setup the created time for reasons + v.Code = id.String() // Generate Random UUID + pendingV = append(pendingV, v) + s.ChannelMessageSend(st.ID, "Hello! You have not yet verified with me. Type !sitnet [your sitnet ID here] to verify who you are. You will get an email explaining what to do next. https://web.cs.sunyit.edu/polymc") + +} + +func alphaOnly(s string) bool { + for _, char := range s { + if !strings.Contains(alpha, strings.ToLower(string(char))) { + return false + } + } + return true +} +func handlePM(s *discordgo.Session, m *discordgo.MessageCreate) { + defer log.PanicSafe() + for k, v := range pendingV { + if v.DiscordID == m.Author.ID { + parts := strings.Split(m.Content, " ") + + if strings.HasPrefix(m.Content, "!sitnet") { + if len(parts) != 2 || !alphaOnly(parts[1]) || len(parts[1]) > 12 { + + s.ChannelMessageSend(m.ChannelID, "You have sent an invalid SITNET ID. Your SITNET ID is your SUNY Poly email address, up until the @sunypoly.edu. For example, urbanc@sunypoly.edu would have a SITNET ID of urbanc. He would say `!sitnet urbanc`") + return + } + v.SITNET = parts[1] + v.sendEmail() + pendingV[k] = v + s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Thanks! I have sent an email to %+v@sunypoly.edu from %+v with further instructions.", v.SITNET, config.MyEmail)) + } + if strings.HasPrefix(m.Content, "!verify") { + if len(parts) != 3 { + s.ChannelMessageSend(m.ChannelID, "You have sent an invalid verification, please ensure you are sending ```!verify $code $minecraftID``` Please ensure there are no extra spaces, also your minecraftID is case sensitive.") + return + } + if parts[1] != v.Code { + s.ChannelMessageSend(m.ChannelID, "You have sent an invalid verification code, please verify using the code from your email.") + return + } + var u User + u.DiscordID = v.DiscordID + u.Email = v.SITNET + u.McUser = strings.Replace(parts[2], "$", "", -1) + if v.SITNET == parts[2] { + s.ChannelMessageSend(m.ChannelID, "SITNET Can not be the same as Minecraft Username. Please append a dollar sign to the end of your minecraft username if it actually is the same as your sitnet. For example urbanc would put urbanc$ as his minecraft username to dismiss this message.") + return + } + log.LogDebug(fmt.Sprintf("Attempting to write %+v from verification %+v", u, v)) + err := u.Write() + if err != nil { + s.ChannelMessageSend(m.ChannelID, "There was an issue with your verification. Please check in with an administrator in the #unverified channel.") + return + } + u.McWhitelist() + s.ChannelMessageSend(m.ChannelID, "Welcome! You have successfully verified. The server address is polymc.cs.sunyit.edu - see you there!") + s.GuildMemberRoleRemove(config.GuildID, u.DiscordID, config.MonitorRole) + s.GuildMemberRoleAdd(config.GuildID, u.DiscordID, config.VerifiedRole) + s.GuildMemberNickname(config.GuildID, u.DiscordID, v.SITNET) + pendingV[k] = pendingV[len(pendingV)-1] // Copy last element to index i. + pendingV = pendingV[:len(pendingV)-1] // Truncate slice. + } + } + } + +} + +func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { + defer log.PanicSafe() + if m.Author.ID == s.State.User.ID { + return + } + if m.GuildID == "" { + handlePM(s, m) + return + } + if m.ChannelID == config.MonitorChan && strings.HasPrefix(m.Content, "!verify") { + for _, v := range pendingV { + if v.DiscordID == m.Author.ID { + return + } + } + setupVerifications(s, m) + return + } + if m.ChannelID == config.MonitorChan { + return + } + var u User + u.DiscordID = m.Author.ID + u.Retrieve() + u.Write() // Update last seen + + if strings.HasPrefix(m.Content, "!verify") && m.ChannelID != config.AdminChan { + s.ChannelMessageSend(m.ChannelID, "That command ONLY works for unverified users in the unverified channel. Instead, for account resets try !resetme") + return + } + if strings.HasPrefix(m.Content, "!resetme") { + mcCommand(fmt.Sprintf("whitelist remove %+v", u.McUser)) + u.Delete() + s.GuildMemberRoleAdd(config.GuildID, m.Author.ID, config.MonitorRole) + s.GuildMemberRoleRemove(config.GuildID, m.Author.ID, config.VerifiedRole) + return + } + if strings.HasPrefix(m.Content, "!online") { + if len(onlineUsers) == 0 { + s.ChannelMessageSend(m.ChannelID, "There are no users online.") + return + } + str := fmt.Sprintf("The following %+v users are online:\n```", len(onlineUsers)) + for _, usr := range onlineUsers { + str += fmt.Sprintf("%+v : %+v\n", usr.Email, usr.McUser) + } + str += "```" + s.ChannelMessageSend(m.ChannelID, str) + return + } + if m.ChannelID == config.VerifiedChan { + log.LogDebug(fmt.Sprintf("Discord message from retrieved user %+v", u)) + // if debug message shows complete user, you may replace userStr with u.SITNresET + if m.Member.Nick != u.Email { + s.GuildMemberNickname(config.GuildID, m.Author.ID, u.Email) + } + mcSay(fmt.Sprintf("[%+v]: %+v", u.Email, m.Content)) + return + } + if m.ChannelID == config.AdminChan { + go handleAdmin(s, m) + } +} + +func manualVerifyUser(s *discordgo.Session, m *discordgo.MessageCreate) { + parts := strings.Split(m.Content, " ") + if len(parts) != 4 { + sendAdminDiscordMessage("Usage !verify $SITNET $McUser $DiscordID") + sendAdminDiscordMessage("Please note, DiscordID is the numerical ID, not their username.") + } + var u User + u.Email = parts[1] + u.McUser = parts[2] + u.DiscordID = parts[3] + u.Write() +} + +func handleAdmin(s *discordgo.Session, m *discordgo.MessageCreate) { + if !strings.HasPrefix(m.Content, "!") { + return + } + if strings.HasPrefix(m.Content, "!verify") { + manualVerifyUser(s, m) + } + if strings.HasPrefix(m.Content, "!mc") { + mcCommand(strings.Replace(m.Content, "!mc", "", -1)) + return + } + if strings.HasPrefix(m.Content, "!wb") { + if strings.Contains(m.Content, "stop") { + config.WbFinished = true + } else { + config.WbFinished = false + } + } + if strings.HasPrefix(m.Content, "!reboot") { + if serverRebooting { + s.ChannelMessageSend(config.AdminChan, fmt.Sprintf("Reboot pending: %+v", time.Until(rebootTS))) + return + } + serverRebooting = true + rebootTS = time.Now().Add(30 * time.Minute) + parts := strings.Split(m.Content, " ") + if len(parts) == 1 { + s.ChannelMessageSend(config.AdminChan, "A reboot has been queued. I will poll for when users log out and then execute.") + timeout := 120 + for { + if len(onlineUsers) == 0 || timeout == 0 { + mcExit() + discordExit() + log.LogPanic("Shutting down server from queued reboot.") + } + time.Sleep(15 * time.Second) + timeout-- + } + return + } + if len(parts) != 2 && len(parts) != 3 { + s.ChannelMessageSend(config.AdminChan, "You dumbass. I take one or two arguments.") + return + } + when, err := strconv.Atoi(parts[1]) + if err != nil { + s.ChannelMessageSend(config.AdminChan, "The first argument must be an int, number of minutes.") + return + } + reminders := 0 + if len(parts) == 3 { + reminders, err = strconv.Atoi(parts[2]) + if err != nil { + s.ChannelMessageSend(config.AdminChan, "You supplied a second argument.. So that also has to be an int, number of reminders.") + return + } + } + mcReboot(when, reminders) + + } +} diff --git a/email.go b/email.go new file mode 100644 index 0000000..316b184 --- /dev/null +++ b/email.go @@ -0,0 +1,25 @@ +package main + +import "fmt" +import "net/smtp" + +func (e Email) send() { + + message := fmt.Sprintf("From: %s\n", config.MyEmail) + for _, recipient := range e.Recipients { + message += fmt.Sprintf("To: %s\n", recipient) + } + message += fmt.Sprintf("Subject: %s\n", e.Subject) + message += e.Body + log.LogInfo("Message created") + log.LogDebug(message) + log.LogInfo("Sending message") + err := smtp.SendMail(config.SmtpServer, + smtp.PlainAuth("", config.MyEmail, config.EmailPass, config.AuthServer), + config.MyEmail, e.Recipients, []byte(message)) + if err != nil { + log.LogErrorType(err) + return + } + log.LogInfo("Email Sent") +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..469fa11 --- /dev/null +++ b/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "bufio" + "encoding/json" + "io/ioutil" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "github.com/rudi9719/loggy" +) + +var ( + // Logging Setup + logOpts = loggy.LogOpts{ + UseStdout: true, + Level: 5, + OutFile: "Minedall.log", + } + log = loggy.NewLogger(logOpts) + config Config + onlineUsers []User + serverRebooting = false +) + +func loadConfig() { + var c Config + confFile, _ := ioutil.ReadFile("config.json") + err := json.Unmarshal([]byte(confFile), &c) + if err != nil { + log.LogErrorType(err) + return + } + config = c +} +func saveConfig() { + file, err := json.Marshal(config) + if err != nil { + log.LogErrorType(err) + } + err = ioutil.WriteFile("config.json", file, 0766) + if err != nil { + log.LogErrorType(err) + } else { + log.LogInfo("Config saved.") + } +} + +func main() { + loadConfig() + if config.DiscordToken == "" { + time.Sleep(5 * time.Second) + return + } + + config.StartTime = time.Now() + go configureDiscord() + go configureMinecraft() + go dbSetup() + // Wait here until CTRL-C or other term signal is received. + log.LogInfo("Minedall 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) + reader := bufio.NewReader(os.Stdin) + for { + text, _ := reader.ReadString('\n') + if strings.Contains(text, "stop") { + saveConfig() + mcExit() + discordExit() + return + } + mcCommand(text) + } + <-sc + + mcExit() + discordExit() +} diff --git a/minecraft.go b/minecraft.go new file mode 100644 index 0000000..018a678 --- /dev/null +++ b/minecraft.go @@ -0,0 +1,216 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "os/exec" + "regexp" + "strings" + "time" +) + +var ( + mcOut io.Reader + mcIn io.Writer + mcStop = false + mcLoaded = false + mcRebooting = false + mcWBRunning = false +) + +func (u User) McWhitelist() { + mcCommand(fmt.Sprintf("whitelist add %+v", u.McUser)) + mcCommand(fmt.Sprintf("nicknames %+v %+v", u.McUser, u.Email)) +} + +func configureMinecraft() { + log.LogInfo("Starting MinecraftServer") + server := exec.Command("java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseG1GC", "-Xms4G", "-Xmx8G", "-Dsun.rmi.dgc.server.gcInterval=2147483646", "-XX:G1NewSizePercent=20", "-XX:MaxGCPauseMillis=50", "-XX:G1HeapRegionSize=32M", "-jar", "/opt/minecraft/paper/server.jar", "nogui") + mcIn, _ = server.StdinPipe() + mcOut, _ = server.StdoutPipe() + mcErr, _ := server.StderrPipe() + mcErrScan := bufio.NewScanner(mcErr) + server.Start() + defer server.Wait() + go listenOut() + for mcErrScan.Scan() { + if mcStop { + break + } + log.LogError(mcErrScan.Text()) + } +} + +func handleMcOnline(s string) { + log.LogDebug("A user has come online or gone offline.") + s = strings.Replace(s, "&e", "", -1) + var u User + parts := strings.Split(s, " ") + log.LogDebug(fmt.Sprintf("%+v", parts)) + if parts[2] == "UUID" { + log.LogDebug(fmt.Sprintf("User %+v detected.", parts[5])) + if serverRebooting { + mcCommand(fmt.Sprintf("kick %+v Server reboot pending.", parts[5])) + } + //mcCommand(fmt.Sprintf("kick %+v Server currently unavailable.", parts[5])) + u.McUser = parts[5] + u.Retrieve() + if u.Email == "" { + u.Email = u.McUser + u.McUser = "" + u.Retrieve() + if u.McUser == "" { + sendAdminDiscordMessage(fmt.Sprintf("Unable to verify %+v", parts[5])) + mcCommand(fmt.Sprintf("kick %+v Verified SITNET not found. Admins have been notified.", parts[5])) + return + } + } else if u.Email != parts[5] { + mcCommand(fmt.Sprintf("lp user %+v parent add player", u.McUser)) + mcCommand(fmt.Sprintf("nicknames %+v %+v", u.McUser, u.Email)) + } + for _, v := range onlineUsers { + if v.Email == u.Email { + return + } + } + onlineUsers = append(onlineUsers, u) + } else { + log.LogDebug(fmt.Sprintf("User %+v detected.", parts[2])) + u.McUser = parts[2] + u.Retrieve() + for k, v := range onlineUsers { + log.LogDebug(fmt.Sprintf("v.McUser: %+v, parts[2]: %+v", v.McUser, parts[2])) + if v.McUser == parts[2] || v.Email == parts[2] { + onlineUsers[k] = onlineUsers[len(onlineUsers)-1] // Copy last element to index i. + onlineUsers = onlineUsers[:len(onlineUsers)-1] // Truncate slice. + } + } + } + u.Write() + log.LogDebug(fmt.Sprintf("Online users: %+v", onlineUsers)) + if len(onlineUsers) == 0 && !config.WbFinished { + log.LogInfo("The last user has logged out.") + mcWorldBuilderToggle(true) + } else { + mcWorldBuilderToggle(false) + } +} + +func listenOut() { + outScan := bufio.NewScanner(mcOut) + var re = regexp.MustCompile(`INFO\]: <(.*?)> (.*)`) + for outScan.Scan() { + if mcStop { + break + } + output := outScan.Text() + go log.LogInfo(fmt.Sprintf("server.jar: %+v", output)) + if !mcLoaded { + if strings.Contains(output, "For help, type \"help\"") { + mcLoaded = true + sendDiscordMessage("Minecraft server is now available.") + mcWorldBuilderToggle(true) + } + continue + } + if strings.Contains(output, "UUID of player") || strings.Contains(output, "left the game") { + go handleMcOnline(output) + } + match := re.FindStringSubmatch(output) + if match != nil { + if match[1] != "Server" { + var u User + u.McUser = match[1] + u.Retrieve() + if u.Email == "" { + u.McUser = "" + u.Email = match[1] + u.Retrieve() + u.Write() + } else if u.Email != match[1] { + mcCommand(fmt.Sprintf("nicknames %+v %+v", u.McUser, u.Email)) + } + log.LogDebug(fmt.Sprintf("Minecraft message from retrieved user %+v", u)) + go sendDiscordMessage(fmt.Sprintf("[%s]: %s", u.Email, match[2])) + continue + } + } else if mcLoaded && !strings.Contains(output, " INFO]: [Server] [") && !strings.Contains(output, "[WorldBorder]") { + go sendAdminDiscordMessage(output) + } + + } + +} +func mcWorldBuilderToggle(b bool) { + log.LogDebug("World builder toggled") + if b && !mcWBRunning { + log.LogDebug("Building world!") + mcCommand("wb world fill 500 300 false") + mcCommand("wb fill confirm") + mcWBRunning = true + return + } else if mcWBRunning { + log.LogDebug("Not building world!") + mcCommand("wb fill cancel") + mcWBRunning = false + } + +} +func mcSay(message string) { + mcCommand(fmt.Sprintf("say %+v", mcMention(message))) +} +func mcMention(s string) string { + var ret string + if !strings.Contains(s, "@") { + return s + } + parts := strings.Split(s, " ") + for _, part := range parts { + if strings.HasPrefix(part, "<@!") { + var u User + u.DiscordID = strings.Replace(strings.Replace(part, "<@!", "", -1), ">", "", -1) + u.Retrieve() + ret += "@" + ret += u.Email + } else { + ret += part + } + ret += " " + log.LogDebug(ret) + } + return ret +} +func mcCommand(message string) { + mcIn.Write([]byte(message)) + mcIn.Write([]byte("\n")) +} +func mcReboot(when int, reminders int) { + if mcRebooting { + return + } + mcRebooting = true + untilReboot := time.Duration(when) * time.Minute + rebootTime := time.Now().Add(untilReboot) + var untilReminder time.Duration + if reminders != 0 { + untilReminder = time.Duration(when / reminders) + } + for { + mcSay(fmt.Sprintf("A reboot has been scheduled for %+v", untilReboot)) + if reminders > 0 { + reminders-- + time.Sleep(untilReminder) + continue + } + time.Sleep(time.Until(rebootTime)) + mcExit() + discordExit() + } +} +func mcExit() { + log.LogCritical("Closing Minecraft (issuing command `stop`)") + mcCommand("stop") + mcStop = true + mcLoaded = false +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..a3d653c --- /dev/null +++ b/types.go @@ -0,0 +1,78 @@ +package main + +import "fmt" +import "time" +import "reflect" + +type Config struct { + GuildID string + MonitorChan string + VerifiedChan string + AdminChan string + MonitorRole string + VerifiedRole string + AdminRole string + StartTime time.Time + DiscordToken string + MyEmail string + EmailPass string + SmtpServer string + AuthServer string + DbFile string + WbFinished bool +} + +type User struct { + DiscordID string + Email string + McUser string + LastSeen time.Time + LastReported time.Time +} + +func userGetField(u *User, field string) string { + r := reflect.ValueOf(u) + f := reflect.Indirect(r).FieldByName(field) + return string(f.String()) +} + +type Report struct { + Time time.Time + Reporter User + Abuser User + Msg string + Target Target +} +type Target struct { + Type string + Place string +} + +type Verification struct { + Created time.Time + DiscordID string + Email Email + SITNET string + Code string +} + +func (v Verification) sendEmail() { + var e Email + e.Recipients = append(e.Recipients, fmt.Sprintf("%+v@sunypoly.edu", v.SITNET)) + e.Subject = "Minecraft Verification" + e.Body = fmt.Sprintf("Hello!\n\nYou are getting this email because you joined the Minecraft Discord channel for SUNY Poly. If you would like to verify your account so that you can use our Discord server and play on the SUNY Poly Minecraft server, please private message the Minedall bot with the command !verify %+v [your case sensitive minecraft username here]. If you are having trouble with this action, please send a message in the \"unverified\" channel, and a moderator will assist you with the process. \n\nWe hope to see you soon in our server!\nCSNet Staff\nadmins@cs.sunyit.edu", v.Code) + e.send() +} + +type Email struct { + Recipients []string + Subject string + Body string +} + +type McAccount struct { + id string + name string +} + +type McAccountArr []McAccount diff --git a/userdb.go b/userdb.go new file mode 100644 index 0000000..fb1725c --- /dev/null +++ b/userdb.go @@ -0,0 +1,153 @@ +package main + +import ( + "database/sql" + "fmt" + "time" + + _ "github.com/mattn/go-sqlite3" +) + +// Inital func to ensure database is properly setup +func dbSetup() { + database, _ := sql.Open("sqlite3", config.DbFile) + defer database.Close() + statement, _ := database.Prepare("CREATE TABLE IF NOT EXISTS users (email TEXT PRIMARY KEY, discordID TEXT, mcUser TEXT, lastSeen TEXT, lastReported TEXT)") + statement.Exec() + statement, _ = database.Prepare("CREATE TABLE IF NOT EXISTS reports (email TEXT PRIMARY KEY, reportedBy TEXT, reportMsg TEXT, reportTime TEXT)") + statement.Exec() +} + +// Write a user to database, also update last seen to time.Now() +func (u User) Write() error { + database, _ := sql.Open("sqlite3", config.DbFile) + defer database.Close() + if u.Email == "" || u.DiscordID == "" || u.McUser == "" { + log.LogDebug(fmt.Sprintf("Attempted to insert invalid user. DENIED.\n%+v", u)) + return nil + } + statement, err := database.Prepare("INSERT INTO users (email, discordID, mcUser, lastSeen, lastReported) VALUES (?, ?, ?, ?, ?)") + if err != nil { + return err + } + log.LogDebug(fmt.Sprintf("Running insert on %+v", u)) + statement.Exec(u.Email, u.DiscordID, u.McUser, time.Now(), u.LastReported) + return nil +} + +// Delete a user from the database. +func (u User) Delete() error { + database, err := sql.Open("sqlite3", config.DbFile) + if err != nil { + return err + } + defer database.Close() + statement, err := database.Prepare("DELETE FROM users WHERE email=? mcUser=? discordID=?") + if err != nil { + return err + } + _, err = statement.Exec(u.Email, u.McUser, u.DiscordID) + return err +} + +// Get a user by a field (deprecated?) +func (u User) GetUserBy(userfield string) { + database, _ := sql.Open("sqlite3", config.DbFile) + defer database.Close() + rows, _ := database.Query("SELECT * FROM users WHERE ? = ?", userfield, userGetField(&u, userfield)) + for rows.Next() { + rows.Scan(&u.Email, &u.DiscordID, &u.McUser, &u.LastSeen, &u.LastReported) + } +} + +// Retrieve a user and fill in missing values +func (u *User) Retrieve() error { + database, _ := sql.Open("sqlite3", config.DbFile) + defer database.Close() + var parms []interface{} + statement := "SELECT * FROM users WHERE" + first := true + if u.DiscordID != "" { + parms = append(parms, u.DiscordID) + statement += " discordID = ? " + first = false + } + if u.Email != "" { + parms = append(parms, u.Email) + if !first { + statement += " AND " + } else { + first = false + } + statement += " email = ?" + } + if u.McUser != "" { + parms = append(parms, u.McUser) + if !first { + statement += " AND " + } else { + first = false + } + statement += " mcUser = ?" + } + log.LogDebug(fmt.Sprintf("Attempting to query using the following statement: %+v\nParams: %+v", statement, parms)) + rows, err := database.Query(statement, parms...) + if err != nil { + return err + } + for rows.Next() { + rows.Scan(&u.Email, &u.DiscordID, &u.McUser, &u.LastSeen, &u.LastReported) + } + go u.Write() + log.LogDebug(fmt.Sprintf("DB Returned: %+v", u)) + return nil + +} + +// Get a slice of all users within the database. +func GetAllUsers() []User { + database, _ := sql.Open("sqlite3", config.DbFile) + defer database.Close() + rows, _ := database.Query("SELECT * FROM users") + var users []User + for rows.Next() { + var u User // Don't need anything else for this + rows.Scan(&u.Email, &u.DiscordID, &u.McUser, &u.LastSeen, &u.LastReported) + users = append(users, u) // You wanna get all users, not just the last one right? + } + + return users +} + +// Get Reports per user via SITNET +func (u User) GetReports() []Report { + database, _ := sql.Open("sqlite3", config.DbFile) + defer database.Close() + rows, _ := database.Query("SELECT * FROM reports WHERE email = ?", u.Email) + var reports []Report + for rows.Next() { + var r Report + rows.Scan(&r.Abuser, &r.Reporter, &r.Msg, &r.Time) + reports = append(reports, r) + } + return reports +} + +// Write a report to the database +func (r Report) Write() error { + //(email TEXT PRIMARY KEY, reportedBy TEXT, reportMsg TEXT, reportTime TEXT) + log.LogDebug(fmt.Sprintf("Attempting Adding report of %+v", r.Abuser)) + database, _ := sql.Open("sqlite3", config.DbFile) + defer database.Close() + statement, err := database.Prepare("INSERT INTO reports (email, reportedBy, reportMsg, reportTime) VALUES (?, ?, ?, ?)") + if err != nil { + log.LogDebug(fmt.Sprintf("Adding report of %+v failed with error %+v", r.Abuser, err)) + return err + } + _, err = statement.Exec(r.Abuser.Email, r.Reporter.Email, r.Msg, r.Time) + if err != nil { + log.LogDebug(fmt.Sprintf("Execution failed with error %+v", err)) + return err + } + return nil +} diff --git a/verification.go b/verification.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/verification.go @@ -0,0 +1 @@ +package main