Skip to content

Commit d5b1f3a

Browse files
authored
Merge pull request #13 from takker99/replace-links
/api/pages/:projectname/replace/links
2 parents 85d31eb + 1b32668 commit d5b1f3a

File tree

3 files changed

+103
-16
lines changed

3 files changed

+103
-16
lines changed

rest/page-data.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,7 @@ export async function importPages(
4848
);
4949
formData.append("name", "undefined");
5050

51-
if (!csrf) {
52-
const result = await getCSRFToken(sid);
53-
if (!result.ok) return result;
54-
csrf = result.value;
55-
}
51+
csrf ??= await getCSRFToken(sid);
5652

5753
const path = `https://scrapbox.io/api/page-data/import/${project}.json`;
5854
const res = await fetch(

rest/replaceLinks.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type {
2+
NotFoundError,
3+
NotLoggedInError,
4+
NotMemberError,
5+
} from "../deps/scrapbox.ts";
6+
import {
7+
cookie,
8+
getCSRFToken,
9+
makeCustomError,
10+
tryToErrorLike,
11+
} from "./utils.ts";
12+
import type { Result } from "./utils.ts";
13+
14+
/** `replaceLinks`の認証情報 */
15+
export interface ReplaceLinksInit {
16+
/** connect.sid */ sid: string;
17+
/** CSRF token
18+
*
19+
* If it isn't set, automatically get CSRF token from scrapbox.io server.
20+
*/
21+
csrf?: string;
22+
}
23+
24+
/** 指定したproject内の全てのリンクを書き換える
25+
*
26+
* リンクと同一のタイトルは書き換わらないので注意
27+
* - タイトルも書き換えたいときは/browser/mod.tsの`patch()`などで書き換えること
28+
*
29+
* @param project これで指定したproject内の全てのリンクが置換対象となる
30+
* @param from 置換前のリンク
31+
* @param to 置換後のリンク
32+
* @param options connect.sidなど
33+
* @return 置換されたリンクがあったページの数
34+
*/
35+
export async function replaceLinks(
36+
project: string,
37+
from: string,
38+
to: string,
39+
init?: ReplaceLinksInit,
40+
): Promise<
41+
Result<
42+
number,
43+
NotFoundError | NotLoggedInError | NotMemberError
44+
>
45+
> {
46+
const path = `https://scrapbox.io/api/pages/${project}/replace/links`;
47+
const sid = init?.sid;
48+
const csrf = init?.csrf ?? await getCSRFToken(sid);
49+
50+
const res = await fetch(
51+
path,
52+
{
53+
method: "POST",
54+
headers: {
55+
"Content-Type": "application/json;charset=utf-8",
56+
"X-CSRF-TOKEN": csrf,
57+
...(sid
58+
? {
59+
Cookie: cookie(sid),
60+
}
61+
: {}),
62+
},
63+
body: JSON.stringify({ from, to }),
64+
},
65+
);
66+
67+
if (!res.ok) {
68+
const value = tryToErrorLike(await res.text()) as
69+
| false
70+
| NotFoundError
71+
| NotLoggedInError
72+
| NotMemberError;
73+
if (!value) {
74+
throw makeCustomError(
75+
"UnexpectedError",
76+
`Unexpected error has occuerd when fetching "${path}"`,
77+
);
78+
}
79+
return {
80+
ok: false,
81+
value,
82+
};
83+
}
84+
85+
// messageには"2 pages have been successfully updated!"というような文字列が入っているはず
86+
const { message } = (await res.json()) as { message: string };
87+
return { ok: true, value: parseInt(message.match(/\d+/)?.[0] ?? "0") };
88+
}

rest/utils.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
import type { ErrorLike } from "../deps/scrapbox.ts";
2+
import { getProfile } from "./profile.ts";
3+
4+
// scrapbox.io内なら`window._csrf`にCSRF tokenが入っている
5+
declare global {
6+
interface Window {
7+
__csrf?: string;
8+
}
9+
}
210

311
/** HTTP headerのCookieに入れる文字列を作る
412
*
@@ -12,17 +20,12 @@ export type Result<T, E> = { ok: true; value: T } | { ok: false; value: E };
1220
* @param sid - connect.sidに入っている文字列。不正な文字列を入れてもCSRF tokenを取得できるみたい
1321
*/
1422
export async function getCSRFToken(
15-
sid: string,
16-
): Promise<Result<string, ErrorLike>> {
17-
const res = await fetch("https://scrapbox.io/api/users/me", {
18-
headers: { Cookie: cookie(sid) },
19-
});
20-
if (!res.ok) {
21-
const value = (await res.json()) as ErrorLike;
22-
return { ok: false, value };
23-
}
24-
const { csrfToken } = (await res.json()) as { csrfToken: string };
25-
return { ok: true, value: csrfToken };
23+
sid?: string,
24+
): Promise<string> {
25+
if (window.__csrf) return window.__csrf;
26+
27+
const user = await getProfile(sid ? { sid } : undefined);
28+
return user.csrfToken;
2629
}
2730

2831
// 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

0 commit comments

Comments
 (0)