Skip to content

Commit 469a819

Browse files
Initial commit
0 parents  commit 469a819

File tree

10 files changed

+310
-0
lines changed

10 files changed

+310
-0
lines changed

.github/workflows/release.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Release
2+
on:
3+
push:
4+
tags:
5+
- 'v*'
6+
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-go@v5
15+
with:
16+
go-version: '1.23.1'
17+
- name: Build
18+
run: |
19+
make build -j6
20+
make package -j6
21+
- name: Create release
22+
uses: ncipollo/release-action@v1
23+
with:
24+
artifacts: 'dist/*.zip, dist/*.tar.gz'
25+
draft: true

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.env
2+
dist/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 benjammin4dayz
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.

Makefile

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
BINARY_NAME=bot
2+
PLATFORMS=darwin linux windows
3+
ARCHITECTURES=amd64 arm64
4+
5+
all: clean build package
6+
7+
build: $(foreach PLATFORM,$(PLATFORMS),$(foreach ARCH,$(ARCHITECTURES),build-$(PLATFORM)-$(ARCH)))
8+
9+
build-%-amd64:
10+
@echo "Building $(BINARY_NAME)-$*-amd64"
11+
@GOOS=$* GOARCH=amd64 go build -o dist/$(BINARY_NAME)-$*-amd64 .
12+
@echo "$(BINARY_NAME)-$*-amd64: $$(sha256sum dist/$(BINARY_NAME)-$*-amd64 | awk '{print $$1}')"
13+
14+
build-%-arm64:
15+
@echo "Building $(BINARY_NAME)-$*-arm64"
16+
@GOOS=$* GOARCH=arm64 go build -o dist/$(BINARY_NAME)-$*-arm64 .
17+
@echo "$(BINARY_NAME)-$*-arm64: $$(sha256sum dist/$(BINARY_NAME)-$*-arm64 | awk '{print $$1}')"
18+
19+
package: $(foreach PLATFORM,$(PLATFORMS),$(foreach ARCH,$(ARCHITECTURES),package-$(PLATFORM)-$(ARCH)))
20+
21+
package-%-amd64:
22+
@echo "Packaging $(BINARY_NAME)-$*-amd64"
23+
@if [ "$*" = "windows" ]; then \
24+
mv dist/$(BINARY_NAME)-$*-amd64 dist/$(BINARY_NAME)-$*-amd64.exe; \
25+
zip -j dist/$(BINARY_NAME)-$*-amd64.zip dist/$(BINARY_NAME)-$*-amd64.exe; \
26+
else \
27+
chmod +x dist/$(BINARY_NAME)-$*-amd64; \
28+
tar -zcvf dist/$(BINARY_NAME)-$*-amd64.tar.gz dist/$(BINARY_NAME)-$*-amd64; \
29+
fi
30+
31+
package-%-arm64:
32+
@echo "Packaging $(BINARY_NAME)-$*-arm64"
33+
@if [ "$*" = "windows" ]; then \
34+
mv dist/$(BINARY_NAME)-$*-arm64 dist/$(BINARY_NAME)-$*-arm64.exe; \
35+
zip -j dist/$(BINARY_NAME)-$*-arm64.zip dist/$(BINARY_NAME)-$*-arm64.exe; \
36+
else \
37+
chmod +x dist/$(BINARY_NAME)-$*-arm64; \
38+
tar -zcvf dist/$(BINARY_NAME)-$*-arm64.tar.gz dist/$(BINARY_NAME)-$*-arm64; \
39+
fi
40+
41+
clean:
42+
@rm -f dist/$(BINARY_NAME)-*
43+
44+
.PHONY: all build clean package

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Active Developer Badge
2+
3+
Earn the "Active Developer" badge from Discord using this simple self-contained bot!
4+
5+
## Getting Started
6+
7+
1. [Download the bot](https://github.com/benjammin4dayz/discord-active-developer-badge/releases/latest) for your system and extract the file(s) anywhere you prefer
8+
9+
2. [Create a Discord application](https://discord.com/developers/applications) on the account you want the badge on
10+
11+
3. Create a Discord server and [enable Community](https://support.discord.com/hc/en-us/articles/360047132851-Enabling-Your-Community-Server)
12+
13+
## Instructions
14+
15+
1. On the Discord application page, go to the `Bot` tab and make a bot account for the application
16+
17+
2. Click the `Reset Token` button to generate a token (if you have 2fa enabled, you'll need your code)
18+
19+
3. Copy the token to your clipboard
20+
21+
4. Start the bot by running the program extracted in the previous step
22+
23+
5. Paste (<kbd>CTRL</kbd><kbd>SHIFT</kbd><kbd>V</kbd>) the token in the terminal when requested
24+
25+
- This can also be set via the `BOT_TOKEN` environment variable (either shell or `.env` file)
26+
27+
6. Click the invite link that appears in the terminal to invite the bot to your server
28+
29+
7. In your server, go to a channel and use the `/ping` command
30+
31+
8. Wait ~24 hours, then go to the [Active Developer](https://discord.com/developers/active-developer) page and claim your badge
32+
33+
> [!Important]
34+
> In order to detect command usage, you or at least one person on the team that owns the app needs to have "Use data to improve Discord" enabled within User Settings > Data & Privacy. At least 24 hours need to pass after detecting a command, so be sure to wait at least 24 hours after enabling this setting before trying again.
35+
36+
## Acknowledgement
37+
38+
This project wouldn't exist without inspiration from [hackermondev & contributors](https://github.com/hackermondev/discord-active-developer-badge)

bot/bot.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package bot
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"os/signal"
8+
"syscall"
9+
10+
"github.com/bwmarrin/discordgo"
11+
)
12+
13+
var Token string
14+
15+
func Run() {
16+
discord, err := discordgo.New("Bot " + Token)
17+
if err != nil {
18+
log.Fatalf("Invalid bot parameters: %v", err)
19+
}
20+
21+
discord.AddHandler(interactionHandler)
22+
23+
err = discord.Open()
24+
if err != nil {
25+
log.Fatalf("Discord connection failed - %v", err)
26+
}
27+
defer discord.Close()
28+
29+
fmt.Printf("Bot is running. Press CTRL-C to exit.\n\nInvite the bot to your server with this link:\n https://discord.com/api/oauth2/authorize?client_id=%v&scope=bot%%20applications.commands&permissions=105227086912\n\n", discord.State.User.ID)
30+
sc := make(chan os.Signal, 1)
31+
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
32+
33+
registerCommands(discord)
34+
<-sc
35+
unregisterCommands(discord)
36+
}
37+
38+
func interactionHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
39+
if i.Type == discordgo.InteractionApplicationCommand {
40+
log.Printf("Handling interaction '%v' in guild '%v'\n", i.ApplicationCommandData().Name, i.GuildID)
41+
switch i.ApplicationCommandData().Name {
42+
case "ping":
43+
handlePing(s, i)
44+
case "help":
45+
handleHelp(s, i)
46+
}
47+
}
48+
}
49+
50+
func registerCommands(s *discordgo.Session) {
51+
registeredCommands := make([]*discordgo.ApplicationCommand, len(commands))
52+
for i, v := range commands {
53+
cmd, err := s.ApplicationCommandCreate(s.State.User.ID, "", v)
54+
if err != nil {
55+
log.Fatalf("Cannot create '%v' command: %v", v.Name, err)
56+
}
57+
registeredCommands[i] = cmd
58+
log.Printf("Created command '%v'\n", v.Name)
59+
}
60+
}
61+
62+
func unregisterCommands(s *discordgo.Session) {
63+
for _, v := range commands {
64+
s.ApplicationCommandDelete(s.State.User.ID, "", v.ID)
65+
log.Printf("Deleted command '%v'\n", v.Name)
66+
}
67+
}

bot/commands.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package bot
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/bwmarrin/discordgo"
8+
)
9+
10+
var commands = []*discordgo.ApplicationCommand{
11+
{
12+
Name: "help",
13+
Description: "Links to the bot's documentation",
14+
},
15+
{
16+
Name: "ping",
17+
Description: "Pings the bot to determine the latency",
18+
},
19+
}
20+
21+
func handleHelp(s *discordgo.Session, i *discordgo.InteractionCreate) {
22+
url := "https://github.com/benjammin4dayz/discord-active-developer-badge?tab=readme-ov-file#getting-started"
23+
basicInteractionResponse(s, i,
24+
fmt.Sprintf("[Click here](%v) to learn how to use this bot to earn your Active Developer badge!", url))
25+
}
26+
27+
func handlePing(s *discordgo.Session, i *discordgo.InteractionCreate) {
28+
basicInteractionResponse(s, i,
29+
fmt.Sprintf("Pong! (%v ms)",
30+
s.HeartbeatLatency().Milliseconds()))
31+
}
32+
33+
func basicInteractionResponse(s *discordgo.Session, i *discordgo.InteractionCreate, response string) {
34+
log.Println(response)
35+
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
36+
Type: discordgo.InteractionResponseChannelMessageWithSource,
37+
Data: &discordgo.InteractionResponseData{
38+
Content: response,
39+
},
40+
})
41+
}

go.mod

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module github.com/benjammin4dayz/discord-active-developer-badge
2+
3+
go 1.23.1
4+
5+
require (
6+
github.com/bwmarrin/discordgo v0.28.1
7+
github.com/joho/godotenv v1.5.1
8+
)
9+
10+
require (
11+
github.com/gorilla/websocket v1.5.3 // indirect
12+
golang.org/x/crypto v0.37.0 // indirect
13+
golang.org/x/sys v0.32.0 // indirect
14+
)

go.sum

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
4+
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
5+
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
6+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
7+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
8+
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
9+
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
10+
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
11+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
12+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
13+
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
14+
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
15+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
16+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
17+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

main.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"io"
7+
"log"
8+
"os"
9+
10+
bot "github.com/benjammin4dayz/discord-active-developer-badge/bot"
11+
"github.com/joho/godotenv"
12+
)
13+
14+
var BotToken = flag.String("token", "", "Bot access token")
15+
16+
func main() {
17+
godotenv.Load()
18+
19+
if os.Getenv("DEBUG") != "true" {
20+
log.SetOutput(io.Discard)
21+
}
22+
23+
bot.Token = getToken()
24+
bot.Run()
25+
}
26+
27+
func getToken() string {
28+
flag.Parse()
29+
if *BotToken != "" {
30+
return *BotToken
31+
}
32+
33+
token := os.Getenv("BOT_TOKEN")
34+
if token != "" {
35+
return token
36+
}
37+
38+
fmt.Print("Enter bot token: ")
39+
fmt.Scanln(&token)
40+
return token
41+
}

0 commit comments

Comments
 (0)