163 lines
4.7 KiB
Go
163 lines
4.7 KiB
Go
// agent-core 客户端核心:设备身份 + 凭据保险库 + WSS 长连接 + 平台执行器。
|
|
// 常驻模型:WinUI 壳 spawn 本进程 → 壳读 data/local.json(随机端口+token)→
|
|
// 经 localhost HTTP 控制(状态/配对/挑战)。CLI 亦可独立运行(-user/-pass 自动配对)。
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"os/signal"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"everypublish/agentcore/internal/config"
|
|
"everypublish/agentcore/internal/device"
|
|
"everypublish/agentcore/internal/exec"
|
|
"everypublish/agentcore/internal/exec/bilibili"
|
|
"everypublish/agentcore/internal/exec/douyin"
|
|
"everypublish/agentcore/internal/exec/fake"
|
|
"everypublish/agentcore/internal/exec/kuaishou"
|
|
"everypublish/agentcore/internal/exec/xiaohongshu"
|
|
"everypublish/agentcore/internal/localserver"
|
|
"everypublish/agentcore/internal/pairing"
|
|
"everypublish/agentcore/internal/vault"
|
|
"everypublish/agentcore/internal/wsclient"
|
|
)
|
|
|
|
func main() {
|
|
var (
|
|
server = flag.String("server", "", "服务器地址(默认取配置)")
|
|
dir = flag.String("dir", "./data", "数据目录(设备密钥/保险库/配置)")
|
|
name = flag.String("name", "", "设备名")
|
|
user = flag.String("user", "", "开发便利:自动配对登录邮箱")
|
|
password = flag.String("pass", "", "开发便利:自动配对登录密码")
|
|
simMS = flag.Int("sim", 0, "假执行器模拟耗时 ms(0 用配置值)")
|
|
token = flag.String("local-token", "", "本机控制服务 token(默认随机)")
|
|
)
|
|
flag.Parse()
|
|
|
|
cfg, err := config.Load(*dir)
|
|
if err != nil {
|
|
log.Fatalf("config: %v", err)
|
|
}
|
|
if *server != "" {
|
|
cfg.Server = *server
|
|
}
|
|
if *name != "" {
|
|
cfg.Name = *name
|
|
}
|
|
if *simMS > 0 {
|
|
cfg.SimMS = *simMS
|
|
}
|
|
_ = cfg.Save(*dir)
|
|
|
|
// 设备身份(私钥本机持久化 0600)
|
|
id, err := device.LoadOrCreate(*dir, cfg.Name)
|
|
if err != nil {
|
|
log.Fatalf("device: %v", err)
|
|
}
|
|
cfg.Name = id.Name
|
|
|
|
// 凭据保险库(平台 cookie/token 加密落盘)
|
|
v, err := vault.Open(*dir)
|
|
if err != nil {
|
|
log.Fatalf("vault: %v", err)
|
|
}
|
|
|
|
// 执行器注册:B站无浏览器投稿 + 抖音/快手/小红书 CDP(D10/D11);
|
|
// fakeAll=true 时全部走假执行器(无真实账号的安装联调)。
|
|
registry := exec.NewRegistry()
|
|
if !cfg.FakeAll {
|
|
browserRoot := filepath.Join(*dir, "browser") // 每账号独立档案
|
|
registry.Register(bilibili.New(v))
|
|
registry.Register(douyin.New(v, browserRoot, cfg.Proxy, "", false))
|
|
registry.Register(kuaishou.New(v, browserRoot, cfg.Proxy, "", false))
|
|
registry.Register(xiaohongshu.New(v, browserRoot, cfg.Proxy, "", false))
|
|
}
|
|
registry.SetFallback(fake.Executor{SimMS: cfg.SimMS})
|
|
|
|
// WSS 客户端(重连/幂等/心跳)
|
|
client := wsclient.New(cfg.Server, id, registry)
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
// 连接启动(配对后;未配对时等壳调 /pair)
|
|
var runOnce sync.Once
|
|
startConn := func() {
|
|
if id.DeviceID == 0 {
|
|
return
|
|
}
|
|
runOnce.Do(func() {
|
|
go func() { _ = client.Run(ctx) }()
|
|
})
|
|
}
|
|
|
|
// 配对回调(本机控制服务 /pair 触发)
|
|
pairFn := func(code, name string) error {
|
|
if id.DeviceID > 0 {
|
|
return errors.New("设备已配对")
|
|
}
|
|
did, err := pairing.PairWithCode(cfg.Server, code, name, "core", "0.2.0", id.PublicKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id.DeviceID = did
|
|
if name != "" {
|
|
id.Name = name
|
|
cfg.Name = name
|
|
}
|
|
if err = id.Save(*dir); err != nil {
|
|
return err
|
|
}
|
|
log.Printf("paired device %d (name %s)", id.DeviceID, id.Name)
|
|
startConn()
|
|
return nil
|
|
}
|
|
|
|
// 开发便利:CLI 自动配对
|
|
if id.DeviceID == 0 && *user != "" && *password != "" {
|
|
did, err := pairing.AutoPair(cfg.Server, *user, *password, cfg.Name, id.PublicKey)
|
|
if err != nil {
|
|
log.Fatalf("auto pair: %v", err)
|
|
}
|
|
id.DeviceID = did
|
|
if err = id.Save(*dir); err != nil {
|
|
log.Fatalf("save identity: %v", err)
|
|
}
|
|
log.Printf("auto paired device %d", id.DeviceID)
|
|
}
|
|
|
|
startConn()
|
|
|
|
// 本机控制服务(WinUI 壳入口)
|
|
localToken := *token
|
|
if localToken == "" {
|
|
localToken = localserver.RandToken()
|
|
}
|
|
ls := localserver.New(client, pairFn, localToken, *dir, func() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"server": cfg.Server,
|
|
"deviceId": id.DeviceID,
|
|
"name": id.Name,
|
|
}
|
|
})
|
|
go func() {
|
|
if err = ls.Listen(); err != nil {
|
|
log.Fatalf("local server: %v", err)
|
|
}
|
|
}()
|
|
|
|
if id.DeviceID == 0 {
|
|
log.Printf("agent-core started UNPAIRED: server=%s(请在客户端输入网页端配对码)", cfg.Server)
|
|
} else {
|
|
log.Printf("agent-core started: server=%s device=%d", cfg.Server, id.DeviceID)
|
|
}
|
|
<-ctx.Done()
|
|
log.Println("agent-core stopped")
|
|
}
|