概要
WWADicscordコミュニティにPLiCyに新着WWAが投稿されると通知するbotを作りました。
今回はこの通知botの動作原理を半分備忘録も兼ねてご紹介したいと思います。
この記事は WWA Advent Calendar 2024 の3日目の記事になります。
構成
構成としては実際の動作にはTypeScriptと node-cron を使って動作しています。
実行環境はVPSサーバーにNodeコンテナを立て、その上で実行させています。
discord.js を使う手も考えたのですが、手元のWSL2のDebian環境がやや古くてNode v16までしか入らず、discord.jsの実行にはNode v18以上の実行が必要なことと、Discordの発言を検知するのではなく、一定時間おきに新着WWAを検知して投稿するポーリングする形式で動作するので単純にDiscordのwebhookを叩けばいいかなと思い採用しました。
ちなみに、ちゃんとdiscord.jsを使って動くbot(テムたんbot)も作ったことがあるのですが、残念ながら今は動いてないです(笑)
Node.jsサイドのロジック
PLiCy管理人のシャイんさんよりPLiCyに投稿された新着WWAを取得するエンドポイントをいただきました。
ただ、肝心のエンドポイントについては頻繁にアクセスされると負荷が大きくなってしまうので表に出さないでほしいとの要望があったので申し訳ないですが非公開とさせてください。
新着WWA取得エンドポイントからは以下のような形で新着WWAデータを20件取得出来ます。
{
"game": [
[
192531,
"//img.plicy.net/ImageLoad?HashFile=0257aa97627f6afae4708dda47962ec5",
"アップグレードダンジョン 体験版",
"",
"とても広いケーブダンジョン風マップを攻略するWWAです。赤いスライムの見た目のアイテムを使用すると..",
6,
"2024年11月06日",
false,
"//img.plicy.net/ImageLoad?HashFile=33e7c7b3b79a2c2a2bb3d94ffdd0ddf7",
240,
0,
5,
"( ̄ー ̄) 遊びやすさはイマイチですが、中々面白い作品だと思いました (;_;) 楽しみにしてます ( |_|)(|_| ) "
]
]
}
上から順に「ゲームID」「サムネURL」「タイトル名」「キャッチコピー」「説明文」「不明」「投稿日」…と並んでます、
PLiCyに投稿されたゲームページの概要を比較すると、作者情報は取れないもののゲームタイトルやキャッチコピーの全文と、コメントの冒頭50文字程度が取れることがわかります。
そのままだと単純な配列で扱いにくいのでこんな感じでObjectに変換し、WWAData型の配列として、取得できたWWAデータ一覧を保管します。
const response = await axios.get(url);
const gameListRowData: { game: string[] } = response.data;
return gameListRowData.game.map((data) => {
return {
id: Number(data[0]),
miniImageURL: getPLiCyGameImageURL(data[1]),
title: data[2],
catchphrase: data[3],
text: data[4],
postDate: data[6],
imageURL: getPLiCyGameImageURL(data[8]),
url: `https://plicy.net/GamePlay/${data[0]}`,
};
});
WWADataの形はこんな形で定義してます。
interface WWAData {
id: number,
miniImageURL: string,
title: string,
catchphrase: string,
text: string,
postDate: string,
imageURL: string,
url: string
}
ただこのままだと最新20件全部が投稿対象となってしまうので、既にDiscordに投稿済みかを判断する必要があります。
本来ならDBを立てて管理する必要があるのですが、今回は件数がそこまで大きくないのでサーバ上に取得済みのWWAデータを postWWAData.json
という名前で保存しています。
ゲーム名で投稿済みかどうかを判断すると同一名称のゲームが複数投稿されたときに投稿済みとして判断されるため、ゲームを一意に識別するためのゲームIDを使って投稿済みかを判断します。
また、初回起動時は投稿済みデータが存在しないため、このままだと初回起動時にdiscordにゲーム通知が20件飛んでしまうため例外処理として、投稿済みデータが1件も無いときにはdiscord投稿を行わないようにしています。
// 新規投稿WWAがあればDiscordに投稿
async function checkNewWWAAndPostDiscord() {
// Discord投稿済みのゲーム一覧
const postedWWADataList: WWAData[] = await readPostedWWAData();
// ポスト済みのデータがない場合にはDiscord投稿をしない
const isPostDiscord = postedWWADataList.length > 0;
// 最新20件のゲームを取得する
const gameDataList: WWAData[] = await getPLiCyWWANewGame();
// 未投稿のゲーム一覧を抽出
const noPostWWAData = gameDataList.filter((gameData) => {
// 未投稿ゲームかを判断
const isMatch = !postedWWADataList.some((pwwa) => {
return pwwa.id === gameData.id;
})
// 未投稿なら投稿済みリストに追加する
if(isMatch) {
postedWWADataList.push(gameData);
}
return isMatch;
});
// これ以降の投稿処理は省略
}
これで実行時に未投稿WWAをDiscordに通知する仕組みが出来ましたので、定期実行出来るようにしていきます。
node-cron
の使い方は実に単純でして、 cron.schedule
関数を使い第一引数にスケジュールを、第二引数で実行したい関数をいれるだけで勝手に処理をしてくれます。
今回は1時間おきに実行したいので以下の書き方をしました。
import cron from 'node-cron';
/** 毎時定期実行 */
cron.schedule('0 0 * * * *', async () => {
await checkNewWWAAndPostDiscord();
});
node-cronの詳しい使い方はこちらの記事が分かりやすいです。
TypeScriptの実行環境
TypeScriptはそのままでは実行できないので本来はコンパイルする必要がありますが、今回は ts-node を使って直接実行します。
TypeScriptと合わせて実行環境作成は以下のコマンドで
npm install --save typescript ts-node
実行するときには以下のように打ち込みます。
npx ts-node index.ts
これで1時間おきにチェックして実行してくれる機構が出来上がりました。
VPS上の実行環境構築
Local環境で動かすと24時間365日ずっとPCを動かし続けないといけなくなるので、個人で借りているVPSサーバ上で実行をしていきます。
VPSサーバに直接NodeやらTSやらをinstallしてもいいのですが、今回は環境に左右されず決められたバージョンのNodeが利用したいのと、自分のVPS上ではどのアプリが動いてるのか分かりやすくするため原則コンテナ実行するようにマイルールを設けてるのでDockerコンテナ上で動かしていきます。
Dockerfile
やら docker-compose.yml
を一から書くのは面倒なので、ChatGPTにポイッと聞いて雛形を出してもらって、若干微調整したりします。
こうして出来上がった Dockerfile
と docker-compose.yml
はご覧の通り
後はVPSサーバ上でコンテナを立ち上げれば完了です。
# ベースイメージとして node:20.10-alpine を使用
FROM node:20.10-alpine
# 作業ディレクトリを設定
WORKDIR /app
# package.jsonとpackage-lock.jsonをコピーして依存関係をインストール
COPY package*.json ./
RUN npm install
# アプリケーションファイル全体をコピー
COPY . .
version: '3.1'
services:
node:
build: .
container_name: typescript_app
volumes:
- .:/app
- /app/node_modules # ホストのnode_modulesを隔離するため
working_dir: /app
command: ["npx", "ts-node", "index.ts"]
これにて完成です。
VPSサーバを持ってなくてもGoogle App Script(GAS)を使ってゴリ押しする方法もありますのでお金がなくても実装できたりしますが、書き方が独特で管理がしにくいので今回は普通のNode+TSで実行する方法を選択しました。
自分の借りてる indigo なら月額500円以内でメモリ1GBのVPSが借りられます。...なんか回し者みたいですねw
最後にログを確認してみるとご覧の通り
ちゃんと1時間おきに定期実行してますし、データが有る時も投稿されてることが分かります。
こうして投稿されたのが冒頭で紹介したこちらになります。
余談ですがアイコンの「ラーン」は去年のWWAアドベントカレンダーで作ったWWA Script紹介動画にも出てきたオリキャラになります!
...と言ったところで余談にそれまくりでしたが、この記事はここまでにしたいと思います。
明日のWWA Advent Calendar 2024はアルクスさんの「WWA Script入門:コピペ可、サンプル配布あり!色々やってみよう」になります。
WWAScriptは導入されたばかりで、まだ参入障壁が高いかも知れませんが気になっている!と思っている方もこれを期にWWAScriptの世界に挑戦してくれると嬉しいですね!
それではまた明日の記事をお楽しみに~