Files
captoys/diigo-dump
Connolly 309f003fa3 feat(diigo-dump): fluent WebApp over RPC + bound fetch + logged errors
Restore the fluent client from mm-diigo's WebApp.js as RpcTarget classes
(src/webapp.ts): WebApp with pathjoin/query/withHeaders/cookies/get/post,
flower-style unions (NuxiNL/flower v0.11, Jan 2019 — merge only if no
shared key contradicts), pathjoin containment guard; RateLimitedWebApp
shares one gate across all derived handles, enforced at get/post only.
src/diigo.ts ports makeDiigoAPI and the dumpBookmarks generator.

RPC surface: DiigoService (WorkerEntrypoint) with open() handing a
rate-limited stub whose cookie is attached server-side only, and
dumpNdjson. Promise pipelining makes a whole chain a single round trip.

Divergences from the original for the RPC setting:
- url is a private field: a public property on an RpcTarget is readable
  by stub holders, and query strings can carry credentials
- Error messages carry no header/param values; URLs have query stripped
  (structured details via @endo/errors deferred until node_modules)
- fetch default is fetch.bind(globalThis): a detached fetch throws
  'Illegal invocation' in workerd (found live, fixed, verified 200)
- dumpPage/open log failures via console.error for observability

README documents config, RPC use, and each divergence.

Generated-by: opencode (GLM)
2026-10-06 22:13:58 -05:00
..

diigo-dump

Cloudflare Worker port of diigo-dump.mjs in the mm-diigo repo (projects/diigo-bak/diigo-dump.mjs) — dump Diigo bookmarks, most-recently-updated first, via the browser session cookie rather than the static API key.

What it does

One page: a page-number field and a dump button. Submitting POSTs to the worker, which fetches

https://www.diigo.com/interact_api/load_user_items
    ?sort=updated&count=24&page_num=<page>

with the session cookie and shows status, total, and the JSON items in a read-only <pre>. GET / is inert — dumps happen only on explicit submits.

Provenance notes: the user-agent / x-requested-with headers and the interact_api endpoint mirror the original script; the fluent client from WebApp.js is restored in src/webapp.ts with WebApp and the rate-limit wrapper as RpcTarget subclasses. src/diigo.ts holds makeDiigoAPI and the dumpBookmarks generator; DiigoService.open() hands an RPC caller a rate-limited stub with the cookie attached only on the server side, and promise pipelining makes a whole pathjoin().query().get() chain a single round trip. One deliberate divergence from the original: url is a private field, not a public property — on an RpcTarget, a public property is readable over RPC (a property read is an argument-less method call), and a query string can carry credentials, so a public url would leak them to any stub holder.

RPC use (from another worker, same account)

// wrangler.jsonc of the caller
"services": [{ "binding": "DIIGO", "service": "diigo-dump", "entrypoint": "DiigoService" }]
import { dumpBookmarks } from "./diigo.js";

const stub = env.DIIGO.open(); // rate-limited, cookie stays server-side
// pipeline: one round trip for the whole chain
const first = JSON.parse(await stub.pathjoin("load_user_items")
	.query({ sort: "updated", count: 24, page_num: 1 }).get());
// or run the whole dump in this worker's address space:
for await (const item of dumpBookmarks({ diigoAPI: stub, pages: 2 })) {
	console.log(item);
}

Configuration

  • Secret DIIGO_COOKIE: the Cookie header from diigo.com devtools.
    • Local dev: .dev.vars (gitignored). Note wrangler reads it only at startup — restart after edits.
    • Deployed: npx wrangler secret put DIIGO_COOKIE.

Run

cd diigo-dump
npx wrangler dev --port 8788   # → http://localhost:8788

Status as of 2026-10-06: manual integration test passes — 200, total: 1674. See the project ladder in ../capsite-checklist.md; context in ../capsite-notes.md. This is the cookie path; the API-key path (diigo-ping, EmDash plugin) is still 401.