338 lines
11 KiB
Go
338 lines
11 KiB
Go
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)
|
|
|
|
}
|
|
}
|