From 26c2867f6a2641912033a53a9e2b8f62a786143a Mon Sep 17 00:00:00 2001 From: tengfe-xy Date: Tue, 18 Jun 2024 16:53:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=AA=E4=BA=BA=E4=B8=93?= =?UTF-8?q?=E4=B8=9A=E7=89=88=E6=97=A0=E6=B3=95=E5=AF=BC=E5=87=BA=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- config.go | 17 +++- error.go | 5 - export.go | 4 +- flag.go | 37 +++++++ go.mod | 4 +- go.sum | 4 +- html.go | 27 ++++-- main.go | 138 +++++++++++---------------- md.go | 25 +++-- plan.go | 19 ++++ team.go | 4 +- time.go | 14 ++- user.go | 64 +++++-------- workspace.go | 265 ++++++++++++++++++++++++++++++++++++++++----------- 15 files changed, 417 insertions(+), 212 deletions(-) delete mode 100644 error.go create mode 100644 flag.go create mode 100644 plan.go diff --git a/.gitignore b/.gitignore index 82d1d89..b92f94c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ docs/ **/.DS_Store -config.yaml* +*.yaml dist/ wolai diff --git a/config.go b/config.go index bfd6205..ce224b8 100644 --- a/config.go +++ b/config.go @@ -7,6 +7,7 @@ import ( "runtime" "strings" + log "github.com/tengfei-xy/go-log" tools "github.com/tengfei-xy/go-tools" "gopkg.in/yaml.v3" ) @@ -17,6 +18,8 @@ func initConfig(config string) (Config, error) { file := filepath.Join(f, config) + log.Infof("读取配置文件:%s", file) + if !tools.FileExist(file) { return Config{}, configGenerate(file) } @@ -86,7 +89,7 @@ func (c *Config) isIgnoreSubspace(ws int, subspaceName string) bool { } for _, subspace := range c.Ignore[ws].Subspaces { - if subspace.Name == subspaceName { + if subspace.Name == subspaceName && len(subspaceName) != 0 { return true } } @@ -116,7 +119,6 @@ func (c *Config) hasHtml() bool { } } return false - } func (c *Config) hasMarkdown() bool { @@ -127,7 +129,9 @@ func (c *Config) hasMarkdown() bool { } } return false - +} +func (c *Config) addTimeBackupPath() { + config.BackupPath = filepath.Join(config.BackupPath, timeGetChineseString()) } func configGenerate(file string) error { var c Config @@ -159,10 +163,15 @@ func configGenerate(file string) error { } type Config struct { - Cookie string `yaml:"cookie"` + Cookie string `yaml:"cookie"` + + // 作为外部文件使用的变量 BackupPath string `yaml:"backupBackupDir"` ExportType []string `yaml:"exportType"` Ignore []Workspace `yaml:"ignore"` + + // 作为绝对路径的、内部程序使用的变量 + backupPath string } type Workspace struct { Name string `yaml:"workspace"` diff --git a/error.go b/error.go deleted file mode 100644 index 199c4b0..0000000 --- a/error.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -import "fmt" - -var errorNoData error = fmt.Errorf("空数据") diff --git a/export.go b/export.go index 8358c40..be93c34 100644 --- a/export.go +++ b/export.go @@ -5,14 +5,14 @@ import ( ) func (wsInfo *workspaceInfo) outputIgnore(ws, sb, page string) { - if wsInfo.is_free_plan() { + if wsInfo.isDefaultSubWorkspace() { log.Warnf("忽略导出 工作区:%s 页面:%s", ws, page) } else { log.Warnf("忽略导出 工作区:%s 子空间:%s 页面:%s", ws, sb, page) } } func (wsInfo *workspaceInfo) outputExport(ws, sb, page string) { - if wsInfo.is_free_plan() { + if wsInfo.isDefaultSubWorkspace() { log.Infof("开始导出 工作区:%s 页面:%s", ws, page) } else { log.Infof("开始导出 工作区:%s 子空间:%s 页面:%s", ws, sb, page) diff --git a/flag.go b/flag.go new file mode 100644 index 0000000..42094a4 --- /dev/null +++ b/flag.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "os" + + log "github.com/tengfei-xy/go-log" +) + +type iflag struct { + help bool + version bool + configFile string + loglevel string +} + +func (f *iflag) checkHelp() { + + if f.help { + description() + os.Exit(0) + } + +} +func (f *iflag) checkVersion() { + + if f.version { + fmt.Printf("%s\n", version) + os.Exit(0) + } + +} +func (f *iflag) checkLogLevel() { + log.SetLevelStr(f.loglevel) + _, v := log.GetLevel() + log.Infof("当前日志等级:%s", v) +} diff --git a/go.mod b/go.mod index b13670b..ca3a54c 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module wolai -go 1.19 +go 1.21.4 require ( - github.com/tengfei-xy/go-log v0.1.2 + github.com/tengfei-xy/go-log v0.2.4 github.com/tengfei-xy/go-tools v0.1.3 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index d602945..e2c10d7 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/tengfei-xy/go-log v0.1.2 h1:4n/x/6YsQF6MzzG5Prhg11OUZOoY11LcQlK5fwPj7TQ= -github.com/tengfei-xy/go-log v0.1.2/go.mod h1:k3W+Vs69F7ldZQ88XqDP1r0b9oo6NvxORmH50FQAnHE= +github.com/tengfei-xy/go-log v0.2.4 h1:Fy1IUgGXwr9taJqeuMrRADUX73yRUzbiOhA8NLT+uPY= +github.com/tengfei-xy/go-log v0.2.4/go.mod h1:WUFar5Siw3S2umNtSxDyeykvgULNqHxsEp2jCnBXj1k= github.com/tengfei-xy/go-tools v0.1.3 h1:/FE1eItW4oaMF3to3qNUwsN9AYvGr3MM9iqucwHG+R0= github.com/tengfei-xy/go-tools v0.1.3/go.mod h1:y2mYZiRBQtBInhOtKVsA1cCvf5xalZ/otoF2v2teQr4= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= diff --git a/html.go b/html.go index 5c7cf48..6045316 100644 --- a/html.go +++ b/html.go @@ -15,15 +15,21 @@ import ( func (wsInfo *workspaceInfo) exportHTMLMain() { + if !config.hasHtml() { + log.Debugf("忽略导出HTML 名称:%s", wsInfo.name) + return + } + // 获取忽略的工作区序号 wsSeq := config.getIgnoreWorkspace(wsInfo.name) for _, subspace := range wsInfo.subspace { if config.isIgnoreSubspace(wsSeq, subspace.name) { - log.Warnf("忽略导出 工作区:%s 子空间:%s", wsInfo.name, subspace.name) + log.Warnf("根据配置文件忽略工作区:%s 子空间:%s", wsInfo.name, subspace.name) continue } - spSeq := config.getIgnoreSubspace(wsInfo.is_free_plan(), wsSeq, subspace.name) + + spSeq := config.getIgnoreSubspace(wsInfo.isDefaultSubWorkspace(), wsSeq, subspace.name) for _, page := range subspace.pages { if config.isIgnorePage(wsSeq, spSeq, page.name) { @@ -38,8 +44,8 @@ func (wsInfo *workspaceInfo) exportHTMLMain() { } } - } + func (wsInfo *workspaceInfo) exportHtmlSingle(subspaceName, pageId, pageName string) error { var e exportHtmlUpJson e.PageID = pageId @@ -60,13 +66,18 @@ func (wsInfo *workspaceInfo) exportHtmlSingle(subspaceName, pageId, pageName str // 设置下载链接和文件名 u, _ := url.ParseRequestURI(fileURL) filename := filepath.Base(u.Path) + var savePath string = filepath.Join(config.BackupPath, "html", wsInfo.name, subspaceName) + if wsInfo.isDefaultSubWorkspace() { + savePath = filepath.Join(config.BackupPath, "html", wsInfo.name) + } - if wsInfo.is_free_plan() { - filename = filepath.Join(config.BackupPath, "html", wsInfo.name, filename) - } else { - filename = filepath.Join(config.BackupPath, "html", wsInfo.name, subspaceName, filename) + // 创建备份文件夹 + if err := mkdir(savePath); err != nil { + log.Fatal(err) } + filename = filepath.Join(savePath, filename) + // 下载文件 if err := tools.FileDownload(fileURL, filename); err != nil { return err @@ -103,7 +114,7 @@ func exportHtmlHtml(data []byte) ([]byte, bool) { req.Header.Set("Sec-Fetch-Mode", `cors`) req.Header.Set("wolai-os-platform", `mac`) req.Header.Set("x-client-timezone", `Asia/Shanghai`) - req.Header.Set("wolai-app-version", `1.2.0-18`) + req.Header.Set("wolai-app-version", `1.2.2-4`) req.Header.Set("wolai-client-platform", `web`) req.Header.Set("x-client-timeoffset", `-480`) req.Header.Set("wolai-client-version", ``) diff --git a/main.go b/main.go index b0af65e..c0da6fe 100644 --- a/main.go +++ b/main.go @@ -4,18 +4,22 @@ import ( "flag" "fmt" "os" - "path/filepath" + "strings" log "github.com/tengfei-xy/go-log" ) -const version string = "v1.1.1" +const version string = "v1.2.0" var config Config -func project() { - fmt.Printf("程序版本:%s\n", version) - fmt.Println("项目链接:https://github.com/tengfei-xy/wolai") +func description() { + fmt.Printf(` _ _ +__ __ ___ | | __ _ (_) +\ \ /\ / / / _ \ | | / _`+"`"+` || | + \ V V / | (_) || || (_| || | 程序版本: %s + \_/\_/ \___/ |_| \__,_||_| 项目链接: https://github.com/tengfei-xy/wolai + %s`, version, "\n") } func mkdir(path string) error { @@ -23,107 +27,79 @@ func mkdir(path string) error { if err != nil && err != os.ErrExist { return err } - log.Infof("创建 保存目标文件夹:%s", path) + log.Infof("创建文件夹 路径:%s", path) return nil } -func initFalg() (bool, string) { - - var exit bool = false - helpText := flag.Bool("h", false, "查看帮助") - versionText := flag.Bool("v", false, "查看版本") - configFileText := flag.String("c", "config.yaml", "指定配置文件") +func initFlag() iflag { + var v iflag + flag.BoolVar(&v.help, "h", false, "查看帮助") + flag.BoolVar(&v.version, "v", false, "查看版本") + flag.StringVar(&v.configFile, "c", "config.yaml", "指定配置文件") + flag.StringVar(&v.loglevel, "l", log.LEVELINFO, fmt.Sprintf("日志等级,可设置参数:%s", strings.Join(log.GetLevelAll(), "、"))) flag.Parse() - if *helpText { - project() - exit = true - - } - if *versionText { - fmt.Printf("%s", version) - exit = true - } - return exit, *configFileText + return v +} +func initTilte() { + description() } func main() { var err error + initTilte() - exit, f := initFalg() - if exit { - return - } - log.Infof("读取配置文件:%s", f) + f := initFlag() + f.checkHelp() + f.checkVersion() + f.checkLogLevel() - // 获取配置 - config, err = initConfig(f) + // 从配置文件中获取配置 + config, err = initConfig(f.configFile) if err != nil { - fmt.Println(err.Error()) + log.Fatal(err) } - userid, err := getUserID() - if err != nil { - panic(err) - } + // 在配置文件的保存地址上,增加时间 + config.addTimeBackupPath() - // 获取官方API的工作区结构 - ws, err := getWorkSpaceStruct() + // 获取用户ID用户所在的空间ID + ui, err := getUserInfo() if err != nil { - panic(err) + log.Fatal(err) } - // 将官方API的工作区结构转化为重要字段的结构体 workspaceInfo - wsInfos := ws.getWorkspaceInfo(userid) + // // 获取官方API的工作区结构 + // ws, err := getWorkSpaceStruct() + // if err != nil { + // panic(err) + // } - // 获取子空间 - for i := range wsInfos { - - if wsInfos[i].id == "" { - continue - } + // // 将官方API的工作区结构转化为重要字段的结构体 workspaceInfo + // wsInfos := ws.getWorkspaceInfo(ui.userid) - // 免费版 - if wsInfos[i].is_free_plan() { - if err := wsInfos[i].getDefaultSubspace(); err != nil { - panic(err) - } - continue - } + for _, ws := range ui.ws { - // 多人工作区 设定结构体长度并子空间的获取ID - if err := wsInfos[i].getTeamSubspace(); err != nil { - panic(err) - } - // 多人工作区 获取名称 - if err := wsInfos[i].getTermPagesMain(); err != nil { - panic(err) - } + // 根据用户所在的空间ID获取用户空间的基本信息 + ws.getBasicInfo() - } + // 一个子空间的处理方式 + if ws.isDefaultSubWorkspace() { - // 输出将被导出的页面 - for _, wsInfo := range wsInfos { - wsInfo.output() - } + // 获取默认子空间的信息 + ws.getDefaultSubspace() + } else { + // 设定结构体长度并子空间的获取ID + ws.getTeamSubspace() - config.BackupPath = filepath.Join(config.BackupPath, timeGetChineseString()) - for _, wsInfo := range wsInfos { - if err := wsInfo.mkdirBackupFolder(); err != nil { - panic(err) + // 获取名称 + ws.getTermPagesMain() } - } - if config.hasMarkdown() { - // 开始导出 - for _, wsInfo := range wsInfos { - wsInfo.exportMDMain() - } - } - if config.hasHtml() { - // 开始导出 - for _, wsInfo := range wsInfos { - wsInfo.exportHTMLMain() - } + // 进行导出MD + ws.exportMDMain() + + // 进行导出HTML + ws.exportHTMLMain() } log.Info("导出结束!欢迎再次使用") diff --git a/md.go b/md.go index d5facf1..1da5508 100644 --- a/md.go +++ b/md.go @@ -15,15 +15,20 @@ import ( func (wsInfo *workspaceInfo) exportMDMain() { + if !config.hasMarkdown() { + log.Debugf("忽略导出MD 名称:%s", wsInfo.name) + return + } + // 获取忽略的工作区序号 wsSeq := config.getIgnoreWorkspace(wsInfo.name) for _, subspace := range wsInfo.subspace { if config.isIgnoreSubspace(wsSeq, subspace.name) { - log.Warnf("忽略导出 工作区:%s 子空间:%s", wsInfo.name, subspace.name) + log.Warnf("根据配置文件忽略工作区:%s 子空间:%s", wsInfo.name, subspace.name) continue } - spSeq := config.getIgnoreSubspace(wsInfo.is_free_plan(), wsSeq, subspace.name) + spSeq := config.getIgnoreSubspace(wsInfo.isDefaultSubWorkspace(), wsSeq, subspace.name) for _, page := range subspace.pages { if config.isIgnorePage(wsSeq, spSeq, page.name) { @@ -40,6 +45,7 @@ func (wsInfo *workspaceInfo) exportMDMain() { } } + func (wsInfo *workspaceInfo) exportMDSingle(subspaceName, pageId, pageName string) error { var e exportUpJson e.PageID = pageId @@ -62,13 +68,18 @@ func (wsInfo *workspaceInfo) exportMDSingle(subspaceName, pageId, pageName strin // 设置下载链接和文件名 u, _ := url.ParseRequestURI(fileURL) filename := filepath.Base(u.Path) + var savePath string = filepath.Join(config.BackupPath, "markdown", wsInfo.name, subspaceName) + if wsInfo.isDefaultSubWorkspace() { + savePath = filepath.Join(config.BackupPath, "markdown", wsInfo.name) + } - if wsInfo.is_free_plan() { - filename = filepath.Join(config.BackupPath, "markdown", wsInfo.name, filename) - } else { - filename = filepath.Join(config.BackupPath, "markdown", wsInfo.name, subspaceName, filename) + // 创建备份文件夹 + if err := mkdir(savePath); err != nil { + log.Fatal(err) } + filename = filepath.Join(savePath, filename) + // 下载文件 if err := tools.FileDownload(fileURL, filename); err != nil { return err @@ -105,7 +116,7 @@ func exportMarkdownHtml(data []byte) ([]byte, bool) { req.Header.Set("Sec-Fetch-Mode", `cors`) req.Header.Set("wolai-os-platform", `mac`) req.Header.Set("x-client-timezone", `Asia/Shanghai`) - req.Header.Set("wolai-app-version", `1.2.0-18`) + req.Header.Set("wolai-app-version", `1.2.2-4`) req.Header.Set("wolai-client-platform", `web`) req.Header.Set("x-client-timeoffset", `-480`) req.Header.Set("wolai-client-version", ``) diff --git a/plan.go b/plan.go new file mode 100644 index 0000000..110e6ca --- /dev/null +++ b/plan.go @@ -0,0 +1,19 @@ +package main + +func getPlanTypeZh(i string) string { + switch i { + case "free": + return "个人免费版" + case "personal_pro": + return "个人专业版" + case "personal_family": + return "团队版" + } + return i +} +func isFree(i string) bool { + return i == "free" +} +func isPersonalPro(i string) bool { + return i == "personal_pro" +} diff --git a/team.go b/team.go index b4ef30b..c8ce9a4 100644 --- a/team.go +++ b/team.go @@ -24,7 +24,7 @@ func getTeam(spacceID string) (teamStruct, error) { return teamStruct{}, fmt.Errorf("错误的数据,%s", ts.Message) } if len(ts.Data) == 0 { - return teamStruct{}, fmt.Errorf("空数据") + return teamStruct{}, fmt.Errorf("空数据,可能您没有导出的权限") } return ts, nil } @@ -47,7 +47,7 @@ func getTeamHtml(spaceID string) (strings.Builder, error) { req.Header.Set("Content-Type", ` application/json`) req.Header.Set("wolai-os-platform", `mac`) req.Header.Set("x-client-timezone", `Asia/Shanghai`) - req.Header.Set("wolai-app-version", `1.2.0-18`) + req.Header.Set("wolai-app-version", `1.2.2-4`) req.Header.Set("wolai-client-platform", `web`) req.Header.Set("x-client-timeoffset", `-480`) req.Header.Set("wolai-client-version", ``) diff --git a/time.go b/time.go index 802ae13..be37d79 100644 --- a/time.go +++ b/time.go @@ -1,8 +1,20 @@ package main -import "time" +import ( + "math/rand" + "time" +) func timeGetChineseString() string { t := time.Now() return t.Format("2006年01月02日15点04分") } +func createRand(len int) string { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + const pool string = "qazwsxedcrfvtgbyhnujmikolpQAZWSXEDCRFVTGBYHNUJMIKOLP12345678900" + bytes := make([]byte, len) + for i := 0; i < len; i++ { + bytes[i] = pool[r.Intn(62)] + } + return string(bytes) +} diff --git a/user.go b/user.go index dba9102..4c988c8 100644 --- a/user.go +++ b/user.go @@ -9,21 +9,32 @@ import ( log "github.com/tengfei-xy/go-log" ) -func getUserID() (string, error) { - var p UserInfoStruct +func getUserInfo() (WUserInfo, error) { + var wui WUserInfo + var p ReqUserInfoStruct h, err := getUserIDHtml() if err != nil { - return "", err + return WUserInfo{}, err } err = json.Unmarshal(h, &p) if err != nil { - return "", err + return WUserInfo{}, err } if p.Code != 1000 { - return "", fmt.Errorf("请求异常 状态码:%d 消息:%s", p.Code, p.Message) + return WUserInfo{}, fmt.Errorf("请求异常 状态码:%d 消息:%s", p.Code, p.Message) } - log.Infof("发现用户:%s", p.Data.UserID) - return p.Data.UserID, nil + wui.userid = p.Data.UserID + + l := len(p.Data.WorkspaceList) + if l == 0 { + log.Fatal("未发现工作空间") + } + wui.ws = make([]workspaceInfo, l) + + for i := range wui.ws { + wui.ws[i].id = p.Data.WorkspaceList[i] + } + return wui, nil } func getUserIDHtml() ([]byte, error) { client := &http.Client{} @@ -44,7 +55,7 @@ func getUserIDHtml() ([]byte, error) { req.Header.Set("Sec-Fetch-Mode", `cors`) req.Header.Set("wolai-os-platform", `mac`) req.Header.Set("x-client-timezone", `Asia/Shanghai`) - req.Header.Set("wolai-app-version", `1.2.0-18`) + req.Header.Set("wolai-app-version", `1.2.2-4`) req.Header.Set("wolai-client-platform", `web`) req.Header.Set("x-client-timeoffset", `-480`) req.Header.Set("wolai-client-version", ``) @@ -66,36 +77,11 @@ func getUserIDHtml() ([]byte, error) { } return resp_data, nil } - -type UserInfoStruct struct { - Code int `json:"code"` - Data UserInfoData `json:"data"` - Message string `json:"message"` +func (wui *WUserInfo) outputUserID() { + log.Infof("发现用户: %s", wui.userid) } -type UserInfoData struct { - UserID string `json:"userId"` - Mobile []string `json:"mobile"` - CountryCode string `json:"countryCode"` - Email string `json:"email"` - UserName string `json:"userName"` - Avatar string `json:"avatar"` - EmailVerified bool `json:"emailVerified"` - Password bool `json:"password"` - Pin bool `json:"pin"` - UserUnshareable bool `json:"userUnshareable"` - UserUnUploadable bool `json:"userUnUploadable"` - CreditUnavailable bool `json:"creditUnavailable"` - UserHash string `json:"userHash"` - RecommendCode string `json:"recommendCode"` - RegisterTime int64 `json:"registerTime"` - IsEligibleForEducationDiscount bool `json:"isEligibleForEducationDiscount"` - EducationDiscountExpirationDate int `json:"educationDiscountExpirationDate"` - IsNewUser bool `json:"isNewUser"` - RegisterMethod string `json:"registerMethod"` - DisableKefu bool `json:"disableKefu"` - InvitedUserCount int `json:"invitedUserCount"` - WorkspaceList []string `json:"workspaceList"` - WechatOpenID string `json:"wechatOpenId"` - WechatWebsiteOpenID string `json:"wechatWebsiteOpenId"` - WechatUnionID string `json:"wechatUnionId"` + +type WUserInfo struct { + userid string + ws []workspaceInfo } diff --git a/workspace.go b/workspace.go index a5429f9..e732a79 100644 --- a/workspace.go +++ b/workspace.go @@ -65,7 +65,7 @@ func getWorkspaceHtml() ([]byte, error) { req.Header.Set("Sec-Fetch-Mode", `cors`) req.Header.Set("wolai-os-platform", `mac`) req.Header.Set("x-client-timezone", `Asia/Shanghai`) - req.Header.Set("wolai-app-version", `1.2.0-18`) + req.Header.Set("wolai-app-version", `1.2.2-4`) req.Header.Set("wolai-client-platform", `web`) req.Header.Set("x-client-timeoffset", `-480`) req.Header.Set("wolai-client-version", ``) @@ -99,39 +99,35 @@ func (ws *workspaceDataStruct) getWorkspaceInfo(userid string) []workspaceInfo { wsInfo[i].name = j.Name wsInfo[i].id = j.ID wsInfo[i].planType = j.Plan.Type + log.Infof("planType=%s", wsInfo[i].planType) + log.Infof("name=%s", wsInfo[i].name) + log.Infof("id=%s", wsInfo[i].id) } return wsInfo } func (wsInfo *workspaceInfo) mkdirBackupFolder() error { - if config.hasHtml() { - // 免费计划 - if wsInfo.is_free_plan() { - return mkdir(filepath.Join(config.BackupPath, "html", wsInfo.name)) - } + var exportType = "html" - for _, subspace := range wsInfo.subspace { - if err := mkdir(filepath.Join(config.BackupPath, "html", wsInfo.name, subspace.name)); err != nil { - return err - } - } - } if config.hasMarkdown() { - // 免费计划 - if wsInfo.is_free_plan() { - return mkdir(filepath.Join(config.BackupPath, "markdown", wsInfo.name)) - } + exportType = "markdown" + } - for _, subspace := range wsInfo.subspace { - if err := mkdir(filepath.Join(config.BackupPath, "markdown", wsInfo.name, subspace.name)); err != nil { - return err - } + if wsInfo.isDefaultSubWorkspace() { + config.backupPath = filepath.Join(config.BackupPath, "html", wsInfo.name) + return mkdir(config.backupPath) + } + + for _, subspace := range wsInfo.subspace { + config.backupPath = filepath.Join(config.BackupPath, exportType, wsInfo.name) + if err := mkdir(filepath.Join(config.backupPath, subspace.name)); err != nil { + return err } } return nil } -func (wsInfo *workspaceInfo) is_free_plan() bool { - return wsInfo.planType == "free" +func (wsInfo *workspaceInfo) isDefaultSubWorkspace() bool { + return isFree(wsInfo.planType) || isPersonalPro(wsInfo.planType) } func (w *Workspaces) member_exist(userid string) bool { var exist bool = false @@ -143,15 +139,12 @@ func (w *Workspaces) member_exist(userid string) bool { return exist } -// 说明: 适用于团队模式或家庭版 -func (wsInfo *workspaceInfo) getTeamSubspace() error { - if wsInfo.is_free_plan() { - return nil - } +// 说明: 适用于团队版 +func (wsInfo *workspaceInfo) getTeamSubspace() { ts, err := getTeam(wsInfo.id) if err != nil { log.Error(err) - return err + return } wsInfo.subspace = make([]subspace, len(ts.Data)) @@ -162,44 +155,41 @@ func (wsInfo *workspaceInfo) getTeamSubspace() error { wsInfo.subspace[i].pages[z].id = line } } - return nil } -// 说明: 适用于团队模式或家庭版 -func (wsInfo *workspaceInfo) getTermPagesMain() error { +// 说明: 适用于团队版 +func (wsInfo *workspaceInfo) getTermPagesMain() { h, err := wsInfo.getPagesHtml() if err != nil { - log.Error(err) - return err + log.Fatal(err) } if err := wsInfo.getTermPagesDeal(h); err != nil { - log.Error(err) - return err + log.Fatal(err) } - return nil } -// 说明: 适用于团队模式或家庭版 +// 说明: 适用于免费版、个人专业版 +func (wsInfo *workspaceInfo) getDefaultSubspace() { -func (wsInfo *workspaceInfo) getDefaultSubspace() error { wsInfo.subspace = make([]subspace, 1) wsInfo.subspace[0].name = "" h, err := wsInfo.getPagesHtml() if err != nil { - log.Error(err) - return err + log.Fatal(err) } if err := wsInfo.getDefaultPagesDeal(h); err != nil { - log.Error(err) - return err + log.Fatal(err) } - return nil } + func (wsInfo *workspaceInfo) getPagesHtml() ([]byte, error) { + url := `https://api.wolai.com/v1/workspace/getWorkspacePages` + data := fmt.Sprintf(`{"spaceId":"%s"}`, wsInfo.id) + log.Debugf("发送请求 链接:%s 数据:%s", url, data) client := &http.Client{} - req, err := http.NewRequest("POST", `https://api.wolai.com/v1/workspace/getWorkspacePages`, strings.NewReader(fmt.Sprintf(`{"spaceId":"%s"}`, wsInfo.id))) + req, err := http.NewRequest("POST", url, strings.NewReader(data)) if err != nil { return nil, err } @@ -216,7 +206,7 @@ func (wsInfo *workspaceInfo) getPagesHtml() ([]byte, error) { req.Header.Set("Sec-Fetch-Mode", `cors`) req.Header.Set("wolai-os-platform", `mac`) req.Header.Set("x-client-timezone", `Asia/Shanghai`) - req.Header.Set("wolai-app-version", `1.2.0-18`) + req.Header.Set("wolai-app-version", `1.2.2-4`) req.Header.Set("wolai-client-platform", `web`) req.Header.Set("x-client-timeoffset", `-480`) req.Header.Set("wolai-client-version", ``) @@ -241,7 +231,7 @@ func (wsInfo *workspaceInfo) getPagesHtml() ([]byte, error) { } func (wsInfo *workspaceInfo) getTermPagesDeal(data []byte) error { - fmt.Println(string(data)) + log.Debug3f(string(data)) var p workSpacePageList err := json.Unmarshal(data, &p) if err != nil { @@ -276,23 +266,182 @@ func (wsInfo *workspaceInfo) getDefaultPagesDeal(data []byte) error { if p.Data.Blocks[id.String()].Value.ParentType != "workspace" { continue } + log.Debug3f("发现 %s", id) wsInfo.subspace[0].pages[i].id = id.String() - wsInfo.subspace[0].pages[i].name = p.Data.Blocks[id.String()].Value.Attributes.Title[0][0] + if len(p.Data.Blocks[id.String()].Value.Attributes.Title) == 0 { + log.Warnf("发现页面没有名称,将使用随机字符") + wsInfo.subspace[0].pages[i].name = "新页面" + createRand(5) + } else { + wsInfo.subspace[0].pages[i].name = p.Data.Blocks[id.String()].Value.Attributes.Title[0][0] + } } return nil } -func (wsInfo *workspaceInfo) output() { - var msg string - for _, j := range wsInfo.subspace { - for _, k := range j.pages { - if wsInfo.is_free_plan() { - msg = fmt.Sprintf("发现工作区:%s 页面:%s", wsInfo.name, k.name) - } else { - msg = fmt.Sprintf("发现工作区:%s 子空间:%s 页面:%s", wsInfo.name, j.name, k.name) - } - log.Infof(msg) - } + +// func (wsInfo *workspaceInfo) output() { +// for _, j := range wsInfo.subspace { +// for _, k := range j.pages { +// if wsInfo.isDefaultSubWorkspace() { +// log.Infof("发现工作区:%s 页面:%s", wsInfo.name, k.name) +// } else { +// log.Infof("发现工作区:%s 子空间:%s 页面:%s", wsInfo.name, j.name, k.name) +// } +// } +// } +// } + +// 说明: 根据workspace的ID获取名称、计划类型 +func (wsInfo *workspaceInfo) getBasicInfo() { + var p reqWorkspace + + // 获取工作区信息 + h, err := getWSInfoHtml(wsInfo.id) + if err != nil { + log.Fatal(err) + return } + + err = json.Unmarshal(h, &p) + if err != nil { + log.Fatal(err) + return + + } + if p.Code != 1000 { + log.Fatalf("请求异常 状态码:%d 消息:%s", p.Code, p.Message) + return + } + wsInfo.name = p.Data.Name + wsInfo.planType = p.Data.Plan.Type + log.Infof("发现工作空间 名称:%s 计划:%s", wsInfo.name, getPlanTypeZh(wsInfo.planType)) +} +func getWSInfoHtml(id string) ([]byte, error) { + + client := &http.Client{} + req, err := http.NewRequest("POST", `https://api.wolai.com/v1/workspace/getWorkspace`, strings.NewReader(fmt.Sprintf(`{"spaceId":"%s"}`, id))) + if err != nil { + return nil, err + } + req.Header.Set("Referer", `https://www.wolai.com/`) + req.Header.Set("Cookie", config.Cookie) + req.Header.Set("User-Agent", `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36`) + req.Header.Set("host", `api.wolai.com`) + req.Header.Set("Origin", `https://www.wolai.com`) + req.Header.Set("Sec-Fetch-Dest", `empty`) + req.Header.Set("Sec-Fetch-Site", `same-site`) + req.Header.Set("Accept-Language", `zh-CN,zh;q=0.9`) + req.Header.Set("Accept", `application/json, text/plain, */*`) + req.Header.Set("Content-Type", ` application/json`) + req.Header.Set("Sec-Fetch-Mode", `cors`) + req.Header.Set("wolai-os-platform", `mac`) + req.Header.Set("x-client-timezone", `Asia/Shanghai`) + req.Header.Set("wolai-app-version", `1.2.2-4`) + req.Header.Set("wolai-client-platform", `web`) + req.Header.Set("x-client-timeoffset", `-480`) + req.Header.Set("wolai-client-version", ``) + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("状态码:%d", resp.StatusCode) + } + + resp_data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("内部错误:%v", err) + } + return resp_data, nil +} + +type ReqUserInfoStruct struct { + Code int `json:"code"` + Data UserInfoData `json:"data"` + Message string `json:"message"` +} +type UserInfoData struct { + UserID string `json:"userId"` + Mobile []string `json:"mobile"` + CountryCode string `json:"countryCode"` + Email string `json:"email"` + UserName string `json:"userName"` + Avatar string `json:"avatar"` + EmailVerified bool `json:"emailVerified"` + Password bool `json:"password"` + Pin bool `json:"pin"` + UserUnshareable bool `json:"userUnshareable"` + UserUnUploadable bool `json:"userUnUploadable"` + CreditUnavailable bool `json:"creditUnavailable"` + UserHash string `json:"userHash"` + RecommendCode string `json:"recommendCode"` + RegisterTime int64 `json:"registerTime"` + IsEligibleForEducationDiscount bool `json:"isEligibleForEducationDiscount"` + EducationDiscountExpirationDate int `json:"educationDiscountExpirationDate"` + IsNewUser bool `json:"isNewUser"` + RegisterMethod string `json:"registerMethod"` + DisableKefu bool `json:"disableKefu"` + InvitedUserCount int `json:"invitedUserCount"` + WorkspaceList []string `json:"workspaceList"` + WechatOpenID string `json:"wechatOpenId"` + WechatWebsiteOpenID string `json:"wechatWebsiteOpenId"` + WechatUnionID string `json:"wechatUnionId"` +} +type reqWorkspace struct { + Code int `json:"code"` + Data reqWorkspaceData `json:"data"` + Message string `json:"message"` +} + +type reqWorkspaceData struct { + ID string `json:"id"` + Active bool `json:"active"` + BilinkColor string `json:"bilink_color"` + BlockCount int `json:"block_count"` + CloseSharePageAd bool `json:"close_share_page_ad"` + CloseSharePageBottomLogo bool `json:"close_share_page_bottom_logo"` + CreatedBy string `json:"created_by"` + CreatedTime int64 `json:"created_time"` + DateFormatType string `json:"date_format_type"` + DbTotalRowCount string `json:"db_total_row_count"` + DisableChangePublicTopPage bool `json:"disable_change_public_top_page"` + DisableCopy bool `json:"disable_copy"` + DisableGuests bool `json:"disable_guests"` + DisableImportPage bool `json:"disable_import_page"` + DisableMemberCreateTeam bool `json:"disable_member_create_team"` + DisableMovePage bool `json:"disable_move_page"` + DisableSharePage bool `json:"disable_share_page"` + Domain string `json:"domain"` + DomainIndexPage string `json:"domain_index_page"` + EditedBy string `json:"edited_by"` + EditedTime int64 `json:"edited_time"` + EnableTryout bool `json:"enable_tryout"` + Icon string `json:"icon"` + ImageAutoOcr bool `json:"image_auto_ocr"` + JoinWorkspaceApply bool `json:"join_workspace_apply"` + LastActiveTime int64 `json:"last_active_time"` + Members []Members `json:"members"` + Name string `json:"name"` + PageDefaultSetting PageDefaultSetting `json:"page_default_setting"` + Pages []string `json:"pages"` + Plan Plan `json:"plan"` + PlanPricePerCapitaPerDay string `json:"plan_price_per_capita_per_day"` + PlanType string `json:"plan_type"` + ShowSharingStatusDots bool `json:"show_sharing_status_dots"` + ShowWatermark bool `json:"show_watermark"` + StartDayOfWeek int `json:"start_day_of_week"` + StorageCount int `json:"storage_count"` + StorageLimit int `json:"storage_limit"` + TeamType string `json:"team_type"` + TimeZoneType string `json:"time_zone_type"` + Version int `json:"version"` + WorkspaceIcon []interface{} `json:"workspace_icon"` + WorkspaceTemplates []interface{} `json:"workspace_templates"` + FaqPages []interface{} `json:"faq_pages"` + FaqFiles []interface{} `json:"faq_files"` + TeamSpaces []interface{} `json:"team_spaces"` } type workSpacePageList struct {