|
|
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 messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { |
|
|
defer log.PanicSafe() |
|
|
if m.Author.ID == s.State.User.ID { |
|
|
return |
|
|
} |
|
|
if m.GuildID == "" { |
|
|
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) |
|
|
|
|
|
} |
|
|
}
|
|
|
|