🔥

Next.jsアプリからDiscordサーバーのロールを取得するBotを作ってみた話

2024/09/14に公開


使用するライブラリは画像の通りです。全てTypeScriptで書いてます。
(どうでもいいけどNext.jsもbunで動かせるようになったらいいなって思います)

また、初学者なのでexpressやHono等使ったことがなく、ベストプラクティスが全く分かっていないのでかなり適当に実装されているのはご容赦を。

Discord Botの登録

https://discord.com/developers/ にアクセスしてbotアプリケーションを登録します。
対象のサーバーにbotを導入します。botの用意については他記事が沢山あるのでそちらを参照ください。
Botのトークンを忘れずにコピーしておいてください。

bun + Hono + Discord.jsアプリの作成

プロジェクトフォルダを作って以下のコマンドを実行し、テンプレートはbunを選択します。

bun create hono@latest your-app-name
? Which template do you want to use?
→bun

次にdiscord.jsをインストールします。

bun install discord.js

ではsrc/index.tsを編集して、Botを起動させてみましょう。

.env.local
DISCORD_BOT_TOKEN="your-bot-TOKEN"
src/index.ts
import { Client, GatewayIntentBits } from "discord.js";
import { Hono } from "hono";

const token = process.env.DISCORD_BOT_TOKEN;
const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.on("ready", (c) => {
 console.log(`${c.user.tag}がログインしました。`);
});

client.login(token);

ひとまずこれで bun src/index.ts を実行してBotがログインするかどうか確認してください。
ログインを確認したらhonoでapiサーバーを作成します。

src/index.ts
import { serve } from "bun";
import { Client, GatewayIntentBits } from "discord.js";
import { Hono } from "hono";
import { guildController } from "./api/guilds";

const port = 4000; //Next.jsのデフォルトのサーバーポートと競合しないような番号で 3001でもいい
const token = process.env.DISCORD_BOT_TOKEN;
const app = new Hono();
export const client = new Client({ intents: [GatewayIntentBits.Guilds] }); //exportして別ファイルでもDiscord.jsのインスタンスを使えるようにする。

app.route("/api/guild", guildController); //サーバー情報を取得するためのapiルートの設定

client.on("ready", (c) => {
 console.log(`${c.user.tag}がログインしました。`);
});

serve({
 fetch: app.fetch,
 port,
}); //bunサーバーの設定 honoのサーバーを設定すると互換性がないのかエラーが発生します。

client.login(token);
console.log(`サーバーが起動しました。 ポート:${port}`);

次にguildController部分の実装をします。

src/api/guild.ts
import { Hono } from "hono";
import { Client, GatewayIntentBits, PermissionsBitField } from "discord.js";

export const guildController = new Hono()
  .get("/:guildId/roles", async (c) => {
    const guildId = c.req.param("guildId"); //リクエストurlにロールを取得するサーバーIDを入れるようにする

    try {
      const guild = await client.guilds.fetch(guildId);
      const roles = await guild.roles.fetch();

      const roleList = roles.map((role) => ({
        id: role.id,
        name: role.name,
        color: role.hexColor,
        position: role.position,
        permissions: role.permissions.toArray(),
      }));

      return c.json({ status: "success", roles: roleList }, 200);
    } catch (error) {
      console.error("エラー :", error);
      return c.json(
        { status: "error", message: (error as Error).message },
        500
      );
    }
  })

多分この様に実装したら今後の拡張性が高くなるんじゃないかなと思います。(知らんけど);

ひとまず、これでバックエンド側は完成です。

Next.jsアプリのセットアップ

セットアップに関しては無限に記事があると思うのでこちらも割愛させていただきます。

apiをfetchしてロール一覧を表示

まず.envに必要な情報を記述します。

.env.local
NEXT_PUBLIC_API_SERVER_URL="http://localhost:4000" # さっき設定したport番号にしてください

では実際に表示してみましょう。

page.tsx
import React from "react";

interface guildRolesResponseType {
  status: string;
  roles: [
    {
      id: string;
      name: string;
      color: string;
      position: number;
      permissions: any[];
    }
  ];
}

export default async function Home() {
  const discord_server_id = ""; //ここに取得したいサーバーid
  const guildRolesResponse = await fetch(
    `${process.env.NEXT_PUBLIC_API_SERVER_URL}/api/guild/${discord_server_id}/roles`,
    {
      method: "GET",
      headers: {
        "Content-Type": "application/json",
      },
      cache: "no-store",
    }
  );

  const guildRoles: guildRolesResponseType = await guildRolesResponse.json();

  return (
    <div>
      {guildRoles.roles.map((role) => (
        <div>{role.name}</div>
      ))}
    </div>
  );
}

おそらくこれでロール名が全て表示されていると思います。お疲れ様でした。

おまけというかなんというか

実装方法についての疑問

当然SCで実装しているのですが、Next.js → api route → backend apiとすべきなのか直接SCでapiを叩くべきなのかがわからないので教えて下さい。
今回は直接叩く方法で実装しました。

ベストプラクティスについて

今回バックエンドライブラリを初めて触ってみたので、使い方があっているのかはわかりませんが、honoは非常に使いやすいライブラリだなと感じました。
apiを増やして行くときは

app.route("/api/guild", guildController);
app.route("/api/users", UserController);

のように増やして、コントローラー別にファイルを作成し、エンドポイントやメソッドを実装して行くといいのかなと思いましたが、これは合っているのでしょうか?

ただ、僕の現状だと、こうしてわざわざバックエンドを作らなくてもNext.jsのapi routeで事足りることが多いのかなとも思います。
今回DiscordBotを使用してDiscordのapiを叩くためのapiが必要だったので作りましたが、バックエンドの知識がない分ちょっとハードルを感じました。

今後の拡張について

NextAuthにDiscordProviderがあるのでそれを使った認証、サーバーIDの取得をしてこのbotを使ってみたいと思っています。
データベースを使ったこともないのでsupabaseあたりも使ってみたいです。

Hono、すごい

honoを使うにあたってムーザルちゃんねるさんのこの動画→ https://www.youtube.com/watch?v=7U41SzC3yis を見たのですが、ものすごい可能性を感じていますし、日本人が開発者なのもあってフロントエンドとバックエンドの理解が進みました。もうjavascript/typescriptだけでフルスタック開発ができる時代なので今後バックエンド開発するときはhonoメインで使っていきたいです。

さいごに

初学者のメモ程度の記事ですが同じ初学者の方の参考になれば幸いです。
また、ミスなどあればご指摘ください。
ここまで閲覧してくださりありがとうございました。

Discussion