2 Commits

Author SHA1 Message Date
5b8b01e31a Say hi again
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-01-14 18:46:37 -05:00
03bcca77f4 Say hi
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-14 18:34:01 -05:00
12 changed files with 401 additions and 631 deletions

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
build/ build/
.DS_Store
public/ public/
disgord-thanos disgord-thanos
disgord-Thanos disgord-Thanos

27
auth.go
View File

@ -7,7 +7,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/bwmarrin/discordgo"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
@ -160,29 +159,3 @@ func detectUser(r *http.Request, callFunc string) (bool, string) {
} }
return false, "" return false, ""
} }
func userFromID(i string) discordgo.User {
u, err := dg.GuildMember(config.GuildID, i)
if err != nil {
log.LogErrorType(err)
return discordgo.User{}
}
return *u.User
}
func idFromUsername(username string) string {
userID := ""
g, err := dg.GuildMembers(config.GuildID, "", 1000)
log.LogInfo("reqPass guild is %+v.", config.GuildID)
if err == nil {
for _, m := range g {
if strings.ToUpper(m.Nick) == strings.ToUpper(username) {
userID = m.User.ID
log.LogInfo("User ID found for %+v as %+v", username, userID)
}
}
} else {
log.LogError("Unable to find user ID for %+v", username)
}
return userID
}

View File

@ -1,176 +0,0 @@
package main
import (
"fmt"
"math/rand"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
func setupCommands() {
reboot := Command{
Name: "Reboot",
Keywords: []string{"reboot", "re", "restart"},
Exec: Reboot,
}
commands = append(commands, reboot)
bumpset := Command{
Name: "BumpSet",
Keywords: []string{"bs", "bumpset", "bumps"},
Exec: BumpSet,
}
commands = append(commands, bumpset)
retrieveVerification := Command{
Name: "Retrieve Verification",
Keywords: []string{"veri", "verification", "retrieve"},
Exec: RetrieveVerification,
}
commands = append(commands, retrieveVerification)
addQuote := Command{
Name: "Add Quote",
Keywords: []string{"quote", "addq", "q"},
Exec: AddQuote,
}
commands = append(commands, addQuote)
snap := Command{
Name: "Snap",
Keywords: []string{"snap", "purge", "sn"},
Exec: Snap,
}
commands = append(commands, snap)
status := Command{
Name: "Status",
Keywords: []string{"st", "status", "stats"},
Exec: Status,
}
commands = append(commands, status)
}
func Reboot(b BotCommand) bool {
defer log.PanicSafe()
if strings.Contains(b.Message.Content, rebootToken) {
exit(b.Session)
return true
}
return false
}
func BumpSet(b BotCommand) bool {
defer log.PanicSafe()
bump = false
parts := strings.Split(b.Message.Content, " ")
timer, err := strconv.Atoi(parts[1])
if err != nil {
b.Session.ChannelMessageSend(b.Message.ChannelID, fmt.Sprintf("Unable to decode timer: %+v", parts[1]))
return false
}
config.BumpTime = time.Now().Add(time.Duration(timer) * time.Minute).Add(-2 * time.Hour)
b.Session.ChannelMessageSend(b.Message.ChannelID, fmt.Sprintf("New bump time: %+v, expecting bump at %+v", config.BumpTime, config.BumpTime.Add(2*time.Hour)))
return true
}
func RetrieveVerification(b BotCommand) bool {
defer log.PanicSafe()
s := b.Session
m := b.Message
parts := strings.Split(m.Content, " ")
discordId := parts[1]
_, err := strconv.Atoi(discordId)
if err != nil {
discordId = idFromUsername(discordId)
}
user, err := s.GuildMember(config.GuildID, discordId)
if err != nil {
log.LogErrorType(err)
}
matches, err := filepath.Glob(fmt.Sprintf("./verifications/*%+v*", discordId))
if err != nil {
log.LogErrorType(err)
return false
}
if len(matches) != 1 {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Error finding verification for ID %+v", discordId))
return false
}
verificationImage, err := os.Open(matches[0])
if err != nil {
log.LogErrorType(err)
return false
}
msg := fmt.Sprintf("```%+v\nJoined: %+v\n```", user.User.Username, user.JoinedAt)
s.ChannelFileSendWithMessage(m.ChannelID, msg, fmt.Sprintf("%+v Verification", discordId), verificationImage)
return true
}
func AddQuote(b BotCommand) bool {
defer log.PanicSafe()
quotes = append(quotes, strings.ReplaceAll(b.Message.Content, b.Command, ""))
return true
}
func Snap(b BotCommand) bool {
defer log.PanicSafe()
go runPurge(b.Session)
b.Session.ChannelMessageSend(config.AdminChannel, quotes[rand.Intn(len(quotes))])
return true
}
func Status(b BotCommand) bool {
defer log.PanicSafe()
status := fmt.Sprintf("Uptime: %+v\n", time.Since(startupTime))
status += fmt.Sprintf("Last bump: %+v\n", time.Since(config.BumpTime))
status += fmt.Sprintf("Last bumper: <@%+v>\n", userFromID(config.LastBumper).Username)
status += fmt.Sprintf("Bump needed: %+v\n", bump)
if len(config.Unverified) > 0 {
status += "Unverified users:\n```"
for k, v := range config.Unverified {
uvUser := userFromID(k)
status += fmt.Sprintf("\n%+v will be removed in %+v", uvUser.Username, time.Until(v.Add(1*time.Hour)))
}
status += "```"
} else {
status += "There are no unverified users.\n"
}
if len(config.Verifications) > 0 {
status += "Pending verifications:\n"
status += "```"
for _, v := range config.Verifications {
status += fmt.Sprintf("%+v has submitted a verification.", v.Username)
}
status += "```"
} else {
status += "There are no pending verifications."
}
if len(config.Probations) > 0 {
status += "\nThe following users are on probation: \n```"
for uid, join := range config.Probations {
probationUser := userFromID(uid)
status += fmt.Sprintf("%+v for another %+v\n", probationUser.Username, time.Until(join.Add(2*time.Hour)))
}
status += "```"
}
b.Session.ChannelMessageSend(config.AdminChannel, status)
statistics := "```"
for k, v := range config.Stats {
adminUser, err := b.Session.GuildMember(config.GuildID, k)
if err == nil {
statistics += fmt.Sprintf("\n%+v: %+v", adminUser.User.Username, v+1)
} else {
log.LogErrorType(err)
}
}
statistics += "\n```"
log.LogInfo("Private statistics: %+v", statistics)
go runPurge(b.Session)
return true
}

110
config.go
View File

@ -3,17 +3,63 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http"
"net/url"
"os" "os"
"strings"
"time" "time"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
) )
func status(s *discordgo.Session) {
defer log.PanicSafe()
status := fmt.Sprintf("Uptime: %+v\n", time.Since(startupTime))
status += fmt.Sprintf("Last bump: %+v\n", time.Since(config.BumpTime))
status += fmt.Sprintf("Last bumper: <@%+v>\n", config.LastBumper)
status += fmt.Sprintf("Bump needed: %+v\n", bump)
if len(config.Unverified) > 0 {
status += "Unverified users:\n```"
for k, v := range config.Unverified {
uvUser := userFromID(s, k)
status += fmt.Sprintf("\n%+v will be removed in %+v", uvUser.Username, time.Until(v.Add(1*time.Hour)))
}
status += "```"
} else {
status += "There are no unverified users.\n"
}
if len(config.Verifications) > 0 {
status += "Pending verifications:\n"
status += "```"
for _, v := range config.Verifications {
status += fmt.Sprintf("%+v has submitted a verification.", v.Username)
}
status += "```"
} else {
status += "There are no pending verifications."
}
if len(config.Probations) > 0 {
status += "\nThe following users are on probation: \n```"
for uid, join := range config.Probations {
probationUser := userFromID(s, uid)
status += fmt.Sprintf("%+v for another %+v\n", probationUser.Username, time.Until(join.Add(2*time.Hour)))
}
status += "```"
}
s.ChannelMessageSend(config.AdminChannel, status)
statistics := "```"
for k, v := range config.Stats {
adminUser, err := s.GuildMember(config.GuildID, k)
if err == nil {
statistics += fmt.Sprintf("\n%+v: %+v", adminUser.User.Username, v+1)
} else {
log.LogErrorType(err)
}
}
statistics += "\n```"
log.LogInfo("Private statistics: %+v", statistics)
go runPurge(s)
return
}
func loadConfig() { func loadConfig() {
var c Config var c Config
confFile, _ := ioutil.ReadFile(configFile) confFile, _ := ioutil.ReadFile(configFile)
@ -58,23 +104,6 @@ func saveConfig() {
} }
} }
func adminInteraction(s *discordgo.Session, m string) {
admin, _ := s.GuildMember(config.GuildID, m)
counter, ok := config.Stats[admin.User.ID]
if !ok {
config.Stats[admin.User.ID] = 0
} else {
config.Stats[admin.User.ID] = counter + 1
}
}
func rebootBump() {
time.Sleep(time.Until(config.BumpTime.Add(2 * time.Hour)))
dg.ChannelMessageSend(config.AdminChannel, "!d bump is ready")
}
func bumpTimer(s *discordgo.Session) { func bumpTimer(s *discordgo.Session) {
if !bump { if !bump {
return return
@ -88,7 +117,6 @@ func bumpTimer(s *discordgo.Session) {
s.ChannelMessageSend(config.AdminChannel, "!d bump is ready.") s.ChannelMessageSend(config.AdminChannel, "!d bump is ready.")
bump = true bump = true
} }
func purgeTimer(s *discordgo.Session) { func purgeTimer(s *discordgo.Session) {
for { for {
runPurge(s) runPurge(s)
@ -111,28 +139,22 @@ func (v Verification) prettyPrint() string {
return ret return ret
} }
func storeVerification(v Verification) { func userFromID(s *discordgo.Session, i string) discordgo.User {
defer log.PanicSafe() u, err := s.GuildMember(config.GuildID, i)
fileURL, _ := url.Parse(v.Photo)
path := fileURL.Path
segments := strings.Split(path, "/")
fileName := segments[len(segments)-1]
file, _ := os.Create(fmt.Sprintf("./verifications/%s-%s-%s", v.UserID, v.Username, fileName))
client := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
resp, err := client.Get(v.Photo)
if err != nil { if err != nil {
log.LogError("Unable to download verification %s-%s-%s", v.UserID, v.Username, fileName) log.LogErrorType(err)
} return discordgo.User{}
defer resp.Body.Close()
defer file.Close()
_, err = io.Copy(file, resp.Body)
if err != nil {
log.LogError("Unable to store verification %s-%s-%s", v.UserID, v.Username, fileName)
} }
return *u.User
}
func adminInteraction(s *discordgo.Session, m string) {
admin, _ := s.GuildMember(config.GuildID, m)
counter, ok := config.Stats[admin.User.ID]
if !ok {
config.Stats[admin.User.ID] = 0
} else {
config.Stats[admin.User.ID] = counter + 1
}
} }

View File

@ -1,116 +0,0 @@
package main
import (
"fmt"
"time"
"github.com/bwmarrin/discordgo"
)
func ready(s *discordgo.Session, event *discordgo.Ready) {
// Set the playing status.
s.UpdateGameStatus(0, fmt.Sprintf("DreamDaddy v%+v %+v", version, gitCommit))
}
func guildMemberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
defer log.PanicSafe()
for role := range m.Roles {
if fmt.Sprintf("%+v", role) == config.MonitorRole {
s.ChannelMessageSend(config.AdminChannel, "New unverified user detected.")
s.ChannelMessageSend(config.MonitorChann, fmt.Sprintf("Welcome %+v, you may PM me your verification, or I will ban you in an hour!\nSay \"!rules\" in this channel, without quotes for the rules. You may private/direct message me for verification instructions.\n\nYou will not be able to read/see other channels or users until you verify.", m.User.Mention()))
config.Unverified[m.User.ID] = time.Now()
config.Probations[m.User.ID] = time.Now()
saveConfig()
}
}
}
func guildMemberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
defer log.PanicSafe()
config.Unverified[m.User.ID] = time.Now()
config.Probations[m.User.ID] = time.Now()
s.GuildMemberRoleAdd(config.GuildID, m.User.ID, config.MonitorRole)
s.ChannelMessageSend(config.MonitorChann, fmt.Sprintf("Welcome %+v, you may PM me your verification, or I will ban you in an hour!\nSay \"!rules\" in this channel, without quotes for the rules. You may private/direct message me for verification instructions.\n\nYou will not be able to read/see other channels or users until you verify.", m.User.Mention()))
saveConfig()
}
func guildMemberBanned(s *discordgo.Session, m *discordgo.GuildBanAdd) {
defer log.PanicSafe()
for uid := range config.Probations {
if m.User.Email == uid {
delete(config.Probations, uid)
s.ChannelMessageDelete(config.IntroChann, introMsg[uid])
}
}
saveConfig()
}
func guildMemberRemove(s *discordgo.Session, m *discordgo.GuildMemberRemove) {
defer log.PanicSafe()
go runPurge(s)
banned := false
for uid, join := range config.Probations {
if time.Since(join) < 2*time.Hour {
if m.User.ID == uid {
banned = true
s.GuildBanCreateWithReason(config.GuildID, m.User.ID, fmt.Sprintf("Left within 2 hours of joining. %+v", time.Since(join)), 0)
delete(config.Probations, uid)
s.ChannelMessageDelete(config.IntroChann, introMsg[uid])
}
} else {
delete(config.Probations, uid)
s.ChannelMessageDelete(config.IntroChann, introMsg[uid])
}
}
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v (@%+v) has left, ban: %+v", m.User.ID, m.User.Username, banned))
delete(config.Unverified, m.User.ID)
for msg, v := range config.Verifications {
if v.UserID == m.User.ID {
delete(config.Verifications, msg)
s.ChannelMessageDelete(config.IntroChann, introMsg[m.User.ID])
}
}
saveConfig()
}
func readReaction(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
defer log.PanicSafe()
if m.ChannelID != config.AdminChannel || m.UserID == s.State.User.ID {
return
}
admin, _ := s.GuildMember(config.GuildID, m.UserID)
adminInteraction(s, admin.User.ID)
verification, ok := config.Verifications[m.MessageID]
if !ok {
return
}
verification.Admin = admin.User.Username
verification.Closed = time.Now()
user := userFromID(verification.UserID)
if user.ID == "" {
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v, that user was not found, they might have left.", admin.Mention()))
delete(config.Verifications, m.MessageID)
return
}
if m.Emoji.Name == "👎" {
rejectVerification(s, user)
verification.Status = "Rejected"
} else if m.Emoji.Name == "👍" {
verifyMember(s, user)
verification.Status = "Accepted"
go storeVerification(verification)
} else if m.Emoji.Name == "👶" {
requestAge(s, user)
log.LogInfo("%+v has requested ASL for user %+v.", admin.User.Username, user.Username)
return
} else if m.Emoji.Name == "⛔" {
s.GuildBanCreateWithReason(config.GuildID, user.ID, fmt.Sprintf("Underage or too many failed verifications. %+v", admin.User.Username), 5)
verification.Status = "Banned"
} else {
return
}
log.LogInfo("%+v", verification.prettyPrint())
delete(config.Verifications, m.MessageID)
}

View File

@ -1,106 +0,0 @@
package main
import (
"fmt"
"strings"
"time"
"github.com/bwmarrin/discordgo"
)
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
defer log.PanicSafe()
var b BotCommand
if strings.HasPrefix(m.Content, s.State.User.Mention()) {
b = BotCommand{
Session: s,
Message: m,
Parts: strings.Split(m.Content, " ")[1:],
}
}
if m.Author.ID == s.State.User.ID || m.Author.Bot {
return
}
if m.GuildID == "" {
handlePM(s, m)
return
}
if m.ChannelID == config.MonitorChann {
if strings.Contains(m.Content, "erif") && !m.Author.Bot {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%+v send me a private message for verification.", m.Author.Mention()))
}
return
}
for role := range m.Member.Roles {
if fmt.Sprintf("%+v", role) == config.AdminRole {
adminInteraction(s, m.Author.ID)
}
}
if m.ChannelID != config.AdminChannel {
lastActiveChan = m.ChannelID
lastActiveTime = time.Now()
}
if strings.HasPrefix(m.Content, "!d bump") {
if time.Since(config.BumpTime) < 2*time.Hour {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Sorry, <@%+v> already claimed the bump. Better luck next time!", config.LastBumper))
return
}
config.LastBumper = m.Author.ID
go bumpTimer(s)
return
}
if time.Since(config.BumpTime) > 2*time.Hour {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%+v please say \"!d bump\" without the quotes to bump our server :)", m.Author.Mention()))
}
if m.ChannelID == config.AdminChannel {
if strings.HasPrefix(m.Content, s.State.User.Mention()) {
for _, cmd := range commands {
for _, keyword := range cmd.Keywords {
if strings.Contains(m.Content, keyword) {
b.Command = keyword
if !cmd.Exec(b) {
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("There was an error running %+v\n%+v", cmd.Name, cmd.Help))
}
}
}
}
}
}
}
func handlePM(s *discordgo.Session, m *discordgo.MessageCreate) {
defer log.PanicSafe()
if strings.Contains(m.Content, "Rule") || strings.Contains(m.Content, "rule") {
s.ChannelMessageSend(m.ChannelID, "I specifically said to say \"!rules\" without quotes in the unverified channel for the rules.")
}
for _, uid := range config.Verifications {
user := userFromID(uid.UserID)
if m.Author.ID == user.ID {
s.ChannelMessageSend(m.ChannelID, "Your verification is pending. An admin will respond to it when they are available.")
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v said: %+v", m.Author.Mention(), m.Content))
return
}
}
if len(m.Attachments) != 1 {
s.ChannelMessageSend(m.ChannelID, "```I am a bot and this is an autoreply.\n\nUntil you send a verification, I will always say the following message:```\nYou may only send me your verification (and nothing else) to be passed to the admins (and no one else). Verification is a clear full face pic, with your pinky finger held to the corner of your mouth.")
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v said: %+v", m.Author.Mention(), m.Content))
return
}
if strings.HasSuffix(strings.ToUpper(m.Attachments[0].ProxyURL), "HEIC") {
s.ChannelMessageSend(m.ChannelID, "You have tried to send an unsupported file (HEIC). Please try again using an image (jpeg, jpg, png, etc).")
return
}
delete(config.Unverified, m.Author.ID)
var v Verification
v.Submitted = time.Now()
v.UserID = m.Author.ID
v.Username = m.Author.Username
v.Photo = m.Attachments[0].ProxyURL
v.Status = "Submitted"
msg, _ := s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v\n%+v", v.Username, v.Photo))
config.Verifications[msg.ID] = v
s.MessageReactionAdd(config.AdminChannel, msg.ID, "👎")
s.MessageReactionAdd(config.AdminChannel, msg.ID, "👍")
s.MessageReactionAdd(config.AdminChannel, msg.ID, "👶")
s.MessageReactionAdd(config.AdminChannel, msg.ID, "⛔")
}

10
go.mod
View File

@ -1,10 +0,0 @@
module git.nightmare.haus/rudi/disgord-thanos
go 1.15
require (
github.com/bwmarrin/discordgo v0.23.2
github.com/gorilla/mux v1.8.0
github.com/gorilla/sessions v1.2.1
github.com/rudi9719/loggy v0.0.0-20201031035735-9438c484de9a
)

16
go.sum
View File

@ -1,16 +0,0 @@
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/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/rudi9719/loggy v0.0.0-20201031035735-9438c484de9a h1:4rkaWoLCWOmra5Mw/dLAWjtDLT/+i5uTX1qhlMVL8WA=
github.com/rudi9719/loggy v0.0.0-20201031035735-9438c484de9a/go.mod h1:s1ANCN8bF6HwwTpJLR458MFVGua9oqKKDbph/2jptL4=
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=
samhofi.us/x/keybase v0.0.0-20200129212102-e05e93be9f3f h1:MHSEiuiRFrFi7BTw46lC22PMk3Fit8IvVRM4xANTt20=
samhofi.us/x/keybase v0.0.0-20200129212102-e05e93be9f3f/go.mod h1:fcva80IUFyWcHtV4bBSzgKg07K6Rvuvi3GtGCLNGkyE=

230
main.go
View File

@ -3,9 +3,13 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"io"
"math/rand" "math/rand"
"net/http"
"net/url"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"time" "time"
@ -27,11 +31,7 @@ var (
setupMsg string setupMsg string
dg *discordgo.Session dg *discordgo.Session
lastPM = make(map[string]time.Time) lastPM = make(map[string]time.Time)
introMsg = make(map[string]string)
quotes = []string{"The hardest choices require the strongest wills.", "You're strong, but I could snap my fingers and you'd all cease to exist.", "Fun isn't something one considers when balancing the universe. But this... does put a smile on my face.", "Perfectly balanced, as all things should be.", "I am inevitable."} quotes = []string{"The hardest choices require the strongest wills.", "You're strong, but I could snap my fingers and you'd all cease to exist.", "Fun isn't something one considers when balancing the universe. But this... does put a smile on my face.", "Perfectly balanced, as all things should be.", "I am inevitable."}
version = "3.0"
gitCommit string
commands []Command
) )
func init() { func init() {
@ -68,7 +68,6 @@ func main() {
dg.AddHandler(guildMemberRemove) dg.AddHandler(guildMemberRemove)
dg.AddHandler(guildMemberAdd) dg.AddHandler(guildMemberAdd)
dg.AddHandler(guildMemberBanned) dg.AddHandler(guildMemberBanned)
go setupCommands()
dg.AddHandler(messageCreate) dg.AddHandler(messageCreate)
dg.AddHandler(readReaction) dg.AddHandler(readReaction)
dg.AddHandler(guildMemberUpdate) dg.AddHandler(guildMemberUpdate)
@ -82,7 +81,6 @@ func main() {
log.LogInfo("Thanos is now running. Press CTRL-C to exit.") log.LogInfo("Thanos is now running. Press CTRL-C to exit.")
go purgeTimer(dg) go purgeTimer(dg)
go rebootBump()
sc := make(chan os.Signal, 1) sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
<-sc <-sc
@ -104,7 +102,6 @@ func runPurge(s *discordgo.Session) {
for uid, join := range config.Probations { for uid, join := range config.Probations {
if time.Since(join) > 2*time.Hour { if time.Since(join) > 2*time.Hour {
delete(config.Probations, uid) delete(config.Probations, uid)
s.ChannelMessageDelete(config.IntroChann, introMsg[uid])
} }
} }
for k, v := range config.Unverified { for k, v := range config.Unverified {
@ -156,14 +153,77 @@ func runPurge(s *discordgo.Session) {
saveConfig() saveConfig()
} }
func ready(s *discordgo.Session, event *discordgo.Ready) {
// Set the playing status.
s.UpdateStatus(0, "DreamDaddy v2.1")
}
func guildMemberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
defer log.PanicSafe()
for role := range m.Roles {
if fmt.Sprintf("%+v", role) == config.MonitorRole {
s.ChannelMessageSend(config.AdminChannel, "New unverified user detected.")
s.ChannelMessageSend(config.MonitorChann, fmt.Sprintf("Welcome %+v, you may PM me your verification, or I will ban you in an hour!\nSay \"!rules\" in this channel, without quotes for the rules. You may private/direct message me for verification instructions.\n\nYou will not be able to read/see other channels or users until you verify.", m.User.Mention()))
config.Unverified[m.User.ID] = time.Now()
config.Probations[m.User.ID] = time.Now()
saveConfig()
}
}
}
func guildMemberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
defer log.PanicSafe()
config.Unverified[m.User.ID] = time.Now()
config.Probations[m.User.ID] = time.Now()
s.GuildMemberRoleAdd(config.GuildID, m.User.ID, config.MonitorRole)
s.ChannelMessageSend(config.MonitorChann, fmt.Sprintf("Welcome %+v, you may PM me your verification, or I will ban you in an hour!\nSay \"!rules\" in this channel, without quotes for the rules. You may private/direct message me for verification instructions.\n\nYou will not be able to read/see other channels or users until you verify.", m.User.Mention()))
saveConfig()
}
func guildMemberBanned(s *discordgo.Session, m *discordgo.GuildBanAdd) {
defer log.PanicSafe()
for uid := range config.Probations {
if m.User.Email == uid {
delete(config.Probations, uid)
}
}
saveConfig()
}
func guildMemberRemove(s *discordgo.Session, m *discordgo.GuildMemberRemove) {
defer log.PanicSafe()
go runPurge(s)
banned := false
for uid, join := range config.Probations {
if time.Since(join) < 2*time.Hour {
if m.User.ID == uid {
banned = true
s.GuildBanCreateWithReason(config.GuildID, m.User.ID, fmt.Sprintf("Left within 2 hours of joining. %+v", time.Since(join)), 0)
delete(config.Probations, uid)
}
} else {
delete(config.Probations, uid)
}
}
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v (@%+v) has left, ban: %+v", m.User.ID, m.User.Username, banned))
delete(config.Unverified, m.User.ID)
for msg, v := range config.Verifications {
if v.UserID == m.User.ID {
delete(config.Verifications, msg)
}
}
saveConfig()
}
func verifyMember(s *discordgo.Session, u discordgo.User) { func verifyMember(s *discordgo.Session, u discordgo.User) {
defer log.PanicSafe() defer log.PanicSafe()
s.GuildMemberRoleAdd(config.GuildID, u.ID, config.VerifiedRole) s.GuildMemberRoleAdd(config.GuildID, u.ID, config.VerifiedRole)
s.GuildMemberRoleRemove(config.GuildID, u.ID, config.MonitorRole) s.GuildMemberRoleRemove(config.GuildID, u.ID, config.MonitorRole)
st, _ := s.UserChannelCreate(u.ID) st, _ := s.UserChannelCreate(u.ID)
s.ChannelMessageSend(st.ID, "Your verification has been accepted, welcome!") s.ChannelMessageSend(st.ID, "Your verification has been accepted, welcome!")
m, _ := s.ChannelMessageSend(config.IntroChann, fmt.Sprintf("Welcome %+v please introduce yourself! :) feel free to check out <#710557387937022034> to tag your roles. Also please mute any channels you are not interested in!", u.Mention())) s.ChannelMessageSend(config.IntroChann, fmt.Sprintf("Welcome %+v please introduce yourself! :) feel free to check out <#710557387937022034> to tag your roles. Also please mute any channels you are not interested in!", u.Mention()))
introMsg[u.ID] = m.ID
} }
func rejectVerification(s *discordgo.Session, u discordgo.User) { func rejectVerification(s *discordgo.Session, u discordgo.User) {
@ -181,3 +241,155 @@ func requestAge(s *discordgo.Session, u discordgo.User) {
s.ChannelMessageSend(st.ID, "What is your ASL? (Age/Sex/Language) Please note, this is NOT requesting your gender, but your biological sex. Gender is a social construct, sex is biology and in the context of pornographic images more important.") s.ChannelMessageSend(st.ID, "What is your ASL? (Age/Sex/Language) Please note, this is NOT requesting your gender, but your biological sex. Gender is a social construct, sex is biology and in the context of pornographic images more important.")
} }
func handlePM(s *discordgo.Session, m *discordgo.MessageCreate) {
defer log.PanicSafe()
if strings.Contains(m.Content, "Rule") || strings.Contains(m.Content, "rule") {
s.ChannelMessageSend(m.ChannelID, "I specifically said to say \"!rules\" without quotes in the unverified channel for the rules.")
}
for _, uid := range config.Verifications {
user := userFromID(s, uid.UserID)
if m.Author.ID == user.ID {
s.ChannelMessageSend(m.ChannelID, "Your verification is pending. An admin will respond to it when they are available.")
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v said: %+v", m.Author.Mention(), m.Content))
return
}
}
if len(m.Attachments) != 1 {
s.ChannelMessageSend(m.ChannelID, "```I am a bot and this is an autoreply.\n\nUntil you send a verification, I will always say the following message:```\nYou may only send me your verification (and nothing else) to be passed to the admins (and no one else). Verification is a clear full face pic, with your pinky finger held to the corner of your mouth.")
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v said: %+v", m.Author.Mention(), m.Content))
return
}
delete(config.Unverified, m.Author.ID)
var v Verification
v.Submitted = time.Now()
v.UserID = m.Author.ID
v.Username = m.Author.Username
v.Photo = m.Attachments[0].ProxyURL
v.Status = "Submitted"
msg, _ := s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v\n%+v", v.Username, v.Photo))
config.Verifications[msg.ID] = v
s.MessageReactionAdd(config.AdminChannel, msg.ID, "👎")
s.MessageReactionAdd(config.AdminChannel, msg.ID, "👍")
s.MessageReactionAdd(config.AdminChannel, msg.ID, "👶")
s.MessageReactionAdd(config.AdminChannel, msg.ID, "⛔")
}
func readReaction(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
defer log.PanicSafe()
if m.ChannelID != config.AdminChannel || m.UserID == s.State.User.ID {
return
}
admin, _ := s.GuildMember(config.GuildID, m.UserID)
adminInteraction(s, admin.User.ID)
verification, ok := config.Verifications[m.MessageID]
if !ok {
return
}
verification.Admin = admin.User.Username
verification.Closed = time.Now()
user := userFromID(s, verification.UserID)
if user.ID == "" {
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v, that user was not found, they might have left.", admin.Mention()))
delete(config.Verifications, m.MessageID)
return
}
if m.Emoji.Name == "👎" {
rejectVerification(s, user)
verification.Status = "Rejected"
} else if m.Emoji.Name == "👍" {
verifyMember(s, user)
verification.Status = "Accepted"
go storeVerification(verification)
} else if m.Emoji.Name == "👶" {
requestAge(s, user)
log.LogInfo("%+v has requested ASL for user %+v.", admin.User.Username, user.Username)
return
} else if m.Emoji.Name == "⛔" {
s.GuildBanCreateWithReason(config.GuildID, user.ID, fmt.Sprintf("Underage or too many failed verifications. %+v", admin.User.Username), 5)
verification.Status = "Banned"
} else {
return
}
log.LogInfo("%+v", verification.prettyPrint())
delete(config.Verifications, m.MessageID)
}
func storeVerification(v Verification) {
defer log.PanicSafe()
fileURL, _ := url.Parse(v.Photo)
path := fileURL.Path
segments := strings.Split(path, "/")
fileName := segments[len(segments)-1]
file, _ := os.Create(fmt.Sprintf("./verifications/%s-%s-%s", v.UserID, v.Username, fileName))
client := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
r.URL.Opaque = r.URL.Path
return nil
},
}
resp, err := client.Get(v.Photo)
if err != nil {
log.LogError("Unable to download verification %s-%s-%s", v.UserID, v.Username, fileName)
}
defer resp.Body.Close()
defer file.Close()
_, err = io.Copy(file, resp.Body)
if err != nil {
log.LogError("Unable to store verification %s-%s-%s", v.UserID, v.Username, fileName)
}
}
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
defer log.PanicSafe()
if m.Author.ID == s.State.User.ID || m.Author.Bot {
return
}
if m.GuildID == "" {
handlePM(s, m)
return
}
if m.ChannelID == config.MonitorChann {
if strings.Contains(m.Content, "erif") && !m.Author.Bot {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%+v send me a private message for verification.", m.Author.Mention()))
}
return
}
for role := range m.Member.Roles {
if fmt.Sprintf("%+v", role) == config.AdminRole {
adminInteraction(s, m.Author.ID)
}
}
if m.ChannelID != config.AdminChannel {
lastActiveChan = m.ChannelID
lastActiveTime = time.Now()
}
if strings.HasPrefix(m.Content, "!d bump") {
if time.Since(config.BumpTime) < 2*time.Hour {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Sorry, <@%+v> already claimed the bump. Better luck next time!", config.LastBumper))
return
}
config.LastBumper = m.Author.ID
go bumpTimer(s)
return
}
if time.Since(config.BumpTime) > 2*time.Hour {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%+v please say \"!d bump\" without the quotes to bump our server :)", m.Author.Mention()))
}
if m.ChannelID == config.AdminChannel {
if strings.HasPrefix(m.Content, rebootToken) {
exit(s)
}
if strings.HasPrefix(m.Content, "!quote") {
quotes = append(quotes, strings.ReplaceAll(m.Content, "!quote", ""))
}
if strings.HasPrefix(m.Content, "!snap") || strings.HasPrefix(m.Content, "!purge") {
go runPurge(s)
s.ChannelMessageSend(config.AdminChannel, quotes[rand.Intn(len(quotes))])
}
if strings.HasPrefix(m.Content, "!st") {
go status(s)
saveConfig()
}
}
}

View File

@ -1,4 +1,4 @@
//Hopper says hi
// Get the modal // Get the modal
const modal = document.getElementById("myModal"); const modal = document.getElementById("myModal");
@ -9,141 +9,145 @@ const btn = document.getElementById("myBtn");
const span = document.getElementsByClassName("close")[0]; const span = document.getElementsByClassName("close")[0];
var mode = new URLSearchParams(window.location.search).get("mode"); var mode = new URLSearchParams(window.location.search).get("mode");
const archiveLink = document.querySelector("#archive-link") const archiveLink = document.querySelector("#archive-link");
const pendingLink = document.querySelector("#pending-link") const pendingLink = document.querySelector("#pending-link");
const statusLink = document.querySelector("#status-link") const statusLink = document.querySelector("#status-link");
function handleForm(event) { event.preventDefault(); } function handleForm(event) {
event.preventDefault();
}
function main() { function main() {
archiveLink.classList.remove("active"); archiveLink.classList.remove("active");
pendingLink.classList.remove("active"); pendingLink.classList.remove("active");
statusLink.classList.remove("active"); statusLink.classList.remove("active");
switch (mode) { switch (mode) {
case "status": case "status":
statusLink.classList.add("active"); statusLink.classList.add("active");
return statusPage(); return statusPage();
case "pending": case "pending":
pendingLink.classList.add("active"); pendingLink.classList.add("active");
break; break;
case "verifications": case "verifications":
archiveLink.classList.add("active"); archiveLink.classList.add("active");
break; break;
default: default:
console.log("No mode"); console.log("No mode");
mode = "verifications" mode = "verifications";
archiveLink.classList.add("active"); archiveLink.classList.add("active");
break; break;
} }
document.getElementById("main-app").innerHTML = ''; document.getElementById("main-app").innerHTML = "";
fetch(`https://thanos.nightmare.haus/api/${mode}`) fetch(`https://thanos.nightmare.haus/api/${mode}`)
.then(response => response.json()) .then((response) => response.json())
.then(data => processData(data)); .then((data) => processData(data));
} }
function statusPage() { function statusPage() {
document.getElementById("main-app").innerHTML = ''; document.getElementById("main-app").innerHTML = "";
fetch(`https://thanos.nightmare.haus/api/config`) fetch(`https://thanos.nightmare.haus/api/config`)
.then(response => response.json()) .then((response) => response.json())
.then(data => { .then((data) => {
var node = document.createElement("config-status"); var node = document.createElement("config-status");
var upTime = document.createElement("div"); var upTime = document.createElement("div");
upTime.setAttribute("slot", "uptime"); upTime.setAttribute("slot", "uptime");
upTime.innerText = data.Uptime; upTime.innerText = data.Uptime;
node.appendChild(upTime); node.appendChild(upTime);
var bumpTime = document.createElement("div"); var bumpTime = document.createElement("div");
bumpTime.setAttribute("slot", "lastbump") bumpTime.setAttribute("slot", "lastbump");
bumpTime.innerText = new Date(data.BumpTime).toLocaleString(); bumpTime.innerText = new Date(data.BumpTime).toLocaleString();
node.appendChild(bumpTime); node.appendChild(bumpTime);
node.setData(data); node.setData(data);
document.getElementById("main-app").appendChild(node); document.getElementById("main-app").appendChild(node);
});
});
} }
function searchPage() { function searchPage() {
var search = document.getElementById("search-bar"); var search = document.getElementById("search-bar");
fetch('https://thanos.nightmare.haus/api/verifications') fetch("https://thanos.nightmare.haus/api/verifications")
.then(response => response.json()) .then((response) => response.json())
.then(data => { .then((data) => {
var searchData = []; var searchData = [];
for (user of data) { for (user of data) {
var match = false; var match = false;
if (user.Username.toLowerCase().includes(search.value.toLowerCase())) { if (user.Username.toLowerCase().includes(search.value.toLowerCase())) {
match = true; match = true;
} }
if (new Date(user.Closed).toLocaleString().includes(search.value)) { if (new Date(user.Closed).toLocaleString().includes(search.value)) {
match = true; match = true;
} }
if (user.UserID.includes(search.value)) { if (user.UserID.includes(search.value)) {
match = true; match = true;
} }
if (match) { if (match) {
searchData.push(user); searchData.push(user);
} }
} }
processData(searchData); processData(searchData);
}); });
} }
function processData(data) { function processData(data) {
document.getElementById("main-app").innerHTML = ''; document.getElementById("main-app").innerHTML = "";
if (data.length == 0) { if (data.length == 0) {
alert("No data."); alert("No data.");
return; return;
} }
data = Object.values(data); data = Object.values(data);
for (user of data) { for (user of data) {
var node = document.createElement("user-card"); var node = document.createElement("user-card");
var nameSlot = document.createElement("div"); var nameSlot = document.createElement("div");
nameSlot.setAttribute("slot", "username"); nameSlot.setAttribute("slot", "username");
nameSlot.innerText = user.Username; nameSlot.innerText = user.Username;
node.appendChild(nameSlot); node.appendChild(nameSlot);
var joinDate = document.createElement("div"); var joinDate = document.createElement("div");
joinDate.setAttribute("slot", "join-date"); joinDate.setAttribute("slot", "join-date");
joinDate.innerText = mode == "pending" ? new Date(user.Submitted).toLocaleString() : new Date(user.Closed).toLocaleString(); joinDate.innerText =
node.appendChild(joinDate); mode == "pending"
? new Date(user.Submitted).toLocaleString()
: new Date(user.Closed).toLocaleString();
node.appendChild(joinDate);
var discordSlot = document.createElement("div"); var discordSlot = document.createElement("div");
discordSlot.setAttribute("slot", "discord-id"); discordSlot.setAttribute("slot", "discord-id");
discordSlot.innerText = user.UserID; discordSlot.innerText = user.UserID;
node.appendChild(discordSlot); node.appendChild(discordSlot);
var picSlot = document.createElement("div"); var picSlot = document.createElement("div");
picSlot.setAttribute("slot", "pic-link"); picSlot.setAttribute("slot", "pic-link");
var aNode = document.createElement("a"); var aNode = document.createElement("a");
aNode.setAttribute("href", mode == "pending" ? user.Photo : `https://thanos.nightmare.haus/${user.Photo}`); aNode.setAttribute(
aNode.innerText = "Verification Photo"; "href",
mode == "pending"
? user.Photo
: `https://thanos.nightmare.haus/${user.Photo}`
);
aNode.innerText = "Verification Photo";
picSlot.appendChild(aNode); picSlot.appendChild(aNode);
node.appendChild(picSlot); node.appendChild(picSlot);
document.getElementById("main-app").appendChild(node); document.getElementById("main-app").appendChild(node);
} }
} }
// When the user clicks on <span> (x), close the modal // When the user clicks on <span> (x), close the modal
span.onclick = function () { span.onclick = function () {
modal.style.display = "none"; modal.style.display = "none";
} };
// When the user clicks anywhere outside of the modal, close it // When the user clicks anywhere outside of the modal, close it
window.onclick = function (event) { window.onclick = function (event) {
if (event.target == modal) { if (event.target == modal) {
modal.style.display = "none"; modal.style.display = "none";
} }
} };
var form = document.getElementById("search-form"); var form = document.getElementById("search-form");
form.addEventListener('submit', handleForm); form.addEventListener("submit", handleForm);
main(); main();

View File

@ -1,3 +1,4 @@
//Hopper says hi again
const basicCard = document.createElement('basic-card'); const basicCard = document.createElement('basic-card');
const configStatus = document.createElement('status'); const configStatus = document.createElement('status');
const style = ` const style = `

View File

@ -3,26 +3,9 @@ package main
import ( import (
"time" "time"
"github.com/bwmarrin/discordgo"
"github.com/rudi9719/loggy" "github.com/rudi9719/loggy"
) )
// BotCommand struct used for modular commands
type BotCommand struct {
Message *discordgo.MessageCreate
Session *discordgo.Session
Parts []string
Command string
}
// Command is the type to store commands
type Command struct {
Name string
Help string
Keywords []string
Exec func(BotCommand) bool
}
// Config struct used for bot // Config struct used for bot
type Config struct { type Config struct {
GuildID string GuildID string