Compare commits

...

204 Commits

Author SHA1 Message Date
Gregory Rudolph 87939c32ef
Update deps 8 months ago
Gregory Rudolph fcc806eb77
Remove deprecated io/ioutil import and calls 8 months ago
Gregory Rudolph dd433f44ad
Formatting 8 months ago
Gregory Rudolph 99e6f7066b
Formatting 8 months ago
Gregory Rudolph 139d378d1a
Remove deprecated io/ioutil import and calls 8 months ago
Gregory Rudolph bddd283127
Formatting 8 months ago
Gregory Rudolph 31ca1cb243
Formatting 8 months ago
Gregory Rudolph 5eb7920ed2
New reaction for admins 1 year ago
Gregory Rudolph f79b842e17
Add mention when link is deleted 2 years ago
Gregory Rudolph bd493223ca
Clean up welcome messages 2 years ago
Gregory Rudolph bdbc2a1196
Reconnect bumpTimer 2 years ago
Gregory Rudolph 5ae7a96c3e
Show channel + author mention for removed messages 2 years ago
Gregory Rudolph f5c59af2b4
Log deleted link 2 years ago
Gregory Rudolph 8af3e9656d
No blank links 2 years ago
Gregory Rudolph c2646ad280
Cleaning up commandwork 2 years ago
Gregory Rudolph a93d4a727b
Only look for commands directly after @Thanos 2 years ago
Gregory Rudolph 706c2b516b
trim domain 2 years ago
Gregory Rudolph efbe429824
trim domain 2 years ago
Gregory Rudolph d68f027f42
Add domains to output 2 years ago
Gregory Rudolph 59926b6b9b
Add domains to output 2 years ago
Gregory Rudolph dc0bf186ae
Add URL Whitelisting to Thanos since @MEE6 wants us to pay for it 2 years ago
Gregory Rudolph d24ff86d68
Add URL Whitelisting to Thanos since @MEE6 wants us to pay for it 2 years ago
Gregory Rudolph 27d5cf3a62
Don't look for user in bump response 2 years ago
Gregory Rudolph 0c3f4ce74a
Allow filtering by guild 2 years ago
Gregory Rudolph 6d42e0fa54
Add utility to listen to DiscordGo for debugging 2 years ago
Gregory Rudolph 6e1c27ca27
Cleaning 2 years ago
Gregory Rudolph cb38b8e5c4
Clean up status times 2 years ago
Gregory Rudolph 140515f19b
Add return to only run command once 2 years ago
Gregory Rudolph d4fc69cd50
Ignore 'i' value for actual role ID 2 years ago
Gregory Rudolph 6fd9d11417
Add additional logging for member verification roles 2 years ago
Gregory Rudolph 283f8dbf65
Add logging for messages erroring out 2 years ago
Gregory Rudolph cce07513f3
Add logging for messages erroring out 2 years ago
Gregory Rudolph d13ee51aa6
Remove bump chaser 2 years ago
Gregory Rudolph 7713e3fc02 Update last bumper 2 years ago
Gregory Rudolph 084ce2aaae Cleaning and pushing 2 years ago
Gregory Rudolph cf494d4c11 Listen for successful bumps 2 years ago
Gregory Rudolph 46e67b7f98 Help? 2 years ago
Gregory Rudolph 07d51266bb Thanos should ignore his own messages. 2 years ago
Gregory Rudolph 7aa9372eaf Cleaning incoming message processing 2 years ago
Gregory Rudolph 931825bd5a make sure there is embed before trying operations 2 years ago
Gregory Rudolph 5c39cbdafb Fixed mention for automatic bump set 2 years ago
Gregory Rudolph 25ea59bff1 Remove debugging messages. 2 years ago
Gregory Rudolph 908d506f5c Cleaning bump set 2 years ago
Gregory Rudolph b323262213 Cleaning 2 years ago
Gregory Rudolph 0013b1fe2b Add debugging 2 years ago
Gregory Rudolph 09046f48dc Add attempts to automatically fix bump timer. 2 years ago
Gregory Rudolph bd4b4508e5 Make this tool more generic 2 years ago
Gregory Rudolph 6232d9e126
Cleanup 3 years ago
Gregory Rudolph 90c5a1c472
Remove social channel setup 3 years ago
Gregory Rudolph 578174e878
Only accept certain formats. 3 years ago
Gregory Rudolph b85794089f
Add Discord time formatting 3 years ago
Gregory Rudolph d85da5989a
More goroutines? 3 years ago
Gregory Rudolph 6f3896a90b
Clean socials, instead of talk about it 3 years ago
Gregory Rudolph cebc18f0a2
Add social channel cleanup on member leave, but don't delete yet. Testing first! 3 years ago
Gregory Rudolph a97993e830
Clean up bump set formatting 3 years ago
Gregory Rudolph 01f5ae64ea
Clean up code warnings 3 years ago
Gregory Rudolph 21f5d2bb56
Fix formatting 3 years ago
Gregory Rudolph fef2e7f08f
Fix reboot command 3 years ago
Gregory Rudolph 7610f22b57
Add user activity 3 years ago
Gregory Rudolph 0df1fae253
Add user activity 3 years ago
Gregory Rudolph 5222b1b3b6
Add user activity 3 years ago
Gregory Rudolph 53b3aa61ab
Add user activity 3 years ago
Gregory Rudolph ab0455cf5b
Add user activity 3 years ago
Gregory Rudolph 80746f5bff
Fix RetrieveVerification 3 years ago
Gregory Rudolph 304144c607
Fix idFromUsername 3 years ago
Gregory Rudolph 2f5c3b6cf3
Fix Verification filename 3 years ago
Gregory Rudolph 70672fd5fd
Fix parts[] for everyone 3 years ago
Gregory Rudolph 4d8e33058a
Fix parts[] for dbeug set 3 years ago
Gregory Rudolph 3d174fd4f0
Fix parts[] for dbeug set 3 years ago
Gregory Rudolph 160625396c
go vet 3 years ago
Gregory Rudolph 62738d8a88
Cleaning up/Happy path 3 years ago
Gregory Rudolph 37ce86beb7
Cleaning up/Happy path 3 years ago
Gregory Rudolph 6bdc997cbe
Dont show bump request on commands 3 years ago
Gregory Rudolph 42969ca27c
Fix bumpset 3 years ago
Gregory Rudolph b6716eba46
Fix bumpset 3 years ago
Gregory Rudolph 3d6d5c64b9
Add help for a few commands 3 years ago
Gregory Rudolph 2a5fe0ceef
Add debug level command 3 years ago
Gregory Rudolph fa53c8b1da
Fix admin role check 3 years ago
Gregory Rudolph fb2636c9c0
Debugging 3 years ago
Gregory Rudolph 5dc1f53a59
Debugging 3 years ago
Gregory Rudolph fd5d3ce0e7
Debugging 3 years ago
Gregory Rudolph 526af29391
Debugging 3 years ago
Gregory Rudolph 03ce2fe74a
Debugging 3 years ago
Gregory Rudolph 29ab9296ec
Fix bot command detection 3 years ago
Gregory Rudolph 81f7d3ccdc
Cleanup admin check 3 years ago
Gregory Rudolph 77b6e9c780
Only show commands available to the user 3 years ago
Gregory Rudolph 06da80de97
Add command to list commands 3 years ago
Gregory Rudolph 51f9f10b17
Bump as a bool instead of time 3 years ago
Gregory Rudolph 17141a8ea4
Commands for everyone 3 years ago
Gregory Rudolph af81e9ddc8
Commands for everyone 3 years ago
Gregory Rudolph 664daa311a
Start new admin interactions at 1 not 0 3 years ago
Gregory Rudolph 43ceafbea0
Cleanup Status output 3 years ago
Gregory Rudolph 575a07dd01
Added help for commands 3 years ago
Gregory Rudolph 90383a3f8b
Cleaning and reorganizing 3 years ago
Gregory Rudolph d8bbdb57e1
Restructure using structs 3 years ago
Gregory Rudolph 3ab280a056
Restructure using structs 3 years ago
Gregory Rudolph cfb3c5b57b
Add bumpset 3 years ago
Gregory Rudolph fbd1f1f8ca
Fix reboot timer 3 years ago
Gregory Rudolph f82db4166a
More cleaning and restructuring 3 years ago
Gregory Rudolph 37fe9fcaba
Cleanup utilities 3 years ago
Gregory Rudolph d1a42a2de2
Remove mention on Status and add startup bump check 3 years ago
Gregory Rudolph 0310008bce
Allow usernames pt 2 3 years ago
Gregory Rudolph 4caeade581
Allow usernames 3 years ago
Gregory Rudolph 75a063efe3
typo 3 years ago
Gregory Rudolph 5e6fe8fc52
Typo correction 3 years ago
Gregory Rudolph 6b92467d1f
Updated status and added verification retrieval 3 years ago
Gregory Rudolph 6d0a99c1a9
NO HEIC 3 years ago
Gregory Rudolph 12d07ce1ff
Less delete of Intro msg 3 years ago
Gregory Rudolph 1b3980821a
Update map to be the right way around 3 years ago
Gregory Rudolph 67e0167133
Fix logic on removing intro msg 3 years ago
Gregory Rudolph 0813bcdffa
Remove macOS files 3 years ago
Gregory Rudolph d33fd7aacd
Convert to go module, and add logic to delete intro message after member leave or probation expire. 3 years ago
Gregory Rudolph 961f2f32b0
Add some CSS to status page 3 years ago
Gregory Rudolph 3ddd84ae16
Add Status page. 3 years ago
Gregory Rudolph 86c95bb012
Add Config API Endpoint 3 years ago
Gregory Rudolph 41e39eb118
Remove more React 3 years ago
Gregory Rudolph 22ccd9fd84
Remove more React 3 years ago
Gregory Rudolph efb6676c0a
Can't update a constant 3 years ago
Gregory Rudolph 33a3270a49
Can't update a constant 3 years ago
Gregory Rudolph aeb8b4fb52
Issues with local files 3 years ago
Gregory Rudolph 988acb4a90
Update to use static site 3 years ago
Gregory Rudolph 950cab1baf
Remove react 3 years ago
Gregory Rudolph ee4f6cdb3d
Where'd my JS go 3 years ago
Gregory Rudolph a205581a44
Working tests 3 years ago
Gregory Rudolph ed59ac11b8
Working tests 3 years ago
Gregory Rudolph 5770ce7fb9
Working tests 3 years ago
Gregory Rudolph 55c6d29ff7
Working tests 3 years ago
Gregory Rudolph aa9eaf11f7
Working tests 3 years ago
Gregory Rudolph 8143f518f5
Added Pending to top of window 3 years ago
Gregory Rudolph d13da924b7
Bypass auth 3 years ago
Gregory Rudolph 00440c97ef
Updates with React and modal attempt 3 years ago
Gregory Rudolph a1eadc1857
Updates with React and modal attempt 3 years ago
Gregory Rudolph 79aa40c476
Updates with React and modal attempt 3 years ago
Gregory Rudolph 7c7857c1d7
Redo with React 3 years ago
Gregory Rudolph 4e96ff10bf
Redo with React 3 years ago
Gregory Rudolph d6013c90b2
Redo with React 3 years ago
Gregory Rudolph 101189d71b
Redo with React 3 years ago
Gregory Rudolph e4b0782d88
Redo with React 3 years ago
Gregory Rudolph 5bfdbabf33
Redo with React 3 years ago
Gregory Rudolph aeafbf31bd
Redo with React 3 years ago
Gregory Rudolph 11dfb49028
Redo with React 3 years ago
Gregory Rudolph edf12e2461
Redo with React 3 years ago
Gregory Rudolph 87a08b0cd5
Redo with React 3 years ago
Gregory Rudolph f3de2e2857
Add css 3 years ago
Gregory Rudolph 37837a508c
Add css 3 years ago
Gregory Rudolph 86b499a5da
Add css 3 years ago
Gregory Rudolph 44838b23f5
Add css 3 years ago
Gregory Rudolph a33940c6f2
Add css 3 years ago
Gregory Rudolph 83de997220
Add css 3 years ago
Gregory Rudolph a5354ddd6e
Show list of verifications 3 years ago
Gregory Rudolph 0b5df2733d
Don't show cookie policy after login 3 years ago
Gregory Rudolph 75db9af48c
Don't show cookie policy after login 3 years ago
Gregory Rudolph 13ebaee9fe
Don't show cookie policy after login 3 years ago
Gregory Rudolph 2743ba1bdb
Absolute path to script 3 years ago
Gregory Rudolph a441fb5e63
Move react container 3 years ago
Gregory Rudolph c8d3f7b0b5
Redo npm 3 years ago
Gregory Rudolph c1e261c85d
Redo npm 3 years ago
Gregory Rudolph 938eac8348
Redo npm 3 years ago
Gregory Rudolph 04dbd66c88
Redo npm 3 years ago
Gregory Rudolph 5e70236ba1
Redo npm 3 years ago
Gregory Rudolph 04097a9d3e
Removed node 3 years ago
Gregory Rudolph 68c821b71b
Removed Node modules 3 years ago
Gregory Rudolph 8a6e029f83
Add react 3 years ago
Gregory Rudolph 2a36f20a4c
Move verification viewing to logged in only 3 years ago
Gregory Rudolph b5e72a88b9
Use /verification staticly 3 years ago
Gregory Rudolph 77ae7f149a
Add API Doc 3 years ago
Gregory Rudolph 9803136783
Add user info endpoint 3 years ago
Gregory Rudolph cc63f82427
Update drone 3 years ago
Gregory Rudolph 6215426a2d
API Endpoints for Verifications and Pending. 3 years ago
Gregory Rudolph 7a41ebb48d
API Endpoints for Verifications and Pending. 3 years ago
Gregory Rudolph 62246e9c23
API Endpoints for Verifications and Pending. 3 years ago
Gregory Rudolph 011dc1362a
API Endpoints for Verifications and Pending. 3 years ago
Gregory Rudolph 277fda3a6d
API Endpoints for Verifications and Pending. 3 years ago
Gregory Rudolph d1122bab29
Admins only get passwords. 3 years ago
Gregory Rudolph 46f9df6dbd
Authy 3 years ago
Gregory Rudolph 3bba74c076
Authy 3 years ago
Gregory Rudolph 8460b10e9f
Authy 3 years ago
Gregory Rudolph f31215027a
Authy 3 years ago
Gregory Rudolph dc1b10afca
Authy 3 years ago
Gregory Rudolph 8236715042
Authy 3 years ago
Gregory Rudolph 0a2dc22837
Authy 3 years ago
Gregory Rudolph b172b1b57a
Authy 3 years ago
Gregory Rudolph 957d34f6c4
Authy 3 years ago
Gregory Rudolph b2832bcef0
Authy 3 years ago
Gregory Rudolph dd1f280495
Authy 3 years ago
Gregory Rudolph c942df7cd5
Authy 3 years ago
Gregory Rudolph 383a4ba691
Authy 3 years ago
Gregory Rudolph 19d9eaae2d
Authy 3 years ago
Gregory Rudolph c36cd98623
Authy 3 years ago
Gregory Rudolph f23dba9454
Authy 3 years ago
Gregory Rudolph 9bdf48602d
Authy 3 years ago
Gregory Rudolph 5d0492695a
Authy 3 years ago
Gregory Rudolph 970d003e2f
Authy 3 years ago
Gregory Rudolph 46963ab2d6
Authy 3 years ago
Gregory Rudolph 1d7d2b1835
Authy 3 years ago
Gregory Rudolph 67c48eb260
PanicSafe() 3 years ago
Gregory Rudolph c3f2fa00de
Updates to ensure redirect happens from reqPass 3 years ago
Gregory Rudolph 78c368c832
typo in reqPass 3 years ago
Gregory Rudolph c03f27249b
Remove verifications from list when user leaves 3 years ago
Gregory Rudolph 7afa4bdb8b
Updated ASL message 3 years ago
Gregory Rudolph dece68594d
Get userID from username 3 years ago
Gregory Rudolph 1c4aba2780
Get userID from username 3 years ago
Gregory Rudolph 0a575c395a
Enable thanos 3 years ago
Gregory Rudolph f8e7051e79
Added skeleton website from kbauth 3 years ago
  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. 276
      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. 38
      types.go

2
.drone.yml

@ -8,6 +8,8 @@ steps: @@ -8,6 +8,8 @@ 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,5 +1,10 @@ @@ -1,5 +1,10 @@
build/
.DS_Store
public/
disgord-thanos
disgord-Thanos
config.json
start.sh
dev.html
verifications/*
node_modules/

BIN
ThanosAPI.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

196
auth.go

@ -0,0 +1,196 @@ @@ -0,0 +1,196 @@
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

@ -0,0 +1,307 @@ @@ -0,0 +1,307 @@
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,66 +3,19 @@ package main @@ -3,66 +3,19 @@ package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"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, _ := ioutil.ReadFile(configFile)
confFile, _ := os.ReadFile(configFile)
err := json.Unmarshal([]byte(confFile), &c)
if err != nil {
log.LogErrorType(err)
@ -98,12 +51,45 @@ func saveConfig() { @@ -98,12 +51,45 @@ func saveConfig() {
if err != nil {
log.LogErrorType(err)
}
err = ioutil.WriteFile(configFile, file, 0600)
err = os.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
@ -111,12 +97,10 @@ func bumpTimer(s *discordgo.Session) { @@ -111,12 +97,10 @@ func bumpTimer(s *discordgo.Session) {
bump = false
config.BumpTime = time.Now()
time.Sleep(2 * time.Hour)
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.")
s.ChannelMessageSend(config.AdminChannel, "/bump is ready.")
bump = true
}
func purgeTimer(s *discordgo.Session) {
for {
runPurge(s)
@ -139,22 +123,28 @@ func (v Verification) prettyPrint() string { @@ -139,22 +123,28 @@ func (v Verification) prettyPrint() string {
return ret
}
func userFromID(s *discordgo.Session, i string) discordgo.User {
u, err := s.GuildMember(config.GuildID, i)
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.LogErrorType(err)
return discordgo.User{}
log.LogError("Unable to download 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
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)
}
}

125
discordEvents.go

@ -0,0 +1,125 @@ @@ -0,0 +1,125 @@
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

@ -0,0 +1,162 @@ @@ -0,0 +1,162 @@
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

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
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

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
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=

276
main.go

@ -3,13 +3,9 @@ package main @@ -3,13 +3,9 @@ package main
import (
"flag"
"fmt"
"io"
"math/rand"
"net/http"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"
@ -19,18 +15,20 @@ import ( @@ -19,18 +15,20 @@ import (
var (
startupTime time.Time
setupToken = fmt.Sprintf("!setup %+v", rand.Intn(9999)+1000)
rebootToken = fmt.Sprintf("!reboot %+v", rand.Intn(9999)+1000)
setupToken = fmt.Sprintf("%+v", rand.Intn(9999)+1000)
rebootToken = fmt.Sprintf("%+v", rand.Intn(9999)+1000)
bump = true
config Config
log = loggy.NewLogger(config.LogOpts)
lastActiveChan string
lastActiveTime time.Time
token string
configFile string
setupMsg string
dg *discordgo.Session
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() {
@ -40,6 +38,7 @@ func init() { @@ -40,6 +38,7 @@ func init() {
}
func main() {
go runWeb()
defer log.PanicSafe()
if configFile == "" {
configFile = "config.json"
@ -49,13 +48,13 @@ func main() { @@ -49,13 +48,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)
dg, err := discordgo.New("Bot " + token)
var err error
dg, err = discordgo.New("Bot " + token)
if err != nil {
log.LogErrorType(err)
log.LogPanic("Unable to create bot using token.")
@ -65,6 +64,7 @@ func main() { @@ -65,6 +64,7 @@ func main() {
dg.AddHandler(guildMemberRemove)
dg.AddHandler(guildMemberAdd)
dg.AddHandler(guildMemberBanned)
go setupCommands()
dg.AddHandler(messageCreate)
dg.AddHandler(readReaction)
dg.AddHandler(guildMemberUpdate)
@ -78,8 +78,9 @@ func main() { @@ -78,8 +78,9 @@ 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, os.Kill)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
<-sc
saveConfig()
dg.Close()
@ -99,6 +100,7 @@ func runPurge(s *discordgo.Session) { @@ -99,6 +100,7 @@ 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 {
@ -120,7 +122,7 @@ func runPurge(s *discordgo.Session) { @@ -120,7 +122,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 in %+v. You may reply to this message for verification instructions.", time.Until(v.Add(1*time.Hour))))
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()))
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))
@ -146,242 +148,74 @@ func runPurge(s *discordgo.Session) { @@ -146,242 +148,74 @@ func runPurge(s *discordgo.Session) {
s.ChannelMessageDelete(config.MonitorChann, message.ID)
}
}
saveConfig()
}
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)
}
}
go cleanSocials(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)
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)
}
}
} else {
delete(config.Probations, uid)
}
}(channel, s)
}
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)
st, _ := s.UserChannelCreate(u.ID)
log.LogDebug("Creating PM channel")
st, err := s.UserChannelCreate(u.ID)
if err != nil {
log.LogErrorType(err)
}
log.LogDebug("Sending acceptance message!")
s.ChannelMessageSend(st.ID, "Your verification has been accepted, welcome!")
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()))
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
}
func rejectVerification(s *discordgo.Session, u discordgo.User) {
defer log.PanicSafe()
st, _ := s.UserChannelCreate(u.ID)
st, err := s.UserChannelCreate(u.ID)
if err != nil {
log.LogErrorType(err)
}
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 %+v", time.Until(time.Now().Add(1*time.Hour))))
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()))
}
config.Unverified[u.ID] = time.Now()
}
func requestAge(s *discordgo.Session, u discordgo.User) {
defer log.PanicSafe()
st, _ := s.UserChannelCreate(u.ID)
s.ChannelMessageSend(st.ID, "What is your ASL? (Age/Sex/Language)")
}
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)
st, err := s.UserChannelCreate(u.ID)
if err != nil {
log.LogError("Unable to store verification %s-%s-%s", v.UserID, v.Username, fileName)
log.LogErrorType(err)
}
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 messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
func requestReupload(s *discordgo.Session, u discordgo.User) {
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()
}
st, err := s.UserChannelCreate(u.ID)
if err != nil {
log.LogErrorType(err)
}
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

@ -0,0 +1,238 @@ @@ -0,0 +1,238 @@
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

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<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

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<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

@ -0,0 +1,149 @@ @@ -0,0 +1,149 @@
// 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

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
<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

@ -0,0 +1,210 @@ @@ -0,0 +1,210 @@
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

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
<!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

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
<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

@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
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

@ -0,0 +1,91 @@ @@ -0,0 +1,91 @@
<!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

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
<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

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
<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

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
<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

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
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

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
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)
}
}

38
types.go

@ -1,7 +1,28 @@ @@ -1,7 +1,28 @@
package main
import "time"
import "github.com/rudi9719/loggy"
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
}
// Config struct used for bot
type Config struct {
@ -11,14 +32,17 @@ type Config struct { @@ -11,14 +32,17 @@ 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
LogOpts loggy.LogOpts
WhitelistURLs []string
}
// Verification struct used for storing and logging
@ -31,3 +55,11 @@ type Verification struct { @@ -31,3 +55,11 @@ 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