package migrator import ( "encoding/json" "fmt" "io" "net/url" "time" "github.com/umputun/remark42/backend/app/store" log "github.com/go-pkgz/lgr" ) // Commento implements Importer from commento export json type Commento struct { DataStore Store } // Credit: https://gitlab.com/commento/commento/-/blob/master/api/domain_import_commento.go#L11-L15 type commentoExport struct { Version int `json:"version"` Comments []commentoComment `json:"comments"` Commenters []commentoCommenter `json:"commenters"` } // Credit: https://gitlab.com/commento/commento/-/blob/master/api/comment.go#L7-L20 type commentoComment struct { CommentHex string `json:"commentHex"` Domain string `json:"domain,omitempty"` Path string `json:"url,omitempty"` CommenterHex string `json:"commenterHex"` Markdown string `json:"markdown"` HTML string `json:"html"` ParentHex string `json:"parentHex"` Score int `json:"score"` State string `json:"state,omitempty"` CreationDate time.Time `json:"creationDate"` Direction int `json:"direction"` Deleted bool `json:"deleted"` } // Credit: https://gitlab.com/commento/commento/-/blob/master/api/commenter.go#L7-L16 type commentoCommenter struct { CommenterHex string `json:"commenterHex,omitempty"` Email string `json:"email,omitempty"` Name string `json:"name"` Link string `json:"link"` Photo string `json:"photo"` Provider string `json:"provider,omitempty"` JoinDate time.Time `json:"joinDate"` IsModerator bool `json:"isModerator"` } // Import comments from Commento and save to store func (d *Commento) Import(r io.Reader, siteID string) (size int, err error) { if e := d.DataStore.DeleteAll(siteID); e != nil { return 0, e } commentsCh := d.convert(r, siteID) failed, passed := 0, 0 for c := range commentsCh { if _, err = d.DataStore.Create(c); err != nil { failed++ continue } passed++ } if failed > 0 { err = fmt.Errorf("failed to save %d comments", failed) if passed == 0 { err = fmt.Errorf("import failed") } } log.Printf("[DEBUG] imported %d comments to site %s", passed, siteID) return passed, err } func (d *Commento) convert(r io.Reader, siteID string) (ch chan store.Comment) { commentsCh := make(chan store.Comment) decoder := json.NewDecoder(r) go func() { var exportedData commentoExport err := decoder.Decode(&exportedData) if err != nil { log.Printf("[WARN] can't decode commento export json, %s", err.Error()) } usersMap := map[string]store.User{} for _, commenter := range exportedData.Commenters { usersMap[commenter.CommenterHex] = store.User{ Name: commenter.Name, ID: "commento_" + store.EncodeID(commenter.CommenterHex), Picture: commenter.Photo, } } usersMap["anonymous"] = store.User{ Name: "Anonymous", ID: "commento_" + store.EncodeID("anonymous"), } for _, comment := range exportedData.Comments { u, ok := usersMap[comment.CommenterHex] if !ok { continue } if comment.Deleted { continue } parentID := comment.ParentHex // comments with ParentHex == "root" are top-level comments if parentID == "root" { parentID = "" } commentURL, e := url.JoinPath("https://", comment.Domain, comment.Path) if e != nil { log.Printf("[WARN] can't construct comment URL in commento import, %s", err.Error()) } log.Printf("[ERROR] commentoURL: %s", commentURL) c := store.Comment{ ID: comment.CommentHex, Locator: store.Locator{ URL: commentURL, SiteID: siteID, }, User: u, Text: comment.Markdown, Timestamp: comment.CreationDate, ParentID: parentID, Imported: true, } commentsCh <- c } close(commentsCh) }() return commentsCh }