puppeteerでscrapboxを自動化してイテレーション資料を宣言的に完成させる (2019/9/5)
こんにちは
自己紹介
geta6geta6.iconと申します
慶應SFC出身(増井研)
フロントエンジニア / プロダクトマネージャー
Booth立ち上げ時にフロントエンドとデザインをちょっとやってました
みなさんご利用いただきありがとうございます
https://gyazo.com/10780384e259d0bf83f0ef8bd33791a5
Scrapbox活用事例
弊チームではイテレーションをScrapboxでやってます
スケジュールを確認したり
共有事項を述べ合ったり
IssueFreezeしたり
https://gyazo.com/e6809d9791ea8265630c5de9488d8c9e
イテレーションの中身
イテレーションの中にこんなコーナーがあります
共有:仕組みの変更など、チームへの共有事項
https://gyazo.com/e51667519d9bdf7b561a4270b546b3a8
相談:困りごとがあったら相談する
https://gyazo.com/aa110e4d079b855caafd45883fd6fc1a
感想:特に大事ではないが言わずにいられない感想
https://gyazo.com/266ab347e6eb4db1f6789ee63cc418b6
Scrapboxでイテレーションをやる利点
リアルタイムで更新できる
本当にやるべき議論だけに集中できる
etc...
問題がある
みんなに共有したいことはSlackでも言うし、Scrapboxにも書く、二度手間になる
鮮度の高いネタを書いてもその場で誰も気づいてくれない危険性がある
1週間後にイテレーションする頃には忘れてる可能性がある
SlackからScrapboxへ書き込めるようにした
Slackでリアルタイムにトピックを出しつつScrapboxで永続化する
思いついたその場で宣言できる
雑談を呼び込むきっかけになる
記憶が呼び戻される
思いついたその場で宣言できる
Scrapboxへ行って書き込むだけだと、書いたその時にチームに伝わらない
共有と記録を同時に行う、イテレーションを待たずその場で反応が得られる
https://gyazo.com/8b0d331c7410c4261f143300c5e1dd51
雑談を呼び込むきっかけになる
心理的安全性を下げる
みんなが何に関心を持ち、どのように認識しているのかがわかる
https://gyazo.com/493ab52d6649682fcf87f4ecb8903261
記憶が呼び戻される
1週間経ってイテレーションをやる頃には忘れていることが多い
「あれ、なんでこれ書いたんだっけ」
Slack経由でScrapboxへ書き込むとメンバーが覚えてくれてる
記憶を外部化できる
「これこういう文脈で言ってたよ」
イテレーションで確認すると、その時の記憶を呼び戻すことができる
エピソード記憶とともに今週を振り返ることができる
https://gyazo.com/5d3b72c19062e60f7d2b98fd0233475e
Scrapboxへ書き込む
ScrapboxはRESTに書き込みAPIがない
ブラウザを経由しないとページ作成も文字の記入もできない
HeadlessChromeならいけるんちゃうか→puppeteer使うか
デモ
Slack
https://gyazo.com/02c4e7e30c87f787ce91fb886f844b6b
Scrapbox
https://gyazo.com/41409e0bb1e377db664cafdf7ec01563
実装
実装は素朴
ScrapboxにログインしてCookieのsidを取ってきてenvへ突っ込む
puppeteerのページにCookieをセットして開く
キー入力をエミュレートする
保存が終わるくらいの時間待って、閉じる
ページを開く
code:scrapbox.ts
const openPage = async (pageName: string) => {
const browser = await puppeteer.launch({
// Herokuで起動するときに必要
});
const page = await browser.newPage();
// おもむろにCookieをセット
await page.setCookie({ name: 'connect.sid', value: process.env.SCRAPBOX_SID as string, domain: 'scrapbox.io' });
await page.goto(https://scrapbox.io/pixiv/${encodeURIComponent(pageName)});
// editorがあるとJSが読み込み終わっていることが多い
await page.waitFor('#editor');
// 念の為1秒くらい待っとく
await new Promise((resolve) => setTimeout(() => resolve(), 1000));
return { browser, page };
};
キー入力をエミュレートする
code:scrapbox.ts
const writeTextWithPage = async (page: puppeteer.Page, text: string) => {
// を入力するとが補完されるので、→とBSをエミュレートして消す // splitに渡す正規表現でキャプチャすると配列にそれ自体が入る
const strings = text.split(/(\[)/);
for (const string of strings) {
await page.keyboard.type(string);
if (string === '[') {
await new Promise((resolve) => setTimeout(() => resolve(), 200));
await page.keyboard.press('ArrowRight');
await page.keyboard.press('Backspace');
}
}
await page.keyboard.press('Enter');
};
// lineをクリック
await page.click(#L${line.id});
await new Promise((resolve) => setTimeout(resolve, 200));
// Slack用の泥臭い変換
const converted = body.replace(/<(http:\/\/.+?)>/, '$1').replace('&', '&');
const writeText = ${converted} [${userName}.icon];
await writeTextWithPage(page, writeText);
console.info(Type: ${body} [${userName}.icon]);
await new Promise((resolve) => setTimeout(resolve, 200));
// 改行を入れる
await page.keyboard.press('Enter');
await new Promise((resolve) => setTimeout(resolve, 200));
// 改行するときにインデントが補完されるので消す
await page.keyboard.press('Backspace');
await new Promise((resolve) => setTimeout(resolve, 1000));
ハマりどころ
Herokuで動かすには少し設定が必要
BuildPackをつっこむ
https://gyazo.com/45fb0c238a69d2c14e7ebf721889b8d5
pupeteer起動時にsandboxに関する設定をいれる
code:sample.ts
puppeteer.launch({
});
まとめ
Slack経由でScrapboxに書き込んでイテレーション資料を宣言的に完成させると
思いついたその場で宣言できて便利
雑談を呼び込むきっかけになって便利
記憶が呼び戻されて最高
Scrapboxは最高