3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Discordで通話中のみのチャットを作るBotをdiscord.jsで作った

Posted at

discord.js-12.3.1 request-2.88.2 Node.js-12.18.3
Discordで通話中のみのチャットが作りたい!
こちらの記事を見て、社内Discordで使いたいという要望があったので、discord.jsで作ってみました。
処理の流れやソースの構成はほぼそのままです。

ソースコード

全体はこちらにあります。

index.js

const Discord = require("discord.js");
const client = new Discord.Client();
const privateChat = require('./main.js')

client.login(process.env.DISCORD_BOT_TOKEN);

// ボイスチャンネルの状態変更イベント時に実行
client.on('voiceStateUpdate', (oldState, newState) => privateChat.onVoiceStateUpdate(oldState, newState));

client.on("ready", () => {
  console.log(`Logged in as ${client.user.tag}!`);
});
main.js

const Discord = require("discord.js");
const client = new Discord.Client();
const request = require("request");

module.exports = {
  onVoiceStateUpdate: onVoiceStateUpdate
};

const CHANNEL_PREFIX = "🔑";
const BOT_ROLE_NAME = "BOT";

async function onVoiceStateUpdate(oldState, newState) {
  if (oldState.channelID === newState.channelID) {
    return;
  }

  if (oldState.channelID != null) {
    const oldChannel = oldState.guild.channels.cache.get(oldState.channelID);
    if (oldChannel.members.size == 0) {
      await txChDelete(oldChannel);
    } else {
      await chExit(oldChannel, newState.member);
    }
  }

  if (newState.channelID != null) {
    let txtChannel;
    const newChannel = newState.guild.channels.cache.get(newState.channelID);
    if (newChannel.members.size == 1) {
      txtChannel = await txChCreate(newChannel, newState.member);
    } else {
      txtChannel = await chJoin(newChannel, newState.member);
    }
    await chSendNotification(txtChannel, newState.member);
  }
}

// テキストチャンネルを作成
async function txChCreate(voiceChannel, voiceJoinedMember) {
  try {
    const guild = voiceChannel.guild;
    // チャンネル名の後ろにボイスチャンネルのIDを付与して一意に
    let chName = CHANNEL_PREFIX + voiceChannel.name + "_" + voiceChannel.id;
    let botRole = guild.roles.cache.find(val => val.name === BOT_ROLE_NAME);
    let result = await guild.channels.create(chName, {
      parent: voiceChannel.parent,
      type: "text",
      // denyでeveryoneユーザは見れないように
      // allowでボイスチャンネルに参加したメンバーは見れるように
      permissionOverwrites: [
        {
          id: guild.roles.everyone.id,
          deny: ["VIEW_CHANNEL"]
        },
        {
          id: voiceJoinedMember.id,
          allow: ["VIEW_CHANNEL"]
        },
        {
          id: botRole.id,
          allow: ["VIEW_CHANNEL"]
        }
      ],
    });
    return result;
  } catch (err) {
    console.log(err);
  }
}

// ボイスチャンネルのIDがついたテキストチャンネルを検索
function chFind(voiceChannel) {
  const guild = voiceChannel.guild;
  let searchCondition = voiceChannel.id;
  let result = guild.channels.cache.find(val => val.name.endsWith(searchCondition));
  return result;
}

// テキストチャンネルを削除
async function txChDelete(ch) {
  let target = await chFind(ch);
  if (target != null) {
    target.delete().catch(console.error);
  } else {
    console.log("削除するチャンネルがないンゴ");
  }
}

// テキストチャンネルの権限を付与
async function chJoin(ch, user) {
  let target = await chFind(ch);
  if (target != null) {
    target.updateOverwrite(user, { VIEW_CHANNEL: true });
    return target;
  } else {
    console.log("チャンネルがないンゴ");
  }
}

// テキストチャンネルの権限を削除
async function chExit(ch, user) {
  let target = await chFind(ch);
  if (target != null) {
    target.updateOverwrite(user, { VIEW_CHANNEL: false });
  } else {
    console.log("チャンネルがないンゴ");
  }
}

// 入室時にメンションを飛ばして案内
async function chSendNotification(ch, user) {
  const guild = ch.guild;
  const sendChannel = await guild.channels.cache.find(val => val.name === ch.name);
  await sendChannel.send(`<@!${user.id}>`)
    .catch(console.error);

  const embed = new Discord.MessageEmbed()
    .setTitle("プライベートチャットに参加しました。")
    .setAuthor("To " + user.displayName)
    .setDescription(
      "ボイスチャンネルに参加している人だけに見えるチャンネルです。\n全員が退出すると削除されます。"
    );
  sendChannel.send(embed);
}

ハマったところ

同じカテゴリにいくつもチャンネルがあるのでテキストチャンネル名の中にボイスチャンネル名を含めて識別しやすくしたいと考えました。
最初はボイスチャンネルと同じ名前でテキストチャンネルを作成しようとしていましたが、Discordのテキストチャンネルとボイスチャンネルに使える文字種が異なってること(大文字不可、[]不可など)を知らなかったため、チャンネル名の不一致によりchFindがうまく動きませんでした。
ボイスチャンネル:Apex Legends - A
テキストチャンネル:apex-legends-a

結果として参考にした記事と同じようにボイスチャンネルIDを末尾につけて識別できるようにしています。

let chName = CHANNEL_PREFIX + voiceChannel.name + "_" + voiceChannel.id;

最後に

導入以来、社内MTGをDiscordのボイスチャンネルで行う際などに重宝してくれてるようです。
また、discord.pyをdiscord.jsで書き直すのも双方の言語やライブラリ仕様の違いについての理解を深めるいい機会になりました。

3
2
0

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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?