UNPKG

instapaper-ts

Version:

A type-safe API client for Instapaper.

146 lines (145 loc) 4.44 kB
// src/index.ts import assert from "node:assert"; import crypto from "node:crypto"; import OAuth from "oauth-1.0a"; var Instapaper = class { baseUrl = "https://www.instapaper.com/api"; authUrl = this.baseUrl + "/1/oauth/access_token"; oauth; username; password; token; constructor({ consumerKey, consumerSecret, username, password, token }) { this.oauth = new OAuth({ consumer: { key: consumerKey, secret: consumerSecret }, signature_method: "HMAC-SHA1", hash_function: (base_string, key) => crypto.createHmac("sha1", key).update(base_string).digest("base64") }); this.username = username; this.password = password; this.token = token; } makeRequest = async (url, params = {}, token) => { const form = new URLSearchParams(); for (const [key, val] of Object.entries(params)) { form.append(key, String(val)); } const headers = this.oauth.toHeader( this.oauth.authorize( { url, method: "POST", data: params }, token ) ); const response = await fetch(url, { method: "POST", headers: new Headers({ ...headers }), body: form }); if (!response.ok) { const errorText = await response.text(); throw new Error(`API error: ${response.status} ${errorText}`); } const contentType = response.headers.get("content-type") || ""; if (contentType.includes("application/json")) { return response.json(); } else { return response.text(); } }; fetchToken = async () => { assert( this.username && this.password, "Please set username and password with setCredentials()." ); const params = { x_auth_username: this.username, x_auth_password: this.password, x_auth_mode: "client_auth" }; const responseText = await this.makeRequest(this.authUrl, params); const data = new URLSearchParams(responseText); const key = data.get("oauth_token"); const secret = data.get("oauth_token_secret"); assert( key && secret, "There was an error fetching the token. One or both of key and secret is null." ); return { key, secret }; }; request = async (endpoint, params = {}) => { if (!this.token) { this.token = await this.fetchToken(); } const url = this.baseUrl + endpoint; return this.makeRequest(url, params, this.token); }; setCredentials = (username, password) => { this.username = username; this.password = password; }; withCredentials = (username, password) => { this.setCredentials(username, password); return this; }; withToken = (token) => { this.token = token; return this; }; verifyCredentials = () => this.request("/1/account/verify_credentials"); bookmarks = { list: (params = {}) => this.request( "/1/bookmarks/list", params ), updateReadProgress: (params) => this.request( "/1/bookmarks/update_read_progress", params ), add: (params) => this.request("/1/bookmarks/add", params), delete: (bookmark_id) => this.request("/1/bookmarks/delete", { bookmark_id }), star: (bookmark_id) => this.request("/1/bookmarks/star", { bookmark_id }), unstar: (bookmark_id) => this.request("/1/bookmarks/unstar", { bookmark_id }), archive: (bookmark_id) => this.request("/1/bookmarks/archive", { bookmark_id }), unarchive: (bookmark_id) => this.request("/1/bookmarks/unarchive", { bookmark_id }), move: (bookmark_id, folder_id) => this.request("/1/bookmarks/move", { bookmark_id, folder_id }), getText: (bookmark_id) => this.request("/1/bookmarks/get_text", { bookmark_id }) }; folders = { list: () => this.request("/1/folders/list"), add: (title) => this.request("/1/folders/add", { title }), delete: (folder_id) => this.request("/1/folders/delete", { folder_id }), setOrder: (order) => this.request("/1/folders/set_order", { order }) }; highlights = { list: (bookmark_id) => this.request( `/1.1/bookmarks/${bookmark_id}/highlights` ), add: (params) => this.request( `/1.1/bookmarks/${params.bookmark_id}/highlight`, { text: params.text, position: params.position } ), delete: (highlight_id) => this.request(`/1.1/highlights/${highlight_id}/delete`) }; }; export { Instapaper };