- Published on
Puppeteer × Discord:レポートのスクリーンショットを送信する
- Authors
- Name
- Nomad Dev Life
ひとりSlackをDiscordに移行した
ひとりSlack愛用者でしたが、Free Planだと90日しかメッセージが表示できなくなってしまったので、Discordに移行しました。
せっかくDiscordに移行したので、これまで作成していたインフルエンザのレポートをDiscordに送信するコードを書いてみました。
やること
今回やることは、以下の3点です。
- Discordの送信先チャンネルにWebhookを作成する
- Puppeteerでインフルエンザのページのスクリーンショットを撮る
- 撮ったスクリーンショットをDiscordのWebhookのURLに送信する
Webhookを作成する
Discordの送信先チャンネルにWebhookを作成します。送信先チャネルを選んだら、左ペインのチャンネル名の横にある"チャンネルの編集"→"連携サービス"→"ウェブフック"をクリックします。 そして"新しいウェブフック"をクリックし、"お名前"を設定して、"ウェブフックURLをコピー"をクリックして、URLを控えておきます。
スクリーンショットを撮り、Discordに送信する
Puppeteerでインフルエンザのページのスクリーンショットを撮り、DiscordのWebhook URLに送信します。コードは、TypeScriptで書きました。
- package.json
{
"name": "image-to-discord",
"version": "1.0.0",
"main": "index.js",
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"form-data": "^4.0.1",
"node-fetch": "^2.7.0",
"puppeteer": "^24.1.0"
},
"devDependencies": {
"@types/node": "^22.10.7",
"@types/node-fetch": "^2.6.12",
"typescript": "^5.7.3"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
- index.ts
import puppeteer, { Browser, Page } from 'puppeteer';
import fetch from 'node-fetch';
import FormData from 'form-data';
import { promises as fs } from 'fs';
import { Blob } from 'node:buffer';
async function sendImageToDiscord(
url: string,
chartSelectorForWait: string,
chartSelectorCapture: string,
outputImage: string,
discordWebhookURL: string
): Promise<void> {
let browser: Browser | null = null;
try {
browser = await puppeteer.launch();
const page: Page = await browser.newPage();
await page.setViewport({
width: 1600,
height: 900,
});
// 1. ページを表示
await page.goto(url);
// 2. チャートが表示されるまで待機
await page.waitForSelector(chartSelectorForWait);
// 3. チャートをキャプチャ
const element = await page.$(chartSelectorCapture);
if (!element) {
throw new Error(`Chart element not found with selector: ${chartSelectorCapture}`);
}
await element.screenshot({ path: outputImage });
// 4. Discordに画像を送信
const formData = new FormData();
const fileBuffer = await fs.readFile(outputImage);
const blob = new Blob([fileBuffer]);
const buffer = await blob.arrayBuffer();
formData.append('file', Buffer.from(buffer), outputImage);
formData.append('content', '2024-25年 インフルエンザ報告数');
const response = await fetch(discordWebhookURL, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`Failed to send message: ${response.status} ${response.statusText}`);
}
console.log(`${outputImage} sent to Discord.`);
} catch (err: any) {
console.error(err);
} finally{
if (browser){
await browser.close();
}
}
}
(async () => {
const pageURL = 'https://flu.nomad-dev-life.net/flu_2024.html';
const discordWebhookURL = '...'; // Discord Webhook URL
try{
await sendImageToDiscord(pageURL, '.chart-wrapper', 'html', 'flu.png', discordWebhookURL);
} catch (err){
console.error(err)
}
})();
インフルエンザのレポートはstliteというwasmの仕組みを使っています。今回スクショを撮る際に、wasmが出力するHTMLのDOM(.chart-wrapper
)をチェックしてチャートが全部表示されたことを確認した後、スクショを撮るようにしています。
また、Viewportを1600x900で設定しています。この設定が無いと、スクショの画像が途中で切れてしまいます。
あとはnpm run build
でビルドし、npm start
で実行することで、Discordのチャンネルに以下のように表示されます。
おわりに
今回は、インフルエンザのレポートのスクショをDiscordのチャンネルに送信しました。あとは、前回のようにGithub Actionに仕込んで定期的に実行されるようにしておけば良いでしょう。
レポートのスクショが送られてくるようになっていると、レポートの内容が大きく変わっていない限り、ブラウザでページを開いて見に行かなくて良いので楽です。