package main import ( "encoding/base64" "encoding/json" "fmt" "io" "log" "net/http" "os" "text/template" "github.com/alexflint/go-arg" ) type NodeData struct { Id string Title string Body string } type Node struct { Children []*Node Data NodeData } type Arguments struct { Baseurl string `arg:"required" help:"Base URL of the Confluence instance (required)"` Spacekey string `arg:"required" help:"Spacekey to export (required)"` User string `arg:"required" help:"User used for authentication (required)"` Token string `arg:"required" help:"Token used for authentication (required)"` } func (n *Node) writeIndex(w io.Writer) { tmpl, err := template.New("root").Parse(` {{ define "tree" }} {{ if gt (len .Children) 0 }} {{ end }} {{ end }} {{ template "tree" . }} `) if err != nil { fmt.Println("ERROR creating template") } err = tmpl.Execute(w, n) if err != nil { fmt.Printf("ERROR executing template: %v", err) } } func (n *Node) getAllChildren() []*Node { var children []*Node for _, child := range n.Children { children = append(children, child) if len(child.Children) > 0 { children = append(children, child.getAllChildren()...) } } return children } func (n *Node) getChildWithID(id string) (node *Node, hasChild bool) { for _, c := range n.Children { if c.Data.Id == id { return c, true } } empty := Node{} return &empty, false } func (n *Node) addChild(child *Node) { n.Children = append(n.Children, child) } func writeHTML(n *Node) { f, err := os.Create(n.Data.Id + ".html") if err != nil { log.Fatalf("Error creating file for ID %v", n.Data.Id) } defer f.Close() log.Printf("Writing file %v", f.Name()) f.Write([]byte(n.Data.Body)) } type spaceResult struct { Results []struct { ID string `json:"id"` Type string `json:"type"` Status string `json:"status"` Title string `json:"title"` Ancestors []struct { ID string `json:"id"` Type string `json:"type"` Status string `json:"status"` Title string `json:"title"` MacroRenderedOutput struct { } `json:"macroRenderedOutput"` Extensions struct { Position int `json:"position"` } `json:"extensions"` Expandable struct { Container string `json:"container"` Metadata string `json:"metadata"` Restrictions string `json:"restrictions"` History string `json:"history"` Body string `json:"body"` Version string `json:"version"` Descendants string `json:"descendants"` Space string `json:"space"` ChildTypes string `json:"childTypes"` Operations string `json:"operations"` SchedulePublishDate string `json:"schedulePublishDate"` Children string `json:"children"` Ancestors string `json:"ancestors"` } `json:"_expandable"` Links struct { Self string `json:"self"` Tinyui string `json:"tinyui"` Editui string `json:"editui"` Webui string `json:"webui"` } `json:"_links"` } `json:"ancestors"` MacroRenderedOutput struct { } `json:"macroRenderedOutput"` Extensions struct { Position int `json:"position"` } `json:"extensions"` Expandable struct { ChildTypes string `json:"childTypes"` Container string `json:"container"` Metadata string `json:"metadata"` Operations string `json:"operations"` SchedulePublishDate string `json:"schedulePublishDate"` Children string `json:"children"` Restrictions string `json:"restrictions"` History string `json:"history"` Body string `json:"body"` Version string `json:"version"` Descendants string `json:"descendants"` Space string `json:"space"` } `json:"_expandable"` Links struct { Self string `json:"self"` Tinyui string `json:"tinyui"` Editui string `json:"editui"` Webui string `json:"webui"` } `json:"_links"` } `json:"results"` Start int `json:"start"` Limit int `json:"limit"` Size int `json:"size"` Links struct { Base string `json:"base"` Context string `json:"context"` Next string `json:"next"` Self string `json:"self"` } `json:"_links"` } type contentResult struct { ID string `json:"id"` Type string `json:"type"` Status string `json:"status"` Title string `json:"title"` MacroRenderedOutput struct { } `json:"macroRenderedOutput"` Body struct { View struct { Value string `json:"value"` Representation string `json:"representation"` Expandable struct { Webresource string `json:"webresource"` EmbeddedContent string `json:"embeddedContent"` MediaToken string `json:"mediaToken"` Content string `json:"content"` } `json:"_expandable"` } `json:"view"` Expandable struct { Editor string `json:"editor"` AtlasDocFormat string `json:"atlas_doc_format"` ExportView string `json:"export_view"` StyledView string `json:"styled_view"` Dynamic string `json:"dynamic"` Storage string `json:"storage"` Editor2 string `json:"editor2"` AnonymousExportView string `json:"anonymous_export_view"` } `json:"_expandable"` } `json:"body"` Extensions struct { Position int `json:"position"` } `json:"extensions"` Expandable struct { ChildTypes string `json:"childTypes"` Container string `json:"container"` Metadata string `json:"metadata"` Operations string `json:"operations"` SchedulePublishDate string `json:"schedulePublishDate"` Children string `json:"children"` Restrictions string `json:"restrictions"` History string `json:"history"` Ancestors string `json:"ancestors"` Version string `json:"version"` Descendants string `json:"descendants"` Space string `json:"space"` } `json:"_expandable"` Links struct { Editui string `json:"editui"` Webui string `json:"webui"` Context string `json:"context"` Self string `json:"self"` Tinyui string `json:"tinyui"` Collection string `json:"collection"` Base string `json:"base"` } `json:"_links"` } func getRequest(args Arguments, url string) ([]byte, error) { b64Auth := base64.StdEncoding.EncodeToString([]byte(args.User + ":" + args.Token)) client := &http.Client{} req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } req.Header.Add("Authorization", "Basic "+b64Auth) resp, err := client.Do(req) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, err } if err != nil { return nil, err } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } return body, nil } func getSpaceChildren(args Arguments) spaceResult { url := args.Baseurl + "/wiki/rest/api/space/" + args.Spacekey + "/content/page?expand=ancestors&limit=9999" body, err := getRequest(args, url) if err != nil { log.Fatal(err) } var result spaceResult json.Unmarshal(body, &result) return result } func getContentFromID(args Arguments, id string) contentResult { url := args.Baseurl + "/wiki/rest/api/content/" + id + "?expand=body.view" body, err := getRequest(args, url) if err != nil { log.Fatal(err) } var result contentResult json.Unmarshal(body, &result) return result } func createPageTree(s *spaceResult) *Node { var parent *Node root := Node{Data: NodeData{Title: "root"}} for _, page := range s.Results { parent = &root // ignore the root node if len(page.Ancestors) == 0 { continue } // create branches from ancestors for i, ancestor := range page.Ancestors { // skip root node if i == 0 { continue } node, hasChild := parent.getChildWithID(ancestor.ID) if hasChild { parent = node continue } n := Node{Data: NodeData{Id: ancestor.ID, Title: ancestor.Title}} parent.addChild(&n) parent = &n } // create leaf node n := Node{Data: NodeData{Id: page.ID, Title: page.Title}} parent.addChild(&n) } return &root } func main() { var args Arguments arg.MustParse(&args) result := getSpaceChildren(args) root := createPageTree(&result) fname := "index.html" f, err := os.Create(fname) if err != nil { log.Fatalf("Failed to open %v", fname) } defer f.Close() root.writeIndex(f) pages := root.getAllChildren() for _, page := range pages { content := getContentFromID(args, page.Data.Id) page.Data.Body = content.Body.View.Value writeHTML(page) } }