Skip to content

/api/pages/:projectname/replace/links #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions rest/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,7 @@ export async function importPages(
);
formData.append("name", "undefined");

if (!csrf) {
const result = await getCSRFToken(sid);
if (!result.ok) return result;
csrf = result.value;
}
csrf ??= await getCSRFToken(sid);

const path = `https://scrapbox.io/api/page-data/import/${project}.json`;
const res = await fetch(
Expand Down
88 changes: 88 additions & 0 deletions rest/replaceLinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type {
NotFoundError,
NotLoggedInError,
NotMemberError,
} from "../deps/scrapbox.ts";
import {
cookie,
getCSRFToken,
makeCustomError,
tryToErrorLike,
} from "./utils.ts";
import type { Result } from "./utils.ts";

/** `replaceLinks`の認証情報 */
export interface ReplaceLinksInit {
/** connect.sid */ sid: string;
/** CSRF token
*
* If it isn't set, automatically get CSRF token from scrapbox.io server.
*/
csrf?: string;
}

/** 指定したproject内の全てのリンクを書き換える
*
* リンクと同一のタイトルは書き換わらないので注意
* - タイトルも書き換えたいときは/browser/mod.tsの`patch()`などで書き換えること
*
* @param project これで指定したproject内の全てのリンクが置換対象となる
* @param from 置換前のリンク
* @param to 置換後のリンク
* @param options connect.sidなど
* @return 置換されたリンクがあったページの数
*/
export async function replaceLinks(
project: string,
from: string,
to: string,
init?: ReplaceLinksInit,
): Promise<
Result<
number,
NotFoundError | NotLoggedInError | NotMemberError
>
> {
const path = `https://scrapbox.io/api/pages/${project}/replace/links`;
const sid = init?.sid;
const csrf = init?.csrf ?? await getCSRFToken(sid);

const res = await fetch(
path,
{
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
"X-CSRF-TOKEN": csrf,
...(sid
? {
Cookie: cookie(sid),
}
: {}),
},
body: JSON.stringify({ from, to }),
},
);

if (!res.ok) {
const value = tryToErrorLike(await res.text()) as
| false
| NotFoundError
| NotLoggedInError
| NotMemberError;
if (!value) {
throw makeCustomError(
"UnexpectedError",
`Unexpected error has occuerd when fetching "${path}"`,
);
}
return {
ok: false,
value,
};
}

// messageには"2 pages have been successfully updated!"というような文字列が入っているはず
const { message } = (await res.json()) as { message: string };
return { ok: true, value: parseInt(message.match(/\d+/)?.[0] ?? "0") };
}
25 changes: 14 additions & 11 deletions rest/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import type { ErrorLike } from "../deps/scrapbox.ts";
import { getProfile } from "./profile.ts";

// scrapbox.io内なら`window._csrf`にCSRF tokenが入っている
declare global {
interface Window {
__csrf?: string;
}
}

/** HTTP headerのCookieに入れる文字列を作る
*
Expand All @@ -12,17 +20,12 @@ export type Result<T, E> = { ok: true; value: T } | { ok: false; value: E };
* @param sid - connect.sidに入っている文字列。不正な文字列を入れてもCSRF tokenを取得できるみたい
*/
export async function getCSRFToken(
sid: string,
): Promise<Result<string, ErrorLike>> {
const res = await fetch("https://scrapbox.io/api/users/me", {
headers: { Cookie: cookie(sid) },
});
if (!res.ok) {
const value = (await res.json()) as ErrorLike;
return { ok: false, value };
}
const { csrfToken } = (await res.json()) as { csrfToken: string };
return { ok: true, value: csrfToken };
sid?: string,
): Promise<string> {
if (window.__csrf) return window.__csrf;

const user = await getProfile(sid ? { sid } : undefined);
return user.csrfToken;
}

// cf. https://blog.uhy.ooo/entry/2021-04-09/typescript-is-any-as/#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E5%AE%9A%E7%BE%A9%E5%9E%8B%E3%82%AC%E3%83%BC%E3%83%89%E3%81%AE%E5%BC%95%E6%95%B0%E3%81%AE%E5%9E%8B%E3%82%92%E3%81%A9%E3%81%86%E3%81%99%E3%82%8B%E3%81%8B
Expand Down