Files
homeagent-sdk/example/vanblog/plugin.go

1335 lines
45 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"strings"
"time"
sdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
)
type Plugin struct {
name string
sdk *sdk.PluginSDK
http *http.Client
baseURL string
token string
resetToken string
}
func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
return &Plugin{name: name}, nil
}
func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) {
return &Plugin{name: name}, nil
}
func (p *Plugin) Name() string { return p.name }
func getSetting(s sdk.SettingsAPI, key string, fallback string) string {
v, err := s.Get(key)
if err != nil || v == nil {
return fallback
}
if sv, ok := v.(string); ok {
return sv
}
return fallback
}
func readArg(args map[string]interface{}, key string) string {
if v, ok := args[key]; ok && v != nil {
if s, ok := v.(string); ok {
return s
}
}
return ""
}
func readArgInt(args map[string]interface{}, key string, fallback int) int {
if v, ok := args[key]; ok && v != nil {
switch n := v.(type) {
case float64:
return int(n)
case int64:
return int(n)
}
}
return fallback
}
func readArgBool(args map[string]interface{}, key string) *bool {
if v, ok := args[key]; ok {
if b, ok := v.(bool); ok {
return &b
}
}
return nil
}
func errResult(msg string) map[string]interface{} {
return map[string]interface{}{"isError": true, "content": msg}
}
func (p *Plugin) Start(s *sdk.PluginSDK) error {
p.sdk = s
s.SetAutoRestart(true)
s.Settings().RegisterDef(sdk.ConfigDef{
Key: "url", Default: "https://blog.jianfgit.xyz", Type: "string",
DisplayName: "VanBlog URL", Description: "VanBlog 站点基地址",
Category: "vanblog",
})
s.Settings().RegisterDef(sdk.ConfigDef{
Key: "token", Default: "", Type: "password",
DisplayName: "API Token", Description: "管理员 API Token长期令牌从后台 Token 管理创建)",
Category: "vanblog",
})
s.Settings().RegisterDef(sdk.ConfigDef{
Key: "reset_token", Default: "", Type: "password",
DisplayName: "重置密码 Token", Description: "用于 auth/restore 重置管理员密码的特殊 Token",
Category: "vanblog",
})
p.http = &http.Client{Timeout: 30 * time.Second}
p.baseURL = strings.TrimRight(getSetting(s.Settings(), "url", "https://blog.jianfgit.xyz"), "/")
p.token = getSetting(s.Settings(), "token", "")
p.resetToken = getSetting(s.Settings(), "reset_token", "")
if p.token == "" {
if coreVal, err := s.Settings().GetCore("plugin.vanblog.token"); err == nil && coreVal != nil {
if sv, ok := coreVal.(string); ok && sv != "" {
p.token = sv
s.Settings().Set("token", sv)
s.Settings().SetCore("plugin.vanblog.token", nil)
log.Printf("[vanblog] migrated token from core config to plugin config")
}
}
}
tp := p.name + "_"
s.RegisterTool(tp+"list_articles", sdk.ToolDef{
Name: tp + "list_articles", Description: "列出 VanBlog 文章,支持分页和搜索",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"page": map[string]interface{}{"type": "integer", "description": "页码从1开始", "default": 1},
"pageSize": map[string]interface{}{"type": "integer", "description": "每页条数", "default": 50},
"keyword": map[string]interface{}{"type": "string", "description": "搜索关键词"},
"category": map[string]interface{}{"type": "string", "description": "按分类筛选"},
"tag": map[string]interface{}{"type": "string", "description": "按标签筛选"},
"sort": map[string]interface{}{"type": "string", "description": "排序: newest|oldest"},
},
},
}, p.handleListArticles)
s.RegisterTool(tp+"get_article", sdk.ToolDef{
Name: tp + "get_article", Description: "获取单篇文章完整内容",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"id": map[string]interface{}{"type": "string", "description": "文章 ID 或 pathname"},
}, "required": []string{"id"},
},
}, p.handleGetArticle)
s.RegisterTool(tp+"create_article", sdk.ToolDef{
Name: tp + "create_article", Description: "新建文章title 和 category 必填",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"title": map[string]interface{}{"type": "string", "description": "标题"},
"content": map[string]interface{}{"type": "string", "description": "Markdown 正文"},
"category": map[string]interface{}{"type": "string", "description": "分类"},
"tags": map[string]interface{}{"type": "string", "description": "标签逗号分隔"},
"pinned": map[string]interface{}{"type": "boolean", "description": "置顶"},
}, "required": []string{"title", "category"},
},
}, p.handleCreateArticle)
s.RegisterTool(tp+"update_article", sdk.ToolDef{
Name: tp + "update_article", Description: "更新文章,只传需改字段",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"id": map[string]interface{}{"type": "string", "description": "文章 ID"},
"title": map[string]interface{}{"type": "string", "description": "新标题"},
"content": map[string]interface{}{"type": "string", "description": "新正文"},
"category": map[string]interface{}{"type": "string", "description": "新分类"},
"tags": map[string]interface{}{"type": "string", "description": "新标签逗号分隔"},
"pinned": map[string]interface{}{"type": "boolean", "description": "置顶"},
}, "required": []string{"id"},
},
}, p.handleUpdateArticle)
s.RegisterTool(tp+"delete_article", sdk.ToolDef{
Name: tp + "delete_article", Description: "删除文章(软删除)",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"id": map[string]interface{}{"type": "string", "description": "文章 ID"},
}, "required": []string{"id"},
},
}, p.handleDeleteArticle)
s.RegisterTool(tp+"search_articles", sdk.ToolDef{
Name: tp + "search_articles", Description: "按链接搜索文章",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"link": map[string]interface{}{"type": "string", "description": "文章链接"},
}, "required": []string{"link"},
},
}, p.handleSearchArticles)
s.RegisterTool(tp+"manage_drafts", sdk.ToolDef{
Name: tp + "manage_drafts",
Description: "管理草稿。命令: list|get|create|update|delete|publish",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|get|create|update|delete|publish"},
"id": map[string]interface{}{"type": "string", "description": "草稿IDget/update/delete/publish需要"},
"title": map[string]interface{}{"type": "string", "description": "标题create/update"},
"content": map[string]interface{}{"type": "string", "description": "正文create/update"},
"category": map[string]interface{}{"type": "string", "description": "分类create/update"},
"tags": map[string]interface{}{"type": "string", "description": "标签逗号分隔create/update"},
"page": map[string]interface{}{"type": "integer", "description": "页码list"},
"pageSize": map[string]interface{}{"type": "integer", "description": "每页条数list"},
}, "required": []string{"command"},
},
}, p.handleManageDrafts)
s.RegisterTool(tp+"manage_categories", sdk.ToolDef{
Name: tp + "manage_categories",
Description: "管理分类。命令: list|get|create|update|delete",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|get|create|update|delete"},
"name": map[string]interface{}{"type": "string", "description": "分类名get/create/update/delete需要"},
"newName": map[string]interface{}{"type": "string", "description": "新名称update"},
}, "required": []string{"command"},
},
}, p.handleManageCategories)
s.RegisterTool(tp+"manage_tags", sdk.ToolDef{
Name: tp + "manage_tags",
Description: "管理标签。命令: list|get|rename|delete",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|get|rename|delete"},
"name": map[string]interface{}{"type": "string", "description": "标签名"},
"newName": map[string]interface{}{"type": "string", "description": "新名称rename"},
}, "required": []string{"command"},
},
}, p.handleManageTags)
s.RegisterTool(tp+"manage_images", sdk.ToolDef{
Name: tp + "manage_images",
Description: "管理图床图片。命令: list|all|upload|scan|export|delete|delete_all",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|all|upload|scan|export|delete|delete_all"},
"page": map[string]interface{}{"type": "integer", "description": "页码list"},
"pageSize": map[string]interface{}{"type": "integer", "description": "每页条数list"},
"file": map[string]interface{}{"type": "string", "description": "本地文件路径upload"},
"sign": map[string]interface{}{"type": "string", "description": "图片签名delete"},
"type": map[string]interface{}{"type": "string", "description": "图片类型upload: favicon/watermark/空)"},
}, "required": []string{"command"},
},
}, p.handleManageImages)
s.RegisterTool(tp+"manage_links", sdk.ToolDef{
Name: tp + "manage_links",
Description: "管理友情链接。命令: list|create|update|delete",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|create|update|delete"},
"data": map[string]interface{}{"type": "string", "description": "JSON数据create/update"},
"name": map[string]interface{}{"type": "string", "description": "链接名delete"},
}, "required": []string{"command"},
},
}, p.handleManageLinks)
s.RegisterTool(tp+"manage_social", sdk.ToolDef{
Name: tp + "manage_social",
Description: "管理社交链接。命令: list|types|create|update|delete",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|types|create|update|delete"},
"data": map[string]interface{}{"type": "string", "description": "JSON数据create/update"},
"stype": map[string]interface{}{"type": "string", "description": "社交类型delete"},
}, "required": []string{"command"},
},
}, p.handleManageSocial)
s.RegisterTool(tp+"manage_rewards", sdk.ToolDef{
Name: tp + "manage_rewards",
Description: "管理打赏设置。命令: list|create|update|delete",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|create|update|delete"},
"data": map[string]interface{}{"type": "string", "description": "JSON数据create/update"},
"name": map[string]interface{}{"type": "string", "description": "打赏项名delete"},
}, "required": []string{"command"},
},
}, p.handleManageRewards)
s.RegisterTool(tp+"manage_pages", sdk.ToolDef{
Name: tp + "manage_pages",
Description: "管理自定义页面。命令: list|get|create|update|delete|folder|file|upload|create_file|create_folder|update_file",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|get|create|update|delete|folder|file|upload|create_file|create_folder|update_file"},
"path": map[string]interface{}{"type": "string", "description": "页面路径"},
"data": map[string]interface{}{"type": "string", "description": "JSON数据create/update"},
"folder": map[string]interface{}{"type": "string", "description": "文件夹路径folder/file相关"},
"file": map[string]interface{}{"type": "string", "description": "文件路径file/upload相关"},
"content": map[string]interface{}{"type": "string", "description": "文件内容create_file/update_file"},
}, "required": []string{"command"},
},
}, p.handleManagePages)
s.RegisterTool(tp+"manage_settings", sdk.ToolDef{
Name: tp + "manage_settings",
Description: "管理站点设置。命令: get_static|set_static|get_waline|set_waline|get_layout|set_layout|get_login|set_login",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "get_static|set_static|get_waline|set_waline|get_layout|set_layout|get_login|set_login"},
"data": map[string]interface{}{"type": "string", "description": "JSON设置数据set_*命令)"},
}, "required": []string{"command"},
},
}, p.handleManageSettings)
s.RegisterTool(tp+"manage_about", sdk.ToolDef{
Name: tp + "manage_about",
Description: "管理关于页。命令: get|update",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "get|update"},
"content": map[string]interface{}{"type": "string", "description": "关于页内容update"},
}, "required": []string{"command"},
},
}, p.handleManageAbout)
s.RegisterTool(tp+"manage_site", sdk.ToolDef{
Name: tp + "manage_site",
Description: "管理站点信息。命令: get|update",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "get|update"},
"data": map[string]interface{}{"type": "string", "description": "JSON站点数据update"},
}, "required": []string{"command"},
},
}, p.handleManageSite)
s.RegisterTool(tp+"manage_menu", sdk.ToolDef{
Name: tp + "manage_menu",
Description: "管理导航菜单。命令: get|update",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "get|update"},
"data": map[string]interface{}{"type": "string", "description": "JSON菜单数据update"},
}, "required": []string{"command"},
},
}, p.handleManageMenu)
s.RegisterTool(tp+"get_analysis", sdk.ToolDef{
Name: tp + "get_analysis", Description: "获取访问统计数据",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"tab": map[string]interface{}{"type": "string", "description": "overview|viewer|article", "default": "overview"},
},
},
}, p.handleGetAnalysis)
s.RegisterTool(tp+"get_logs", sdk.ToolDef{
Name: tp + "get_logs", Description: "获取系统日志",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"page": map[string]interface{}{"type": "integer", "description": "页码"},
"pageSize": map[string]interface{}{"type": "integer", "description": "每页条数"},
"event": map[string]interface{}{"type": "string", "description": "按事件类型筛选"},
},
},
}, p.handleGetLogs)
s.RegisterTool(tp+"manage_backup", sdk.ToolDef{
Name: tp + "manage_backup",
Description: "备份管理。命令: export|import",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "export|import"},
"file": map[string]interface{}{"type": "string", "description": "备份JSON文件路径import"},
}, "required": []string{"command"},
},
}, p.handleManageBackup)
s.RegisterTool(tp+"manage_caddy", sdk.ToolDef{
Name: tp + "manage_caddy",
Description: "管理Caddy配置。命令: get_https|set_https|get_log|clear_log|get_config",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "get_https|set_https|get_log|clear_log|get_config"},
"data": map[string]interface{}{"type": "string", "description": "JSON数据set_https"},
}, "required": []string{"command"},
},
}, p.handleManageCaddy)
s.RegisterTool(tp+"manage_isr", sdk.ToolDef{
Name: tp + "manage_isr",
Description: "ISR增量渲染。命令: get|trigger|update",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "get|trigger|update"},
"data": map[string]interface{}{"type": "string", "description": "JSON数据update"},
}, "required": []string{"command"},
},
}, p.handleManageISR)
s.RegisterTool(tp+"manage_pipelines", sdk.ToolDef{
Name: tp + "manage_pipelines",
Description: "管理流水线。命令: list|get|config|create|trigger|update|delete",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|get|config|create|trigger|update|delete"},
"id": map[string]interface{}{"type": "string", "description": "流水线IDget/trigger/update/delete"},
"data": map[string]interface{}{"type": "string", "description": "JSON数据create/update"},
}, "required": []string{"command"},
},
}, p.handleManagePipelines)
s.RegisterTool(tp+"manage_collaborators", sdk.ToolDef{
Name: tp + "manage_collaborators",
Description: "管理协作者。命令: list|list_all|create|update|delete",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|list_all|create|update|delete"},
"data": map[string]interface{}{"type": "string", "description": "JSON数据create/update"},
"id": map[string]interface{}{"type": "string", "description": "协作者IDdelete"},
}, "required": []string{"command"},
},
}, p.handleManageCollaborators)
s.RegisterTool(tp+"manage_tokens", sdk.ToolDef{
Name: tp + "manage_tokens",
Description: "管理API Token。命令: list|create|delete",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "list|create|delete"},
"id": map[string]interface{}{"type": "string", "description": "Token IDdelete"},
"expiresIn": map[string]interface{}{"type": "integer", "description": "有效期毫秒create默认1年"},
}, "required": []string{"command"},
},
}, p.handleManageTokens)
s.RegisterTool(tp+"auth", sdk.ToolDef{
Name: tp + "auth",
Description: "VanBlog认证。命令: login|logout|restore|update",
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{
"command": map[string]interface{}{"type": "string", "description": "login|logout|restore|update"},
"username": map[string]interface{}{"type": "string", "description": "用户名login"},
"password": map[string]interface{}{"type": "string", "description": "密码login/restore/update"},
"newName": map[string]interface{}{"type": "string", "description": "新用户名update"},
"newPassword": map[string]interface{}{"type": "string", "description": "新密码update"},
}, "required": []string{"command"},
},
}, p.handleAuth)
s.RegisterTool(tp+"get_meta", sdk.ToolDef{
Name: tp + "get_meta", Description: "获取VanBlog元信息版本、站点等",
Parameters: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}},
}, p.handleGetMeta)
log.Printf("[vanblog] started: %s (url=%s, token=%t)", p.name, p.baseURL, p.token != "")
return nil
}
func (p *Plugin) Stop() error { return nil }
func (p *Plugin) do(method, path string, body interface{}) (interface{}, error) {
return p.doRaw(method, path, body, nil)
}
func (p *Plugin) doRaw(method, path string, body interface{}, headers map[string]string) (interface{}, error) {
url := p.baseURL + path
var reqBody io.Reader
if body != nil {
b, _ := json.Marshal(body)
reqBody = bytes.NewReader(b)
}
req, err := http.NewRequest(method, url, reqBody)
if err != nil {
return nil, fmt.Errorf("请求失败: %w", err)
}
if p.token != "" {
req.Header.Set("token", p.token)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := p.http.Do(req)
if err != nil {
return nil, fmt.Errorf("请求失败: %w", err)
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(raw))
}
var result interface{}
if err := json.Unmarshal(raw, &result); err != nil {
return string(raw), nil
}
return result, nil
}
func jsonArg(args map[string]interface{}, key string) (map[string]interface{}, bool) {
s := readArg(args, key)
if s == "" {
return nil, false
}
var m map[string]interface{}
if json.Unmarshal([]byte(s), &m) != nil {
return nil, false
}
return m, true
}
// ======== Article Handlers ========
func (p *Plugin) handleListArticles(args map[string]interface{}) (interface{}, error) {
page := readArgInt(args, "page", 1)
pageSize := readArgInt(args, "pageSize", 50)
path := fmt.Sprintf("/api/admin/article?page=%d&pageSize=%d", page, pageSize)
if v := readArg(args, "keyword"); v != "" {
path += "&keyword=" + v
}
if v := readArg(args, "category"); v != "" {
path += "&category=" + v
}
if v := readArg(args, "tag"); v != "" {
path += "&tag=" + v
}
if v := readArg(args, "sort"); v != "" {
path += "&sort=" + v
}
return p.do("GET", path, nil)
}
func (p *Plugin) handleGetArticle(args map[string]interface{}) (interface{}, error) {
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
return p.do("GET", "/api/admin/article/"+id, nil)
}
func (p *Plugin) handleCreateArticle(args map[string]interface{}) (interface{}, error) {
title := readArg(args, "title")
category := readArg(args, "category")
if title == "" || category == "" {
return errResult("title 和 category 不能为空"), nil
}
body := map[string]interface{}{"title": title, "category": category}
if v := readArg(args, "content"); v != "" {
body["content"] = v
}
if v := readArg(args, "tags"); v != "" {
body["tags"] = strings.Split(v, ",")
}
if v := readArgBool(args, "pinned"); v != nil {
body["pinned"] = *v
}
return p.do("POST", "/api/admin/article", body)
}
func (p *Plugin) handleUpdateArticle(args map[string]interface{}) (interface{}, error) {
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
body := map[string]interface{}{}
if v := readArg(args, "title"); v != "" {
body["title"] = v
}
if v := readArg(args, "content"); v != "" {
body["content"] = v
}
if v := readArg(args, "category"); v != "" {
body["category"] = v
}
if v := readArg(args, "tags"); v != "" {
body["tags"] = strings.Split(v, ",")
}
if v := readArgBool(args, "pinned"); v != nil {
body["pinned"] = *v
}
return p.do("PUT", "/api/admin/article/"+id, body)
}
func (p *Plugin) handleDeleteArticle(args map[string]interface{}) (interface{}, error) {
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
return p.do("DELETE", "/api/admin/article/"+id, nil)
}
func (p *Plugin) handleSearchArticles(args map[string]interface{}) (interface{}, error) {
link := readArg(args, "link")
if link == "" {
return errResult("link 不能为空"), nil
}
return p.do("POST", "/api/admin/article/searchByLink", map[string]interface{}{"link": link})
}
// ======== Draft ========
func (p *Plugin) handleManageDrafts(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
page := readArgInt(args, "page", 1)
pageSize := readArgInt(args, "pageSize", 50)
return p.do("GET", fmt.Sprintf("/api/admin/draft?page=%d&pageSize=%d", page, pageSize), nil)
case "get":
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
return p.do("GET", "/api/admin/draft/"+id, nil)
case "create":
body := map[string]interface{}{}
if v := readArg(args, "title"); v != "" {
body["title"] = v
}
if v := readArg(args, "content"); v != "" {
body["content"] = v
}
if v := readArg(args, "category"); v != "" {
body["category"] = v
}
if v := readArg(args, "tags"); v != "" {
body["tags"] = strings.Split(v, ",")
}
return p.do("POST", "/api/admin/draft", body)
case "update":
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
body := map[string]interface{}{}
if v := readArg(args, "title"); v != "" {
body["title"] = v
}
if v := readArg(args, "content"); v != "" {
body["content"] = v
}
if v := readArg(args, "category"); v != "" {
body["category"] = v
}
if v := readArg(args, "tags"); v != "" {
body["tags"] = strings.Split(v, ",")
}
return p.do("PUT", "/api/admin/draft/"+id, body)
case "delete":
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
return p.do("DELETE", "/api/admin/draft/"+id, nil)
case "publish":
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
return p.do("POST", "/api/admin/draft/publish", map[string]interface{}{"id": id})
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Category ========
func (p *Plugin) handleManageCategories(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
return p.do("GET", "/api/admin/category/all", nil)
case "get":
name := readArg(args, "name")
if name == "" {
return errResult("name 不能为空"), nil
}
return p.do("GET", "/api/admin/category/"+name, nil)
case "create":
name := readArg(args, "name")
if name == "" {
return errResult("name 不能为空"), nil
}
return p.do("POST", "/api/admin/category", map[string]interface{}{"name": name})
case "update":
name := readArg(args, "name")
if name == "" {
return errResult("name 不能为空"), nil
}
newName := readArg(args, "newName")
return p.do("PUT", "/api/admin/category/"+name, map[string]interface{}{"name": newName})
case "delete":
name := readArg(args, "name")
if name == "" {
return errResult("name 不能为空"), nil
}
return p.do("DELETE", "/api/admin/category/"+name, nil)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Tag ========
func (p *Plugin) handleManageTags(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
return p.do("GET", "/api/admin/tag/all", nil)
case "get":
name := readArg(args, "name")
if name == "" {
return errResult("name 不能为空"), nil
}
return p.do("GET", "/api/admin/tag/"+name, nil)
case "rename":
name := readArg(args, "name")
newName := readArg(args, "newName")
if name == "" || newName == "" {
return errResult("name 和 newName 不能为空"), nil
}
return p.do("PUT", "/api/admin/tag/"+name, map[string]interface{}{"name": newName})
case "delete":
name := readArg(args, "name")
if name == "" {
return errResult("name 不能为空"), nil
}
return p.do("DELETE", "/api/admin/tag/"+name, nil)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Image ========
func (p *Plugin) handleManageImages(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
page := readArgInt(args, "page", 1)
pageSize := readArgInt(args, "pageSize", 50)
return p.do("GET", fmt.Sprintf("/api/admin/img?page=%d&pageSize=%d", page, pageSize), nil)
case "all":
return p.do("GET", "/api/admin/img/all", nil)
case "upload":
filePath := readArg(args, "file")
if filePath == "" {
return errResult("file 路径不能为空"), nil
}
imgType := readArg(args, "type")
return p.uploadImage(filePath, imgType)
case "scan":
return p.do("POST", "/api/admin/img/scan", nil)
case "export":
return p.do("POST", "/api/admin/img/export", nil)
case "delete":
sign := readArg(args, "sign")
if sign == "" {
return errResult("sign 不能为空"), nil
}
return p.do("DELETE", "/api/admin/img/"+sign, nil)
case "delete_all":
return p.do("DELETE", "/api/admin/img/all/delete", nil)
default:
return errResult("未知命令: " + cmd), nil
}
}
func (p *Plugin) uploadImage(filePath, imgType string) (interface{}, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("打开文件失败: %w", err)
}
defer f.Close()
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
part, err := w.CreateFormFile("image", filePath)
if err != nil {
return nil, fmt.Errorf("创建表单失败: %w", err)
}
if _, err := io.Copy(part, f); err != nil {
return nil, fmt.Errorf("写入文件失败: %w", err)
}
if imgType != "" {
w.WriteField("type", imgType)
}
w.Close()
req, err := http.NewRequest("POST", p.baseURL+"/api/admin/img/upload", &buf)
if err != nil {
return nil, fmt.Errorf("创建请求失败: %w", err)
}
req.Header.Set("token", p.token)
req.Header.Set("Content-Type", w.FormDataContentType())
resp, err := p.http.Do(req)
if err != nil {
return nil, fmt.Errorf("上传失败: %w", err)
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(raw))
}
var result interface{}
if json.Unmarshal(raw, &result) != nil {
return string(raw), nil
}
return result, nil
}
// ======== Link ========
func (p *Plugin) handleManageLinks(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
return p.do("GET", "/api/admin/meta/link", nil)
case "create":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("POST", "/api/admin/meta/link", data)
case "update":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/meta/link", data)
case "delete":
name := readArg(args, "name")
if name == "" {
return errResult("name 不能为空"), nil
}
return p.do("DELETE", "/api/admin/meta/link/"+name, nil)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Social ========
func (p *Plugin) handleManageSocial(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
return p.do("GET", "/api/admin/meta/social", nil)
case "types":
return p.do("GET", "/api/admin/meta/social/types", nil)
case "create":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("POST", "/api/admin/meta/social", data)
case "update":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/meta/social", data)
case "delete":
stype := readArg(args, "stype")
if stype == "" {
return errResult("stype 不能为空"), nil
}
return p.do("DELETE", "/api/admin/meta/social/"+stype, nil)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Reward ========
func (p *Plugin) handleManageRewards(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
return p.do("GET", "/api/admin/meta/reward", nil)
case "create":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("POST", "/api/admin/meta/reward", data)
case "update":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/meta/reward", data)
case "delete":
name := readArg(args, "name")
if name == "" {
return errResult("name 不能为空"), nil
}
return p.do("DELETE", "/api/admin/meta/reward/"+name, nil)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Custom Page ========
func (p *Plugin) handleManagePages(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
return p.do("GET", "/api/admin/customPage/all", nil)
case "get":
path := readArg(args, "path")
if path == "" {
return p.do("GET", "/api/admin/customPage", nil)
}
return p.do("GET", "/api/admin/customPage?path="+path, nil)
case "create":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("POST", "/api/admin/customPage", data)
case "update":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/customPage", data)
case "delete":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("DELETE", "/api/admin/customPage", data)
case "folder":
folder := readArg(args, "folder")
return p.do("GET", "/api/admin/customPage/folder?folder="+folder, nil)
case "file":
file := readArg(args, "file")
return p.do("GET", "/api/admin/customPage/file?file="+file, nil)
case "upload":
filePath := readArg(args, "file")
if filePath == "" {
return errResult("file 路径不能为空"), nil
}
return p.uploadCustomPageFile(filePath)
case "create_file":
file := readArg(args, "file")
content := readArg(args, "content")
return p.do("POST", "/api/admin/customPage/file", map[string]interface{}{"file": file, "content": content})
case "create_folder":
folder := readArg(args, "folder")
return p.do("POST", "/api/admin/customPage/folder", map[string]interface{}{"folder": folder})
case "update_file":
file := readArg(args, "file")
content := readArg(args, "content")
return p.do("PUT", "/api/admin/customPage/file", map[string]interface{}{"file": file, "content": content})
default:
return errResult("未知命令: " + cmd), nil
}
}
func (p *Plugin) uploadCustomPageFile(filePath string) (interface{}, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("打开文件失败: %w", err)
}
defer f.Close()
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
part, err := w.CreateFormFile("file", filePath)
if err != nil {
return nil, fmt.Errorf("创建表单失败: %w", err)
}
if _, err := io.Copy(part, f); err != nil {
return nil, fmt.Errorf("写入文件失败: %w", err)
}
w.Close()
req, err := http.NewRequest("POST", p.baseURL+"/api/admin/customPage/upload", &buf)
if err != nil {
return nil, fmt.Errorf("创建请求失败: %w", err)
}
req.Header.Set("token", p.token)
req.Header.Set("Content-Type", w.FormDataContentType())
resp, err := p.http.Do(req)
if err != nil {
return nil, fmt.Errorf("上传失败: %w", err)
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(raw))
}
var result interface{}
if json.Unmarshal(raw, &result) != nil {
return string(raw), nil
}
return result, nil
}
// ======== Settings ========
func (p *Plugin) handleManageSettings(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "get_static":
return p.do("GET", "/api/admin/setting/static", nil)
case "set_static":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/setting/static", data)
case "get_waline":
return p.do("GET", "/api/admin/setting/waline", nil)
case "set_waline":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/setting/waline", data)
case "get_layout":
return p.do("GET", "/api/admin/setting/layout", nil)
case "set_layout":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/setting/layout", data)
case "get_login":
return p.do("GET", "/api/admin/setting/login", nil)
case "set_login":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/setting/login", data)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== About ========
func (p *Plugin) handleManageAbout(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "get":
return p.do("GET", "/api/admin/meta/about", nil)
case "update":
content := readArg(args, "content")
return p.do("PUT", "/api/admin/meta/about", map[string]interface{}{"content": content})
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Site ========
func (p *Plugin) handleManageSite(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "get":
return p.do("GET", "/api/admin/meta/site", nil)
case "update":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/meta/site", data)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Menu ========
func (p *Plugin) handleManageMenu(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "get":
return p.do("GET", "/api/admin/meta/menu", nil)
case "update":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/meta/menu", data)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Analysis ========
func (p *Plugin) handleGetAnalysis(args map[string]interface{}) (interface{}, error) {
tab := readArg(args, "tab")
if tab == "" {
tab = "overview"
}
return p.do("GET", "/api/admin/analysis?tab="+tab, nil)
}
// ======== Log ========
func (p *Plugin) handleGetLogs(args map[string]interface{}) (interface{}, error) {
page := readArgInt(args, "page", 1)
pageSize := readArgInt(args, "pageSize", 50)
path := fmt.Sprintf("/api/admin/log?page=%d&pageSize=%d", page, pageSize)
if v := readArg(args, "event"); v != "" {
path += "&event=" + v
}
return p.do("GET", path, nil)
}
// ======== Backup ========
func (p *Plugin) handleManageBackup(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "export":
return p.do("GET", "/api/admin/backup/export", nil)
case "import":
filePath := readArg(args, "file")
if filePath == "" {
return errResult("file 路径不能为空"), nil
}
data, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("读取备份文件失败: %w", err)
}
var body interface{}
if json.Unmarshal(data, &body) != nil {
return errResult("无效的备份JSON"), nil
}
return p.do("POST", "/api/admin/backup/import", body)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Caddy ========
func (p *Plugin) handleManageCaddy(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "get_https":
return p.do("GET", "/api/admin/caddy/https", nil)
case "set_https":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/caddy/https", data)
case "get_log":
return p.do("GET", "/api/admin/caddy/log", nil)
case "clear_log":
return p.do("DELETE", "/api/admin/caddy/log", nil)
case "get_config":
return p.do("GET", "/api/admin/caddy/config", nil)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== ISR ========
func (p *Plugin) handleManageISR(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "get":
return p.do("GET", "/api/admin/isr", nil)
case "trigger":
return p.do("POST", "/api/admin/isr", nil)
case "update":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/isr", data)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Pipeline ========
func (p *Plugin) handleManagePipelines(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
return p.do("GET", "/api/admin/pipeline", nil)
case "config":
return p.do("GET", "/api/admin/pipeline/config", nil)
case "get":
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
return p.do("GET", "/api/admin/pipeline/"+id, nil)
case "create":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("POST", "/api/admin/pipeline", data)
case "trigger":
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
return p.do("POST", "/api/admin/pipeline/trigger/"+id, nil)
case "update":
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/pipeline/"+id, data)
case "delete":
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
return p.do("DELETE", "/api/admin/pipeline/"+id, nil)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Collaborator ========
func (p *Plugin) handleManageCollaborators(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
return p.do("GET", "/api/admin/collaborator", nil)
case "list_all":
return p.do("GET", "/api/admin/collaborator/list", nil)
case "create":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("POST", "/api/admin/collaborator", data)
case "update":
data, ok := jsonArg(args, "data")
if !ok {
return errResult("data JSON 不能为空"), nil
}
return p.do("PUT", "/api/admin/collaborator", data)
case "delete":
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
return p.do("DELETE", "/api/admin/collaborator/"+id, nil)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Token ========
func (p *Plugin) handleManageTokens(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "list":
return p.do("GET", "/api/admin/token", nil)
case "create":
expiresIn := readArgInt(args, "expiresIn", 3153600000)
return p.do("POST", "/api/admin/token", map[string]interface{}{"expiresIn": expiresIn})
case "delete":
id := readArg(args, "id")
if id == "" {
return errResult("id 不能为空"), nil
}
return p.do("DELETE", "/api/admin/token/"+id, nil)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Auth ========
func (p *Plugin) handleAuth(args map[string]interface{}) (interface{}, error) {
cmd := readArg(args, "command")
switch cmd {
case "login":
username := readArg(args, "username")
password := readArg(args, "password")
if username == "" || password == "" {
return errResult("username 和 password 不能为空"), nil
}
result, err := p.do("POST", "/api/admin/auth/login", map[string]interface{}{"username": username, "password": password})
if err != nil {
return nil, err
}
if m, ok := result.(map[string]interface{}); ok {
if data, ok := m["data"].(map[string]interface{}); ok {
if token, ok := data["token"].(string); ok && token != "" {
p.token = token
p.sdk.Settings().Set("token", token)
}
}
}
return result, nil
case "logout":
return p.do("POST", "/api/admin/auth/logout", nil)
case "restore":
password := readArg(args, "password")
if password == "" {
return errResult("password 不能为空"), nil
}
return p.doRaw("POST", "/api/admin/auth/restore", map[string]interface{}{"password": password}, map[string]string{"token": p.resetToken})
case "update":
body := map[string]interface{}{}
if v := readArg(args, "newName"); v != "" {
body["name"] = v
}
if v := readArg(args, "newPassword"); v != "" {
body["password"] = v
}
return p.do("PUT", "/api/admin/auth", body)
default:
return errResult("未知命令: " + cmd), nil
}
}
// ======== Meta ========
func (p *Plugin) handleGetMeta(args map[string]interface{}) (interface{}, error) {
return p.do("GET", "/api/admin/meta", nil)
}