commit 87da626b356031300ee8e2e79ce7e4196866174d Author: Penguin-71630 Date: Sun Dec 7 02:11:14 2025 +0800 Initialized openAPI doc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a311e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +.DS_Store + +################################ +# Node.js/React (web/) 專屬 +################################ +# 忽略所有 node_modules 資料夾 +node_modules +# 忽略 npm 快取和日誌 +npm-debug.log* +.yarn-integrity +.pnp.* + +# 忽略打包後的產物 +web/dist +web/build + + +################################ +# Go (server/ & bot/) 專屬 +################################ +# 忽略 Go 專案生成的執行檔 +*.exe +*.dll +*.so +# 忽略跨平台編譯的產物 +server/bin/ +bot/bin/ +# 忽略測試快取 +*.test +*.out + + +################################ +# IDE/OS/Config 專屬 +################################ +# VS Code (常見) +.vscode/ + +# JetBrains (GoLand/WebStorm) +.idea/ + +# macOS 專屬檔案 +.DS_Store + +# 環境變數與機敏檔案 (重要!) +.env +.env.local \ No newline at end of file diff --git a/Group 21 - Golang Final Project Proposal.pdf b/Group 21 - Golang Final Project Proposal.pdf new file mode 100644 index 0000000..de31948 Binary files /dev/null and b/Group 21 - Golang Final Project Proposal.pdf differ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..841c8a5 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +.PHONY: viewapi + +# 定義要檢查的模組目錄 +NODE_MODULES_PATH := node_modules + +viewapi: + @echo "Bootstrapping API document viewer..." + if [ ! -d "$(NODE_MODULES_PATH)" ]; then \ + echo "Dependencies not installed, installing swagger-ui-express, yamljs..."; \ + [ ! -f "package.json" ] && npm init -y > /dev/null; \ + npm install express swagger-ui-express yamljs; \ + else \ + echo "Dependencies already installed."; \ + fi && \ + echo "Booting up API document viewer..." && \ + node api/view-API-doc.js \ No newline at end of file diff --git a/README-en.md b/README-en.md new file mode 100644 index 0000000..2a879fc --- /dev/null +++ b/README-en.md @@ -0,0 +1,62 @@ +# 🤖 Memebot: Discord Meme Management System + +Memebot is a project that combines a **Discord Bot** and a **Web Frontend interface** to provide an efficient and customizable platform for community members to store, manage, and use their meme images. + +## 🎯 Project Goals + +* Provide an efficient Discord bot implemented in **Go** that can trigger meme output based on keywords. +* Build a **React + TypeScript** driven web interface to facilitate users in viewing, uploading, and managing memes and their custom aliases. +* Implement a **Many-to-Many** relationship model: one image can have multiple aliases, and one alias can also correspond to multiple images. + +--- + +## 🏗️ System Architecture (Topology) + +The project utilizes a microservice concept, dividing the functionality into three main components: the Discord Bot, the Web Backend (API), and the Web Frontend, all interacting with a shared Database/Storage. + +
+    +
+ + +--- + +## 🤖 Discord Bot Features + +The bot is responsible for two main functions: image management and automated responses. + +### 1. Image Trigger and Response + +* **Trigger Mechanism:** The bot monitors chat messages. Once it detects that the message content contains any pre-defined **Alias**, it triggers a response. +* **Matching Algorithm:** For high-efficiency, real-time matching across a large set of aliases, we will use the **Aho-Corasick algorithm**. +* **Output Logic:** If the alias `` is matched, the bot will **randomly select** and send one image associated with that `` to the channel. + +### 2. Discord Commands (Slash Commands) + +| Command | Description | +| :--- | :--- | +| `/image upload` | Uploads an image. **Must include an image attachment** (JPG/PNG/GIF). The bot generates a unique **Image ID** (e.g., `670bc00e94fd77cf6852afc7`) and stores it. | +| `/image link alias: image: ` | Adds or links an `` to the specified ``. | + +--- + +## 🌐 Web Frontend Interface + +The interface is built using **React + TypeScript** with **Tailwind CSS** to provide a user-friendly, visual management tool that is more convenient than using Discord commands alone. + +### Page Layout and Functions + +* **Navbar:** Displays the Bot's name on the left and contains **[Upload Image]** and **[Login]** buttons on the right. +* **Left Sidebar:** Serves as a **Table of Contents** for all **Aliases**. The last entry is for "**Images without aliases**". +* **Search Bar:** Located at the top of the right content area, featuring **real-time feedback** search functionality. +* **Content Area:** Displays image thumbnails grouped by their corresponding Alias. + +### 1. 🔎 View and Management (Find) + +* **Image Preview:** Clicking any image thumbnail will open a **floating window (Modal)**. +* **Image Information:** The modal will display the image, its uploader, upload time, and all associated Aliases. +* **Editing Features:** Users can edit Aliases or delete the image from this modal (login required). + +### 2. ⬆️ Upload Image + +Clicking the **[Upload Image]** button opens a modal for submission: \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a52ef4c --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +{%hackmd @penguin71630/theme %} + +# Go Final Project + +主題:Discord 機器人,功能是存放一些梗圖,然後讓 Discord user 可以自行新增圖片、自定義 alias、根據 alias 叫出圖片。 + +除了 Discord bot 之外,還會有一個前端的網頁可以方便使用者查看、編輯這些圖片。 + + + +## Topology +
+ +
+ + +## Discord Bot Command (Draft) + +圖片與 alias 的對應:多對多,一張圖片可以有很多 aliases,一個 alias 也可以對應到很多張圖片。 + +輸出圖片是被動觸發的:只要在使用者的聊天訊息偵測到特定關鍵詞(聊天訊息包含任意一個 alias,以第一個匹配成功的為主,匹配方式我們打算使用 Aho-Corasick algorithm 因為我們準備 ICPC 的時候學過),假設該 alias 是 ``,bot 會從含有該 `` 的圖片隨機挑選一個輸出。 + +在 Discord 對話框輸入 `/image upload` 並附上一個圖片檔(可以是 JPG/PNG/GIF),bot 會為這張圖片生成隨機一個唯一的 ID(例:`670bc00e94fd77cf6852afc7`),並將這個圖片存在 Database。 + +在 Discord 對話框輸入 `/image link alias: image: `,會為該圖片新增一個 alias。 + + + +## Web Backend API (Draft) + +- `GET /api/image/`:取得某張圖片檔案。 +- `GET /api/image-list`:列出 db 內所有圖片檔案名稱。 +- `GET /api/alias-list`:列出 db 內所有 alias。 +- `/auth/gen-access-url`:預計使用 token-JWT exchange based authentication 來作為使用後端 API 的身分驗證。 + +其實應該還要有很多,還沒想清楚。 + + +## Web Frontend + +除了 Discord bot 之外,預計用 React + TypeScript(CSS 用 Tailwind)寫一個前端網頁。 + +前端網頁大概長這樣: +``` +Memebot [Upload-Image] [Login] +--------------------------------------------------------------- +[ sadge ] ( Search Bar ) +[ 不要吼我啦 ] +[ 什麼都願意做 ] Alias: 什麼都願意做 +[ 好時代來臨力 ] [ 方形圖片框1 ] [ 方形圖片框2 ] [ 方形圖片框3 ] +[ 野獸 ] [ 方形圖片框4 ] +[ poop ] +[ rrrrr ] Alias: 好時代來臨力 +[ 讓我看看 ] [ 方形圖片框1 ] [ 方形圖片框2 ] +[ 我愛慕虛榮啦 ] +[ 一輩子 ] Alias: 野獸 +[ 是又怎樣 ] [ 方形圖片框1 ] [ 方形圖片框2 ] [ 方形圖片框3 ] +[ Img w/o alias ] +``` + +上方是 Navbar: +- Navbar 左側是我們的 Bot 名稱。 +- Navbar 右側是功能按鈕,目前只有 Upload Image、Login。 + +### Find + +- 左側 sidebar 是所有 aliases 的 table of content。類似 HackMD、mdBook 瀏覽某篇文章的功能。 + - 最後一列是「尚未有暱稱的圖片」。 +- 右側緊接在 Navbar 底下是 search bar + - search bar 是即時反饋,使用者不需要按 enter 就會自動列出符合當前條件的圖片們。 +- 右側在 search bar 下方是各個 alias 對應的圖片們。 + - 點一下圖片之後,會跳出一個浮動視窗,顯示這張圖片以及圖片相關資訊(包含誰upload 這張圖片、什麼時候 upload 這張圖片、alias 有哪些),以及可以在這裡編輯 alias、刪除圖片。 + +### Upload Image + +使用者按下這個按鈕之後,跳出一個浮動視窗: + +``` +====================================== +| |--------------------------------| | +| | | | +| | | | +| | Drag your image here | | +| | | | +| | | | +| |--------------------------------| | +| | +| Aliases of this image: | +| [ alias_01 ] | +| [ alias_02 ] | +| [+] [ (Add a new alias) ] | +| | +| | +====================================== +``` + +### Login + +驗證當前操作前端的 Discord User,未登入的話只能使用查看相關的操作,不能編輯、上傳、刪除。 + +這個有空再來做。 + + + diff --git a/api/openapi.yml b/api/openapi.yml new file mode 100644 index 0000000..cf0da2d --- /dev/null +++ b/api/openapi.yml @@ -0,0 +1,347 @@ +openapi: 3.0.3 +info: + title: Discord MemeBot API + description: > + Go Final Project 後端 API 文件。 + 包含圖片上傳、檢索、Alias 管理以及 Discord OAuth 認證流程。 + version: 1.0.0 + +servers: + - url: http://localhost:8080 + description: 本地開發伺服器 + +# 定義 Tag 的順序與描述 (讓文件更漂亮) +tags: + - name: Image + description: 圖片相關操作 (查詢、上傳、刪除、取得檔案) + - name: Alias + description: 全域 Alias 列表 + - name: Linking/Unlinking + description: 圖片與 Alias 之間的關聯管理 + - name: Authentication + description: 使用者身分驗證與 Token 交換 + +# 全域安全驗證 +security: + - bearerAuth: [] + +paths: + # ---------------------------------------------------------------- + # Image (圖片資源) + # ---------------------------------------------------------------- + /api/images: + get: + tags: + - Image + summary: 取得圖片列表 + description: 列出圖片,支援關鍵字搜尋、分頁與無 Alias 過濾。 + parameters: + - name: search + in: query + description: 搜尋 Alias 關鍵字 (模糊搜尋) + schema: + type: string + - name: null_alias + in: query + description: 若為 true,只回傳沒有任何 alias 的圖片 + schema: + type: boolean + - name: limit + in: query + description: 分頁:每頁幾筆 + schema: + type: integer + default: 20 + - name: page + in: query + description: 分頁:第幾頁 + schema: + type: integer + default: 1 + responses: + '200': + description: 成功取得列表 + content: + application/json: + schema: + type: object + properties: + images: + type: array + items: + $ref: '#/components/schemas/Image' + + post: + tags: + - Image + summary: 上傳圖片 + description: 上傳圖片並設定初始 aliases。 + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + imgfile: + type: string + format: binary + description: 圖片檔案 + aliases: + type: array + items: + type: string + description: 初始 Alias 列表 (支援重複 key 傳送) + required: + - imgfile + responses: + '201': + description: 圖片建立成功 + content: + application/json: + schema: + $ref: '#/components/schemas/Image' + + /api/images/{id}: + get: + tags: + - Image + summary: 取得單張圖片資訊 (Metadata) + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 成功 + content: + application/json: + schema: + $ref: '#/components/schemas/Image' + + delete: + tags: + - Image + summary: 刪除圖片 + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '204': + description: 刪除成功 (No Content) + + /api/images/{id}/file: + get: + tags: + - Image + summary: 取得圖片原始檔案 (Binary) + description: 用於 + security: [] # 若圖片公開可留空 + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: 圖片串流 + content: + image/png: + schema: + type: string + format: binary + image/jpeg: + schema: + type: string + format: binary + image/gif: + schema: + type: string + format: binary + + # ---------------------------------------------------------------- + # Alias (全域列表) + # ---------------------------------------------------------------- + /api/aliases: + get: + tags: + - Alias + summary: 取得所有 Alias + responses: + '200': + description: 成功 + content: + application/json: + schema: + type: object + properties: + aliases: + type: array + items: + type: string + example: ["114514", "哼哼啊啊啊啊", "poop"] + + # ---------------------------------------------------------------- + # Linking/Unlinking (關聯管理) + # ---------------------------------------------------------------- + /api/images/{id}/aliases: + post: + tags: + - Linking/Unlinking + summary: 新增單一 Alias (關聯) + parameters: + - name: id + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + alias: + type: string + example: "先輩" + responses: + '200': + description: 新增成功,回傳更新後的圖片資訊 + content: + application/json: + schema: + $ref: '#/components/schemas/Image' + + delete: + tags: + - Linking/Unlinking + summary: 刪除單一 Alias (關聯) + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: alias + in: query + description: 要刪除的 alias 文字 + required: true + schema: + type: string + example: "先輩" + responses: + '204': + description: 刪除成功 (No Content) + + put: + tags: + - Linking/Unlinking + summary: 批次取代 Alias 列表 + description: 用於前端編輯視窗的 Save 功能,完全取代舊有列表。 + parameters: + - name: id + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + aliases: + type: array + items: + type: string + example: ["先輩", "好時代", "114514"] + responses: + '200': + description: 更新成功,回傳更新後的圖片資訊 + content: + application/json: + schema: + $ref: '#/components/schemas/Image' + + # ---------------------------------------------------------------- + # Authentication (驗證) + # ---------------------------------------------------------------- + /auth/gen-access-url: + post: + tags: + - Authentication + summary: JWT 交換 (Login) + description: 使用 Discord OAuth Code 交換 Access Token + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + code: + type: string + description: Discord OAuth Code + redirect_uri: + type: string + grant_type: + type: string + default: authorization_code + required: + - code + responses: + '200': + description: 登入成功 + content: + application/json: + schema: + type: object + properties: + access_token: + type: string + token_type: + type: string + example: "Bearer" + expires_in: + type: integer + example: 3600 + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + Image: + type: object + properties: + id: + type: string + example: "101" + uploaded_user_id: + type: string + example: "konchin.shih" + uploaded_at: + type: string + format: date-time + example: "2023-10-20T12:00:00Z" + aliases: + type: array + items: + type: string + example: ["野獸", "先輩", "114514"] + url: + type: string + description: 圖片二進制檔案的 API 路徑 + example: "/api/images/101/file" \ No newline at end of file diff --git a/api/view-API-doc.js b/api/view-API-doc.js new file mode 100644 index 0000000..94a114e --- /dev/null +++ b/api/view-API-doc.js @@ -0,0 +1,28 @@ +const express = require('express'); +const app = express(); +const swaggerUi = require('swagger-ui-express'); +const YAML = require('yamljs'); +const path = require('path'); + +// 設定你的 yaml 檔案路徑 +// 假設這個 js 檔在根目錄,而 yaml 在 api 資料夾內 +const filePath = path.join(__dirname, 'openapi.yml'); + +try { + // 嘗試讀取檔案 + const swaggerDocument = YAML.load(filePath); + + // 設定路由 + app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); + + console.log('=================================================='); + console.log(`文件伺服器已啟動!`); + console.log(`請用瀏覽器打開: http://localhost:3000/docs`); + console.log('=================================================='); + +} catch (e) { + console.error("讀取 yaml 檔案失敗,請檢查路徑是否正確:", filePath); + console.error(e); +} + +app.listen(3000); \ No newline at end of file