{%hackmd @penguin71630/theme %} # Memebot Using DiscordGo **NYCU 2025 Golang Final Project** ###### Group 21:111550013 施羿廷 / 111550055 黃致皓 > **GitHub Repos**: DC Bot: https://gitea.konchin.com/Go2025/frontend Webpage: https://gitea.konchin.com/Go2025/frontend Backend: https://gitea.konchin.com/Go2025/backend ## Project Overview 這是一款為了讓 Discord 群組更熱鬧而設計的梗圖機器人。它的運作方式很簡單:只要在聊天中提到關鍵字,機器人就會隨機丟出一張對應的梗圖來回應,並且群組內的所有成員都可以上傳照片。 | | | | :--------: | :--------: | | <img src="https://hackmd.io/_uploads/BygEhYwX-x.png"> | <img src="https://hackmd.io/_uploads/H1a52FvQWx.png"> | 如果需要整理這些大量的圖片,使用者只需輸入 `/web` 指令,機器人就會傳送一個 private link,讓使用者直接在網頁瀏覽器上方便地管理圖庫。另外網頁還附帶關鍵字搜尋的功能。 <!-- ![image](https://hackmd.io/_uploads/S1hI6FPm-x.png) <p style="text-align: center;">Fig: Private link</p> ![image](https://hackmd.io/_uploads/HJmZRKD7Zg.png) <p style="text-align: center;">Fig: Webpage</p> --> ### Overview: Discord Bot Currently, here are the supported commands: - `/ping`: Check if bot is responsive. ![image](https://hackmd.io/_uploads/S1cDmcwXWl.png =250x) - `/greet`: Greet the bot and you'll receive a friendly reply. ![image](https://hackmd.io/_uploads/HkGFX5vX-x.png =250x) - `/echo <msg>`: Bot repeats what you say. ![image](https://hackmd.io/_uploads/H1iiQcvQWg.png =250x) - `/web`:Get a private link to the webpage. ![image](https://hackmd.io/_uploads/S1hI6FPm-x.png =550x) ### Overview: Webpage | Fig: Webpage | Fig: Modifying image | | :--------: | :--------: | | ![image](https://hackmd.io/_uploads/HJmZRKD7Zg.png) | ![image](https://hackmd.io/_uploads/Hy1sCtPmZl.png) | | Fig: Uploading image | Fig: Search with keywords | |:--------:|:--------:| | ![image](https://hackmd.io/_uploads/ryVh0tPX-e.png) | ![image](https://hackmd.io/_uploads/HyFVk9vmWe.png) | ## Work Results ###### Finished - [x] Discord bot core commands (`/ping`, `/greet`, `/echo`, `/web`) - [x] Webpage (Navigation in Repository) - [x] Webpage (Search Image With Keywords) - [x] Webpage (Uploading Image) - [x] Webpage (Modifying Image) - [x] System Integration (Integrated all backend services using Docker Compose) - [x] BunRouter - [x] Uptrace/ClickHouse/Redis setup - [x] Authentication Mechanism (Generate unique, temporary access tokens and links for the `/web` command. - [x] Image Files Storage (Integrate MinIO SDK for scalable image file storage and retrieval (S3-compatible)) - [x] PostgreSQL schemas (storing metadata (image tags, uploader info, timestamps)) ###### Future Work - [ ] (Web) Responsive Web Design - [ ] (Web) Better Indexing with sidebar - [ ] (Web) Slight issues regarding CSS - [ ] (Web) Add websocket to notify changes on aliases - [ ] (DC Bot) Usage message (type `/help` ⇨ bot will show usage of each commands) - [ ] (DC Bot) More commands (e.g. `/image upload`, `/image link`, `/image unlink`, …) - [ ] (DC Bot) Imitating the behavior of “sending a sticker” (Delete the message user just sent) - [ ] (DC Bot) Enable partial matching mode: If user’s message triggers certain keywords, send images. (Implement Aho-Corasick Algorithm - [ ] (DC Bot) Split the service for different guilds ## Technical Architecture ![image](https://hackmd.io/_uploads/rJR_Sqw7Zx.png) This project adopts a decoupled frontend-backend architecture, with the core backend services and the Discord bot both developed in **Go** to ensure high performance under high concurrency. The system is composed of the following modules: ###### Backend - **Backend Server**: Serving as the system hub, it utilizes the **BunRouter** framework to build RESTful APIs, handling requests from both the Discord Bot and the Web Frontend while managing unified business logic. ###### Frontend - **Discord Bot**: Implemented with the [DiscordGo](https://github.com/bwmarrin/discordgo) library, interfacing directly with the Discord platform to provide interactive features for users within chat rooms. - **Webpage**: Developed using **TypeScript**, **React**, and **Tailwind CSS**, providing a visualized interface for meme repository management. ###### Data Storage - **Relational DB**: Uses **PostgreSQL** to store structured data such as user information and keyword mappings. - **Image Storage**: Deploys **MinIO** as the image storage server, specifically handling the access and storage of meme binary files. ###### Observability - **Uptrace / ClickHouse**: Integrates **Uptrace** for system monitoring and distributed tracing. This monitoring module relies on **ClickHouse** as the backend for log and telemetry data storage. - **Redis**: Functions as a dedicated cache for **Uptrace** to ensure real-time monitoring data processing and high query performance. ### OpenAPI - HTTP REST API - Leveraged [swaggo/swag](https://github.com/swaggo/swag) to automatically generate OpenAPI documentation, ensuring consistent specifications between the frontend and backend to prevent development conflicts. ![](https://hackmd.io/_uploads/S1Jja5w7Zx.png) ### Backend Written using BunRouter: https://bunrouter.uptrace.dev/ To ensure high maintainability and robust error handling within our Go backend, we implemented a sophisticated request-processing pipeline using **Middlewares** and **Go Context**. #### 1. Context-Driven Metadata Management We utilized the standard `context` package to propagate request-scoped metadata across different architectural layers (from API handlers to database queries). * **Identity Propagation**: After successful authentication, the user’s identity and session metadata are stored within the `http.Request` context. This allows downstream logic to access caller information without passing redundant parameters. * **Tracing Integration**: The **OpenTelemetry TraceID** is injected into the context at the start of each request. This ensures that every log entry and database operation performed during that specific request is linked to the same trace, enabling end-to-end visibility. #### 2. Centralized Middleware Pipeline We developed a custom middleware stack using **BunRouter** to decouple cross-cutting concerns from our core business logic: * **Global Error Handling**: A centralized middleware intercepts panics and errors returned by handlers. It ensures that the system always returns a standardized JSON error response and logs the stack trace for debugging, preventing service crashes. * **OpenTelemetry & Tracing Middleware**: This layer automatically starts a new span for every incoming HTTP request. It records critical metrics such as request duration, HTTP status codes, and path parameters, exporting them directly to **Uptrace**. * **Authentication Guard**: A dedicated middleware validates the **Access Token (JWT)** before allowing the request to reach sensitive endpoints, streamlining the security check process for all protected routes. ### Development To ensure a consistent and "one-click" developer experience, we containerized the entire backend technology stack—including the Go backend, PostgreSQL, MinIO, Redis, and ClickHouse—using **Docker Compose**. This eliminates the "it works on my machine" problem by providing an identical environment for all team members. Furthermore, we implemented a **Makefile** to abstract complex Docker commands into simple shortcuts (e.g., `make dev`, `make build`), significantly accelerating daily development workflows and local testing. This automated infrastructure allows developers to focus on feature implementation rather than manual service configuration. ```make= .PHONY: all swagger install postgres test test-ci mac reset \ docker docker-quiet docker-clean SWAG ?= go run github.com/swaggo/swag/cmd/swag@v1.16.4 DOCKER ?= docker COMPOSE_ARGS += --progress plain COMPOSE ?= $(DOCKER) compose $(COMPOSE_ARGS) GO_ENV += CGO_ENABLED=0 SOURCE := $(shell find . -type f -name '*.go') TARGET := backend all: swagger docker-build-native docker mac: swagger docker-build-run docker reset: $(COMPOSE) exec backend reset swagger: $(SWAG) fmt $(SWAG) init -o docs -g cmds/serve.go -pdl 1 docker-build-native: $(TARGET) $(DOCKER) build . --target native \ -t go2025/backend:native \ -t go2025/backend:latest docker-build-run: $(DOCKER) build . --target build-run \ -t go2025/backend:build-run \ -t go2025/backend:latest docker: $(COMPOSE) up -d --force-recreate backend $(TARGET): $(SOURCE) $(GO_ENV) go build -o $@ install: $(GO_ENV) go install ... ``` <p style="text-align:center"> Code Snippet: Makefile </p> <p style="text-align:center"> <img src="https://hackmd.io/_uploads/SyLx3yuXWx.png" width=60%> <br> Fig: Docker Compose startup </p> ### Discord Bot Written using DiscordGo: https://github.com/bwmarrin/discordgo ``` . ├── README.md ├── api │   ├── client.go │   ├── getImages.go │   └── postGenLoginUrl.go ├── bot │   ├── assets │   │   ├── Vita-banner.jpg │   │   └── Vita.png │   ├── bot.go │   ├── handleSlashEcho.go │   ├── handleSlashGreet.go │   ├── handleSlashPing.go │   ├── handleSlashWeb.go │   └── onMessageCreate.go ├── go.mod ├── go.sum ├── log ├── main.go └── tracing └── tracer.go ``` ###### Backend API Handler - `api/client.go`: Define structs required for communication with the backend. - `api/*.go`: Access backend APIs. ###### Frontend User Commands Handler - `bot/bot.go`: Register Discord commands. - `bot/handleSlash*.go`: Handle user requests triggered by slash commands (`/<cmd>`). ###### Tracing - `tracing/tracer.go`: Implement tracing and logging. We implemented the interface for each command. ```go= // bot/bot.go type CommandHandler = func( *discordgo.Session, *discordgo.InteractionCreate) type Command interface { ApplicationCommand() *discordgo.ApplicationCommand Handler() CommandHandler } // bot/commands/echo.go type EchoCommand struct { bot *bot.Bot } func NewEchoCommand(bot *bot.Bot) bot.Command { return &EchoCommand{bot: bot} } ``` ### Webpage Vibe-coded with Claude Sonnet 4.5. To trace codes and do debugging, we used the following tools: - Trace for any rendering errors: `DevTools Console` (F12 in browser) - Verify requests sent from webpage meets the API spec: `curl` in terminal - Trace for any backend request errors: `Uptrace`, `DevTools Console` ► `Network` - Check if the cookie formats are correct: `DevTools Console` ► `Application` ```bash= # ping-get-aliases.bash #!/bin/bash if [ -z "$TOKEN" ]; then echo "Error: TOKEN environment variable is required" echo "Usage: TOKEN=your_token_here ./ping-get-aliases.bash" exit 1 fi curl -X POST http://localhost:8080/auth/login \ -H "Content-Type: application/json" \ -d "{\"token\":\"$TOKEN\"}" \ -c cookies.txt \ -v > test.log 2>&1 REFRESH_TOKEN=$(grep refresh_token cookies.txt | awk '{print $7}') curl -b "refresh_token=$REFRESH_TOKEN" http://localhost:8080/api/aliases -v >> test.log 2>&1 echo "Test completed. Check test.log for details." ``` <p style="text-align:center"> Code Snippet: Bash script for testing backend APIs. </p> ### Logging and Tracing By integrating a centralized logging pipeline, we ensured that all system logs are automatically exported to the **OpenTelemetry Collector** within the **Uptrace APM** ecosystem. This approach achieves deep correlation between structured logs and distributed traces, allowing us to pinpoint the exact state of the application during a specific request. This unified observability stack significantly streamlines the debugging process, enabling us to resolve complex backend issues and race conditions with high precision. <p style="text-align:center"> <img src="https://hackmd.io/_uploads/SJk7RkuQ-x.png" width=80%> <br> Fig: Uptrace dashboard </p> <p style="text-align:center"> <img src="https://hackmd.io/_uploads/Sk5s0yuQZe.png" width=80%> <br> Fig: Analyze and trace a request with ease </p> ### CI/CD #### E2E Testing We ensured system reliability by implementing comprehensive E2E testing with the go test framework, programmatically simulating the full PSK-to-JWT authentication journey. These tests are integrated into a Gitea Actions CI pipeline that utilizes Docker Compose to orchestrate real instances of PostgreSQL and MinIO for authentic environment validation. This automated "Test-to-Release" workflow validates critical paths like token parsing and cookie persistence before automatically building and pushing production-ready container images, ensuring high-quality deployments with minimal manual overhead. ```go= // tests/01_login_test.go var payload genLoginUrlPayload resp, err := client.R(). SetBody(`{"userId": "testuser1"}`). SetAuthToken("poop"). SetResult(&payload). Post("/bot/auth/gen-login-url") if err != nil || resp.StatusCode() != http.StatusOK { t.Fatal("failed to get login url") } loginUrl, err := url.Parse(payload.LoginUrl) if err != nil { t.Logf("login-url: %s", payload.LoginUrl) t.Fatal("failed to parse login url") } resp, err = client.R(). SetBody(loginPayload{Token: loginUrl.Query().Get("token")}). SetAuthToken("poop"). Post("/auth/login") if err != nil || resp.StatusCode() != http.StatusOK { t.Fatal("failed to login") } for _, rawCookie := range resp.Header()["Set-Cookie"] { cookie, err := http.ParseSetCookie(rawCookie) if err != nil { t.Fatal("failed to parse cookie") } if cookie.Name == "refresh_token" { if len(cookie.Value) == 0 { t.Fatal("empty refresh token") } client.SetCookie(cookie) return } } t.Fatal("refresh token not exist") ``` <p style="text-align:center"> Code Snippet: Unit Test </p> <p style="text-align:center"> <img src="https://hackmd.io/_uploads/Hku7ke_mWg.png" width=80%> <br> Fig: Gitea Actions </p> #### Continuous Deployment To strictly adhere to GitOps principles, we deployed FluxCD to synchronize the Kubernetes cluster state directly from our Git repository. This setup ensures that any change to the manifests is automatically applied to the cluster without manual intervention. We structured our deployment using Kustomize, as shown in the snippet, to aggregate distinct resources like deployments, services, and ingresses. Furthermore, to safely manage sensitive credentials in version control, we integrated Mozilla SOPS, allowing us to commit encrypted secrets that are automatically decrypted inside the cluster. ```yaml= --- apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: vita generatorOptions: disableNameSuffixHash: true resources: - namespace.yaml - cm.backend.yaml - cm.dcbot.yaml - deploy.backend.yaml - deploy.dcbot.yaml - deploy.frontend.yaml - svc.backend.yaml - svc.frontend.yaml - secret.backend.yaml - secret.dcbot.yaml - ingress.yaml ``` <p style="text-align:center"> Code Snippet: Configuration for Aggregating Cluster Resources </p> <p style="text-align:center"> <img src="https://hackmd.io/_uploads/r14Nelu7Zx.png" width=80%> <br> Fig: FluxCD </p> ## Authentication ![image](https://hackmd.io/_uploads/ryy-fx_mZx.png) The authentication process for this project ensures a seamless and secure transition for users moving from the Discord platform to the web management interface. The following sections detail the step-by-step flow and the underlying security architecture. :::warning **Step 1: Login URL Generation** ::: * **Trigger**: A user executes the `/web` command within a Discord channel. * **Action**: The Discord Bot sends a request (`POST /auth/gen-login-url`) to the Backend Server to generate a unique login session. * **Verification**: The request is verified using a **Pre-shared Key (PSK)** configured between the Bot and the Backend to ensure trusted server-to-server communication. * **Output**: The Backend generates a URL containing a one-time use **OTP Token**, which the Bot returns to the user as an ephemeral message. :::warning **Step 2: Web Session Initialization** ::: * **Trigger**: The user clicks the generated link to open the Web Frontend. * **Action**: The Frontend extracts the **OTP Token** from the URL and exchanges it for long-term credentials (`POST /auth/login`). * **Logic**: The Backend validates the OTP Token (checking for expiration and single-use status). Upon successful validation, the Backend issues a **Refresh Token (JWT)** to the Frontend. :::warning **Step 3: Data Access & Token Maintenance** ::: * **Action**: For subsequent API requests (e.g., managing images), the Frontend uses an **Access Token**. * **Auto-Refresh Mechanism**: The system utilizes a dual-token strategy where the **Access Token** has a short expiration for security, while the **Refresh Token** handles session persistence. * **Rotation**: Both tokens are automatically rotated upon request to maintain an active and secure session. :::info ### Security Implementation Details **Pre-shared Key (PSK)**: This key is pre-configured to verify that requests for login URLs originate exclusively from the authorized Discord Bot, preventing unauthorized session spoofing. **OTP Token (One-Time Password)**: These tokens provide temporary access to web sessions and are invalidated immediately after their first use or after a short expiration period. **Dual JWT Strategy**: - **Refresh Token**: Used to identify the user session with a long expiration time and mandatory auto-rotation on every request. - **Access Token**: Used to verify API access permissions with a short expiration time to minimize the impact of potential token theft. ::: ## AI Collaboration (Webpage) ![image](https://hackmd.io/_uploads/B1XY-kd7-g.png) <p style="text-align: center;">Fig: Initial generation of webpage</p> ![image](https://hackmd.io/_uploads/BJmuGJOQZe.png) <p style="text-align: center;">Fig: Modifcation of webpage</p> ![image](https://hackmd.io/_uploads/H1ak7y_7bg.png) <p style="text-align: center;">Bug Report</p> ## Challenges Encountered :::danger Tried `GET /api/aliases` but still got `401 (Unauthorized)` even though the authentication passed. ::: We traced related logs and found the error: ![image](https://hackmd.io/_uploads/HyKTmJOQZl.png) > ###### Time spent: 2.5 hours :::danger Tried `GET /api/aliases` but still got `401` right after `docker compose up`, but after 1 ~ 2 minutes the error disappeared (status code became `200`). ::: Upon investigating reported synchronization issues, we analyzed the distributed traces and system logs; however, no immediate errors were logged as the system was technically functioning, albeit in the wrong order. By performing a deep dive into the React component lifecycle within `app.tsx`, we identified a race condition involving multiple `useEffect` hooks. Specifically, the `loadData()` function—which fetches user-specific image metadata—was occasionally executing before the `api.login()` process had successfully established a valid session. To resolve this, we refactored the initialization logic to enforce strict sequential execution, ensuring that data fetching is only triggered after the authentication state is confirmed. This fix eliminates intermittent "unauthorized" errors and ensures a stable user experience upon redirection from Discord. ![image](https://hackmd.io/_uploads/Sy9S4yd7Wx.png) > ###### Time spent: 1 hour ### Lessons Learned: The Reality of AI-Assisted Development While AI tools significantly accelerated our initial prototyping phase, the latter stages of the project revealed critical limitations in relying solely on "vibe coding" and AI-generated snippets. * **The Reliability Gap**: We learned that AI is not 100% reliable, often producing code that appears functionally correct but harbors subtle architectural flaws or logical inconsistencies. * **Invisible Logic Errors**: Many issues, such as the race condition discovered in `app.tsx`, were not immediately apparent in tracing or logs because the AI-generated logic was syntactically valid but fundamentally misunderstood the React component lifecycle. * **The Fallacy of Vibe Coding**: "Vibe coding"—relying on high-level prompts without a deep manual audit of the implementation—proved less reliable than imagined, especially when managing complex asynchronous state transitions between our Go backend and React frontend. * **Debug Complexity**: We encountered numerous unlisted errors and edge cases where AI-generated solutions failed to account for our specific infrastructure, such as the nuanced interactions between MinIO storage and PostgreSQL metadata. * **Human Oversight Requirement**: This experience reinforced that while AI is a powerful co-pilot, rigorous manual code reviews, comprehensive testing suites, and a deep understanding of the underlying tech stack are indispensable for building a production-ready system. ### Appendix: Some Photos ![on-premise deployment](https://hackmd.io/_uploads/r1FXq1O7Wg.png) ![api design and discussions](https://hackmd.io/_uploads/Syx2Q5y_X-l.png)