@pipedream/readwise 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,11 @@
1
1
  # Overview
2
2
 
3
- The Readwise API is an amazing resource for building powerful applications that
4
- use Readwise's unique data and insights. Using the Readwise API, you can build
5
- applications that:
3
+ The Readwise API allows you to access and manipulate your Readwise data, which includes highlights, notes, and books from your reading list. With this API, you can automate the retrieval of your reading highlights, synchronize them across various platforms, or trigger custom actions based on new highlights added. Pipedream, as a serverless integration and compute platform, enables you to create workflows that leverage the Readwise API to build powerful automations, connecting your reading insights to countless other apps and services to enrich productivity and data management.
6
4
 
7
- - Retrieve annotations and highlights from any book or article you’ve read
8
- using Readwise.
9
- - Analyze and visualize insights from your annotations and highlights.
10
- - Perfoerm sentiment analysis on the data to identify topics.
11
- - Create interactive books or articles to help you better understand what
12
- you’ve read.
13
- - Permalink any highlight or annotation you’ve saved through Readwise.
14
- - Create reading lists and summaries.
15
- - Send custom alerts and notifications when certain topics or keywords appear
16
- in your highlights and annotations.
17
- - Create summary and progress reports for books or articles you’re reading.
5
+ # Example Use Cases
6
+
7
+ - **Sync Highlights to Notion Database**: Automatically add new Readwise highlights to a Notion database. Upon detecting a new highlight via the Readwise API, Pipedream can trigger a workflow that creates or updates a corresponding page in Notion, allowing for seamless management and review of reading notes.
8
+
9
+ - **Create Weekly Reading Digest Email**: Compile a weekly digest of your Readwise highlights and send it via email. Pipedream schedules a weekly workflow that fetches the latest highlights and uses an email service, such as SendGrid, to craft and send a personalized reading digest.
10
+
11
+ - **Trigger Slack Notifications for New Highlights**: Receive immediate notifications in Slack whenever a new highlight is added to your Readwise account. Pipedream listens for new Readwise highlights and sends a formatted message to a specified Slack channel, keeping you and your team informed about new insights.
@@ -0,0 +1,41 @@
1
+ import readwise from "../../readwise.app.mjs";
2
+
3
+ export default {
4
+ key: "readwise-get-highlight-details",
5
+ name: "Get Highlight Details",
6
+ description: "Get Highlight´s Details [See the docs here](https://readwise.io/api_deets)",
7
+ version: "0.0.4",
8
+ annotations: {
9
+ destructiveHint: false,
10
+ openWorldHint: true,
11
+ readOnlyHint: true,
12
+ },
13
+ type: "action",
14
+ props: {
15
+ readwise,
16
+ bookId: {
17
+ propDefinition: [
18
+ readwise,
19
+ "bookId",
20
+ ],
21
+ },
22
+ highlightId: {
23
+ propDefinition: [
24
+ readwise,
25
+ "highlightId",
26
+ (c) => ({
27
+ bookId: c.bookId,
28
+ }),
29
+ ],
30
+ },
31
+ },
32
+ async run({ $ }) {
33
+ const highlight = await this.readwise.getHighlight({
34
+ $,
35
+ highlightId: this.highlightId,
36
+ });
37
+
38
+ $.export("$summary", `Successfully returned highlight (${this.highlightId})`);
39
+ return highlight;
40
+ },
41
+ };
@@ -0,0 +1,55 @@
1
+ import readwise from "../../readwise.app.mjs";
2
+
3
+ export default {
4
+ key: "readwise-list-highlights",
5
+ name: "List Highlights",
6
+ description: "A list of highlights with a pagination metadata. The rate limit of this endpoint is restricted to 20 requests per minute. Each request returns 1000 items. [See the docs here](https://readwise.io/api_deets)",
7
+ version: "0.0.4",
8
+ annotations: {
9
+ destructiveHint: false,
10
+ openWorldHint: true,
11
+ readOnlyHint: true,
12
+ },
13
+ type: "action",
14
+ props: {
15
+ readwise,
16
+ bookId: {
17
+ propDefinition: [
18
+ readwise,
19
+ "bookId",
20
+ ],
21
+ optional: true,
22
+ },
23
+ limit: {
24
+ propDefinition: [
25
+ readwise,
26
+ "limit",
27
+ ],
28
+ },
29
+ },
30
+ async run({ $ }) {
31
+ const params = {
32
+ page_size: 1000,
33
+ limit: this.limit,
34
+ };
35
+ if (this.bookId) params.book_id = this.bookId;
36
+
37
+ const items = [];
38
+
39
+ const paginator = this.readwise.paginate({
40
+ $,
41
+ fn: this.readwise.listHighlights,
42
+ params,
43
+ });
44
+ for await (const item of paginator) {
45
+ items.push(item);
46
+ }
47
+
48
+ const suffix = items.length === 1
49
+ ? ""
50
+ : "s";
51
+
52
+ $.export("$summary", `Successfully returned ${items.length} highlight${suffix}`);
53
+ return items;
54
+ },
55
+ };
@@ -0,0 +1,20 @@
1
+ /* eslint-disable no-unused-vars */
2
+ export const clearObj = (obj) => {
3
+ return Object.entries(obj)
4
+ .filter(([
5
+ _,
6
+ v,
7
+ ]) => v != null)
8
+ .reduce(
9
+ (acc, [
10
+ k,
11
+ v,
12
+ ]) => ({
13
+ ...acc,
14
+ [k]: v === Object(v)
15
+ ? clearObj(v)
16
+ : v,
17
+ }),
18
+ {},
19
+ );
20
+ };
package/package.json CHANGED
@@ -1,21 +1,18 @@
1
1
  {
2
2
  "name": "@pipedream/readwise",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Pipedream Readwise Components",
5
- "main": "dist/app/readwise.app.mjs",
5
+ "main": "readwise.app.mjs",
6
6
  "keywords": [
7
7
  "pipedream",
8
8
  "readwise"
9
9
  ],
10
- "files": [
11
- "dist"
12
- ],
13
10
  "homepage": "https://pipedream.com/apps/readwise",
14
11
  "author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
15
12
  "publishConfig": {
16
13
  "access": "public"
17
14
  },
18
15
  "dependencies": {
19
- "@pipedream/platform": "^1.2.0"
16
+ "@pipedream/platform": "^1.6.8"
20
17
  }
21
18
  }
@@ -0,0 +1,147 @@
1
+ import { axios } from "@pipedream/platform";
2
+ import { clearObj } from "./common/utils.mjs";
3
+
4
+ export default {
5
+ type: "app",
6
+ app: "readwise",
7
+ propDefinitions: {
8
+ bookId: {
9
+ type: "integer",
10
+ label: "Book Id",
11
+ description: "Id of the book to list highlights",
12
+ async options({ page }) {
13
+ const { results } = await this.listBooks({
14
+ params: {
15
+ page: page + 1,
16
+ },
17
+ });
18
+
19
+ return results.map(({
20
+ title, id,
21
+ }) => ({
22
+ label: title,
23
+ value: id,
24
+ })) || [];
25
+ },
26
+ },
27
+ limit: {
28
+ type: "integer",
29
+ label: "Limit",
30
+ description: "Specify number of results per page (default is 100, max is 1000)",
31
+ default: 100,
32
+ },
33
+ highlightId: {
34
+ type: "integer",
35
+ label: "Highlight Id",
36
+ description: "Id of the highlight to list details",
37
+ async options({
38
+ page, bookId: book_id,
39
+ }) {
40
+ const { results } = await this.listHighlights({
41
+ params: {
42
+ book_id,
43
+ page: page + 1,
44
+ },
45
+ });
46
+
47
+ return results.map(({
48
+ text, id,
49
+ }) => ({
50
+ label: `(${id}) ${text}`,
51
+ value: id,
52
+ })) || [];
53
+ },
54
+ },
55
+ },
56
+ methods: {
57
+ _getBaseUrl() {
58
+ return "https://readwise.io/api";
59
+ },
60
+ _getHeaders() {
61
+ return {
62
+ "Authorization": `Token ${this.$auth.access_token}`,
63
+ };
64
+ },
65
+ async _makeRequest({
66
+ $, path, ...otherConfig
67
+ }) {
68
+ const config = {
69
+ url: `${this._getBaseUrl()}/${path}`,
70
+ headers: this._getHeaders(),
71
+ ...otherConfig,
72
+ };
73
+
74
+ return axios($ || this, clearObj(config));
75
+ },
76
+ async listBooks({
77
+ $, params,
78
+ }) {
79
+ return this._makeRequest({
80
+ $,
81
+ path: "v2/books",
82
+ params,
83
+ });
84
+ },
85
+ async listHighlights({
86
+ $, params,
87
+ }) {
88
+ return this._makeRequest({
89
+ $,
90
+ path: "v2/highlights",
91
+ params,
92
+ });
93
+ },
94
+ async listDocuments({
95
+ $, params,
96
+ }) {
97
+ return this._makeRequest({
98
+ $,
99
+ path: "v3/list",
100
+ params,
101
+ });
102
+ },
103
+ async getHighlight({
104
+ $, highlightId,
105
+ }) {
106
+ return this._makeRequest({
107
+ $,
108
+ path: `v2/highlights/${highlightId}`,
109
+ });
110
+ },
111
+ async *paginate({
112
+ $, fn, params = {}, page,
113
+ }) {
114
+ const { limit } = params;
115
+ let count = 0;
116
+
117
+ do {
118
+ const {
119
+ results,
120
+ next,
121
+ } = await fn({
122
+ $,
123
+ params: {
124
+ ...params,
125
+ page,
126
+ },
127
+ });
128
+
129
+ for (const d of results) {
130
+ yield d;
131
+
132
+ if (limit && ++count === limit) {
133
+ return count;
134
+ }
135
+ }
136
+ page = null;
137
+
138
+ if (next) {
139
+ const regex = next.match(/page=([^&]+)/g);
140
+ const pageNumber = regex[0].split("=");
141
+ page = pageNumber[1];
142
+ }
143
+
144
+ } while (page);
145
+ },
146
+ },
147
+ };
@@ -0,0 +1,83 @@
1
+ import readwise from "../../readwise.app.mjs";
2
+ import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
3
+
4
+ export default {
5
+ key: "readwise-new-documents",
6
+ name: "New Documents",
7
+ description: "Emit new document [See the documentation](https://readwise.io/reader_api)",
8
+ version: "0.0.2",
9
+ type: "source",
10
+ dedupe: "unique",
11
+ props: {
12
+ readwise,
13
+ timer: {
14
+ label: "Polling interval",
15
+ description: "Pipedream polls the Readwise API on this schedule",
16
+ type: "$.interface.timer",
17
+ default: {
18
+ intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
19
+ },
20
+ },
21
+ location: {
22
+ type: "string",
23
+ label: "Location",
24
+ description: "(Optional) The document's location. [See the documentation](https://readwise.io/reader_api).",
25
+ options: [
26
+ "new",
27
+ "later",
28
+ "shortlist",
29
+ "archive",
30
+ "feed",
31
+ ],
32
+ optional: true,
33
+ },
34
+ category: {
35
+ type: "string",
36
+ label: "Category",
37
+ description: "(Optional) The document's category. [See the documentation](https://readwise.io/reader_api).",
38
+ options: [
39
+ "article",
40
+ "email",
41
+ "rss",
42
+ "highlight",
43
+ "note",
44
+ "pdf",
45
+ "epub",
46
+ "tweet",
47
+ "video",
48
+ ],
49
+ optional: true,
50
+ },
51
+ },
52
+ hooks: {
53
+ async activate() {
54
+ await this.processEvent();
55
+ },
56
+ },
57
+ methods: {
58
+ async processDocuments(params) {
59
+ const { results: events } = await this.readwise.listDocuments({
60
+ params,
61
+ });
62
+ for (const event of events.slice(0, 100)) {
63
+ this.emitEvent(event);
64
+ }
65
+ },
66
+ async processEvent() {
67
+ await this.processDocuments({
68
+ location: this.location || "",
69
+ category: this.category || "",
70
+ });
71
+ },
72
+ emitEvent(event) {
73
+ this.$emit(event, {
74
+ id: event.id,
75
+ summary: `New Document: ${event.id}`,
76
+ ts: new Date(event.created_at),
77
+ });
78
+ },
79
+ },
80
+ async run() {
81
+ return this.processEvent();
82
+ },
83
+ };
@@ -0,0 +1,85 @@
1
+ import readwise from "../../readwise.app.mjs";
2
+ import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
3
+
4
+ export default {
5
+ key: "readwise-new-highlights",
6
+ name: "New Highlights",
7
+ description: "Emit new Highlight",
8
+ version: "0.0.5",
9
+ type: "source",
10
+ dedupe: "unique",
11
+ props: {
12
+ readwise,
13
+ db: "$.service.db",
14
+ timer: {
15
+ label: "Polling interval",
16
+ description: "Pipedream will poll the Readwise API on this schedule",
17
+ type: "$.interface.timer",
18
+ default: {
19
+ intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
20
+ },
21
+ },
22
+ bookId: {
23
+ propDefinition: [
24
+ readwise,
25
+ "bookId",
26
+ ],
27
+ optional: true,
28
+ },
29
+ },
30
+ hooks: {
31
+ async activate() {
32
+ await this.processHighlighs({
33
+ book_id: this.bookId,
34
+ page_size: 25,
35
+ });
36
+ },
37
+ },
38
+ methods: {
39
+ _getLastHighlightedAt() {
40
+ return this.db.get("lastHighlightedAt");
41
+ },
42
+ _setLastHighlightedAt(lastHighlightedAt) {
43
+ this.db.set("lastHighlightedAt", lastHighlightedAt);
44
+ },
45
+ async retrieveTicket(id) {
46
+ return this.readwise.retrieveTicket({
47
+ id,
48
+ });
49
+ },
50
+ async processHighlighs(params) {
51
+ const { results: events } = await this.readwise.listHighlights({
52
+ params,
53
+ });
54
+ for (const event of events) {
55
+ this.emitEvent(await this.readwise.getHighlight({
56
+ highlightId: event.id,
57
+ }));
58
+ }
59
+ },
60
+ async processEvent() {
61
+ const lastHighlightedAt = this._getLastHighlightedAt();
62
+ await this.processHighlighs({
63
+ book_id: this.bookId,
64
+ highlighted_at__gt: lastHighlightedAt,
65
+ });
66
+ },
67
+ emitEvent(event, lastHighlightedAt = null) {
68
+ lastHighlightedAt = lastHighlightedAt || this._getLastHighlightedAt();
69
+
70
+ if (!lastHighlightedAt || (new Date(event.highlighted_at) > new Date(lastHighlightedAt)))
71
+ this._setLastHighlightedAt(event.highlighted_at);
72
+
73
+ const ts = Date.parse(event.highlighted_at);
74
+ this.$emit(event, {
75
+ id: `${event.id}_${ts}`,
76
+ ts,
77
+ summary: `New highlight: ${event.id}`,
78
+ });
79
+ },
80
+ },
81
+ async run() {
82
+ console.log("Raw received event:");
83
+ return this.processEvent();
84
+ },
85
+ };