Commit ef7329caea0b

Vincent Demeester <vincent@sbr.pm>
2024-06-12 19:15:24
tools: init go-org-readwise 📕
A tool to import readwise highlights into org mode. Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 2694145
Changed files (10)
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;
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 { };
 
tools/go-org-readwise/internal/org/org.go
@@ -0,0 +1,1 @@
+package org
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
+}
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"`
+}
tools/go-org-readwise/.gitignore
@@ -0,0 +1,1 @@
+/go-org-readwise
\ No newline at end of file
tools/go-org-readwise/default.nix
@@ -0,0 +1,7 @@
+{ buildGoModule }:
+
+buildGoModule rec {
+  name = "go-org-readwise";
+  src = ./.;
+  vendorHash = null;
+}
tools/go-org-readwise/go.mod
@@ -0,0 +1,3 @@
+module github.com/vdemeester/home/tools/go-org-readwise
+
+go 1.22
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)
+		// }
+	}
+}
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
+
+