Skip to content

Commit 42240cb

Browse files
support markdown
1 parent f18258c commit 42240cb

File tree

17 files changed

+710
-135
lines changed

17 files changed

+710
-135
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ data
1313
go_build_*
1414
output
1515
MusebotAdmin*
16+
img/*

Dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ COPY . .
2020

2121
# Build the Go application
2222
RUN go build -ldflags="-s -w" -o MuseBot main.go
23+
RUN go build -ldflags="-s -w" -o MuseBotAdmin admin/main.go
2324

2425

2526
# ----------------------
@@ -64,8 +65,10 @@ RUN mkdir -p ./conf/i18n ./conf/mcp
6465

6566
# Copy compiled Go application
6667
COPY --from=builder /app/MuseBot .
68+
COPY --from=builder /app/MuseBotAdmin .
6769
COPY --from=builder /app/conf/i18n/ ./conf/i18n/
6870
COPY --from=builder /app/conf/mcp/ ./conf/mcp/
71+
COPY --from=builder /app/conf/img/ ./conf/img/
6972

7073
# Copy FFmpeg binaries
7174
COPY --from=ffmpeg-builder /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
@@ -78,6 +81,7 @@ USER appuser
7881

7982
# Expose application port
8083
EXPOSE 36060
84+
EXPOSE 18080
8185

8286
# Set entrypoint
83-
ENTRYPOINT ["./MuseBot"]
87+
ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf", "-n"]

MuseAdmin

37.7 MB
Binary file not shown.

admin/utils/linux_utils.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ func StartDetachedProcess(argsStr string) error {
2424
var args []string
2525
for _, l := range lines {
2626
trimmed := strings.TrimSpace(l)
27+
kvs := strings.SplitN(l, "=", 2)
2728
if trimmed != "" {
28-
args = append(args, trimmed)
29+
args = append(args, kvs[0]+"='"+escapeForAppleScript(kvs[1])+"'")
2930
}
3031
}
3132

@@ -51,3 +52,12 @@ end tell`, cmdStr)
5152
return cmd.Start()
5253
}
5354
}
55+
56+
func escapeForAppleScript(s string) string {
57+
s = strings.ReplaceAll(s, `\`, `\\`)
58+
s = strings.ReplaceAll(s, `'`, `\'`)
59+
s = strings.ReplaceAll(s, `"`, `\"`)
60+
s = strings.ReplaceAll(s, "\n", " ")
61+
s = strings.ReplaceAll(s, "\r", "")
62+
return s
63+
}

conf/conf.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
type BaseConf struct {
1515
StartTime int64 `json:"-"`
16+
ImageDay int `json:"-"`
1617

1718
TelegramBotToken *string `json:"telegram_bot_token"`
1819
DiscordBotToken *string `json:"discord_bot_token"`

http/http.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type HTTPServer struct {
2828
}
2929

3030
func InitHTTP() {
31+
initImg()
3132
pprofServer := NewHTTPServer(fmt.Sprintf("%s", *conf.BaseConfInfo.HTTPHost))
3233
pprofServer.Start()
3334
}
@@ -89,6 +90,8 @@ func (p *HTTPServer) Start() {
8990
mux.HandleFunc("/cron/delete", DeleteCron)
9091
mux.HandleFunc("/cron/list", GetCrons)
9192

93+
mux.HandleFunc("/image", imageHandler)
94+
9295
wrappedMux := WithRequestContext(mux)
9396

9497
var err error

http/img.go

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
package http
2+
3+
import (
4+
"io"
5+
"io/ioutil"
6+
"net/http"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
"time"
11+
12+
"github.com/yincongcyincong/MuseBot/conf"
13+
"github.com/yincongcyincong/MuseBot/logger"
14+
"github.com/yincongcyincong/MuseBot/param"
15+
"github.com/yincongcyincong/MuseBot/utils"
16+
)
17+
18+
var (
19+
BaseFolderPath = utils.GetAbsPath("/conf/img/")
20+
oneLayerDirPaths = make(map[string][]string)
21+
twoLayerDirPaths = make(map[string][][]string)
22+
validExtensions = map[string]bool{
23+
".jpg": true,
24+
".jpeg": true,
25+
".png": true,
26+
".gif": true,
27+
".webp": true,
28+
}
29+
)
30+
31+
func initImg() {
32+
conf.BaseConfInfo.ImageDay = -1
33+
err := loadAllImagePaths()
34+
if err != nil {
35+
logger.Error("Failed to complete image path loading process", "error", err)
36+
}
37+
}
38+
39+
func loadTwoLayerPaths(imageType string, dirPath string) error {
40+
// 读取第一层子目录 (例如 CategoryA, CategoryB)
41+
categoryDirs, err := ioutil.ReadDir(dirPath)
42+
if err != nil {
43+
logger.Error("Failed to read 2-layer base directory", "directory", dirPath, "error", err)
44+
return err
45+
}
46+
47+
var allPathsForThisType [][]string // 用于存储所有类别的图片路径
48+
49+
for _, catDir := range categoryDirs {
50+
if !catDir.IsDir() {
51+
continue // 跳过非目录文件,只处理子目录
52+
}
53+
54+
categoryName := catDir.Name()
55+
currentCategoryPath := filepath.Join(dirPath, categoryName)
56+
57+
// 读取第二层子目录中的文件
58+
files, err := ioutil.ReadDir(currentCategoryPath)
59+
if err != nil {
60+
logger.Error("Failed to read 2-layer category directory", "directory", currentCategoryPath, "error", err)
61+
continue
62+
}
63+
64+
var pathsInThisCategory []string
65+
for _, file := range files {
66+
if !file.IsDir() {
67+
ext := strings.ToLower(filepath.Ext(file.Name()))
68+
if validExtensions[ext] {
69+
fullPath := filepath.Join(currentCategoryPath, file.Name())
70+
pathsInThisCategory = append(pathsInThisCategory, fullPath)
71+
}
72+
}
73+
}
74+
75+
if len(pathsInThisCategory) > 0 {
76+
// 将当前类别下的所有图片路径作为一个切片存入
77+
allPathsForThisType = append(allPathsForThisType, pathsInThisCategory)
78+
logger.Info("Loaded 2-layer category paths", "type", imageType, "category", categoryName, "count", len(pathsInThisCategory))
79+
}
80+
}
81+
82+
if len(allPathsForThisType) > 0 {
83+
twoLayerDirPaths[strings.ToLower(imageType)] = allPathsForThisType
84+
}
85+
86+
return nil
87+
}
88+
89+
// loadAllImagePaths 是主加载函数,负责区分一层和两层结构
90+
func loadAllImagePaths() error {
91+
oneLayerDirPaths = make(map[string][]string)
92+
twoLayerDirPaths = make(map[string][][]string)
93+
94+
dirs, err := ioutil.ReadDir(BaseFolderPath)
95+
if err != nil {
96+
logger.Error("Failed to read image directory", "directory", BaseFolderPath, "error", err)
97+
return err
98+
}
99+
100+
for _, dir := range dirs {
101+
if !dir.IsDir() {
102+
continue // Skip non-directory files
103+
}
104+
105+
imageType := dir.Name() // 文件夹名作为 'type'
106+
currentDirPath := filepath.Join(BaseFolderPath, imageType)
107+
108+
// 1. 读取当前目录内容以判断结构
109+
contents, err := ioutil.ReadDir(currentDirPath)
110+
if err != nil {
111+
logger.Error("Failed to read directory contents", "directory", currentDirPath, "error", err)
112+
continue
113+
}
114+
115+
// 2. 检查目录中是否存在子目录,以判断是否为 2-Layer 结构
116+
isTwoLayer := false
117+
for _, content := range contents {
118+
if content.IsDir() {
119+
isTwoLayer = true
120+
break
121+
}
122+
}
123+
124+
if isTwoLayer {
125+
err = loadTwoLayerPaths(imageType, currentDirPath)
126+
if err != nil {
127+
logger.Error("Failed to load 2-layer paths", "type", imageType, "error", err)
128+
}
129+
continue
130+
}
131+
132+
// --- 1-Layer Structure (如果没有子目录,则按一层结构处理) ---
133+
var paths []string
134+
for _, file := range contents {
135+
if !file.IsDir() {
136+
ext := strings.ToLower(filepath.Ext(file.Name()))
137+
if validExtensions[ext] {
138+
fullPath := filepath.Join(currentDirPath, file.Name())
139+
paths = append(paths, fullPath)
140+
}
141+
}
142+
}
143+
144+
if len(paths) > 0 {
145+
oneLayerDirPaths[strings.ToLower(imageType)] = paths
146+
logger.Info("Loaded 1-layer image paths", "type", imageType, "count", len(paths))
147+
}
148+
}
149+
150+
// 最终总结日志
151+
logger.Info("Image Loading Completed",
152+
"one_layer_types_count", len(oneLayerDirPaths),
153+
"two_layer_types_count", len(twoLayerDirPaths))
154+
155+
lestTwoLayerNum := -1
156+
for _, paths := range twoLayerDirPaths {
157+
if lestTwoLayerNum == -1 {
158+
lestTwoLayerNum = len(paths)
159+
}
160+
if len(paths) < lestTwoLayerNum {
161+
lestTwoLayerNum = len(paths)
162+
}
163+
}
164+
165+
if lestTwoLayerNum > 0 {
166+
conf.BaseConfInfo.ImageDay = time.Now().YearDay() % lestTwoLayerNum
167+
}
168+
169+
return nil
170+
}
171+
172+
func imageHandler(w http.ResponseWriter, r *http.Request) {
173+
ctx := r.Context()
174+
query := r.URL.Query()
175+
imageType := strings.ToLower(query.Get("type"))
176+
if imageType == "" {
177+
logger.Error("Missing 'type' query parameter in image request")
178+
utils.Failure(ctx, w, r, param.CodeParamError, param.MsgParamError, "")
179+
return
180+
}
181+
182+
rStr := query.Get("rand")
183+
rInt := utils.ParseInt(rStr)
184+
185+
var imagePath string
186+
if _, ok := twoLayerDirPaths[imageType]; ok {
187+
dayIdx := time.Now().YearDay() % len(twoLayerDirPaths[imageType])
188+
imgNum := len(twoLayerDirPaths[imageType][dayIdx])
189+
imagePath = twoLayerDirPaths[imageType][dayIdx][int(rInt)%imgNum]
190+
} else if _, ok := oneLayerDirPaths[imageType]; ok {
191+
imgNum := len(oneLayerDirPaths[imageType])
192+
imagePath = oneLayerDirPaths[imageType][int(rInt)%imgNum]
193+
}
194+
195+
selectedFileName := filepath.Base(imagePath)
196+
197+
file, err := os.Open(imagePath)
198+
if err != nil {
199+
logger.Error("Failed to open image file", "file", imagePath, "error", err)
200+
utils.Failure(ctx, w, r, param.CodeParamError, param.MsgParamError, "")
201+
return
202+
}
203+
defer file.Close()
204+
205+
mimeType := getMimeType(selectedFileName)
206+
w.Header().Set("Content-Type", mimeType)
207+
w.Header().Set("X-Selected-File", selectedFileName) // Debug info
208+
209+
_, err = io.Copy(w, file)
210+
if err != nil {
211+
logger.Error("Failed to copy image file to response", "file", imagePath, "error", err)
212+
}
213+
}
214+
215+
// getMimeType infers the MIME Type based on the file extension.
216+
func getMimeType(filename string) string {
217+
ext := strings.ToLower(filepath.Ext(filename))
218+
switch ext {
219+
case ".jpg", ".jpeg":
220+
return "image/jpeg"
221+
case ".png":
222+
return "image/png"
223+
case ".gif":
224+
return "image/gif"
225+
case ".webp":
226+
return "image/webp"
227+
default:
228+
// Default for unknown or non-image types
229+
return "application/octet-stream"
230+
}
231+
}

0 commit comments

Comments
 (0)