├── .gitattributes ├── go.mod ├── example_config.json ├── .gitignore ├── dev └── main.go ├── LICENSE ├── go.sum ├── cordkit_test.go ├── README.md └── cordkit.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pure-nomad/cordkit 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/bwmarrin/discordgo v0.28.1 // indirect 7 | github.com/gorilla/websocket v1.4.2 // indirect 8 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect 9 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /example_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bot_token": "xxx", 3 | "guild_id": "1358227184719757312", 4 | "active_category_id": "1358230105838850239", 5 | "dead_category_id": "1358230135295180910", 6 | "transcript_category_id": "1358501105906090117", 7 | "active_channel_prefix": "active_connection", 8 | "dead_channel_prefix": "dead_connection", 9 | "logging_enabled": true, 10 | "custom_commands_enabled": true 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | -------------------------------------------------------------------------------- /dev/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | dc "github.com/bwmarrin/discordgo" 8 | "github.com/pure-nomad/cordkit" 9 | ) 10 | 11 | func main() { 12 | bot, err := cordkit.NewBot("../client.json") 13 | if err != nil { 14 | log.Fatalf("Error creating bot: %v", err) 15 | } 16 | 17 | bot.Commands = append(bot.Commands, cordkit.Command{ 18 | Name: "ping", 19 | Description: "Responds with pong", 20 | Action: func(b *cordkit.Bot, i *dc.InteractionCreate) { 21 | b.SendMsg(i.ChannelID, "Pong!") 22 | }, 23 | }) 24 | 25 | defer bot.Stop() 26 | bot.Start() 27 | log.Println("Bot is online") 28 | 29 | conn := bot.HandleConnection("fakeconn1") 30 | time.Sleep(time.Second * 1) 31 | bot.KillConnection(conn) 32 | 33 | select {} 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Charlie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= 2 | github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= 3 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 4 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= 6 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 7 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 8 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= 9 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 10 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 11 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 12 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 13 | -------------------------------------------------------------------------------- /cordkit_test.go: -------------------------------------------------------------------------------- 1 | package cordkit 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "sync" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestConcurrentSessions(t *testing.T) { 12 | // Create a test bot with mock configuration 13 | bot, err := NewBot("client.json") 14 | if err != nil { 15 | t.Fatalf("Failed to create bot: %v", err) 16 | } 17 | 18 | // Start the bot 19 | bot.Start() 20 | defer bot.Stop() 21 | 22 | // Number of concurrent sessions 23 | numSessions := 10 24 | var wg sync.WaitGroup 25 | wg.Add(numSessions) 26 | 27 | // Create a channel to track active connections 28 | connections := make(chan *Connection, numSessions) 29 | 30 | // Function to simulate a session 31 | simulateSession := func(id int) { 32 | defer wg.Done() 33 | 34 | // Create a unique session ID 35 | sessionID := fmt.Sprintf("testsession-%d", id) 36 | 37 | // Handle the connection 38 | conn := bot.HandleConnection(sessionID) 39 | connections <- conn 40 | 41 | // Simulate random activity duration 42 | activityDuration := time.Duration(rand.Intn(5)+1) * time.Second 43 | time.Sleep(activityDuration) 44 | 45 | // Kill the connection 46 | bot.KillConnection(conn) 47 | } 48 | 49 | // Launch concurrent sessions 50 | for i := 0; i < numSessions; i++ { 51 | go simulateSession(i) 52 | } 53 | 54 | // Wait for all sessions to complete 55 | wg.Wait() 56 | close(connections) 57 | 58 | // Verify all connections were handled 59 | connCount := 0 60 | for conn := range connections { 61 | if conn == nil { 62 | t.Error("Received nil connection") 63 | } 64 | connCount++ 65 | } 66 | 67 | if connCount != numSessions { 68 | t.Errorf("Expected %d connections, got %d", numSessions, connCount) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CordKit 2 | 3 | CordKit connects your C2 infrastructure with Discord, delivering real-time notifications, session management, and logging for effortless control from anywhere. 4 | 5 | It includes dynamic slash command management that lets you easily extend the bot with your own custom commands. 6 | 7 | ## Features 8 | 9 | ### Custom Commands 10 | - Dynamic slash command registration with a built in management system. 11 | - Default Commands: 12 | - `/start` - Enable Bot 13 | - `/stop` - Disable Bot 14 | - `/purge` - Clean up dead session channels 15 | - `/nuke` - Complete server cleanup and stop the server process 16 | 17 | ### Session Management 18 | - Organized session channels (active, dead) 19 | - Configurable naming conventions! 20 | 21 | ### Logging 22 | - Automatic transcript archiving for logging your operations. 23 | - Info & Error Logging 24 | 25 | ## Configuration 26 | 27 | CordKit requires the following configuration parameters: 28 | - Discord Bot Token 29 | - The Guild ID of your Server 30 | - Category IDs (Active Connections, Dead Connections, Transcripts) 31 | - Channel Prefixes (Active, Dead) 32 | 33 | ## TODO 34 | 35 | 1. Integrate with Stellarlink ✅ 36 | 2. Make server setup script for a quick setup 37 | 3. Integrate with larger projects: 38 | - Sliver 39 | - Evilginx 40 | - Ligolo 41 | - Tracescout 42 | 43 | ## Usage 44 | 45 | ```go 46 | // Basic Setup 47 | clientSettings := cordkit.NewClient( 48 | "BOT_TOKEN", // Discord Bot Token 49 | "GUILD_ID", // Discord Server ID 50 | "ACTIVE_CATEGORY_ID", // ID of your active category 51 | "DEAD_CATEGORY_ID", // ID of your dead category 52 | "TRANSCRIPT_CATEGORY_ID", // ID of your transcript category 53 | "active", // Prefix for active connections 54 | "dead", // Prefix for dead connections 55 | ) 56 | 57 | // Start bot with client settings & logging enabled 58 | bot := cordkit.NewBot(clientSettings, true) 59 | bot.CustomCommands = true 60 | 61 | // Add a custom command with options 62 | bot.Commands = append(bot.Commands, cordkit.Command{ 63 | Name: "ping", 64 | Description: "Responds with pong", 65 | Options: []*dc.ApplicationCommandOption{ 66 | { 67 | Type: dc.ApplicationCommandOptionString, 68 | Name: "message", 69 | Description: "Custom message to reply with", 70 | Required: false, 71 | }, 72 | }, 73 | Action: func(b *cordkit.Bot, i *dc.InteractionCreate) { 74 | opts := i.ApplicationCommandData().Options 75 | if len(opts) > 0 { 76 | msg := opts[0].StringValue() 77 | b.SendMsg(i.ChannelID, msg) 78 | } else { 79 | b.SendMsg(i.ChannelID, "Pong!") 80 | } 81 | }, 82 | }) 83 | 84 | defer bot.Stop() 85 | bot.Start() 86 | 87 | // Session Management 88 | conn := bot.HandleConnection("connection_name") 89 | time.Sleep(time.Second * 1) 90 | bot.KillConnection(conn) 91 | ``` 92 | -------------------------------------------------------------------------------- /cordkit.go: -------------------------------------------------------------------------------- 1 | package cordkit 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | "sync" 9 | "time" 10 | 11 | dc "github.com/bwmarrin/discordgo" 12 | ) 13 | 14 | type Config struct { 15 | BotToken string `json:"bot_token"` 16 | GuildID string `json:"guild_id"` 17 | ActiveCategoryID string `json:"active_category_id"` 18 | DeadCategoryID string `json:"dead_category_id"` 19 | TranscriptCategoryID string `json:"transcript_category_id"` 20 | ActiveChannelPrefix string `json:"active_channel_prefix"` 21 | DeadChannelPrefix string `json:"dead_channel_prefix"` 22 | LoggingEnabled bool `json:"logging_enabled"` 23 | CustomCommandsEnabled bool `json:"custom_commands_enabled"` 24 | } 25 | 26 | type Bot struct { 27 | Client *Client 28 | Running bool 29 | Logging bool 30 | LogChannelID string 31 | CustomCommands bool 32 | Commands []Command 33 | connMutex sync.Mutex 34 | runningMutex sync.RWMutex 35 | connections map[string]*Connection 36 | } 37 | 38 | type Command struct { 39 | Name string 40 | Description string 41 | Options []*dc.ApplicationCommandOption 42 | Action func(*Bot, *dc.InteractionCreate) 43 | } 44 | 45 | func NewBot(configPath string) (*Bot, error) { 46 | configData, err := os.ReadFile(configPath) 47 | if err != nil { 48 | return nil, fmt.Errorf("error reading config file: %w", err) 49 | } 50 | 51 | var config Config 52 | if err := json.Unmarshal(configData, &config); err != nil { 53 | return nil, fmt.Errorf("error parsing config file: %w", err) 54 | } 55 | 56 | client := NewClient( 57 | config.BotToken, 58 | config.GuildID, 59 | config.ActiveCategoryID, 60 | config.DeadCategoryID, 61 | config.TranscriptCategoryID, 62 | config.ActiveChannelPrefix, 63 | config.DeadChannelPrefix, 64 | ) 65 | 66 | return &Bot{ 67 | Client: client, 68 | Running: true, 69 | Logging: config.LoggingEnabled, 70 | CustomCommands: config.CustomCommandsEnabled, 71 | Commands: []Command{}, 72 | connections: make(map[string]*Connection), 73 | }, nil 74 | } 75 | 76 | func (b *Bot) Start() { 77 | b.runningMutex.Lock() 78 | b.Running = true 79 | b.runningMutex.Unlock() 80 | 81 | sess, err := dc.New("Bot " + b.Client.botToken) 82 | if err != nil { 83 | panic(err) 84 | } 85 | 86 | b.Client.botRef = sess 87 | b.Client.botRef.AddHandler(b.handleSlash) 88 | 89 | err = b.Client.botRef.Open() 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | if b.Logging { 95 | 96 | transcriptName := fmt.Sprintf("transcript-%05d", time.Now().UnixNano()%100000) 97 | logChannel, err := b.Client.botRef.GuildChannelCreateComplex(b.Client.guildID, dc.GuildChannelCreateData{ 98 | Name: transcriptName, 99 | ParentID: b.Client.transcriptCategoryID, 100 | }) 101 | 102 | if err != nil { 103 | panic(err) 104 | } 105 | 106 | b.LogChannelID = logChannel.ID 107 | log.Println("Created logging channel with ID: ", logChannel.ID) 108 | 109 | } 110 | 111 | now := time.Now() 112 | botStartMSG := fmt.Sprintf("Bot Started at %v", now.Format("03:04PM")) 113 | b.SendInfoLog(botStartMSG) 114 | 115 | cmds := []*dc.ApplicationCommand{ 116 | {Name: "start", Description: "Enable bot logic"}, 117 | {Name: "stop", Description: "Disable bot logic"}, 118 | {Name: "purge", Description: "Delete all channels in the dead category"}, 119 | {Name: "nuke", Description: "Delete all channels and stop the bot"}, 120 | } 121 | 122 | if b.CustomCommands { 123 | for _, cmd := range b.Commands { 124 | cmds = append(cmds, &dc.ApplicationCommand{ 125 | Name: cmd.Name, 126 | Description: cmd.Description, 127 | Options: cmd.Options, 128 | }) 129 | } 130 | } 131 | 132 | _, err = b.Client.botRef.ApplicationCommandBulkOverwrite(sess.State.User.ID, b.Client.guildID, cmds) 133 | if err != nil { 134 | panic(err) 135 | } 136 | } 137 | 138 | func (b *Bot) IsRunning() bool { 139 | b.runningMutex.RLock() 140 | defer b.runningMutex.RUnlock() 141 | return b.Running 142 | } 143 | 144 | func (b *Bot) Stop() error { 145 | b.runningMutex.Lock() 146 | b.Running = false 147 | b.runningMutex.Unlock() 148 | 149 | now := time.Now() 150 | botEndMSG := fmt.Sprintf("Bot stopped at %v", now.Format("03:04PM")) 151 | if b.Logging { 152 | b.SendErrorLog(botEndMSG) 153 | } 154 | return b.Client.botRef.Close() 155 | } 156 | 157 | func (b *Bot) SendInfoLog(content string) (*dc.Message, error) { 158 | msgContent := fmt.Sprintf("[✅] %s", content) 159 | return b.SendMsg(b.LogChannelID, msgContent) 160 | } 161 | 162 | func (b *Bot) SendErrorLog(content string) (*dc.Message, error) { 163 | msgContent := fmt.Sprintf("[❌] %s", content) 164 | return b.SendMsg(b.LogChannelID, msgContent) 165 | } 166 | 167 | func (b *Bot) handleSlash(s *dc.Session, i *dc.InteractionCreate) { 168 | if i.Type != dc.InteractionApplicationCommand { 169 | return 170 | } 171 | 172 | if !b.IsRunning() { 173 | b.BotRespond(i, "Bot is currently stopped.") 174 | return 175 | } 176 | 177 | switch i.ApplicationCommandData().Name { 178 | case "start": 179 | if b.Running { 180 | b.BotRespond(i, "Already running.") 181 | return 182 | } 183 | b.Running = true 184 | b.BotRespond(i, "Started.") 185 | 186 | case "stop": 187 | if !b.Running { 188 | b.BotRespond(i, "Already stopped.") 189 | return 190 | } 191 | b.Running = false 192 | b.BotRespond(i, "Stopped.") 193 | 194 | case "purge": 195 | channels, err := s.GuildChannels(b.Client.guildID) 196 | if err != nil { 197 | b.BotRespond(i, "Error fetching channels: "+err.Error()) 198 | return 199 | } 200 | 201 | deletedCount := 0 202 | for _, channel := range channels { 203 | if channel.ParentID == b.Client.deadCategoryID { 204 | _, err := b.DeleteChannel(channel.ID) 205 | if err != nil { 206 | b.BotRespond(i, fmt.Sprintf("Error deleting channel %s: %s", channel.Name, err.Error())) 207 | return 208 | } 209 | deletedCount++ 210 | } 211 | } 212 | 213 | msg := fmt.Sprintf("Purged %d channels", deletedCount) 214 | b.BotRespond(i, msg) 215 | if b.Logging { 216 | b.SendInfoLog(msg) 217 | } 218 | 219 | case "nuke": 220 | channels, err := s.GuildChannels(b.Client.guildID) 221 | if err != nil { 222 | b.BotRespond(i, "Error fetching channels: "+err.Error()) 223 | return 224 | } 225 | 226 | deletedCount := 0 227 | for _, channel := range channels { 228 | if channel.Type == dc.ChannelTypeGuildCategory { 229 | continue 230 | } 231 | 232 | _, err := b.DeleteChannel(channel.ID) 233 | if err != nil { 234 | b.BotRespond(i, fmt.Sprintf("Error deleting channel %s: %s", channel.Name, err.Error())) 235 | return 236 | } 237 | deletedCount++ 238 | } 239 | 240 | b.Running = false 241 | if err := b.Client.botRef.Close(); err != nil { 242 | b.BotRespond(i, "Error stopping bot: "+err.Error()) 243 | return 244 | } 245 | 246 | os.Exit(0) 247 | 248 | default: 249 | if b.CustomCommands { 250 | for _, cmd := range b.Commands { 251 | if cmd.Name == i.ApplicationCommandData().Name { 252 | cmd.Action(b, i) 253 | return 254 | } 255 | } 256 | } 257 | } 258 | } 259 | 260 | func (b *Bot) BotRespond(i *dc.InteractionCreate, msg string) { 261 | b.Client.botRef.InteractionRespond(i.Interaction, &dc.InteractionResponse{ 262 | Type: dc.InteractionResponseChannelMessageWithSource, 263 | Data: &dc.InteractionResponseData{Content: msg}, 264 | }) 265 | } 266 | 267 | type Connection struct { 268 | id string 269 | channelID string 270 | createdAt time.Time 271 | lastSeen time.Time 272 | status string 273 | // Transcript []string 274 | // metadata map[string]string 275 | } 276 | 277 | func (b *Bot) HandleConnection(id string) *Connection { 278 | b.connMutex.Lock() 279 | defer b.connMutex.Unlock() 280 | newConnChannel, err := b.CreateChannel(id) 281 | if err != nil { 282 | panic(err) 283 | } 284 | 285 | now := time.Now() 286 | newConnMSG := fmt.Sprintf("New Connection %s\nBegan at %v", id, now.Format("03:04PM")) 287 | 288 | if b.Logging { 289 | b.SendInfoLog(newConnMSG) 290 | } 291 | 292 | b.SendMsg(newConnChannel.ID, newConnMSG) 293 | 294 | return &Connection{ 295 | id: id, 296 | createdAt: time.Now(), 297 | status: "active", 298 | channelID: newConnChannel.ID, 299 | } 300 | } 301 | 302 | func (b *Bot) KillConnection(conn *Connection) *Connection { 303 | b.connMutex.Lock() 304 | defer b.connMutex.Unlock() 305 | deadChannel, err := b.MakeChannelDead(conn.channelID) 306 | if err != nil { 307 | panic(err) 308 | } 309 | 310 | now := time.Now() 311 | 312 | deadConnMsg := fmt.Sprintf("Connection %v died\nEnded at %v", conn.id, now.Format("03:04PM")) 313 | 314 | if b.Logging { 315 | b.SendErrorLog(deadConnMsg) 316 | } 317 | 318 | b.SendMsg(deadChannel.ID, deadConnMsg) 319 | 320 | conn = &Connection{ 321 | lastSeen: time.Now(), 322 | status: "dead", 323 | channelID: deadChannel.ID, 324 | } 325 | 326 | return conn 327 | } 328 | 329 | type Client struct { 330 | botToken string 331 | guildID string 332 | activeCategoryID string 333 | deadCategoryID string 334 | transcriptCategoryID string 335 | botRef *dc.Session 336 | activeChannelPrefix string 337 | deadChannelPrefix string 338 | } 339 | 340 | func NewClient(token string, guildID string, activeCategory string, deadCategory string, transcriptCategory string, activeChannelPrefix string, deadChannelPrefix string) *Client { 341 | 342 | c := Client{ 343 | botToken: token, 344 | guildID: guildID, 345 | activeCategoryID: activeCategory, 346 | deadCategoryID: deadCategory, 347 | transcriptCategoryID: transcriptCategory, 348 | activeChannelPrefix: activeChannelPrefix, 349 | deadChannelPrefix: deadChannelPrefix, 350 | } 351 | 352 | return &c 353 | } 354 | 355 | func (b *Bot) SendMsg(channelID, content string) (*dc.Message, error) { 356 | return b.Client.botRef.ChannelMessageSend(channelID, content) 357 | } 358 | 359 | func (b *Bot) CreateChannel(name string) (*dc.Channel, error) { 360 | return b.Client.botRef.GuildChannelCreateComplex(b.Client.guildID, dc.GuildChannelCreateData{ 361 | ParentID: b.Client.activeCategoryID, 362 | Name: b.Client.activeChannelPrefix + "-" + name, 363 | Type: dc.ChannelTypeGuildText, 364 | }) 365 | } 366 | 367 | func (b *Bot) DeleteChannel(channelID string) (*dc.Channel, error) { 368 | return b.Client.botRef.ChannelDelete(channelID) 369 | } 370 | 371 | func (b *Bot) MakeChannelActive(channelID string) (*dc.Channel, error) { 372 | channel, err := b.Client.botRef.Channel(channelID) 373 | if err != nil { 374 | return nil, err 375 | } 376 | 377 | // Remove dead prefix if present and add active prefix 378 | name := channel.Name 379 | if len(b.Client.deadChannelPrefix) > 0 && len(name) > len(b.Client.deadChannelPrefix)+1 { 380 | name = name[len(b.Client.deadChannelPrefix)+1:] 381 | } 382 | 383 | return b.Client.botRef.ChannelEdit(channelID, &dc.ChannelEdit{ 384 | ParentID: b.Client.activeCategoryID, 385 | Name: b.Client.activeChannelPrefix + "-" + name, 386 | }) 387 | } 388 | 389 | func (b *Bot) MakeChannelDead(channelID string) (*dc.Channel, error) { 390 | channel, err := b.Client.botRef.Channel(channelID) 391 | if err != nil { 392 | return nil, err 393 | } 394 | 395 | // Remove active prefix if present and add dead prefix 396 | name := channel.Name 397 | if len(b.Client.activeChannelPrefix) > 0 && len(name) > len(b.Client.activeChannelPrefix)+1 { 398 | name = name[len(b.Client.activeChannelPrefix)+1:] 399 | } 400 | 401 | return b.Client.botRef.ChannelEdit(channelID, &dc.ChannelEdit{ 402 | ParentID: b.Client.deadCategoryID, 403 | Name: b.Client.deadChannelPrefix + "-" + name, 404 | }) 405 | } 406 | --------------------------------------------------------------------------------