Compare commits

..

No commits in common. 'master' and 'Current' have entirely different histories.

  1. 2
      .drone.yml
  2. 5
      .gitignore
  3. BIN
      ThanosAPI.png
  4. 196
      auth.go
  5. 307
      commands.go
  6. 134
      config.go
  7. 125
      discordEvents.go
  8. 162
      discordMessage.go
  9. 18
      go.mod
  10. 26
      go.sum
  11. 272
      main.go
  12. 238
      site-api.go
  13. 21
      static/404.tpl
  14. 21
      static/500.tpl
  15. 149
      static/app.js
  16. 11
      static/card.tpl
  17. 210
      static/components.js
  18. 44
      static/header.tpl
  19. 17
      static/home.tpl
  20. 73
      static/index.css
  21. 91
      static/index.html
  22. 8
      static/loggedIn.tpl
  23. 31
      static/login.tpl
  24. 31
      static/pass.tpl
  25. 51
      tools/listen.go
  26. 37
      tools/runFunction.go
  27. 36
      types.go

2
.drone.yml

@ -8,8 +8,6 @@ steps: @@ -8,8 +8,6 @@ steps:
commands:
- go get github.com/bwmarrin/discordgo
- go get github.com/rudi9719/loggy
- go get github.com/gorilla/mux
- go get github.com/gorilla/sessions
- go vet -v
- go build
- name: gitea_release

5
.gitignore vendored

@ -1,10 +1,5 @@ @@ -1,10 +1,5 @@
build/
.DS_Store
public/
disgord-thanos
disgord-Thanos
config.json
start.sh
dev.html
verifications/*
node_modules/

BIN
ThanosAPI.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

196
auth.go

@ -1,196 +0,0 @@ @@ -1,196 +0,0 @@
package main
import (
"fmt"
"math/rand"
"net/http"
"strings"
"time"
"github.com/bwmarrin/discordgo"
"github.com/gorilla/mux"
)
func reqPass(w http.ResponseWriter, r *http.Request) {
defer log.PanicSafe()
log.LogInfo("reqPass called.")
username := r.URL.Query()["UserName"][0]
log.LogInfo("reqPass username is %+v.", username)
var userID string
if dg == nil {
log.LogError("Discord session was nill.")
}
g, err := dg.GuildMembers(config.GuildID, "", 1000)
log.LogInfo("reqPass guild is %+v.", config.GuildID)
if err == nil {
for _, m := range g {
if strings.EqualFold(m.Nick, username) {
for _, r := range m.Roles {
if r == config.AdminRole {
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)
}
ipaddr := r.Header.Get("X-Real-IP")
log.LogInfo("reqPass IP is %+v.", ipaddr)
log.LogInfo(fmt.Sprintf("reqPass called:```username: %s\nip : %s```", username, ipaddr))
go sendPassword(userID, ipaddr)
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
}
func tryLogin(w http.ResponseWriter, r *http.Request) {
defer log.PanicSafe()
session, err := store.Get(r, "2fa")
if err != nil {
log.LogWarn("Error opening session for 2fa store")
}
vars := mux.Vars(r)
username := vars["username"]
password := vars["password"]
ip := r.Header.Get("X-Real-IP")
if len(username) == 0 {
username = r.FormValue("UserName")
password = r.FormValue("TempPass")
}
access, _ := detectUser(r, "tryLogin")
if !access {
log.LogDebug(fmt.Sprintf("%s is attempting login", getSessionIdentifier(r)))
access = usePassword(username, password, ip)
if access {
log.LogInfo(fmt.Sprintf("%s has successfully logged in from %s", username, ip))
log.LogDebug(fmt.Sprintf("```%+v```", session.Values))
session.Values["username"] = username
session.Values["ip"] = ip
session.Values["timestamp"] = fmt.Sprintf("%+v", time.Now())
err = session.Save(r, w)
if err != nil {
log.LogWarn(fmt.Sprintf("Error saving cookie. ```%+v```", err))
}
}
}
greetUser(w, r)
}
func usePassword(user string, pass string, ip string) bool {
defer log.PanicSafe()
log.LogInfo("%+v", toks)
tok := toks[strings.ToUpper(user)]
delete(toks, strings.ToUpper(user))
if tok.IP != ip {
log.LogWarn(fmt.Sprintf("%s attempted to use an improper IP.", user))
return false
}
if tok.Password != pass {
log.LogWarn(fmt.Sprintf("%s attempted to use an improper password. %s vs %s", user, tok.Password, pass))
return false
}
if time.Since(tok.Timestamp) > (time.Minute * 5) {
log.LogWarn("%s attempted to use expired token. \n%+v\n%+v\n%+v", user, time.Since(tok.Timestamp), tok.Timestamp, time.Now())
return false
}
return true
}
func genPassword(length int) string {
defer log.PanicSafe()
rand.Seed(time.Now().UnixNano())
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789")
var b strings.Builder
for i := 0; i < length; i++ {
b.WriteRune(chars[rand.Intn(len(chars))])
}
return b.String() // E.g. "ExcbsVQs"
}
func sendPassword(user string, ipaddr string) {
defer log.PanicSafe()
str := genPassword(15)
log.LogInfo("sending password to %+v for %+v: %+v", ipaddr, user, str)
m, err := dg.GuildMember(config.GuildID, user)
if err != nil {
log.LogErrorType(err)
}
now := time.Now()
toks[strings.ToUpper(m.Nick)] = Tokens{
Username: user,
IP: ipaddr,
Password: str,
Timestamp: now,
}
pmChann, err := dg.UserChannelCreate(user)
if err != nil {
log.LogErrorType(err)
}
dg.ChannelMessageSend(pmChann.ID, fmt.Sprintf("A temporary password was requested from %s:", ipaddr))
dg.ChannelMessageSend(pmChann.ID, fmt.Sprintf("```%s```", str))
}
func getSessionIdentifier(r *http.Request) string {
defer log.PanicSafe()
ipaddr := r.Header.Get("X-Real-IP")
if ipaddr == "" {
ipaddr = r.RemoteAddr
}
uri := r.URL.Path
return fmt.Sprintf("%s:%s", ipaddr, uri)
}
func detectUser(r *http.Request, callFunc string) (bool, string) {
defer log.PanicSafe()
log.LogInfo(fmt.Sprintf("%s called detectUser", getSessionIdentifier(r)))
session, err := store.Get(r, "2fa")
if err != nil {
log.LogDebug(fmt.Sprintf("Unable to open 2fa session in %s", callFunc))
}
if session.Values["username"] != nil {
return true, fmt.Sprintf("%s", session.Values["username"])
}
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.EqualFold(m.User.Username, 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
}
func isAdmin(m *discordgo.Member) bool {
log.LogDebug("Checking %+v for %+v", m.Roles, config.AdminRole)
for _, role := range m.Roles {
if role == config.AdminRole {
return true
} else {
log.LogDebug("%+v != %+v", role, config.AdminRole)
}
}
return false
}

307
commands.go

@ -1,307 +0,0 @@ @@ -1,307 +0,0 @@
package main
import (
"fmt"
"math/rand"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"github.com/rudi9719/loggy"
)
func setupCommands() {
reboot := Command{
Name: "Reboot",
RequiresAdmin: true,
Help: "Reboot me, requires token from logs.",
Keywords: []string{"reboot", "re", "restart"},
Exec: Reboot,
}
commands = append(commands, reboot)
bumpset := Command{
Name: "BumpSet",
RequiresAdmin: true,
Help: "Set the bump timer (requires time in minutes until next bump).",
Keywords: []string{"bs", "bumpset", "bumps"},
Exec: BumpSet,
}
commands = append(commands, bumpset)
retrieveVerification := Command{
Name: "Retrieve Verification",
RequiresAdmin: true,
Help: "Retrieve verification either by discord ID or by nickname",
Keywords: []string{"veri", "verification", "retrieve"},
Exec: RetrieveVerification,
}
commands = append(commands, retrieveVerification)
addQuote := Command{
Name: "Add Quote",
RequiresAdmin: true,
Keywords: []string{"quote", "addq", "q"},
Exec: AddQuote,
}
commands = append(commands, addQuote)
snap := Command{
Name: "Snap",
Help: "Trigger a purge!",
RequiresAdmin: false,
Keywords: []string{"snap", "purge", "sn"},
Exec: Snap,
}
commands = append(commands, snap)
status := Command{
Name: "Status",
RequiresAdmin: true,
Help: "Show the current status of Thanos/Verifications and probations",
Keywords: []string{"st", "status", "stats"},
Exec: Status,
}
commands = append(commands, status)
listCommands := Command{
Name: "List Commands",
RequiresAdmin: false,
Keywords: []string{"help", "commands", "cmd", "cmds"},
Exec: Commands,
}
commands = append(commands, listCommands)
debugLevel := Command{
Name: "Debug Level",
RequiresAdmin: true,
Keywords: []string{"debug"},
Exec: Debug,
Help: "Set the log level for loggy",
}
commands = append(commands, debugLevel)
activityReport := Command{
Name: "Activity Report",
RequiresAdmin: false,
Keywords: []string{"activity", "active", "list"},
Exec: ActivityReport,
Help: "List activity for the discord. Supply a number to get the top N users (5 would be top 5 users) or all for all users!",
}
commands = append(commands, activityReport)
urlWhitelist := Command{
Name: "Whitelist URL",
RequiresAdmin: true,
Keywords: []string{"whitelist", "wl"},
Exec: WhitelistURL,
Help: "Add a domain to the HTTP whitelist domains are in the format `thisvid.com` without the subdomain.",
}
commands = append(commands, urlWhitelist)
}
func Commands(b BotCommand) bool {
defer log.PanicSafe()
print := "Available commands:\n"
for _, cmd := range commands {
if cmd.RequiresAdmin {
if isAdmin(b.Message.Member) {
print += fmt.Sprintf("```%+v\n%+v\n%+v```\n", cmd.Name, cmd.Keywords, cmd.Help)
}
} else {
print += fmt.Sprintf("```%+v\n%+v\n%+v```\n", cmd.Name, cmd.Keywords, cmd.Help)
}
}
b.Session.ChannelMessageSend(b.Message.ChannelID, print)
return true
}
func Debug(b BotCommand) bool {
defer log.PanicSafe()
level, err := strconv.Atoi(b.Parts[0])
if err != nil {
return false
}
config.LogOpts.Level = loggy.LogLevel(level)
log = loggy.NewLogger(config.LogOpts)
return true
}
func Reboot(b BotCommand) bool {
defer log.PanicSafe()
if strings.Contains(b.Message.Content, rebootToken) {
exit(b.Session)
return true
}
return false
}
func ActivityReport(b BotCommand) bool {
useCounter := true
counterStop := 4
if len(b.Parts) > 0 {
test, err := strconv.Atoi(b.Parts[0])
if err == nil {
counterStop = test
} else {
useCounter = false
}
}
statistics := "```"
n := map[int][]string{}
counter := 0
var a []int
for k, v := range config.Activity {
n[v] = append(n[v], k)
}
for k := range n {
a = append(a, k)
}
sort.Sort(sort.Reverse(sort.IntSlice(a)))
for _, k := range a {
for _, s := range n[k] {
if useCounter && counter == counterStop-1 {
return true
}
user, err := b.Session.GuildMember(config.GuildID, s)
if err == nil {
statistics += fmt.Sprintf("\n%+v: %+v", user.User.Username, k)
counter++
} else {
log.LogErrorType(err)
}
}
}
statistics += "\n```"
return true
}
func BumpSet(b BotCommand) bool {
defer log.PanicSafe()
bump = false
timer, err := strconv.Atoi(b.Parts[0])
if err != nil {
b.Session.ChannelMessageSend(b.Message.ChannelID, fmt.Sprintf("Unable to decode timer: %+v", b.Parts[0]))
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 last bump time: <t:%+v:t>, expecting next bump at <t:%+v:t>", config.BumpTime.Unix(), config.BumpTime.Add(2*time.Hour).Unix()))
return true
}
func RetrieveVerification(b BotCommand) bool {
defer log.PanicSafe()
discordId := b.Parts[0]
_, err := strconv.Atoi(discordId)
if err != nil {
discordId = idFromUsername(discordId)
}
user, err := b.Session.GuildMember(config.GuildID, discordId)
if err != nil {
log.LogErrorType(err)
return false
}
matches, err := filepath.Glob(fmt.Sprintf("./verifications/*%+v*", discordId))
if err != nil {
log.LogErrorType(err)
return false
}
if len(matches) != 1 {
b.Session.ChannelMessageSend(b.Message.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)
b.Session.ChannelFileSendWithMessage(b.Message.ChannelID, msg, matches[0], 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 active time: %+v\n", time.Since(lastActiveTime))
status += fmt.Sprintf("Last bump: <t:%+v:t>\n", config.BumpTime.Unix())
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 at <t:%+v:t>", uvUser.Username, v.Add(1*time.Hour).Unix())
}
status += "\n"
} else {
status += "There are no unverified users.\n"
}
if len(config.Verifications) > 0 {
status += "Pending verifications:\n"
for _, v := range config.Verifications {
status += fmt.Sprintf("%+v has submitted a verification.", v.Username)
}
status += "\n"
} else {
status += "There are no pending verifications.\n"
}
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 until <t:%+v:t>\n", probationUser.Username, join.Add(2*time.Hour).Unix())
}
status += "\n"
} else {
status += "There are no users on probation.\n"
}
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
}
func WhitelistURL(b BotCommand) bool {
defer log.PanicSafe()
newURL := strings.TrimSpace(
strings.ReplaceAll(
strings.ReplaceAll(b.Message.Content, b.Command, ""),
"<@688025671968096341>", ""),
)
if len(newURL) > 0 {
config.WhitelistURLs = append(config.WhitelistURLs, newURL)
}
domains := strings.Join(config.WhitelistURLs, "\n")
b.Session.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("Current whitelisted domains: %+v", domains))
log.LogDebug(fmt.Sprintf("Current whitelisted domains: %+v", domains))
return true
}

134
config.go

@ -3,19 +3,66 @@ package main @@ -3,19 +3,66 @@ package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"io/ioutil"
"os"
"strings"
"time"
"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() {
var c Config
confFile, _ := os.ReadFile(configFile)
confFile, _ := ioutil.ReadFile(configFile)
err := json.Unmarshal([]byte(confFile), &c)
if err != nil {
log.LogErrorType(err)
@ -51,45 +98,12 @@ func saveConfig() { @@ -51,45 +98,12 @@ func saveConfig() {
if err != nil {
log.LogErrorType(err)
}
err = os.WriteFile(configFile, file, 0600)
err = ioutil.WriteFile(configFile, file, 0600)
if err != nil {
log.LogErrorType(err)
}
}
func adminInteraction(s *discordgo.Session, m string) {
defer log.PanicSafe()
admin, _ := s.GuildMember(config.GuildID, m)
counter, ok := config.Stats[admin.User.ID]
if !ok {
config.Stats[admin.User.ID] = 1
} else {
config.Stats[admin.User.ID] = counter + 1
}
}
func activeInteraction(s *discordgo.Session, m string) {
defer log.PanicSafe()
if config.Activity == nil {
config.Activity = make(map[string]int)
}
user, _ := s.GuildMember(config.GuildID, m)
counter, ok := config.Activity[user.User.ID]
if !ok {
config.Activity[user.User.ID] = 1
} else {
config.Activity[user.User.ID] = counter + 1
}
}
func rebootBump() {
time.Sleep(time.Until(config.BumpTime.Add(2 * time.Hour)))
dg.ChannelMessageSend(config.AdminChannel, "/bump is ready")
}
func bumpTimer(s *discordgo.Session) {
if !bump {
return
@ -97,10 +111,12 @@ func bumpTimer(s *discordgo.Session) { @@ -97,10 +111,12 @@ func bumpTimer(s *discordgo.Session) {
bump = false
config.BumpTime = time.Now()
time.Sleep(2 * time.Hour)
s.ChannelMessageSend(config.AdminChannel, "/bump is ready.")
if time.Since(lastActiveTime) < (5*time.Minute) && lastActiveChan != config.AdminChannel {
s.ChannelMessageSend(lastActiveChan, "!d bump is ready, please use it. (say \"!d bump\" without the quotes)")
}
s.ChannelMessageSend(config.AdminChannel, "!d bump is ready.")
bump = true
}
func purgeTimer(s *discordgo.Session) {
for {
runPurge(s)
@ -123,28 +139,22 @@ func (v Verification) prettyPrint() string { @@ -123,28 +139,22 @@ func (v Verification) prettyPrint() string {
return ret
}
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)
func userFromID(s *discordgo.Session, i string) discordgo.User {
u, err := s.GuildMember(config.GuildID, i)
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
}
}

125
discordEvents.go

@ -1,125 +0,0 @@ @@ -1,125 +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 rev %+v", gitCommit))
}
func guildMemberUpdate(s *discordgo.Session, m *discordgo.GuildMemberUpdate) {
defer log.PanicSafe()
log.LogDebug("Member %+v has been updated", m.User.Username)
for _, role := range m.Roles {
if fmt.Sprintf("%+v", role) == config.MonitorRole {
log.LogDebug("Role found, Monitor Role")
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()
return
}
log.LogDebug("Monitor Role not found: %+v != %+v", fmt.Sprintf("%+v", role), config.MonitorRole)
}
}
func guildMemberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
defer log.PanicSafe()
log.LogDebug("Adding user to Unverified and Probations")
config.Unverified[m.User.ID] = time.Now()
config.Probations[m.User.ID] = time.Now()
log.LogDebug("Giving user monitor role")
s.GuildMemberRoleAdd(config.GuildID, m.User.ID, config.MonitorRole)
log.LogDebug("Calling saveConfig")
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 == "🔄" {
requestReupload(s, user)
log.LogInfo("%+v has requested reupload for user %+v.", admin.User.Username, user.Username)
return
} else if m.Emoji.Name == "⛔" {
s.GuildBanCreateWithReason(config.GuildID, user.ID, fmt.Sprintf("Underage, female, 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)
}

162
discordMessage.go

@ -1,162 +0,0 @@ @@ -1,162 +0,0 @@
package main
import (
"fmt"
"regexp"
"strings"
"time"
"github.com/bwmarrin/discordgo"
)
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
defer log.PanicSafe()
if m.Author.ID == "302050872383242240" && len(m.Embeds) > 0 {
if strings.Contains(m.Embeds[0].Description, "minutes until the server can be bumped") {
log.LogDebug("Failed bump detected")
re := regexp.MustCompile("Please wait another (.*) minutes until the server can be bumped")
match := re.FindStringSubmatch(m.Embeds[0].Description)
m.Content = fmt.Sprintf("%+v bs %+v", s.State.User.Mention(), match[1])
BumpSet(BotCommand{
Message: m,
Session: s,
Parts: strings.Split(m.Content, " ")[2:],
})
} else {
go bumpTimer(s)
}
return
}
if m.Author.Bot || m.Author.ID == s.State.User.ID {
return
}
if m.GuildID == "" {
handlePM(s, m)
return
}
if isAdmin(m.Member) {
adminInteraction(s, m.Author.ID)
}
if m.ChannelID == config.MonitorChann && !isAdmin(m.Member) {
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
}
if m.ChannelID != config.AdminChannel {
lastActiveTime = time.Now()
if len(m.Attachments) > 0 {
activeInteraction(s, m.Author.ID)
}
}
if strings.Contains(m.Content, "http") {
safe := false
for _, testURL := range config.WhitelistURLs {
if strings.Contains(m.Content, testURL) {
safe = true
}
}
if !safe {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("%+v: That domain is not approved by the admins. Please contact Admins if the domain should be whitelisted.", m.Author.Mention()))
s.ChannelMessageDelete(m.ChannelID, m.ID)
channel, err := s.Channel(m.ChannelID)
if err != nil {
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("DELETED %+v [%+v]: %+v", m.Author.Mention(), m.ChannelID, m.Content))
} else {
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("DELETED %+v [%+v]: %+v", m.Author.Mention(), channel.Name, m.Content))
}
}
}
parts := strings.Split(m.Content, " ")
if strings.Contains(m.Content, s.State.User.ID) {
b := BotCommand{
Session: s,
Message: m,
Parts: parts[2:],
}
log.LogDebug("%+v", b.Parts)
for _, cmd := range commands {
for _, keyword := range cmd.Keywords {
log.LogDebug("Checking if %+v contains %+v", m.Content, keyword)
if strings.Contains(parts[1], keyword) {
log.LogDebug("%+v found!", keyword)
b.Command = keyword
if !cmd.RequiresAdmin {
log.LogDebug("%+v does not require admin, running!", cmd.Name)
if !cmd.Exec(b) {
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("There was an error running %+v\n%+v", cmd.Name, cmd.Help))
} else {
log.LogInfo("Ran command %+v for %+v", cmd.Name, m.Author.Username)
}
} else {
log.LogDebug("%+v does require admin, checking!", cmd.Name)
if isAdmin(m.Member) {
if !cmd.Exec(b) {
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("There was an error running %+v\n%+v", cmd.Name, cmd.Help))
} else {
log.LogInfo("Ran command %+v for %+v", cmd.Name, m.Author.Username)
}
} else {
log.LogInfo("%+v tried to run an admin command (%+v) but isn't an admin.", m.Author.Username, keyword)
}
}
return
}
}
}
}
}
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 - this is a PM :) .")
}
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
}
if strings.HasSuffix(strings.ToUpper(m.Attachments[0].ProxyURL), "MP4") {
s.ChannelMessageSend(m.ChannelID, "You have tried to send an unsupported file (MP4 Video). Please try again using an image (jpeg, jpg, png, etc).")
return
}
if strings.HasSuffix(strings.ToUpper(m.Attachments[0].ProxyURL), "MP3") {
s.ChannelMessageSend(m.ChannelID, "You have tried to send an unsupported file (MP3 Audio). 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, "👶")
s.MessageReactionAdd(config.AdminChannel, msg.ID, "⛔")
}

18
go.mod

@ -1,18 +0,0 @@ @@ -1,18 +0,0 @@
module git.nightmare.haus/rudi/disgord-thanos
go 1.21
require (
github.com/bwmarrin/discordgo v0.27.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/sessions v1.2.1
github.com/rudi9719/loggy v0.0.0-20201031035735-9438c484de9a
)
require (
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/sys v0.12.0 // indirect
samhofi.us/x/keybase v1.0.0 // indirect
)

26
go.sum

@ -1,26 +0,0 @@ @@ -1,26 +0,0 @@
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
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.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
samhofi.us/x/keybase v0.0.0-20200129212102-e05e93be9f3f/go.mod h1:fcva80IUFyWcHtV4bBSzgKg07K6Rvuvi3GtGCLNGkyE=
samhofi.us/x/keybase v1.0.0 h1:ht//EtYMS/hQeZCznA1ibQ515JCKaEkvTD/tarw/9k8=
samhofi.us/x/keybase v1.0.0/go.mod h1:fcva80IUFyWcHtV4bBSzgKg07K6Rvuvi3GtGCLNGkyE=

272
main.go

@ -3,9 +3,13 @@ package main @@ -3,9 +3,13 @@ package main
import (
"flag"
"fmt"
"io"
"math/rand"
"net/http"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"
@ -15,20 +19,18 @@ import ( @@ -15,20 +19,18 @@ import (
var (
startupTime time.Time
setupToken = fmt.Sprintf("%+v", rand.Intn(9999)+1000)
rebootToken = fmt.Sprintf("%+v", rand.Intn(9999)+1000)
setupToken = fmt.Sprintf("!setup %+v", rand.Intn(9999)+1000)
rebootToken = fmt.Sprintf("!reboot %+v", rand.Intn(9999)+1000)
bump = true
config Config
log = loggy.NewLogger(config.LogOpts)
lastActiveChan string
lastActiveTime time.Time
token string
configFile string
dg *discordgo.Session
setupMsg string
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."}
gitCommit string
commands []Command
)
func init() {
@ -38,7 +40,6 @@ func init() { @@ -38,7 +40,6 @@ func init() {
}
func main() {
go runWeb()
defer log.PanicSafe()
if configFile == "" {
configFile = "config.json"
@ -48,13 +49,13 @@ func main() { @@ -48,13 +49,13 @@ func main() {
log = loggy.NewLogger(config.LogOpts)
startupTime = time.Now()
lastActiveTime = time.Now()
lastActiveChan = config.AdminChannel
if token == "" {
log.LogPanic("No token provided. Please run: disgord-thanos -t <bot token>")
}
log.LogCritical("SetupToken: %+v\nRebootToken: %+v", setupToken, rebootToken)
var err error
dg, err = discordgo.New("Bot " + token)
dg, err := discordgo.New("Bot " + token)
if err != nil {
log.LogErrorType(err)
log.LogPanic("Unable to create bot using token.")
@ -64,7 +65,6 @@ func main() { @@ -64,7 +65,6 @@ func main() {
dg.AddHandler(guildMemberRemove)
dg.AddHandler(guildMemberAdd)
dg.AddHandler(guildMemberBanned)
go setupCommands()
dg.AddHandler(messageCreate)
dg.AddHandler(readReaction)
dg.AddHandler(guildMemberUpdate)
@ -78,9 +78,8 @@ func main() { @@ -78,9 +78,8 @@ func main() {
log.LogInfo("Thanos is now running. Press CTRL-C to exit.")
go purgeTimer(dg)
go rebootBump()
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
<-sc
saveConfig()
dg.Close()
@ -100,7 +99,6 @@ func runPurge(s *discordgo.Session) { @@ -100,7 +99,6 @@ func runPurge(s *discordgo.Session) {
for uid, join := range config.Probations {
if time.Since(join) > 2*time.Hour {
delete(config.Probations, uid)
s.ChannelMessageDelete(config.IntroChann, introMsg[uid])
}
}
for k, v := range config.Unverified {
@ -122,7 +120,7 @@ func runPurge(s *discordgo.Session) { @@ -122,7 +120,7 @@ func runPurge(s *discordgo.Session) {
lastPM[k] = time.Now()
pmChann, _ := s.UserChannelCreate(k)
s.ChannelMessageSend(pmChann.ID,
fmt.Sprintf("This is a reminder that you have not verified with me and will be removed at <t:%+v:t>. You may reply to this message for verification instructions.", v.Add(1*time.Hour).Unix()))
fmt.Sprintf("This is a reminder that you have not verified with me and will be removed in %+v. You may reply to this message for verification instructions.", time.Until(v.Add(1*time.Hour))))
if time.Since(v) > (time.Hour * 1) {
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v was removed.", m.Mention()))
s.GuildMemberDeleteWithReason(config.GuildID, k, fmt.Sprintf("Unverified user %+v.", v))
@ -148,74 +146,242 @@ func runPurge(s *discordgo.Session) { @@ -148,74 +146,242 @@ func runPurge(s *discordgo.Session) {
s.ChannelMessageDelete(config.MonitorChann, message.ID)
}
}
go cleanSocials(s)
saveConfig()
}
func cleanSocials(s *discordgo.Session) {
for _, channel := range config.SocialChanns {
go func(channel string, s *discordgo.Session) {
messages, _ := s.ChannelMessages(channel, 100, "", "", "")
for _, message := range messages {
_, err := s.GuildMember(config.GuildID, message.Author.ID)
if err != nil {
s.ChannelMessageDelete(channel, message.ID)
func ready(s *discordgo.Session, event *discordgo.Ready) {
// Set the playing status.
s.UpdateStatus(0, "DreamDaddy v1.0")
}
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)
}
}
}(channel, s)
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)
}
}
delete(config.Unverified, m.User.ID)
s.ChannelMessageSend(config.AdminChannel, fmt.Sprintf("%+v (@%+v) has left, ban: %+v", m.User.ID, m.User.Username, banned))
saveConfig()
}
func verifyMember(s *discordgo.Session, u discordgo.User) {
defer log.PanicSafe()
log.LogDebug("Adding verified roll")
s.GuildMemberRoleAdd(config.GuildID, u.ID, config.VerifiedRole)
log.LogDebug("Removing monitor role")
s.GuildMemberRoleRemove(config.GuildID, u.ID, config.MonitorRole)
log.LogDebug("Creating PM channel")
st, err := s.UserChannelCreate(u.ID)
if err != nil {
log.LogErrorType(err)
}
log.LogDebug("Sending acceptance message!")
st, _ := s.UserChannelCreate(u.ID)
s.ChannelMessageSend(st.ID, "Your verification has been accepted, welcome!")
log.LogDebug("Sending Intro message")
m, err := 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()))
if err != nil {
log.LogErrorType(err)
}
log.LogDebug("Storing introMsg ID to be deleted later")
introMsg[u.ID] = m.ID
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()))
}
func rejectVerification(s *discordgo.Session, u discordgo.User) {
defer log.PanicSafe()
st, err := s.UserChannelCreate(u.ID)
if err != nil {
log.LogErrorType(err)
}
st, _ := s.UserChannelCreate(u.ID)
if st != nil {
s.ChannelMessageSend(st.ID, fmt.Sprintf("Your verification has been rejected. This means it did not clearly show your face, with your pinkie finger held to the corner of your mouth, or the photo looked edited/filtered. No filters will be accepted.\n\nPlease try again before <t:%+v:t>", time.Now().Add(1*time.Hour).Unix()))
s.ChannelMessageSend(st.ID, fmt.Sprintf("Your verification has been rejected. This means it did not clearly show your face, with your pinkie finger held to the corner of your mouth, or the photo looked edited/filtered. No filters will be accepted.\n\nPlease try again before %+v", time.Until(time.Now().Add(1*time.Hour))))
}
config.Unverified[u.ID] = time.Now()
}
func requestAge(s *discordgo.Session, u discordgo.User) {
defer log.PanicSafe()
st, err := s.UserChannelCreate(u.ID)
if err != nil {
log.LogErrorType(err)
st, _ := s.UserChannelCreate(u.ID)
s.ChannelMessageSend(st.ID, "What is your ASL? (Age/Sex/Language)")
}
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 measurable 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 requestReupload(s *discordgo.Session, u discordgo.User) {
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()
st, err := s.UserChannelCreate(u.ID)
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.LogErrorType(err)
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()
}
}
s.ChannelMessageSend(st.ID, "Hello! Your verification has been denied because it failed to load. Please try again! The instructions will follow this message:")
rejectVerification(s, u)
}

238
site-api.go

@ -1,238 +0,0 @@ @@ -1,238 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
)
var (
store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
toks = make(map[string]Tokens)
)
func topWrapper(r *http.Request) string {
defer log.PanicSafe()
headerTemplate, err := os.ReadFile("./static/header.tpl")
if err != nil {
log.LogError(fmt.Sprintf("Unable to open header template: ```%+v```", err))
return ""
}
header := string(headerTemplate)
login := "Login"
loggedIn, user := detectUser(r, "topWrapper")
if loggedIn {
login = fmt.Sprintf("Logout %s", user)
}
header = strings.Replace(header, "$LOGIN", login, -1)
return header
}
func bodyWrapper(r *http.Request, template string) string {
defer log.PanicSafe()
bodyTemplate, err := os.ReadFile(fmt.Sprintf("./static/%+v.tpl", template))
if err != nil {
log.LogError(fmt.Sprintf("Attempt to load %s.tpl failed. ```%+v```", template, err))
return bodyWrapper(r, "404")
}
return string(bodyTemplate)
}
func pageBuilder(r *http.Request, pageName string) string {
defer log.PanicSafe()
pageCode := topWrapper(r)
pageCode += bodyWrapper(r, pageName)
return pageCode
}
func greetUser(w http.ResponseWriter, r *http.Request) {
defer log.PanicSafe()
log.LogInfo(fmt.Sprintf("%s called greetUser", getSessionIdentifier(r)))
loggedIn, _ := detectUser(r, "Homepage")
if loggedIn {
bodyTemplate, _ := os.ReadFile("./static/index.html")
fmt.Fprint(w, string(bodyTemplate))
} else {
fmt.Fprint(w, pageBuilder(r, "home"))
}
}
func passPage(w http.ResponseWriter, r *http.Request) {
defer log.PanicSafe()
log.LogInfo(fmt.Sprintf("%s called passPage", getSessionIdentifier(r)))
fmt.Fprint(w, pageBuilder(r, "pass"))
}
func loginPage(w http.ResponseWriter, r *http.Request) {
defer log.PanicSafe()
log.LogInfo(fmt.Sprintf("%s called loginPage", getSessionIdentifier(r)))
session, err := store.Get(r, "2fa")
if err != nil {
log.LogWarn("Unable to open 2fa session in loginpage")
}
loggedIn, _ := detectUser(r, "loginPage")
if loggedIn {
session.Values["username"] = nil
err = session.Save(r, w)
if err != nil {
log.LogWarn("Error logging out from loginPage()")
}
fmt.Fprint(w, pageBuilder(r, "home"))
return
}
fmt.Fprint(w, pageBuilder(r, "login"))
}
func notFoundPage(w http.ResponseWriter, r *http.Request) {
defer log.PanicSafe()
go log.LogWarn(fmt.Sprintf("%s triggered notFoundPage", getSessionIdentifier(r)))
fmt.Fprint(w, topWrapper(r))
fmt.Fprint(w, card("Oops! That Page Was Not found.",
"Sorry, a 404 error has occured. The requested page not found! <br><br>"+
"<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/t3otBjVZzT0\" frameborder=\"0\" allow=\"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>",
"<div class=\"error-actions\"><a href=\"/\" class=\"btn btn-primary btn-lg\"><span class=\"glyphicon glyphicon-home\"></span>Take Me Home </a> <a href=\"mailto://rudi@nmare.net\" class=\"btn btn-default btn-lg\"><span class=\"glyphicon glyphicon-envelope\"></span> Contact Support </a></div>"))
}
func card(title string, content string, footer string) string {
defer log.PanicSafe()
cardTemplate, err := os.ReadFile("./static/card.tpl")
if err != nil {
log.LogError("Unable to open card template")
return ""
}
cardString := string(cardTemplate)
cardString = strings.Replace(cardString, "$TITLE", title, -1)
cardString = strings.Replace(cardString, "$CONTENT", content, -1)
cardString = strings.Replace(cardString, "$FOOTER", footer, -1)
return cardString
}
func getPending(w http.ResponseWriter, r *http.Request) {
defer log.PanicSafe()
loggedIn, _ := detectUser(r, "getPending")
if loggedIn {
pending, err := json.Marshal(config.Verifications)
if err != nil {
log.LogErrorType(err)
notFoundPage(w, r)
}
fmt.Fprint(w, string(pending))
} else {
notFoundPage(w, r)
}
}
func getConfig(w http.ResponseWriter, r *http.Request) {
defer log.PanicSafe()
loggedIn, _ := detectUser(r, "getConfig")
if loggedIn {
pending, err := json.Marshal(config)
if err != nil {
log.LogErrorType(err)
notFoundPage(w, r)
}
fmt.Fprint(w, string(pending))
} else {
notFoundPage(w, r)
}
}
func getProbations(w http.ResponseWriter, r *http.Request) {
defer log.PanicSafe()
loggedIn, _ := detectUser(r, "getProbations")
if loggedIn {
pending, err := json.Marshal(config.Probations)
if err != nil {
log.LogErrorType(err)
notFoundPage(w, r)
}
fmt.Fprint(w, string(pending))
} else {
notFoundPage(w, r)
}
}
func getVerifications(w http.ResponseWriter, r *http.Request) {
defer log.PanicSafe()
loggedIn, _ := detectUser(r, "getVerifications")
if !loggedIn {
notFoundPage(w, r)
return
}
var files []string
root := "./verifications/"
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
files = append(files, path)
return nil
})
if err != nil {
log.LogErrorType(err)
}
var v []Verification
for _, file := range files {
info := strings.Split(file, "-")
if len(info) < 2 {
continue
}
var ver Verification
ver.UserID = strings.Replace(info[0], "verifications/", "", -1)
ver.Username = info[1]
ver.Photo = file
fileStat, _ := os.Stat(file)
ver.Closed = fileStat.ModTime()
v = append(v, ver)
}
verifications, err := json.Marshal(v)
if err != nil {
log.LogErrorType(err)
}
fmt.Fprint(w, string(verifications))
}
func getUser(w http.ResponseWriter, r *http.Request) {
loggedIn, _ := detectUser(r, "getVerifications")
if !loggedIn {
notFoundPage(w, r)
return
}
vars := mux.Vars(r)
username := vars["userID"]
if len(username) == 0 {
username = r.FormValue("userID")
}
m, err := dg.GuildMember(config.GuildID, username)
if err != nil {
log.LogErrorType(err)
}
ret, err := json.Marshal(m)
if err != nil {
log.LogErrorType(err)
}
fmt.Fprint(w, string(ret))
}
func runWeb() {
defer log.PanicSafe()
router := mux.NewRouter().StrictSlash(true)
log.LogInfo("Adding HandleFuncs to router")
router.NotFoundHandler = http.HandlerFunc(notFoundPage)
router.HandleFunc("/pass", passPage)
router.HandleFunc("/login", loginPage)
router.HandleFunc("/api/login", tryLogin)
router.HandleFunc("/api/config", getConfig)
router.HandleFunc("/api/pending", getPending)
router.HandleFunc("/api/verifications", getVerifications)
router.HandleFunc("/api/probations", getProbations)
router.HandleFunc("/api/passreq", reqPass)
router.HandleFunc("/api/user", getUser)
router.HandleFunc("/", greetUser)
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
router.PathPrefix("/verifications/").Handler(http.StripPrefix("/verifications/", http.FileServer(http.Dir("./verifications"))))
log.LogInfo("Starting server")
log.LogErrorType(http.ListenAndServe(":8080", router))
}

21
static/404.tpl

@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="error-template">
<h1>
Oops!</h1><br>
<h2>
404 Not Found</h2>
<div class="error-details">
Sorry, an error has occured, Requested page not found!
</div>
<br>
<div class="error-actions">
<a href="/" class="btn btn-primary btn-lg"><span class="glyphicon glyphicon-home"></span>
Take Me Home </a> <a href="mailto://rudi@nmare.net" class="btn btn-default btn-lg"><span class="glyphicon glyphicon-envelope"></span> Contact Support </a>
</div>
</div>
</div>
</div>
<br>
</div>

21
static/500.tpl

@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="error-template">
<h1>
Oops!</h1><br>
<h2>Something has gone HORRIBLY wrong! Congrats!</h2>
<div class="error-details">
Not really sorry, but an error has occured. 500 means something went wrong on the server side, but.. Let's be honest. It was really a user malfunction.
</div>
<br>
<div class="error-actions">
<a href="/" class="btn btn-primary btn-lg"><span class="glyphicon glyphicon-home"></span>
Take Me Home </a> <a href="mailto://rudi@nmare.net" class="btn btn-default btn-lg"><span class="glyphicon glyphicon-envelope"></span> Contact Support </a>
</div>
</div>
</div>
</div>
<br>
<iframe width="560" height="315" src="https://www.youtube.com/embed/t3otBjVZzT0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>

149
static/app.js

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

11
static/card.tpl

@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
<br><br>
<div class="container h-100 d-flex justify-content-center align-items-center">
<div class="card" style="width: 50rem;">
<div class="card-body col">
<h2 class="card-title">$TITLE</h1>
<p class="card-text">$CONTENT</p>
<p class="card-text">$FOOTER</p>
</div>
</div>
</div>

210
static/components.js

@ -1,210 +0,0 @@ @@ -1,210 +0,0 @@
const basicCard = document.createElement('basic-card');
const configStatus = document.createElement('status');
const style = `
<style>
.column {
float: left;
width: 24%;
height: 100%;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.137);
border-radius: 3%;
transition: 0.3s;
margin: 15px 3.33%;
padding: 12px 16px;
background-color: #282c34;
color: #FFFFFF;
}
.column:hover {
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.795);
}
/* Clear floats after the columns */
.row {
content: "";
clear: both;
display: flex;
flex-wrap: wrap;
}
p {
text-align: center;
}
a {
text-align: center;
color: white;
}
.card {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.137);
border-radius: 3%;
transition: 0.3s;
position: relative;
width: 250px;
height: 200px;
padding: 5px;
background-color: #282c34;
color: #FFFFFF;
}
.card:hover {
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.795);
}
.container {
padding: 6px 16px;
}
label{
display: inline;
float: left;
clear: left;
width: 250px;
text-align: left;
}
input {
display: inline;
float: right;
}
</style>
`;
basicCard.innerHTML = `
${style}
<div id="card-container" class="container">
<div class="card">
<h4><p><slot name="username" /></p></h4>
<div>
<p><slot name="join-date" /></p>
<p><slot id="discord-id" name="discord-id" /></p>
<p><slot id="pic-link" name="pic-link" /></p>
</div>
</div>
</div>
`;
configStatus.innerHTML = `
${style}
<div class="row">
<div class="column">
<h4><p>Status</p></h4>
<p>Uptime: <slot name="uptime" /></p>
<p>Last Bump was <slot name="lastbump" /></p>
<p>by <slot id="lastbumper" name="lastbumper" /></p>
</div>
<br>
<div class="column">
<h4><p>Config</p></h4>
<form>
<label for="Guild">Guild: </label>
<input type="text" id="Guild" name="Guild" readonly/>
<label for="AdminChannel">Admin Channel: </label>
<input type="select" id="AdminChannel" name="AdminChannel"/>
<label for="AdminRole">Admin Role: </label>
<input type="select" id="AdminRole" name="AdminRole"/>
<label for="MonitorChannel">Monitor Channel: </label>
<input type="select" id="MonitorChannel" name="MonitorChannel"/>
<label for="MonitorRole">Monitor Role: </label>
<input type="select" id="MonitorRole" name="MonitorRole"/>
<label for="IntroChannel">Intro Channel: </label>
<input type="select" id="IntroChannel" name="IntroChannel"/>
<label for="VerifiedRole">Verified Role: </label>
<input type="select" id="VerifiedRole" name="VerifiedRole"/>
<label for="OutFile">Logging OutFile: </label>
<input type="text" id="OutFile" name="OutFile" />
<label for="KBTeam">KB Team: </label>
<input type="text" id="KBTeam" name="KBTeam" />
<label for="KBChann">KB Channel: </label>
<input type="text" id="KBChann" name="KBChann" />
<label for="level">Logging Level: </label>
<input type="number" id="Level" name="Level" />
<label for="ProgName">Program Name: </label>
<input type="text" id="ProgName" name="ProgName" />
<label for="UseStdout">Use stdout: </label>
<input type="checkbox" id="UseStdout" name="UseStdout"/>
<label for="submitchanges"></label>
<input style="width: 100%;" type="submit" id="submitchanges" name="submitchanges" onClick="alert('Post blocked.')">
</form>
</div>
<br>
<div id="admin-stats" class="column">
<h4><p>Admin Stats</p></h4>
</div>
</div>
`;
class ConfigStatusClass extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(configStatus.cloneNode(true));
}
async setData(data) {
this.shadowRoot.querySelector("#submitchanges").addEventListener('submit', handleForm);
this.shadowRoot.querySelector("#Guild").value = data.GuildID;
this.shadowRoot.querySelector("#AdminChannel").value = data.AdminChannel;
this.shadowRoot.querySelector("#AdminRole").value = data.AdminRole;
this.shadowRoot.querySelector("#MonitorChannel").value = data.MonitorChann;
this.shadowRoot.querySelector("#MonitorRole").value = data.MonitorRole;
this.shadowRoot.querySelector("#IntroChannel").value = data.IntroChann;
this.shadowRoot.querySelector("#VerifiedRole").value = data.VerifiedRole;
this.shadowRoot.querySelector("#OutFile").value = data.LogOpts.OutFile;
this.shadowRoot.querySelector("#KBTeam").value = data.LogOpts.KBTeam;
this.shadowRoot.querySelector("#KBChann").value = data.LogOpts.KBChann;
this.shadowRoot.querySelector("#Level").value = data.LogOpts.Level;
this.shadowRoot.querySelector("#ProgName").value = data.LogOpts.ProgName;
this.shadowRoot.querySelector("#UseStdout").checked = data.LogOpts.UseStdout == "true";
fetch('https://thanos.nightmare.haus/api/user?userID=' + data.LastBumper)
.then(response => response.json())
.then(userData => {
this.shadowRoot.querySelector("#lastbumper").innerHTML = userData.user.username;
});
this.loadStats(data.Stats)
}
async loadStats(data) {
var shadowRoot = this.shadowRoot;
Object.keys(data).forEach(function(uid) {
var score = data[uid];
fetch('https://thanos.nightmare.haus/api/user?userID=' + uid)
.then(response => response.json())
.then(userData => {
var stats = shadowRoot.querySelector("#admin-stats");
var userName = document.createElement("p");
userName.innerText = userData.user.username + ": " + score;
stats.appendChild(userName);
});
})
}
}
class UserCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(basicCard.cloneNode(true));
}
connectedCallback() {
this.shadowRoot.querySelector('#card-container').addEventListener('click', () => this.showModal());
}
showModal() {
const userID = this.shadowRoot.querySelector('#discord-id').assignedNodes()[0].textContent;
const userPic = this.shadowRoot.querySelector("#pic-link").assignedNodes()[0].querySelector("a").getAttribute("href");
fetch('https://thanos.nightmare.haus/api/user?userID=' + userID)
.then(response => response.json())
.then(data => {
if (data === undefined || data === null) {
alert("User not found.");
return;
}
document.querySelector("#myModal").style.display = "block";
document.querySelector('#modal-join').textContent = new Date(data.joined_at).toLocaleString();
document.querySelector('#modal-userID').textContent = data.user.username;
document.querySelector('#modal-avatar').src = `https://cdn.discordapp.com/avatars/${data.user.id}/${data.user.avatar}.png?size=256`;
document.querySelector('#modal-verification').src = userPic;
document.querySelector('#modal-verification').style = "max-height: 500px;";
});
}
}
window.customElements.define('user-card', UserCard);
window.customElements.define('config-status', ConfigStatusClass);

44
static/header.tpl

@ -1,44 +0,0 @@ @@ -1,44 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Thanos</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<link rel="icon" type="image/png" href="https://lh6.googleusercontent.com/BvczCCYhJUsKIs3dsowl1vuvnBtCGSDcMDekt5PehwQk3cQLfHkEn80cR3IuMxUFmd5Sh_UQ=w16383">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<link href="/static/css/main.5c7015b9.chunk.css" rel="stylesheet">
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
<div class="container">
<a class="navbar-brand" href="/">
<img src="" alt="">
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item active">
<a class="nav-link" href="/">Home
<span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pass">Request Token</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/login">$LOGIN</a>
</li>
</ul>
</div>
</div>
</nav>
</head>

17
static/home.tpl

@ -1,17 +0,0 @@ @@ -1,17 +0,0 @@
<br><br>
<div class="container h-100 d-flex justify-content-center align-items-center">
<div id="react_app"></div>
<div class="container h-100 d-flex justify-content-center align-items-center">
<div class="card" style="width: 50rem;">
<div class="card-body col">
<h2 class="card-title">Cookie Policy</h1>
<p class="card-text">What website doesn't use cookies nowadays?</p>
<p class="card-text">This website does not use any 3rd party cookies. All cookies are encrypted and
only used by this website. </p>
<p class="card-text">If you are actually reading this, chances are your data isn't valuable enough
for me to care about tracking you.</p>
</div>
</div>
</div>

73
static/index.css

@ -1,73 +0,0 @@ @@ -1,73 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.card {
/* Add shadows to create the "card" effect */
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.137);
border-radius: 3%;
transition: 0.3s;
position: relative;
width: 300px;
height: 200px;
padding: 10px;
background-color: #282c34;
color: #FFFFFF;
}
/* On mouse-over, add a deeper shadow */
.card:hover {
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.795);
}
/* Add some padding inside the card container */
.container {
padding: 6px 16px;
}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
/* Modal Content/Box */
.modal-content {
background-color: #282c34;
margin: 15% auto; /* 15% from the top and centered */
padding: 20px;
border: 1px solid #888;
width: 80%; /* Could be more or less, depending on screen size */
color: white;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}

91
static/index.html

@ -1,91 +0,0 @@ @@ -1,91 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="JavaScript web interface for ThanOS API">
<meta name="author" content="rudi@nightmare.haus">
<link rel="icon" href="https://cdn.discordapp.com/avatars/688025671968096341/7ad6b70b550cec8fb9dba7cec489838e.png?size=32">
<title>Thanos2</title>
<!-- Bootstrap core CSS -->
<link href="https://getbootstrap.com/docs/4.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="https://getbootstrap.com/docs/4.0/examples/starter-template/starter-template.css" rel="stylesheet">
<!-- My imports -->
<link href="./static/index.css" rel="stylesheet">
<!-- End of my imports -->
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="https://git.nightmare.haus/rudi/disgord-Thanos">ThanOS</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault"
aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item active" id="archive-link">
<a class="nav-link" href="?mode=verifications">Archive <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item" id="pending-link">
<a class="nav-link" href="?mode=pending">Pending</a>
</li>
<li class="nav-item" id="status-link">
<a class="nav-link disabled" href="?mode=status">Status</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0" id="search-form">
<input class="form-control mr-sm-2" id="search-bar" type="text" placeholder="Search Verifications"
aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" id="search-button" type="submit" onclick="searchPage()">Search</button>
</form>
</div>
</nav>
<!-- The Modal -->
<div id="myModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<div class="container">
<span class="close">&times;</span>
<div class="row">
<div class="col-sm">
<img id="modal-avatar" alt="Avatar">
<p id="modal-join"></p>
<p id="modal-userID"></p>
</div>
<div class="col-sm">
<img id="modal-verification" alt="Avatar" style="width: 100%;">
</div>
</div>
</div>
</div>
</div>
<div id="main-app" style="display: flex; flex-wrap: wrap;">
</div>
<script src="./static/components.js"></script>
<script src="./static/app.js"></script>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"></script>
<script>window.jQuery || document.write('<script src="https://getbootstrap.com/docs/4.0/assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
<script src="https://getbootstrap.com/docs/4.0/assets/js/vendor/popper.min.js"></script>
<script src="https://getbootstrap.com/docs/4.0/dist/js/bootstrap.min.js"></script>
</body>
</html>

8
static/loggedIn.tpl

@ -1,8 +0,0 @@ @@ -1,8 +0,0 @@
<div id="react_app"></div>
<div class="container h-100 d-flex justify-content-center align-items-center">
<script>!function (e) { function t(t) { for (var n, l, a = t[0], f = t[1], i = t[2], c = 0, s = []; c < a.length; c++)l = a[c], Object.prototype.hasOwnProperty.call(o, l) && o[l] && s.push(o[l][0]), o[l] = 0; for (n in f) Object.prototype.hasOwnProperty.call(f, n) && (e[n] = f[n]); for (p && p(t); s.length;)s.shift()(); return u.push.apply(u, i || []), r() } function r() { for (var e, t = 0; t < u.length; t++) { for (var r = u[t], n = !0, a = 1; a < r.length; a++) { var f = r[a]; 0 !== o[f] && (n = !1) } n && (u.splice(t--, 1), e = l(l.s = r[0])) } return e } var n = {}, o = { 1: 0 }, u = []; function l(t) { if (n[t]) return n[t].exports; var r = n[t] = { i: t, l: !1, exports: {} }; return e[t].call(r.exports, r, r.exports, l), r.l = !0, r.exports } l.m = e, l.c = n, l.d = function (e, t, r) { l.o(e, t) || Object.defineProperty(e, t, { enumerable: !0, get: r }) }, l.r = function (e) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e, "__esModule", { value: !0 }) }, l.t = function (e, t) { if (1 & t && (e = l(e)), 8 & t) return e; if (4 & t && "object" == typeof e && e && e.__esModule) return e; var r = Object.create(null); if (l.r(r), Object.defineProperty(r, "default", { enumerable: !0, value: e }), 2 & t && "string" != typeof e) for (var n in e) l.d(r, n, function (t) { return e[t] }.bind(null, n)); return r }, l.n = function (e) { var t = e && e.__esModule ? function () { return e.default } : function () { return e }; return l.d(t, "a", t), t }, l.o = function (e, t) { return Object.prototype.hasOwnProperty.call(e, t) }, l.p = "/"; var a = this.webpackJsonpthanos = this.webpackJsonpthanos || [], f = a.push.bind(a); a.push = t, a = a.slice(); for (var i = 0; i < a.length; i++)t(a[i]); var p = f; r() }([])</script>
<script src="/static/js/2.55237e37.chunk.js"></script>
<script src="/static/js/main.a24cc14e.chunk.js"></script>
</div>
</body>

31
static/login.tpl

@ -1,31 +0,0 @@ @@ -1,31 +0,0 @@
<div class="container-fluid">
<div class="row no-gutter">
<div class="d-none d-md-flex col-md-4 col-lg-6 bg-image"></div>
<div class="col-md-8 col-lg-6">
<div class="login d-flex align-items-center py-5">
<div class="container">
<div class="row">
<div class="col-md-9 col-lg-8 mx-auto">
<h3 class="login-heading mb-4">Thanos OTP Login</h3>
<form action="/api/login">
<div class="form-label-group">
<input type="text" name="UserName" id="Username" class="form-control" placeholder="Username" required autofocus>
<label for="inputEmail">Username</label>
</div>
<div class="form-label-group">
<input type="password" name="TempPass" id="TempPassword" class="form-control" placeholder="Do Not Use Your Discord Password" required>
<label for="inputPassword">Temporary Password</label>
</div>
<button class="btn btn-lg btn-primary btn-block btn-login text-uppercase font-weight-bold mb-2" type="submit">Sign in</button>
<div class="text-center">
<a class="small" href="/pass">Need to request a password?</a></div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

31
static/pass.tpl

@ -1,31 +0,0 @@ @@ -1,31 +0,0 @@
<body>
<br><br>
<div class="row h-100 justify-content-center align-items-center">
<div class="card">
<div class="container">
<form action="/api/passreq">
<table>
<tr>
<td>
Discord User:
</td>
<td>
<input type="text" name="UserName" value="">
</td>
</tr>
<tr>
<td>
</td>
<td>
<input type="submit" value="Submit" class="btn btn-primary" style="float: right;">
</td>
</tr>
</table>
</form>
<p>Click the "Submit" button and a temporary password will be sent to the Discord User.</p>
</div>
</div>
</div>
</body>
</html>

51
tools/listen.go

@ -1,51 +0,0 @@ @@ -1,51 +0,0 @@
package tools
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/bwmarrin/discordgo"
)
var (
token string
dg *discordgo.Session
guild string
)
func init() {
flag.StringVar(&token, "t", "", "Bot Token")
flag.StringVar(&guild, "g", "", "Guild ID")
flag.Parse()
}
func main() {
if token == "" {
fmt.Printf("No token provided. Please run: disgord-thanos -t <bot token>")
}
dg, _ = discordgo.New("Bot " + token)
dg.AddHandler(messageCreate)
_ = dg.Open()
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
<-sc
dg.Close()
}
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
if guild != "" {
if m.GuildID != guild {
return
}
}
jsonMsg, err := json.Marshal(m)
if err != nil {
jsonMsg = append(jsonMsg, '0')
}
log.Printf("----------\n%+v: %+v\n\n%+v\n------------------------------\n\n", m.Author.Username, m.Content, string(jsonMsg))
}

37
tools/runFunction.go

@ -1,37 +0,0 @@ @@ -1,37 +0,0 @@
package tools
import (
"flag"
"fmt"
"github.com/bwmarrin/discordgo"
)
var (
token string
dg *discordgo.Session
guild string
)
func init() {
flag.StringVar(&token, "t", "", "Bot Token")
flag.StringVar(&guild, "g", "", "Guild ID")
flag.Parse()
}
func main() {
if token == "" {
fmt.Printf("No token provided. Please run: disgord-thanos -t <bot token>")
}
dg, _ = discordgo.New("Bot " + token)
_ = dg.Open()
runFunction()
dg.Close()
}
func runFunction() {
bans, _ := dg.GuildBans(guild)
for _, v := range bans {
dg.GuildBanDelete(guild, v.User.ID)
}
}

36
types.go

@ -1,28 +1,7 @@ @@ -1,28 +1,7 @@
package main
import (
"time"
"github.com/bwmarrin/discordgo"
"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
RequiresAdmin bool
Help string
Keywords []string
Exec func(BotCommand) bool
}
import "time"
import "github.com/rudi9719/loggy"
// Config struct used for bot
type Config struct {
@ -32,17 +11,14 @@ type Config struct { @@ -32,17 +11,14 @@ type Config struct {
MonitorRole string
IntroChann string
MonitorChann string
SocialChanns []string
VerifiedRole string
BumpTime time.Time
LastBumper string
Stats map[string]int
Activity map[string]int
Unverified map[string]time.Time
Verifications map[string]Verification
Probations map[string]time.Time
LogOpts loggy.LogOpts
WhitelistURLs []string
}
// Verification struct used for storing and logging
@ -55,11 +31,3 @@ type Verification struct { @@ -55,11 +31,3 @@ type Verification struct {
Admin string
Closed time.Time
}
// Tokens are the Login Token struct
type Tokens struct {
Username string
IP string
Password string
Timestamp time.Time
}

Loading…
Cancel
Save