Skip to content

Instantly share code, notes, and snippets.

@codemicro
Created February 16, 2022 21:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codemicro/f26e0d38cc00beef0f84a25ba505476a to your computer and use it in GitHub Desktop.
Save codemicro/f26e0d38cc00beef0f84a25ba505476a to your computer and use it in GitHub Desktop.
Go program to export Google Keep data to GitJournal Markdown format
package main
import (
"encoding/json"
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"path"
"strings"
"time"
)
var (
inputDir = os.Args[1]
outputDir = os.Args[2]
)
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "Unhandled error: %v", err)
os.Exit(1)
}
}
type file struct {
Attachments []struct {
FilePath string `json:"filePath"`
Mimetype string `json:"mimetype"`
} `json:"attachments"`
TextContent string `json:"textContent"`
Title string `json:"title"`
ListContent []struct {
Text string `json:"text"`
IsChecked bool `json:"isChecked"`
} `json:"listContent"`
UserEditedTimestampUsec int64 `json:"userEditedTimestampUsec"`
CreatedTimestampUsec int64 `json:"createdTimestampUsec"`
Labels []struct {
Name string `json:"name"`
} `json:"labels"`
}
func run() error {
_ = os.MkdirAll(outputDir, os.ModeDir)
inputDirFS := os.DirFS(inputDir)
inputJSONFiles, err := fs.Glob(inputDirFS, "*.json")
if err != nil {
return err
}
for _, filename := range inputJSONFiles {
fullpath := path.Join(inputDir, filename)
f, err := parseNoteFile(fullpath)
if err != nil {
return err
}
if err := ioutil.WriteFile(
path.Join(outputDir, time.UnixMicro(f.CreatedTimestampUsec).Format("2006-01-02-15-04-05")+".md"),
[]byte(f.ToMarkdown()),
0644,
); err != nil {
return err
}
if err := f.CopyAttachments(inputDirFS, outputDir); err != nil {
return err
}
}
return nil
}
func parseNoteFile(filename string) (*file, error) {
fcont, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
f := new(file)
if err := json.Unmarshal(fcont, f); err != nil {
return nil, err
}
return f, nil
}
func addYAMLParameter(sbr *strings.Builder, name, content string) {
(*sbr).WriteString(name)
(*sbr).WriteString(": ")
(*sbr).WriteString(content)
(*sbr).WriteString("\n")
}
func (f *file) ToMarkdown() string {
const timeFormat = "2006-01-02 15:04:05 -0700"
var sb strings.Builder
sb.WriteString("---\n")
addYAMLParameter(
&sb,
"created",
time.UnixMicro(f.CreatedTimestampUsec).Format(timeFormat),
)
addYAMLParameter(
&sb,
"modified",
time.UnixMicro(f.UserEditedTimestampUsec).Format(timeFormat),
)
addYAMLParameter(
&sb,
"title",
f.Title,
)
if len(f.ListContent) != 0 {
addYAMLParameter(
&sb,
"type",
"Checklist",
)
}
sb.WriteString("---\n")
if len(f.ListContent) != 0 {
for _, item := range f.ListContent {
sb.WriteString("- [")
mark := " "
if item.IsChecked {
mark = "x"
}
sb.WriteString(mark)
sb.WriteString("] ")
sb.WriteString(strings.ReplaceAll(strings.TrimSpace(item.Text), "\n", "\n "))
sb.WriteString("\n")
}
} else {
sb.WriteString(strings.ReplaceAll(strings.TrimSpace(f.TextContent), "\n", "\n\n"))
}
for _, img := range f.Attachments {
sb.WriteString("\n\n![Image](")
sb.WriteString(img.FilePath)
sb.WriteString(")")
}
return sb.String()
}
func (f *file) CopyAttachments(fileSys fs.FS, toDir string) error {
for _, attachment := range f.Attachments {
fin, err := fileSys.Open(strings.ReplaceAll(attachment.FilePath, ".jpeg", ".jpg"))
if err != nil {
return err
}
defer fin.Close()
fout, err := os.Create(path.Join(toDir, attachment.FilePath))
if err != nil {
return err
}
defer fout.Close()
_, err = io.Copy(fout, fin)
if err != nil {
return err
}
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment