Files
homeagent-sdk/example/vanblog/plugin.go
JianFeeeee b6a6168e65 chore(module)!: module 路径随托管地迁移 gitcode → github
SDK 仓已迁到 GitHub 作为权威源,go module 路径同步:
`gitcode.com/JianFeeeee/homeagent-sdk` → `github.com/JianFeeeee/homeagentsdk`

## 范围

74 个文件:71 个 Go 源码 + go.mod/go.sum + hmapdev 模板 + CI 与打包脚本。

主仓(HomeAgent)侧对应改动是 6d38a164。本仓这些文件当时**不在工作树上**
(见下),所以那次的批量替换扫不到它们——这里是补偿。

## 为何这批文件当时不在工作树上(事故与恢复)

10-08 07:58 主仓做 checkout/cherry-pick 序列时,把本仓工作树的 20 个文件
误清理了(`example/*/plugin.go` + `plg.json`,10 个示例)。

根因是**结构误判**:本仓是「一目录两仓库」结构下主仓 `third_party/` 里的
**独立 git 仓库**,而主仓 .gitignore 忽略了 `example/`,于是它在主仓
`git status` 里不可见 —— 看着像「不受跟踪的构建产物」,实际是本仓受跟踪的源码。

已全部恢复(`git checkout -- <D 文件>`,只恢复删除、不动迁移改动),
20 个文件与 HEAD 逐字节一致。

**破坏的连锁后果**:恢复出来的 `plugin.go` 仍写着旧 module 路径,
因为迁移时它们已不在工作树上。所以这次补偿是必须的,不是可选优化。

## 保留 gitcode 引用

以下位置的 `gitcode` **有意保留**,不要"顺手清掉":
- README.md / docs 里「二进制实际分发地址目前是 gitcode」的说明
- mkdocs.yml 的 gitcode 镜像链接(国内直连更快)
- `tools/hmapdev/templates.go` 里「SDK 已从 gitcode 迁到 GitHub」的注释

gitcode 仍是**只读镜像**(源码同步;release 附件暂时仍在那边)。

## 验证

- 本仓根 module:`go build ./...` + `go test ./...` 全通过
- 全部 20 个 example:`go vet` 通过(补偿前 10 个因旧 module 路径失败)
- example/qq:`go test` 通过,且能从源码 build 出可用 plugin 产物
2026-10-09 07:25:25 +08:00

1335 lines
45 KiB
Go
Raw 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 "github.com/JianFeeeee/homeagentsdk/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": "草稿ID(get/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": "流水线ID(get/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": "协作者ID(delete)"},
}, "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 ID(delete)"},
"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)
}