#概要
Discordで参加or非参加連絡を募って自動的にスプレッドシートに記入したいと思ったので作ってみた。
#事前知識
GAS
Google Apps Script。javascriptライクで実装可能。
Googleスプレッドシートなどへの書き込みができる。
Glitch
Node.jsで簡単にアプリ開発できるWebサービス。
登録から3分でサービスがリリースできる。簡単すぎて当初笑った。
Discord.js
discordへの応答が簡単にできるライブラリ。
詳細はここ見れば大抵解決する。
#設計について
ggって見た感じ、bot作成に関してはPCを常時稼働前提or鯖に突っ込む前提の記事しかなかった。
僕の要件としては、
・PC常時稼働は論外
・借りてるVPSに突っ込むのは環境設定周りで面倒
・追加でサーバー借りるつもりもない
じゃぁ金かけず実装して記事書こう!ってことで実装開始。
①discordでチャットに自動応答するbotを作ろうとするとサーバを建てる必要があった。
-> Glitchを利用して解決
②Glitchが5分程度で落ちる。
-> GASのトリガー機能を実行して毎分postして接続を維持する。(公式で提示されてる手法なので問題なし)
という流れでこの構成に落ち着いた。もともとGASは使う予定だったので最小構成にできたと思う。
###[応答機序]
Discord・・・ユーザの書き込み
-> Glitch・・・チャンネルへの書き込みを取得、特定の形式だった場合にそのままGASにpostする。
-> GAS・・・glitchからのpostを取得して解析、実行結果に応じてGlitchにレスポンス投げる。
-> スプレッドシート書き込み
-> Glitch・・・GASからのpostを取得、discordにsendする。
-> discord・・・botからの書き込み
###[接続維持]
GASトリガー・・・毎分GAS関数を実行。1分の次は5分でしか設定できないので妥協。
-> GAS・・・Glitchへ空のpostを投げる。
-> Glitch・・・接続があったので自動スリープ時間延長
#サンプル
まぁまずは見てください
Glitchの処理。
全部main.jsに突っ込んだ状態
const DC = this;
// msgの一時退避
var msg;
var discord = require('discord.js');
var client = null;
// Response for Uptime Robot
const http = require('http');
http.createServer(function(request, response)
{
connectDiscord();
response.end('Discord bot is active now.');
}).listen(3000);
function connectDiscord(){
console.log('00');
// 状態によって処理変更する。
if (client == null){
client = new discord.Client();
} else {
if (client.readyAt != null){
switch(client.status){
case 0: // READY
case 1: // CONNECTING
case 2: // RECONNECTING
case 3: // IDLE
case 4: // NEARLY
return;
case 5: // DISCONNECTED
client.destroy();
break;
default:
client = new discord.Client();
break;
}
}
}
client.login( process.env.DISCORD_BOT_TOKEN );
client.on('ready', message =>
{
console.log('bot is ready!');
});
client.on('message', message =>
{
// 自分のメッセージには反応しない
if (message.author.id == /* botのCLIENT_IDを入力 */) {
return;
}
msg = message;
// botへのリプライ応対
if(msg.isMemberMentioned(client.user))
{
if (msg.content.indexOf('content') > 0){
msg.reply(msg.content);
console.log(msg.content);
return;
}
if (msg.content.indexOf('logout') > 0){
msg.reply('ログアウトします。');
client.destroy();
return;
}
if (msg.content.indexOf('status') > 0){
msg.reply(client.status);
return;
}
if (msg.content.indexOf('help') > 0){
msg.reply('ヘルプメッセージ');
return;
}
sendGAS(msg);
return;
}
// botへのリプライじゃなかった場合
else {
// []でくくったコマンドに応対する。
if(msg.content.match(/^\[(?=.*\])/i)){
sendGAS(msg);
return;
}
}
});
if(process.env.DISCORD_BOT_TOKEN == undefined)
{
console.log('please set ENV: DISCORD_BOT_TOKEN');
process.exit(0);
}
}
// send
var sendGAS = function(msg){
var params = msg.content.split(' ');
var userId = params[0];
var value = null;
for (var n=1; n < params.length; n++){
if (n == 1)
value = params[n];
else
value = value + ' ' + params[n];
}
var jsonData = {
'userId':msg.member.id,
'value':value,
'message':msg.content,
'channelId':msg.channel.id,
}
post(process.env.GAS_URI, jsonData)
}
// post
var post = function(uri, jsonData){
var request = require('request');
var options = {
uri: uri,
headers: {"Content-type": "application/json"},
json: jsonData,
followAllRedirects: true,
}
request.post(options, function(error, response, body){
if (error != null){
msg.reply('更新に失敗しました');
return;
}
var userid = response.body.userid;
var channelid = response.body.channelid;
var message = response.body.message;
if(userid != undefined && channelid != undefined && message != undefined){
var channel = client.channels.get(channelid);
if (channel != null){
channel.send(message);
}
}
});
}
SECRET=
MADE_WITH=
DISCORD_BOT_TOKEN=/* botのトークンを設定 */
GAS_URI=/* GASヘpostするURLを背一定 */
次にGAS
/*この関数をスケジューラーで回して接続を維持する。*/
function doRetain() {
var json = {
'userid':'',
'channelid':'',
'message':'',
};
sendGlitch(GLITCH_URL, json);
}
function sendGlitch(uri, json) {
var params = {
'contentType' : 'application/json; charset=utf-8',
'method' : 'post',
'payload' : json,
'headers' : json,
'muteHttpExceptions': true
};
response = UrlFetchApp.fetch(uri, params);
}
// discordからのvalueを受け取ってリプライ用メッセージを返す
function receiveDiscordValue(message, userId) {
var resMsg = null;
var values = message.split(' ');
// []でくくったコマンドを判定
if(values[0].match(/^\[(?=.*\])/i)){
var fastValue = values[0].split(']')[1];
var order = values[0].split(']')[0].substring(1);
/* ここらへんにGASに書き込んだりコマンドを実行したりする処理を追加*/
resMsg = 'glitchに返すメッセージを設定';
}
// 命令判定(glitchで返すもの以外)
else if(values[0].match('<@'+ BOT_CLIENT +'>')){
var fastValue = value[0].split('>')[1];
if (fastValue == 'help'){
resMsg = 'ヘルプメッセージを設定';
}
}
if (resMsg == null)
resMsg = '不正な処理です。'
return resMsg;
}
// スプレッドシート
var SS = SpreadsheetApp.openById(/*スプレッドシートのIDを設定*/);
// BOT_TOKEN
var BOT_TOKEN = /* botのトークンを設定 */;
// BOT_CLIENT_ID
var BOT_CLIENT = /* botのCLIENT_IDを設定 */;
// GlitchのURL
var GLITCH_URL = /*postするglitchのURLを設定*/;
#詳細説明
特に思いつかなかったので要望があったら追加検討します。