From c52e7d93c31e7bdacdd143ba6df4524918d732b3 Mon Sep 17 00:00:00 2001 From: takker99 <37929109+takker99@users.noreply.github.com> Date: Wed, 20 Jul 2022 08:40:12 +0900 Subject: [PATCH] :sparkles: Implement wrappers for /api/page-snapshots/ --- deps/scrapbox-rest.ts | 2 + rest/getSnapshots.ts | 150 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 rest/getSnapshots.ts diff --git a/deps/scrapbox-rest.ts b/deps/scrapbox-rest.ts index af75dbe..d9a62f9 100644 --- a/deps/scrapbox-rest.ts +++ b/deps/scrapbox-rest.ts @@ -16,11 +16,13 @@ export type { NotPrivilegeError, Page, PageList, + PageSnapshot, ProjectId, ProjectResponse, ProjectSearchResult, SearchedTitle, SearchResult, SessionError, + Snapshot, TweetInfo, } from "https://raw.githubusercontent.com/scrapbox-jp/types/0.3.5/rest.ts"; diff --git a/rest/getSnapshots.ts b/rest/getSnapshots.ts new file mode 100644 index 0000000..9d3e071 --- /dev/null +++ b/rest/getSnapshots.ts @@ -0,0 +1,150 @@ +import type { + ErrorLike, + NotFoundError, + NotLoggedInError, + NotMemberError, + PageSnapshot, + Snapshot, +} from "../deps/scrapbox-rest.ts"; +import { tryToErrorLike } from "../is.ts"; +import { cookie } from "./auth.ts"; +import { BaseOptions, Result, setDefaults } from "./util.ts"; +import { UnexpectedResponseError } from "./error.ts"; + +/** 不正なfollowingIdを渡されたときに発生するエラー */ +export interface InvalidPageSnapshotIdError extends ErrorLike { + name: "InvalidPageSnapshotIdError"; +} + +export interface GetSnapshotsOptions extends BaseOptions { + /** 次のsnapshots listを示すID */ + followingId?: string; +} + +/** get page snapshots + * + * @param options connect.sid etc. + */ +export const getSnapshots = async ( + project: string, + pageId: string, + options?: GetSnapshotsOptions, +): Promise< + Result< + (PageSnapshot & { followingId: string }), + | NotFoundError + | NotLoggedInError + | NotMemberError + | InvalidPageSnapshotIdError + > +> => { + const { sid, hostName, fetch, followingId } = setDefaults(options ?? {}); + const path = `https://${hostName}/api/page-snapshots/${project}/${pageId}/${ + followingId ? `?followingId=${followingId}` : "" + }`; + + const res = await fetch( + path, + sid ? { headers: { Cookie: cookie(sid) } } : undefined, + ); + + if (!res.ok) { + if (res.status === 422) { + return { + ok: false, + value: { + name: "InvalidPageSnapshotIdError", + message: await res.text(), + }, + }; + } + const text = await res.text(); + const value = tryToErrorLike(text); + if (!value) { + throw new UnexpectedResponseError({ + path: new URL(path), + ...res, + body: text, + }); + } + return { + ok: false, + value: value as (NotFoundError | NotLoggedInError | NotMemberError), + }; + } + + const data = (await res.json()) as PageSnapshot; + return { + ok: true, + value: { ...data, followingId: res.headers.get("X-following-id") ?? "" }, + }; +}; + +/** 指定したページのsnapshotsを、responseに入っている塊ごとに全て返す + * + * @param project ページのproject name + * @param pageId page id + * @return 認証が通らなかったらエラーを、通ったらasync generatorを返す + */ +export const readSnapshotsBulk = async ( + project: string, + pageId: string, + options?: BaseOptions, +): Promise< + | NotFoundError + | NotLoggedInError + | NotMemberError + | InvalidPageSnapshotIdError + | AsyncGenerator +> => { + const first = await getSnapshots(project, pageId, options); + if (!first.ok) return first.value; + + return async function* () { + yield first.value.snapshots; + let followingId = first.value.followingId; + + while (followingId) { + const result = await getSnapshots(project, pageId, { + followingId, + ...options, + }); + + // すでに認証は通っているので、ここでエラーになるはずがない + if (!result.ok) { + throw new Error("The authorization cannot be unavailable"); + } + yield result.value.snapshots; + followingId = result.value.followingId; + } + }(); +}; + +/** 指定したページの全てのsnapshotsを取得し、一つづつ返す + * + * @param project ページのproject name + * @param pageId page id + * @return 認証が通らなかったらエラーを、通ったらasync generatorを返す + */ +export const readSnapshots = async ( + project: string, + pageId: string, + options?: BaseOptions, +): Promise< + | NotFoundError + | NotLoggedInError + | NotMemberError + | InvalidPageSnapshotIdError + | AsyncGenerator +> => { + const reader = await readSnapshotsBulk(project, pageId, options); + if ("name" in reader) return reader; + + return async function* () { + for await (const titles of reader) { + for (const title of titles) { + yield title; + } + } + }(); +};