scrapbox の擬似的な書き込みAPIを作る

Scrapbox、いろいろな用途につかえて便利だが、Scrapboxには書き込みAPIがない。

実はこれは今後実装予定とかでもなくて、Scrapboxの思想に合わないので、実装されていない ということらしい。

この思想が間違ってるとは思わないが、たまにプログラムからScrapboxへ書き込みしたくなることがある。 ブラウザでリンクにアクセスするとページが作られるという方法 はあるがどうしてもブラウザは必要になる。

puppeteer を使ってブラウザ操作をAPI化する

ブラウザが必要であれば、プログラムからブラウザを操作すればいいじゃないということで puppeteer というソフトウェアが使える。

じつは、すでに過去のScrapboxのイベントで既にやっている方がいる。

ここに書いてある方法で早速試してみたら、たしかに書き込みができた!

書き込みの正確さと速度をさらに上げる

しかし、上記の方法ではページの本文を1文字ずつキーボード入力をエミュレートすることでやっているため、長文になると時間がかかる&たまにカーソル位置がずれるという問題があった。

これをどうにかできないか考えていたら、そういえば、最初から本文を入力した状態で作る方法 があったじゃん!と思い出した。

?body={本文} とURLにつけることで本文が入力された状態で新しいページを作ることができる。これを使えば、キー入力をエミュレートする必要はなくなる。

というわけで、Scrapbox に新しいページを書き込むだけの関数を作った。

import puppeteer from 'puppeteer';

export const writeToScrapbox = async (sid: string, project: string, pageName: string, text: string) => {
    const url = new URL(`https://scrapbox.io/${project}/${encodeURIComponent(pageName)}?body=${encodeURIComponent(text)}`);
    const browser = await puppeteer.launch({
        args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });
    const page = await browser.newPage();

    await page.setCookie({ name: 'connect.sid', value: sid, domain: 'scrapbox.io' });
    await page.goto(url.toString());

    await page.waitFor('#editor');
    await new Promise((resolve) => setTimeout(() => resolve(), 1000));

    await browser.close();
};

connect.sid というのはScrapboxのログイン情報で、対象のScrapboxに書き込みできるアカウントの認証情報を入れる。これは次の記事に書かれていた。

これで、単一の関数呼び出しだけでプログラムからScrapboxに書き込めるようになった!

たとえば、次のようなコードを実行すると

import { writeToScrapbox } from 'path/to/scrapbox';

const project = 'xxxxx';
const sid = 'xxxxxxxxxxx...';
const pageName = 'test-title';
const text = 'test-text\n[test]\n[test2]\n[test3]';
await writeToScrapbox(sid, project, pageName, text)

ページが作られた!🎉

f:id:castaneai:20200430002547p:plain