LoginSignup
44
47

More than 3 years have passed since last update.

自動応答のDiscord Botを完全無料で構築する[gas][glitch]

Posted at

概要

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に突っ込んだ状態

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);
      }
    }
  });
}
config.env
SECRET=
MADE_WITH=
DISCORD_BOT_TOKEN=/* botのトークンを設定 */
GAS_URI=/* GASヘpostするURLを背一定 */

次にGAS

RetainGlitch.gs
/*この関数をスケジューラーで回して接続を維持する。*/
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);
}
receiver.gs
// 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;
}
env.gs
// スプレッドシート
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を設定*/;

詳細説明

特に思いつかなかったので要望があったら追加検討します。

44
47
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
47