From ba5c7bba2b8f6a61f853c43af6a71d878544f56f Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Fri, 28 Mar 2025 17:34:52 -0400 Subject: [PATCH 1/7] move configurable times to env file --- cmd/bot/main.go | 65 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/cmd/bot/main.go b/cmd/bot/main.go index 3a90826..f9aa30b 100644 --- a/cmd/bot/main.go +++ b/cmd/bot/main.go @@ -8,6 +8,7 @@ import ( "math/rand" "os" "os/signal" + "strconv" "syscall" "time" @@ -31,19 +32,55 @@ var ( conspiracyIndex int ) -const ( - // TODO: update value after debugging - SendMessageIntervalMin = 5 - // TODO: update value after debugging - SendMessageIntervalMax = 10 - // TODO: update value after debugging - SendMessageUnit = time.Second - // TODO: update value after debugging - DeleteConspiracyDelay = 3 * time.Second - // TODO: update value after debugging - ConspiracyProbability = 0.4 +var ( + SendMessageIntervalMin int + SendMessageIntervalMax int + SendMessageUnit time.Duration + DeleteConspiracyDelay time.Duration + ConspiracyProbability float64 ) +func loadEnvConfig() error { + var err error + + // Message intervals + SendMessageIntervalMin, err = strconv.Atoi(os.Getenv("SEND_MESSAGE_INTERVAL_MIN")) + if err != nil { + return fmt.Errorf("SEND_MESSAGE_INTERVAL_MIN: %w", err) + } + SendMessageIntervalMax, err = strconv.Atoi(os.Getenv("SEND_MESSAGE_INTERVAL_MAX")) + if err != nil { + return fmt.Errorf("SEND_MESSAGE_INTERVAL_MAX: %w", err) + } + + // Duration unit + unit := os.Getenv("SEND_MESSAGE_UNIT") + switch unit { + case "second", "seconds": + SendMessageUnit = time.Second + case "minute", "minutes": + SendMessageUnit = time.Minute + case "millisecond", "milliseconds": + SendMessageUnit = time.Millisecond + default: + return fmt.Errorf("unsupported SEND_MESSAGE_UNIT: %s", unit) + } + + // Conspiracy delete delay + DeleteConspiracyDelay, err = time.ParseDuration(os.Getenv("DELETE_CONSPIRACY_DELAY")) + if err != nil { + return fmt.Errorf("DELETE_CONSPIRACY_DELAY: %w", err) + } + + // Conspiracy probability + ConspiracyProbability, err = strconv.ParseFloat(os.Getenv("CONSPIRACY_PROBABILITY"), 64) + if err != nil { + return fmt.Errorf("CONSPIRACY_PROBABILITY: %w", err) + } + + return nil +} + func main() { // Load environment variables if err := godotenv.Load(); err != nil { @@ -56,6 +93,10 @@ func main() { log.Fatal("[x] Missing DISCORD_BOT_TOKEN or DISCORD_CHANNEL_ID in .env") } + if err := loadEnvConfig(); err != nil { + log.Fatalf("[x] Error loading env config: %v", err) + } + // Load messages at build time if err := loadMessages(); err != nil { log.Fatalf("[x] Error loading messages: %v", err) @@ -127,7 +168,7 @@ func startScheduler(s *discordgo.Session, channelID string) { praiseIndex++ // Chance to send a conspiracy theory - if rand.Float32() < ConspiracyProbability { + if rand.Float64() < ConspiracyProbability { discordMessage := sendMessage(conspiracies[conspiracyIndex%len(conspiracies)], s, channelID) conspiracyIndex++ From 97b469d0f9dab4fabb3ed0c12c32ae2fb78ca076 Mon Sep 17 00:00:00 2001 From: TwiN Date: Fri, 28 Mar 2025 17:41:30 -0400 Subject: [PATCH 2/7] build: Add Dockerfile and go mod tidy dependencies --- .gitignore | 2 ++ Dockerfile | 10 ++++++++++ go.mod | 7 +++++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 Dockerfile diff --git a/.gitignore b/.gitignore index 915d6c8..95ea213 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .env bin +vendor +.idea diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8741a99 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM golang:alpine AS builder +RUN apk --update add ca-certificates +WORKDIR /app +COPY . ./ +RUN go mod tidy +ENV DISCORD_BOT_TOKEN="" +ENV GIT_COMMIT="" +ENV BUILD_DATE="" +RUN go build -ldflags="-s -w -X main.CommitHash=$GIT_COMMIT -X main.BuildDate=$BUILD_DATE" -o bin/swiftspiracybot cmd/bot/main.go +ENTRYPOINT ["bin/swiftspiracybot"] diff --git a/go.mod b/go.mod index f5cb850..5e741e2 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,12 @@ module github.com/khinshankhan/swiftspiracy go 1.23.4 require ( - github.com/bwmarrin/discordgo v0.28.1 // indirect + github.com/bwmarrin/discordgo v0.28.1 + github.com/joho/godotenv v1.5.1 +) + +require ( github.com/gorilla/websocket v1.4.2 // indirect - github.com/joho/godotenv v1.5.1 // indirect golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect ) From 464a753f71199917389077ac802d2c4377670796 Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Fri, 28 Mar 2025 17:43:56 -0400 Subject: [PATCH 3/7] reorganize env loading logic --- cmd/bot/main.go | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/cmd/bot/main.go b/cmd/bot/main.go index f9aa30b..e0db7ae 100644 --- a/cmd/bot/main.go +++ b/cmd/bot/main.go @@ -32,6 +32,11 @@ var ( conspiracyIndex int ) +var ( + botToken string + channelID string +) + var ( SendMessageIntervalMin int SendMessageIntervalMax int @@ -41,9 +46,21 @@ var ( ) func loadEnvConfig() error { + if err := godotenv.Load(); err != nil { + log.Println("[!] No .env file found, using system environment variables.") + } + + botToken = os.Getenv("DISCORD_BOT_TOKEN") + if botToken == "" { + return fmt.Errorf("Missing DISCORD_BOT_TOKEN") + } + channelID = os.Getenv("DISCORD_CHANNEL_ID") + if botToken == "" { + return fmt.Errorf("Missing DISCORD_CHANNEL_ID") + } + var err error - // Message intervals SendMessageIntervalMin, err = strconv.Atoi(os.Getenv("SEND_MESSAGE_INTERVAL_MIN")) if err != nil { return fmt.Errorf("SEND_MESSAGE_INTERVAL_MIN: %w", err) @@ -53,9 +70,7 @@ func loadEnvConfig() error { return fmt.Errorf("SEND_MESSAGE_INTERVAL_MAX: %w", err) } - // Duration unit - unit := os.Getenv("SEND_MESSAGE_UNIT") - switch unit { + switch os.Getenv("SEND_MESSAGE_UNIT") { case "second", "seconds": SendMessageUnit = time.Second case "minute", "minutes": @@ -63,16 +78,14 @@ func loadEnvConfig() error { case "millisecond", "milliseconds": SendMessageUnit = time.Millisecond default: - return fmt.Errorf("unsupported SEND_MESSAGE_UNIT: %s", unit) + return fmt.Errorf("unsupported SEND_MESSAGE_UNIT: %s", os.Getenv("SEND_MESSAGE_UNIT")) } - // Conspiracy delete delay DeleteConspiracyDelay, err = time.ParseDuration(os.Getenv("DELETE_CONSPIRACY_DELAY")) if err != nil { return fmt.Errorf("DELETE_CONSPIRACY_DELAY: %w", err) } - // Conspiracy probability ConspiracyProbability, err = strconv.ParseFloat(os.Getenv("CONSPIRACY_PROBABILITY"), 64) if err != nil { return fmt.Errorf("CONSPIRACY_PROBABILITY: %w", err) @@ -82,19 +95,8 @@ func loadEnvConfig() error { } func main() { - // Load environment variables - if err := godotenv.Load(); err != nil { - log.Println("[!] No .env file found, using system environment variables.") - } - - // Retrieve token and channel ID - botToken, channelID := os.Getenv("DISCORD_BOT_TOKEN"), os.Getenv("DISCORD_CHANNEL_ID") - if botToken == "" || channelID == "" { - log.Fatal("[x] Missing DISCORD_BOT_TOKEN or DISCORD_CHANNEL_ID in .env") - } - if err := loadEnvConfig(); err != nil { - log.Fatalf("[x] Error loading env config: %v", err) + log.Fatalf("[x] Failed to load configuration: %v", err) } // Load messages at build time From a4af597c2f54c32f8f004c108c0afaf5362bf5b3 Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Fri, 28 Mar 2025 17:44:23 -0400 Subject: [PATCH 4/7] add sample env file --- .env-sample | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .env-sample diff --git a/.env-sample b/.env-sample new file mode 100644 index 0000000..9dc84f2 --- /dev/null +++ b/.env-sample @@ -0,0 +1,14 @@ +# Discord bot credentials +DISCORD_BOT_TOKEN=your-bot-token-here +DISCORD_CHANNEL_ID=your-channel-id-here + +# Timing configuration +SEND_MESSAGE_INTERVAL_MIN=5 +SEND_MESSAGE_INTERVAL_MAX=10 +SEND_MESSAGE_UNIT=second # "second" | "seconds" | "minute" | "minutes" | "millisecond" | "milliseconds" + +# Delay before deleting a conspiracy message (eg "3s", "500ms", "1m") +DELETE_CONSPIRACY_DELAY=3s + +# Probability of sending a conspiracy message (0.0 to 1.0) +CONSPIRACY_PROBABILITY=0.4 From a9a4e1a20c9ee3bcdf5a9622ef26ee735b33f365 Mon Sep 17 00:00:00 2001 From: TwiN Date: Fri, 28 Mar 2025 17:48:35 -0400 Subject: [PATCH 5/7] cd: Add publish-latest workflow --- .github/workflows/publish-latest.yml | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/publish-latest.yml diff --git a/.github/workflows/publish-latest.yml b/.github/workflows/publish-latest.yml new file mode 100644 index 0000000..0f978d8 --- /dev/null +++ b/.github/workflows/publish-latest.yml @@ -0,0 +1,33 @@ +name: publish-latest +on: + push: + branches: [main] +concurrency: + group: ${{ github.event.workflow_run.head_repository.full_name }}::${{ github.event.workflow_run.head_branch }}::${{ github.workflow }} + cancel-in-progress: true +jobs: + publish-latest: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + - name: Get image repository + run: echo IMAGE_REPOSITORY=$(echo ghcr.io/${{ github.actor }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64 + pull: true + push: true + tags: ${{ env.IMAGE_REPOSITORY }}:latest From 422bf3c546bfb90c53de817bd3d46cffcf337833 Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Fri, 28 Mar 2025 17:52:52 -0400 Subject: [PATCH 6/7] add basic readme to explain .env --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 505bc41..ca7fa59 100644 --- a/README.md +++ b/README.md @@ -1 +1,28 @@ # swiftspiracy + +A playful Discord bot that sends random praises and (occasionally) a conspiracy theory — and cleans up after itself. + +## Setup + +### Configuration + +Create a `.env` file in the root of your project with the following keys: + +- `DISCORD_BOT_TOKEN`: Your bot token from the Discord developer portal. +- `DISCORD_CHANNEL_ID`: The ID of the channel where the bot should send messages. + +- `SEND_MESSAGE_INTERVAL_MIN`: Minimum interval between messages (as an integer). +- `SEND_MESSAGE_INTERVAL_MAX`: Maximum interval between messages (as an integer). +- `SEND_MESSAGE_UNIT`: Time unit for the interval. Acceptable values: "second", "seconds", "minute", "minutes", + "millisecond", "milliseconds". + +- `DELETE_CONSPIRACY_DELAY`: Time to wait before deleting a conspiracy message. Use Go duration format (eg "3s", + "500ms", "1m"). +- `CONSPIRACY_PROBABILITY`: Probability (from 0.0 to 1.0) that a conspiracy message is sent after a praise. Eg, 0.4 = + 40% chance. + +You can view the [.env-sample](./.env-sample) as an example. + +## Notes + +- The bot gracefully shuts down on CTRL+C. From 8d3eff34304139f06abb5d3d9ab4db6b644e4831 Mon Sep 17 00:00:00 2001 From: TwiN Date: Fri, 28 Mar 2025 18:51:15 -0400 Subject: [PATCH 7/7] build: Fix repository url --- .github/workflows/publish-latest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-latest.yml b/.github/workflows/publish-latest.yml index 0f978d8..852148d 100644 --- a/.github/workflows/publish-latest.yml +++ b/.github/workflows/publish-latest.yml @@ -17,12 +17,12 @@ jobs: - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - name: Get image repository - run: echo IMAGE_REPOSITORY=$(echo ghcr.io/${{ github.actor }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + run: echo IMAGE_REPOSITORY=$(echo ghcr.io/the-programmers-hangout/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV - name: Login to registry uses: docker/login-action@v3 with: registry: ghcr.io - username: ${{ github.actor }} + username: the-programmers-hangout password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker image uses: docker/build-push-action@v6