Compare commits
25 Commits
6e1c27ca27
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
87939c32ef
|
|||
|
fcc806eb77
|
|||
|
dd433f44ad
|
|||
|
99e6f7066b
|
|||
|
139d378d1a
|
|||
|
bddd283127
|
|||
|
31ca1cb243
|
|||
|
5eb7920ed2
|
|||
|
f79b842e17
|
|||
|
bd493223ca
|
|||
|
bdbc2a1196
|
|||
|
5ae7a96c3e
|
|||
|
f5c59af2b4
|
|||
|
8af3e9656d
|
|||
|
c2646ad280
|
|||
|
a93d4a727b
|
|||
|
706c2b516b
|
|||
|
efbe429824
|
|||
|
d68f027f42
|
|||
|
59926b6b9b
|
|||
|
dc0bf186ae
|
|||
|
d24ff86d68
|
|||
|
27d5cf3a62
|
|||
|
0c3f4ce74a
|
|||
|
6d42e0fa54
|
24
commands.go
24
commands.go
@ -92,6 +92,14 @@ func setupCommands() {
|
||||
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 {
|
||||
@ -281,3 +289,19 @@ func Status(b BotCommand) bool {
|
||||
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
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -16,7 +15,7 @@ import (
|
||||
|
||||
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)
|
||||
@ -52,7 +51,7 @@ 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)
|
||||
}
|
||||
@ -87,7 +86,7 @@ func activeInteraction(s *discordgo.Session, m string) {
|
||||
|
||||
func rebootBump() {
|
||||
time.Sleep(time.Until(config.BumpTime.Add(2 * time.Hour)))
|
||||
dg.ChannelMessageSend(config.AdminChannel, "!d bump is ready")
|
||||
dg.ChannelMessageSend(config.AdminChannel, "/bump is ready")
|
||||
|
||||
}
|
||||
|
||||
@ -98,7 +97,7 @@ func bumpTimer(s *discordgo.Session) {
|
||||
bump = false
|
||||
config.BumpTime = time.Now()
|
||||
time.Sleep(2 * time.Hour)
|
||||
s.ChannelMessageSend(config.AdminChannel, "!d bump is ready.")
|
||||
s.ChannelMessageSend(config.AdminChannel, "/bump is ready.")
|
||||
bump = true
|
||||
}
|
||||
|
||||
|
||||
@ -36,8 +36,6 @@ func guildMemberAdd(s *discordgo.Session, m *discordgo.GuildMemberAdd) {
|
||||
config.Probations[m.User.ID] = time.Now()
|
||||
log.LogDebug("Giving user monitor role")
|
||||
s.GuildMemberRoleAdd(config.GuildID, m.User.ID, config.MonitorRole)
|
||||
log.LogDebug("Sending Monitored message")
|
||||
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()))
|
||||
log.LogDebug("Calling saveConfig")
|
||||
saveConfig()
|
||||
}
|
||||
@ -112,8 +110,12 @@ func readReaction(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
|
||||
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)
|
||||
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
|
||||
|
||||
@ -24,13 +24,8 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
Parts: strings.Split(m.Content, " ")[2:],
|
||||
})
|
||||
|
||||
}
|
||||
if strings.Contains(m.Embeds[0].Description, "Bump done!") {
|
||||
log.LogDebug("Finding string %+v", m.Embeds[0].Description)
|
||||
re := regexp.MustCompile("<@(.*)>")
|
||||
match := re.FindStringSubmatch(m.Embeds[0].Description)
|
||||
activeInteraction(s, match[1])
|
||||
config.LastBumper = match[1]
|
||||
} else {
|
||||
go bumpTimer(s)
|
||||
}
|
||||
return
|
||||
|
||||
@ -38,10 +33,12 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
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)
|
||||
}
|
||||
@ -59,25 +56,36 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
activeInteraction(s, m.Author.ID)
|
||||
}
|
||||
}
|
||||
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
|
||||
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))
|
||||
}
|
||||
}
|
||||
go bumpTimer(s)
|
||||
return
|
||||
}
|
||||
parts := strings.Split(m.Content, " ")
|
||||
if strings.Contains(m.Content, s.State.User.ID) {
|
||||
b := BotCommand{
|
||||
Session: s,
|
||||
Message: m,
|
||||
Parts: strings.Split(m.Content, " ")[2:],
|
||||
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(m.Content, keyword) {
|
||||
if strings.Contains(parts[1], keyword) {
|
||||
log.LogDebug("%+v found!", keyword)
|
||||
b.Command = keyword
|
||||
if !cmd.RequiresAdmin {
|
||||
@ -110,7 +118,7 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
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.")
|
||||
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)
|
||||
@ -147,6 +155,7 @@ func handlePM(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
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, "⛔")
|
||||
|
||||
12
go.mod
12
go.mod
@ -1,10 +1,18 @@
|
||||
module git.nightmare.haus/rudi/disgord-thanos
|
||||
|
||||
go 1.15
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.23.2
|
||||
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
|
||||
)
|
||||
|
||||
24
go.sum
24
go.sum
@ -1,16 +1,26 @@
|
||||
github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4=
|
||||
github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
||||
github.com/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.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
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-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
samhofi.us/x/keybase v0.0.0-20200129212102-e05e93be9f3f h1:MHSEiuiRFrFi7BTw46lC22PMk3Fit8IvVRM4xANTt20=
|
||||
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=
|
||||
|
||||
12
main.go
12
main.go
@ -206,6 +206,16 @@ func requestAge(s *discordgo.Session, u discordgo.User) {
|
||||
if err != nil {
|
||||
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 biology and in the context of pornographic images more important.")
|
||||
s.ChannelMessageSend(st.ID, "What is your ASL? (Age/Sex/Language) Please note, this is NOT requesting your gender, but your biological sex. Gender is a social construct, sex is measurable and in the context of pornographic images more important.")
|
||||
|
||||
}
|
||||
|
||||
func requestReupload(s *discordgo.Session, u discordgo.User) {
|
||||
defer log.PanicSafe()
|
||||
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)
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -20,7 +19,7 @@ var (
|
||||
|
||||
func topWrapper(r *http.Request) string {
|
||||
defer log.PanicSafe()
|
||||
headerTemplate, err := ioutil.ReadFile("./static/header.tpl")
|
||||
headerTemplate, err := os.ReadFile("./static/header.tpl")
|
||||
if err != nil {
|
||||
log.LogError(fmt.Sprintf("Unable to open header template: ```%+v```", err))
|
||||
return ""
|
||||
@ -37,7 +36,7 @@ func topWrapper(r *http.Request) string {
|
||||
|
||||
func bodyWrapper(r *http.Request, template string) string {
|
||||
defer log.PanicSafe()
|
||||
bodyTemplate, err := ioutil.ReadFile(fmt.Sprintf("./static/%+v.tpl", template))
|
||||
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")
|
||||
@ -58,7 +57,7 @@ func greetUser(w http.ResponseWriter, r *http.Request) {
|
||||
loggedIn, _ := detectUser(r, "Homepage")
|
||||
|
||||
if loggedIn {
|
||||
bodyTemplate, _ := ioutil.ReadFile("./static/index.html")
|
||||
bodyTemplate, _ := os.ReadFile("./static/index.html")
|
||||
fmt.Fprint(w, string(bodyTemplate))
|
||||
} else {
|
||||
fmt.Fprint(w, pageBuilder(r, "home"))
|
||||
@ -103,7 +102,7 @@ func notFoundPage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
func card(title string, content string, footer string) string {
|
||||
defer log.PanicSafe()
|
||||
cardTemplate, err := ioutil.ReadFile("./static/card.tpl")
|
||||
cardTemplate, err := os.ReadFile("./static/card.tpl")
|
||||
if err != nil {
|
||||
log.LogError("Unable to open card template")
|
||||
return ""
|
||||
|
||||
51
tools/listen.go
Normal file
51
tools/listen.go
Normal file
@ -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))
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package tools
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
Reference in New Issue
Block a user