mirror of
https://github.com/Penguin-71630/meme-bot-frontend-dc.git
synced 2026-03-12 20:40:16 +08:00
init DiscordGo
This commit is contained in:
16
Makefile
16
Makefile
@@ -1,16 +0,0 @@
|
|||||||
.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
|
|
||||||
347
api/openapi.yml
347
api/openapi.yml
@@ -1,347 +0,0 @@
|
|||||||
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: 用於 <img src="...">
|
|
||||||
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"
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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);
|
|
||||||
79
bot/bot.go
Normal file
79
bot/bot.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/Penguin-71630/meme-bot-frontend-dc/api"
|
||||||
|
"github.com/Penguin-71630/meme-bot-frontend-dc/config"
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bot struct {
|
||||||
|
session *discordgo.Session
|
||||||
|
config *Config
|
||||||
|
apiClient *api.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Token string
|
||||||
|
APIClient *api.Client
|
||||||
|
Prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.Config) (*Bot, error) {
|
||||||
|
// Create Discord session
|
||||||
|
session, err := discordgo.New("Bot " + cfg.DiscordToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating Discord session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create API client
|
||||||
|
apiClient := api.NewClient(cfg.APIBaseURL)
|
||||||
|
|
||||||
|
bot := &Bot{
|
||||||
|
session: session,
|
||||||
|
apiClient: apiClient,
|
||||||
|
config: &Config{
|
||||||
|
Token: cfg.DiscordToken,
|
||||||
|
APIClient: apiClient,
|
||||||
|
Prefix: cfg.BotPrefix,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register handlers
|
||||||
|
bot.registerHandlers()
|
||||||
|
|
||||||
|
// Set intents
|
||||||
|
session.Identify.Intents = discordgo.IntentsGuildMessages | discordgo.IntentsDirectMessages | discordgo.IntentsMessageContent
|
||||||
|
|
||||||
|
return bot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) registerHandlers() {
|
||||||
|
b.session.AddHandler(b.onReady)
|
||||||
|
b.session.AddHandler(b.onMessageCreate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) onReady(s *discordgo.Session, event *discordgo.Ready) {
|
||||||
|
log.Printf("Logged in as: %v#%v", s.State.User.Username, s.State.User.Discriminator)
|
||||||
|
|
||||||
|
// Set bot status
|
||||||
|
err := s.UpdateGameStatus(0, fmt.Sprintf("%shelp for commands", b.config.Prefix))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error setting status: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) Start() error {
|
||||||
|
if err := b.session.Open(); err != nil {
|
||||||
|
return fmt.Errorf("error opening connection: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) Stop() {
|
||||||
|
if b.session != nil {
|
||||||
|
b.session.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
35
config/config.go
Normal file
35
config/config.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
DiscordToken string
|
||||||
|
APIBaseURL string
|
||||||
|
BotPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() (*Config, error) {
|
||||||
|
token := os.Getenv("DISCORD_BOT_TOKEN")
|
||||||
|
if token == "" {
|
||||||
|
return nil, errors.New("DISCORD_BOT_TOKEN is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
apiURL := os.Getenv("API_BASE_URL")
|
||||||
|
if apiURL == "" {
|
||||||
|
apiURL = "http://localhost:8080" // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := os.Getenv("BOT_PREFIX")
|
||||||
|
if prefix == "" {
|
||||||
|
prefix = "!" // Default prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Config{
|
||||||
|
DiscordToken: token,
|
||||||
|
APIBaseURL: apiURL,
|
||||||
|
BotPrefix: prefix,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
14
go.mod
Normal file
14
go.mod
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module github.com/Penguin-71630/meme-bot-frontend-dc
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bwmarrin/discordgo v0.27.1
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
golang.org/x/crypto v0.14.0 // indirect
|
||||||
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
|
)
|
||||||
48
main.go
Normal file
48
main.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/Penguin-71630/meme-bot-frontend-dc/bot"
|
||||||
|
"github.com/Penguin-71630/meme-bot-frontend-dc/config"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Load environment variables
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
log.Println("No .env file found, using system environment variables")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load configuration
|
||||||
|
cfg, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to load configuration: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize bot
|
||||||
|
discordBot, err := bot.New(cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create bot: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start bot
|
||||||
|
if err := discordBot.Start(); err != nil {
|
||||||
|
log.Fatalf("Failed to start bot: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Bot is now running. Press CTRL-C to exit.")
|
||||||
|
|
||||||
|
// Wait for interrupt signal
|
||||||
|
sc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
||||||
|
<-sc
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
discordBot.Stop()
|
||||||
|
fmt.Println("Bot stopped gracefully.")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user