home

My NixOS systems configurations.
Log | Files | Refs | LICENSE

commit ef7329caea0bc3f91af7dc3a8f1e1a216876e6fb
parent 26941459345894a188c1e655ef9fbf41e831a1c7
Author: Vincent Demeester <vincent@sbr.pm>
Date:   Wed, 12 Jun 2024 19:15:24 +0200

tools: init go-org-readwise 📕

A tool to import readwise highlights into org mode.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

Diffstat:
Mnix/overlays/sbr.nix | 1+
Mnix/packages/default.nix | 1+
Atools/go-org-readwise/.gitignore | 2++
Atools/go-org-readwise/README.org | 17+++++++++++++++++
Atools/go-org-readwise/default.nix | 7+++++++
Atools/go-org-readwise/go.mod | 3+++
Atools/go-org-readwise/internal/org/org.go | 1+
Atools/go-org-readwise/internal/readwise/readwise.go | 45+++++++++++++++++++++++++++++++++++++++++++++
Atools/go-org-readwise/internal/readwise/types.go | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Atools/go-org-readwise/main.go | 39+++++++++++++++++++++++++++++++++++++++
10 files changed, 165 insertions(+), 0 deletions(-)

diff --git a/nix/overlays/sbr.nix b/nix/overlays/sbr.nix @@ -13,6 +13,7 @@ rec { inherit (self) stdenv; }; bekind = super.callPackage ../../tools/bekind { }; + go-org-readwise = super.callPackage ../../tools/go-org-readwise { }; my = import ../packages { inherit (self) pkgs; diff --git a/nix/packages/default.nix b/nix/packages/default.nix @@ -6,6 +6,7 @@ rec { vrsync = pkgs.callPackage ./my/vrsync { }; vde-thinkpad = pkgs.callPackage ./my/vde-thinkpad { }; bekind = pkgs.callPackage ../../tools/bekind { }; + go-org-readwise = pkgs.callPackage ../../tools/go-org-readwise { }; chmouzies.kubernetes = pkgs.callPackage ./chmouzies/kubernetes.nix { }; diff --git a/tools/go-org-readwise/.gitignore b/tools/go-org-readwise/.gitignore @@ -0,0 +1 @@ +/go-org-readwise+ \ No newline at end of file diff --git a/tools/go-org-readwise/README.org b/tools/go-org-readwise/README.org @@ -0,0 +1,17 @@ +#+TITLE: `go-org-readwise` + +This is a very very simple project that sync from readwise API to a +set of org files in a folder. It implements just what is needed from +the [readwise API](https://readwise.io/api_deets) to work. + +* Readwise API + +We are /only/ going to implement the export part of the API as, this should be the only one +we need. + +#+begin_quote +If you want to pull all of the highlights from a user's account into your service (eg +notetaking apps, backups, etc) this endpoint is all you need! +#+end_quote + + diff --git a/tools/go-org-readwise/default.nix b/tools/go-org-readwise/default.nix @@ -0,0 +1,7 @@ +{ buildGoModule }: + +buildGoModule rec { + name = "go-org-readwise"; + src = ./.; + vendorHash = null; +} diff --git a/tools/go-org-readwise/go.mod b/tools/go-org-readwise/go.mod @@ -0,0 +1,3 @@ +module github.com/vdemeester/home/tools/go-org-readwise + +go 1.22 diff --git a/tools/go-org-readwise/internal/org/org.go b/tools/go-org-readwise/internal/org/org.go @@ -0,0 +1 @@ +package org diff --git a/tools/go-org-readwise/internal/readwise/readwise.go b/tools/go-org-readwise/internal/readwise/readwise.go @@ -0,0 +1,45 @@ +package readwise + +// TODO: support pages + +import ( + "context" + "encoding/json" + "io" + "net/http" + "time" +) + +func FetchFromAPI(ctx context.Context, apikey string, updateAfter *time.Time) (Export, error) { + export := Export{} + endpoint := "https://readwise.io/api/v2/export" + if updateAfter != nil { + endpoint = endpoint + "/?updateAfter=" + updateAfter.Format(time.RFC3339) + } + + httpClient := &http.Client{} + + req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) + if err != nil { + return export, err + } + req.Header.Add("Authorization", "Token "+apikey) + resp, err := httpClient.Do(req) + if err != nil { + return export, err + } + + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return export, err + } + + err = json.Unmarshal(body, &export) + if err != nil { + return export, err + } + + return export, nil +} diff --git a/tools/go-org-readwise/internal/readwise/types.go b/tools/go-org-readwise/internal/readwise/types.go @@ -0,0 +1,49 @@ +package readwise + +type Export struct { + Count int `json:"count"` + NextPageCursor *int `json:"nextPageCursor"` + Results []Result `json:"results"` +} + +type Result struct { + UserBookId int `json:"use_book_id"` + Title string `json:"title"` + ReadableTitle string `json:"readable_title"` + CoverImageURL string `json:"cover_image_url"` + Author string `json:"author"` + UniqueURL string `json:"unique_url"` + BookTags []Tag `json:"book_tags"` + Category string `json:"category"` + DocumentNote string `json:"document_note"` + Summary string `json:"summary"` + ReadwiseURL string `json:"readwise_url"` + Source string `json:"source"` + SourceURL string `json:"source_url"` + Highlights []Highlight `json:"highlights"` +} + +type Highlight struct { + Text string `json:"text"` + ID int `json:"id"` + Note string `json:"note"` + Location int `json:"location"` + LocationType string `json:"location_type"` + HighlightedAt string `json:"highlighted_at"` + BookID int `json:"book_id"` + URL string `json:"url"` + Color string `json:"color"` + Updated string `json:"updated"` + Tags []Tag `json:"tags"` +} + +type Tags struct { + Count int `json:"count"` + Next string `json:"next"` + Results []Tag `json:"results"` +} + +type Tag struct { + ID int `json:"id"` + Name string `json:"name"` +} diff --git a/tools/go-org-readwise/main.go b/tools/go-org-readwise/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/vdemeester/home/tools/go-org-readwise/internal/readwise" +) + +func main() { + ctx := context.Background() + highlights, merr := readwise.FetchFromAPI(ctx, os.Getenv("READWISE_KEY"), nil) + if merr != nil { + fmt.Fprintf(os.Stderr, "%v\n", merr) + os.Exit(1) + } + fmt.Println("count", highlights.Count) + fmt.Println("nextPageCursor", *highlights.NextPageCursor) + fmt.Println("size", len(highlights.Results)) + + updateAfter := time.Now().Add(-1000 * time.Hour) + fmt.Println("updateAfter:", updateAfter) + highlights, merr = readwise.FetchFromAPI(ctx, os.Getenv("READWISE_KEY"), &updateAfter) + if merr != nil { + fmt.Fprintf(os.Stderr, "%v\n", merr) + os.Exit(1) + } + fmt.Println("count", highlights.Count) + fmt.Println("nextPageCursor", *highlights.NextPageCursor) + fmt.Println("size", len(highlights.Results)) + for _, h := range highlights.Results { + fmt.Println("title", h.Title, len(h.Highlights), h.BookTags) + // for _, hh := range h.Highlights { + // fmt.Println(">>>", hh.ID, hh.Tags) + // } + } +}