🙆

ニッチなDiscordのBotを作った話

2024/04/12に公開

コードを見たい方はこちらへ
https://github.com/Fyphen1223/MK8DX-TA-Bot-Discord

招待したい方はこちらへ
https://discord.com/oauth2/authorize?client_id=1197118650046492702&permissions=1067299687424&scope=bot+applications.commands

コードは非常に汚いですがご容赦を。

About

自分はNintendo様のマリオカート8DXのプレイヤーなのですが、同じくプレイヤーの友人からタイムアタック(コースを走ってその合計タイムを競う競技モード)のタイムをコースごとに記録できるBotがほしい、という要望があったので作ってみました。

Built with

言語
Node.js

パッケージマネージャー
pnpm

使用ライブラリ
axios
discord.js
dotenv
redis

データベース
RedisのAlways Free枠(25MB)

ホスティング
OCIのAlways Free枠内のインスタンス1C1GB

状況

使用サーバー数47個
ユーザー様58人(Redisのキー数、Metricsタブで確認)

コード紹介

MK8DXのタイム表記が正しいか確認するコード
/util/utils.js

function isValidTimeFormat(time) {
	var regex = /^(0?[0-9]|1[0-9]|2[0-3]):([0-5][0-9])(\.([0-9]{1,3}))?$/;
	return regex.test(time);
}

2:12.312とか

/update.js

const axios = require('axios');
const record = require('./data/data.json');
const fs = require('fs');
async function update() {
	const data = await axios.get('https://mkwrs.com/mk8dx/');

	const splittedData = data.data
		.split('</td><td class="lap">')
		.slice(1)
		.join('')
		.split('        </tr>\n    <tr>')[0];

	const lines = splittedData.split('\n');

	function CCParse(line) {
		return line.split('<small>')[1].split('</small>')[0];
	}

	function betweenTD(line) {
		return line.split('<td>')[1].split('</td>')[0];
	}

	const infoLines = 9;
	const jumpIndex = 2 + 2;

	let lineIndex = 2;
	let lastCC = -1;

	const arr = [];
	let arrIndex = 0;

	while (lineIndex <= lines.length) {
		arr[arrIndex] = {
			cc: arr[arrIndex]?.cc,
			time: lines[lineIndex].includes('target="_blank">')
				? lines[lineIndex].split('target="_blank">')[1].split('</a>')[0]
				: lines[lineIndex].split('<td>')[1].split(' <img ')[0],
			yt_url: lines[lineIndex].includes('target="_blank">')
				? lines[lineIndex].split('href = "')[1].split('"')[0]
				: null,
			user_name: lines[lineIndex + 1].split('">')[1].split('</a>')[0],
			country: lines[lineIndex + 2].split('alt = "')[1].split('"')[0],
			date: betweenTD(lines[lineIndex + 3]),
			duration: betweenTD(lines[lineIndex + 4]),
			character: betweenTD(lines[lineIndex + 5]),
			vehicle: betweenTD(lines[lineIndex + 6]),
			tires: betweenTD(lines[lineIndex + 7]),
			glider: betweenTD(lines[lineIndex + 8]),
		};

		if ((arrIndex + 1) % 2 == 1 && lines[lineIndex - 2].includes('cc')) {
			arr[arrIndex].cc = CCParse(lines[lineIndex - 2]);
			arr[arrIndex + 1] = {
				cc: (lastCC = CCParse(lines[lineIndex - 1])),
			};
		} else {
			if ((arrIndex + 1) % 2 == 1) {
				arr[arrIndex + 1] = { cc: lastCC };
			}
		}

		lineIndex += jumpIndex + infoLines; // 4 + 9
		arrIndex++;
	}

	let i = 1;
	while (i <= 96) {
		record.wr.ta150[i] = [];
		record.wr.ta150[i].push(
			convertIntoMs(arr[getRecordInfo(i - 1, 0)].time)
		);
		record.wr.ta150[i].push(arr[getRecordInfo(i - 1, 0)].user_name);
		record.wr.ta150[i].push(arr[getRecordInfo(i - 1, 0)].yt_url);
		record.wr.ta200[i] = [];
		record.wr.ta200[i].push(
			convertIntoMs(arr[getRecordInfo(i - 1, 1)].time)
		);
		record.wr.ta200[i].push(arr[getRecordInfo(i - 1, 1)].user_name);
		record.wr.ta200[i].push(arr[getRecordInfo(i - 1, 1)].yt_url);
		i++;
	}
	fs.writeFileSync('./data/latest.json', JSON.stringify(record));
}

function convertIntoMs(time) {
	var regex = /(\d+)'(\d+)"(\d+)/;
	var match = time.match(regex);
	if (match) {
		var minutes = parseInt(match[1]);
		var seconds = parseInt(match[2]);
		var milliseconds = parseInt(match[3]);
		return (minutes * 60 + seconds) * 1000 + milliseconds;
	} else {
		return 'Invalid time format';
	}
}

function getRecordInfo(track, cc) {
	if (cc == 0) {
		if (track == 0) {
			return 0;
		} else {
			return track * 2;
		}
	} else {
		if (track == 0) {
			return 1;
		} else {
			return track * 2 + 1;
		}
	}
}

update();

こちら友人に書いていただいたコードです。いっろいろカオスですが私は修正できませんので放置です。

ランニングコスト

API: ¥0
ホスティング: ¥0
データベース: ¥0

総計: ¥0
最高!

宣伝した手段

X(旧Twitter)、GitHubのみ
思ったより悪くないインプレッション数取れたことに驚きです。

結論

数ヶ月前後でこれだけのユーザー数が取れるのは私が今まで行った個人開発のプロジェクトの中で最も成功した部類です…。きちんとニーズを付けば成功するっていう経験が取れました。
GG!

Discussion