mirror of
https://github.com/Penguin-71630/meme-bot-frontend-dc.git
synced 2026-03-12 12:30:15 +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