見出し画像

Discord⇔LINE連携Bot作った備忘録

ヘッダー画像はwrtnくんにLogger.logについて聞いたら「SDGsの記事でも見てろ」って言われた画像

ということで YouTube→Discordで通知するやつ(これ(参考にさせてくれてありがとう))をLINEでもみたいなって思ったので実装した話。
見た感じ最近の参考記事があんまなさそう(僕の検索力説)なので備忘録がてら駄文をば。
こうやって書いてる間にも動いてて僕はとっても嬉しい。


0.仕組みとか

もっとクールなやり方はあるんでしょうけど、僕の場合はDiscordに過去に開発したやつの遺物があったのでDiscordに送信された情報をLINEに横流ししてるってのが大まかな仕組み。
Discord→LINEの仕組みに関しては、一番最後にある参考サイト様の方に分かりやすい図が載ってますが、一応。

Discord ==(Discord-bot-token)==> render.com ==(GASURL)==> GAS ==(LINE-access-token)==> LINE

で、LINE→Discordは基本逆を辿るだけなんですが、render.comを経由せずに、LINEからGASに飛んで直接Discordに飛ぶ形になってます。

LINE ==(GASURL)==> GAS ==(Discord-webhook)==> Discord

まあだいたいこんな感じですかね。

1.LINE→Discord

こっちの方が簡単なのでこっちから実装。

1.1 LINEの設定

まずはLINE developersでサクっと新しいプロバイダーを作ります。
そしたらmessaging-APIで新規チャネルを作成。なんか云々やらされるので指示に沿ってやってもらって、作成が出来たらmessaging-APIの設定に飛んでWebhookの利用にチェックを入れる。
次に公式アカウント機能を以下のように設定。

messaging-APIのタブの真ん中下ぐらい

最後に一番下から、チャネルアクセストークン(長期)を発行してどっかにメモしとく。

ここは一旦こんだけ。

1.2 GASの設定

GASの新しいプロジェクトを作ったら以下のコードをぺしって貼り付ける。

LineMessageSender.gs:

var channel_access_token = "メモったチャネルアクセストークン";
var group_ID = "";

function doPost(e) {
  //ここで場合分けする(GlitchからもDiscordのメッセをPostするので)
  var events = JSON.parse(e.postData.contents).events;
  events.forEach(function(event) {
    if(event.type == "message") {
      sendToDiscord(event); // D-bot
    } else if(event.type == "follow") {
      follow(event);
    } else if(event.type == "unfollow") {
      unFollow(event);
    }else if(event.type == "discord") {
      sendToLine(event); // L-bot
    }
 });
}

// D-bot
function sendToDiscord(e) {
  // LINEからユーザ名を取得するためのリクエストヘッダー
  var requestHeader = {
    "headers" : {
      "Authorization" : "Bearer " + channel_access_token
    }
  };
  var userID = e.source.userId;
  var groupid_tmp = e.source.groupId;
  // LINEにユーザープロフィールリクエストを送信(返り値はJSON形式)
  var response = UrlFetchApp.fetch("https://api.line.me/v2/bot/group/"+groupid_tmp+"/member/"+userID, requestHeader);

  var message = e.message.text;
  // レスポンスからユーザーのディスプレイネームを抽出
  var name = JSON.parse(response.getContentText()).displayName;
  sendDiscordMessage(name, message);
  // LINEにステータスコード200を返す(これがないと動かない)
  return response.getResponseCode();
}

// L-bot
function sendToLine(e) {
  // メッセージの内容(送信先と内容)
  var message = {
    "to" : group_ID,
    "messages" : [
      {
        "type" : "text",
        "text" : e.name + "  " + e.message
      }
    ]
  };
  // LINEにpostするメッセージデータ
  var replyData = {
    "method" : "post",
    "headers" : {
      "Content-Type" : "application/json",
      "Authorization" : "Bearer " + channel_access_token
    },
    "payload" : JSON.stringify(message)
  };
  // LINEにデータを投げる
  var response = UrlFetchApp.fetch("https://api.line.me/v2/bot/message/push", replyData);
  // LINEにステータスコード200を返す
  return response.getResponseCode();
}


/* フォローされた時の処理 */
function follow(e) {
  // 今のところ空白だけど自由に実装してみると楽しい。
  // よくある「友だち追加ありがとうございます」はここで実装する。
  // 不特定多数にフォローされるような運用はしないのでスルー。
}

/* アンフォローされた時の処理 */
function unFollow(e){
  // ここでは普通こちらからもフォローを切る処理を入れる。
  // ここも不特定多数にフォローされるような運用はしないのでスルー。
}

実はこれにDiscord→LINEのコードも入ってて、sendToLineがそれにあたる。
ここのコードは大きく改変してないので詳しくは参考サイト様の方を見てね。
1行目にさっきメモしたLINEのチャネルアクセストークンを長いですが貼り付けてあれば大丈夫です。
2行目のGroupIdは今は空っぽのままでOKです。

で、GASにもう一つ追加します。

DiscordWebhook.gs:

function sendDiscordMessage(name, message) {
  var webhookURL = "DiscordのウェブフックURL";
  // Discord webhookに投げるメッセージの内容
  var options = {
    "content" : name+" 「"+message+"」"
  };
  // データを作って投げる
  var response = UrlFetchApp.fetch(
    webhookURL,
    {
      method: "POST",
      contentType: "application/json",
      payload: JSON.stringify(options),
      muteHttpExceptions: true,
    }
  );
  // こちらはステータスコードを返す必要はない
}

これについても特に改変はしてないので詳しくは参考サイト様の方へ~
あとは、LINEからのメッセを受信したいところを対象にwebhook作って2行目のところにぺちっとURL貼ってくれちゃってください。

出来たらWebアプリとして一旦デプロイしてください。アクセス対象は全員です。
完了したらURLをコピーしてLINE developersに戻ります。

先ほど有効化したWebhook設定の所に"Webhook URL"っていう欄が出来てるはずなので、そこにURLをぶっこみます。
検証して「成功」が出ればOK。

これでLINE→Discordは動きます。

2.Discord→LINE

さっき簡単なのでL→Dからとか言いましたが、こっちの内容もL→Dありきなんですよね。 片方だけっていう風にしたくても一旦L→Dが必要。

2.1 Discord Botの作成

Discord Developer Portalちゃんから新しいBotを作ります。
この時、Botタブの真ん中ぐらいにある"Privileged Gateway Intents"の一番下の"Message Content Intent"をオンにしないと空っぽのメッセージが送信されまくります。
噂によると上2つも入れなきゃダメっていう話があるんだけど今んとこはこれだけでも動いてますと。

Botタブ真ん中ぐらい

できたらBot tokenをありがたく頂戴してメモって、OAuth2タブからBot→administrator権限で招待リンクを発行してやってください。
出来たら稼働させるサーバーに入れちゃいましょう。

2.2 コード作成

一番頭を悩まされた手順ですね。
最初はGlitchで作成してちゃんと動いてたんですが、なんか知らんけど早朝にnpm再読み込みしてから止まったんですよね。
起動はしてるのにBotにログインしてくれないと。
腹が立ってですね(?)、ローカル環境で試したらなんとびっくりしっかり動作しやがったんですね。
ということでこれはGlitchが悪い!ということで少し導入はややこしいもののrender.comに動作を移すことにしました。

ということで、まずは適当なところにファイルを作り、その中にコードを書きます。
main.jsがメイン処理の記述、package.jsonは依存関係とかをやるファイルです。確か。
参考サイトのものをGPTくんにバージョン上げしてもらってます。

main.js:

// Response for GAS
const http = require("http");
http.createServer(function (request, response) {
    response.writeHead(200, { "Content-Type": "text/plain" });
    response.end("Discord bot is active now \n");
  })
  .listen(3000);

// Discord bot implements
const { Client, Intents } = require("discord.js");
const client = new Client({
  intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES]
});

client.on("ready", () => {
  console.log("a")
  // botのステータス表示
  client.user.setPresence({
    activities: [{ name: "with discord.js" }],
    status: "online",
  });
  console.log("bot is ready!");
});

const botid = "BotのユーザーID";
client.on("messageCreate", message => {
  // 自分(botid)のメッセージには反応しない
  if (message.author.id === botid) {
    return;
  }
  // DMには応答しない
  if (message.channel.type == "dm") {
    return;
  }

  var msg = message;

  // botへのリプライは無視
  if (message.mentions.has(client.user)) {
    return;
  } else {
    // GASにメッセージを送信
    sendGAS(msg);
    //デバッグ時はコメント解除する
    //console.log(msg)
    return;
  }

  function sendGAS(msg) {
    // LINE Messaging API風の形式に仕立てる(GASでの場合分けが楽になるように)
    var jsonData = {
      events: [
        {
          type: "discord",
          name: message.author.username,
          message: message.content
        }
      ]
    };
    // GAS URLに送る
    post(process.env.GAS_URL, jsonData);
  }

  function post(url, data) {
    //requestモジュールを使う
    var request = require("request");
    var options = {
      uri: url,
      headers: { "Content-type": "application/json" },
      json: data,
      followAllRedirects: true
    };
    // postする
    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.cache.get(channelid);
        if (channel != null) {
          channel.send(message);
          console.log(message);
        }
      }
    });
  }
});

if (process.env.DISCORD_BOT_TOKEN == undefined) {
  console.log("please set ENV: DISCORD_BOT_TOKEN");
  process.exit(0);
}

console.log("Attempting to log in...");
client.login(process.env.DISCORD_BOT_TOKEN)
  .then(() => {
    console.log("logged in");
  })
  .catch(err => {
    console.error("Login Error:", err);
  });

package.json:

{
    "name": "glitch-discord-bot",
    "version": "0.0.0",
    "description": "discord bot sample on Glitch",
    "main": "main.js",
    "dependencies": {
        "discord.js": "^13.14.0",
        "request": "^2.88.2"
    },
    "scripts": {
        "start": "node main.js",
        "test": "node main.js"
    },
    "engines": {
        "node": "16.14.2"
    }
}

依存関係、脆弱性とか云々は分からんのですが動いたんで大丈夫でしょう。
今回はwebhookで送信されたメッセージを転送したいので、

client.on("messageCreate", message => {
  // 自分(botid)のメッセージには反応しない
  if (message.author.id === botid) {
    return;
  }

となってますが、ここは用途に合わせて変えてください。
(この場合はこれの上ぐらいでbotidを定義してるのでさっき作ったBotのidを取得して代入すれば大丈夫になる)
現時点では探査の対象が全チャンネルになってますがここを触れば(改変なりif追加なり)いい感じに仕上がると思います。
これぐらいはChatGPTに聞けばすぐコード投げ返してくれるでしょ。

最後にコマンドプロンプト開いて、プロジェクトのディレクトリまで移動して、

npm install discord.js

そしたらファイルが自動生成されるので多分これでOK。(node.jsなりの入れ方は各自でどうぞ…)

完了したら、Gitをインストール。
ほいでGithubも登録しておく。
出来たらGithubのマイページから新しいリポジトリを作成する。(プライベートでOK)
このときのリポジトリ名は後で使うので分かりやすいものにする。
作成出来たら、なんかガイドが出てくるのでそれに従いつつ、プロジェクトディレクトリに移動したコマンドプロンプトでgitコマンドを打ちまくる。
途中(初回だけ?)接続の為にメアドとユーザー名の認証を求められるのでGithubのものを代入して両方コマンド送信して、続きのコマンドを打つ。
僕の場合は確かこんな感じだったってのを書いておく。

git init
git remote add origin <YOUR_REPOSITORY_URL>
//ここのURLはガイドに書いてあるのでそっちコピペしたらOK
git add .
git commit -m "Initial commit"
//僕はここでメール等の認証を求められた
//Initial commitは多分任意の文字列でOK。多分。
git push -u origin master

これでGithubにさっきのコードが上がったので、これをrender.comで動かす。

2.3 render.comの設定

続いて、render.comに登録したらWebappでプロジェクトを作成する。
プロジェクト1つ動かすだけなら無料版で大丈夫。
Githubから先ほどのコードをインポートし、環境設定(.env)を設定する。
GAS_URLにはGASのデプロイURLを、DISCORD_BOT_TOKENにはDiscordのBotトークンを入れる。
これであとはしばらく待てばBotが起動して完了。
BotがDiscord上でもオンラインになるはずである。
GASのデプロイURLは後で更新するのだが、左のタブを探せばenv系も弄れるはずなので安心。

2.4 GASに追記

GASの30行目ぐらいを、

 var message = e.message.text;

から

 var message = e.message.text + groupid_tmp;

にして、新規でデプロイしなおす。
LINE側のWebhookをこれに更新し、適当にBotを入れたLINEグループで呟く。(僕はこれで時間溶かした)
すると、Discord側でメッセージのあとに文字列が付いてくるはずである。

この文字列をコピーし2行目に貼り付ける。これがグループIDである。
※BotごとにこのIDは変わるので要注意!!

これは割と機密情報なのでコードを元に戻して置く。
あとでデプロイするから一旦OK。

出来たらrender.comを定期起動するためのコードを書く。

waker.gs:

var GLITCH_URL = "https://yourname.onrender.com/";
function wakeGlitch(){
 var params = {
   'contentType' : 'application/json; charset=utf-8',
   'method' : 'post',
   'muteHttpExceptions': true
 };
 response = UrlFetchApp.fetch(GLITCH_URL, params);
}

一行目のURLのyournameになっているところをGithubリポジトリの名前に変える。
実際にここにアクセスして"Discord bot is active now "ってなればOK。

これでもう一回新規デプロイして二か所のGASURLを更新する。

そして、waker.gsが定期実行されるように1分ごとにwakeGlitchにトリガーを掛ける。


これで大体完成のはず…!
おつかれさまでした。自分も。

3.今後の課題(2024/9/10追記)

1週間弱運用してみて課題がいくつか出てきたので追記。

3.1 LINEAPIの実行制限

まず、知らないうちに無料版でのLINEAPI(APIを使って公式アカウントからのメッセージを送信する)の実行制限が1000回から200回になっていた。
この影響もあり、現在16チャンネルからの速報を受け取っていた当Botは、たったの4日で実行制限に達してしまった。
日付ごとに経由するAPIを変えることも考えたが、1つのLINEグループに入れられる公式アカウントは一つの為(初めて知った)、この策も封じられたのである。
確認するチャンネル数は今後も追加し続ける予定だったため、送信するメッセージを最小限にしても、まあ十中八九お陀仏するのである。
このままでは「毎月初旬だけ送信するBot」になってしまったため、打開策を考える必要がある。
安ければ有料プランへの移行も考えたが、最安のプランが5000メッセージで5000円/月と、正直一般金欠学生には厳しいので断念。
どうにかしなければならない…

3.2 D/L両方での運用の難しさ

Discordでは、様々な文字装飾機能が実装されている。
**で囲んで太字だったり、[文字](リンク)でハイパーリンクが作れたり。
だがLINEにはこのような機能が全くないのだ。辛うじてPC版でマークダウン記法が使える程度である。
そういった面で、Discordでの最適なUIとLINEでの最適なUIが全く一致しない。
というかそもそもスマホ版LINEには文字装飾という概念すらも存在しないわけである。
Discordを経由してLINEに飛ばすという非常に不審なことを行っている以上仕方のないことだが、ここは改善出来たらいいなと思う。

また、LINEにメッセージを送信するのはどのチャンネルにおいても同一の公式アカウントのため、それだけでも視認性が低下するのも課題の一つである。

ただ、まあ基盤となるこれらの機能は何かしら使えるところもあるはずだ。
良さげな所だけもじって発展させていってくださいお願いです

参考サイト

ありがとう!

いいなと思ったら応援しよう!