Merge pull request #348 from aa2013/1.1.0

括彩云CDN插件
1.1.0
v-me-50 2025-08-27 09:15:33 +08:00 committed by GitHub
commit de385343a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 261 additions and 0 deletions

165
plugins/kuocai/action.go Normal file
View File

@ -0,0 +1,165 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net/http"
"net/url"
"strings"
"time"
)
type LoginInfo struct {
account string
password string
}
type LoginResponse struct {
Code string
Message string
Data string
Success bool
}
type HTTPSConfig struct {
HTTPSStatus string `json:"https_status"`
CertificateSource string `json:"certificate_source"`
CertificateName string `json:"certificate_name"`
CertificateValue string `json:"certificate_value"`
PrivateKey string `json:"private_key"`
}
type DeployConfig struct {
DomainID string `json:"doMainId"`
HTTPS HTTPSConfig `json:"https"`
}
func randomString(n int) string {
rand.Seed(time.Now().UnixNano())
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b)
}
const baseUrl = "https://www.kuocaiyun.com/"
func login(loginInfo LoginInfo) (string, error) {
loginUrl := baseUrl + "login/loginUser"
formData := url.Values{}
formData.Set("userAccount", loginInfo.account)
formData.Set("userPwd", loginInfo.password)
formData.Set("remember", "true")
resp, err := http.Post(loginUrl, "application/x-www-form-urlencoded", strings.NewReader(formData.Encode()))
const emptyString = ""
if err != nil {
outputError("请求失败", err)
return emptyString, err
}
if resp.StatusCode != 200 {
return emptyString, errors.New("请求失败:" + string(rune(resp.StatusCode)))
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
outputError("关闭响应流失败", err)
}
}(resp.Body)
// 读取响应体
var loginResp LoginResponse
if err := json.NewDecoder(resp.Body).Decode(&loginResp); err != nil {
outputError("解析登录请求结果失败", err)
return emptyString, err
}
if !loginResp.Success || loginResp.Data == "" {
return emptyString, errors.New("登录请求失败:" + loginResp.Message)
}
return loginResp.Data, nil
}
func deployCert(token string, domainId string, certKey string, certValue string) (map[string]interface{}, error) {
deployUrl := baseUrl + "CdnDomainHttps/httpsConfiguration"
params := DeployConfig{
DomainID: domainId,
HTTPS: HTTPSConfig{
HTTPSStatus: "on",
CertificateSource: "0",
CertificateName: "cert_" + randomString(13),
CertificateValue: certValue,
PrivateKey: certKey,
},
}
// 序列化参数
jsonData, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("JSON序列化失败: %v", err)
}
req, err := http.NewRequest("POST", deployUrl, strings.NewReader(string(jsonData)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Cookie", "kuocai_cdn_token="+token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("HTTP请求失败: %v", err)
}
defer resp.Body.Close()
// 解析响应
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("响应解析失败: %v", err)
}
if !result["success"].(bool) {
return result, fmt.Errorf("更新证书失败")
}
return result, nil
}
func Upload(cfg map[string]any) (*Response, error) {
if cfg == nil {
return nil, fmt.Errorf("config cannot be nil")
}
certStr, ok := cfg["cert"].(string)
if !ok || certStr == "" {
return nil, fmt.Errorf("cert is required and must be a string")
}
keyStr, ok := cfg["key"].(string)
if !ok || keyStr == "" {
return nil, fmt.Errorf("key is required and must be a string")
}
username, ok := cfg["username"].(string)
if !ok || username == "" {
return nil, fmt.Errorf("username is required and must be a string")
}
password, ok := cfg["password"].(string)
if !ok || password == "" {
return nil, fmt.Errorf("password is required and must be a string")
}
domainId, ok := cfg["domainId"].(string)
if !ok || domainId == "" {
return nil, fmt.Errorf("domainId is required and must be a string")
}
token, err := login(LoginInfo{account: username, password: password})
if err != nil || token == "" {
return nil, fmt.Errorf("fetch token failed, err %v", err)
}
res, err := deployCert(token, domainId, keyStr, certStr)
if err != nil {
return nil, err
}
return &Response{
Status: "success",
Message: "success",
Result: res,
}, nil
}

96
plugins/kuocai/main.go Normal file
View File

@ -0,0 +1,96 @@
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
type ActionInfo struct {
Name string `json:"name"`
Description string `json:"description"`
Params map[string]any `json:"params,omitempty"`
}
type Request struct {
Action string `json:"action"`
Params map[string]interface{} `json:"params"`
}
type Response struct {
Status string `json:"status"`
Message string `json:"message"`
Result map[string]interface{} `json:"result"`
}
var pluginMeta = map[string]interface{}{
"name": "kuocai",
"description": "部署到括彩云",
"version": "1.0.0",
"author": "coclyun",
"config": map[string]interface{}{
"username": "括彩云账号",
"password": "括彩云密码",
},
"actions": []ActionInfo{
{
Name: "upload",
Description: "部署到括彩云",
Params: map[string]any{},
},
},
}
func outputJSON(resp *Response) {
_ = json.NewEncoder(os.Stdout).Encode(resp)
}
func outputError(msg string, err error) {
outputJSON(&Response{
Status: "error",
Message: fmt.Sprintf("%s: %v", msg, err),
})
}
func main() {
var req Request
input, err := io.ReadAll(os.Stdin)
if err != nil {
outputError("读取输入失败", err)
return
}
if err := json.Unmarshal(input, &req); err != nil {
outputError("解析请求失败", err)
return
}
switch req.Action {
case "get_metadata":
outputJSON(&Response{
Status: "success",
Message: "插件信息",
Result: pluginMeta,
})
case "list_actions":
outputJSON(&Response{
Status: "success",
Message: "支持的动作",
Result: map[string]interface{}{"actions": pluginMeta["actions"]},
})
case "upload":
rep, err := Upload(req.Params)
if err != nil {
outputError("CDN 部署失败", err)
return
}
outputJSON(rep)
default:
outputJSON(&Response{
Status: "error",
Message: "未知 action: " + req.Action,
})
}
}