1
0

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 1 year has passed since last update.

インタラクティブなボットメッセージング: ボタンとタスクトリガー 🖱️🔥

Posted at

Discord ボットのインタラクティブな可能性を解き放ちます。このチャプターでは、魅力的なボタンメッセージを作成し、ユーザーのインタラクションによってタスクがトリガーされる設定方法に焦点を当てます。Skeet の機能を利用して、ボットを単なる機能的な存在から、インタラクティブでエンゲージングなものへとデザインする方法を学びます。

Firestore ユーザーモデルの設計 🛠️📊

最初の重要なステップとして、私たちはFirestoreデータベースにユーザーモデルを慎重に設計します。このモデルは、ユーザー情報の管理を効率化し、アプリケーションのパフォーマンスを最適化します。このモデルはユーザーID、名前、そしてアプリケーション固有のカスタムフィールドを含むことができます。

本書では、DiscordUser と StripeUser の2つの異なるモデルを使って、ユーザーデータを管理します。単一の User モデルでの実装が可能かもしれませんが、今回はメンバーシップのステータスを持つ二種類のユーザー、つまり一般メンバーと有料会員メンバーを別々に実装します。多数の一般ユーザーと限られた数の会員メンバーのデータを効率的に処理するため、分けて管理することでクエリのコストを削減し、スケーラビリティを向上させます。

このセクションでは、ユーザーのID、名前、アイコン、役割、レベル、HP、MP、経験値、作成日時、更新日時などを含めた総合的なユーザーモデルを構築します。ここでは、簡潔ながらも柔軟性に富んだモデルを紹介し、実践的なコーディング例を通してその構築方法を学びます。

functions/skeet/src/lib/models/discordUserModels.ts における DiscordUser の型定義は以下の通りです:

import { Timestamp, FieldValue } from '@skeet-framework/firestore'

// CollectionId: DiscordUser
// DocumentId: auto
// Path: DiscordUser
export const DiscordUserCN = 'DiscordUser'
export type DiscordUser = {
  id?: string
  pubkey: string
  username: string
  email: string
  iconUrl: string | null
  roles: string[]
  lv: number
  hp: number
  mp: number
  exp: number
  isMembership: boolean
  createdAt?: Timestamp | FieldValue
  updatedAt?: Timestamp | FieldValue
}

これで、Firestore データベースにユーザーモデルを作成する準備が整いました。

3つボタンを使ってユーザーのデータを Firestore と連携 🖲️💾

次に、Discord ボットにボタン機能を実装し、ユーザーがボタンをクリックすることで Firestore に自動的に登録されるようにします。これにより、ユーザーが簡単にボットの機能を利用できるようになります。

以下の3つのボタンを作成します。

  • 登録ボタン
  • ステータスを見るボタン
  • ログインボーナスをもらうボタン

registerMessage.ts を以下のように追加します。

functions/skeet/src/lib/discord/messages/registerMessage.ts

import {
  ButtonStyleTypes,
  MessageComponentTypes,
} from '@skeet-framework/discord-utils'

export const registerMessage = () => {
  const body = {
    content: 'ボタンを押してね!',
    components: [
      {
        type: MessageComponentTypes.ACTION_ROW,
        components: [
          {
            type: MessageComponentTypes.BUTTON,
            style: ButtonStyleTypes.PRIMARY,
            label: '📗 登録する',
            custom_id: 'register-button',
          },
        ],
      },
      {
        type: MessageComponentTypes.ACTION_ROW,
        components: [
          {
            type: MessageComponentTypes.BUTTON,
            style: ButtonStyleTypes.PRIMARY,
            label: '📕 ステータスを見る',
            custom_id: 'view-button',
          },
        ],
      },
      {
        type: MessageComponentTypes.ACTION_ROW,
        components: [
          {
            type: MessageComponentTypes.BUTTON,
            style: ButtonStyleTypes.PRIMARY,
            label: '📘 ログインボーナスをもらう',
            custom_id: 'bonus-button',
          },
        ],
      },
    ],
  }
  return body
}

そして、3 つの custom_id に対応するアクションを functions/skeet/src/lib/discord/actions/ に作成します。

  • registerAction.ts
  • viewAction.ts
  • bonusAction.ts

ボタンを押したら Firestore にユーザーを登録する 🖲️💾

まずは、registerAction.ts を以下のように追加します。

functions/skeet/src/lib/discord/actions/registerAction.ts

import { DiscordUser, DiscordUserCN } from '@/models/discordUserModels'
import {
  DiscordRouterParams,
  deferResponse,
  updateResponse,
} from '@skeet-framework/discord-utils'
import { add } from '@skeet-framework/firestore'
import { Response } from 'firebase-functions/v1'
import { inspect } from 'util'

export const registerAction = async (
  res: Response,
  db: FirebaseFirestore.Firestore,
  discordToken: string,
  body: DiscordRouterParams
) => {
  console.log('helloAction')
  console.log(inspect(body))
  await deferResponse(discordToken, body.id, body.token)

  const userParams: DiscordUser = {
    pubkey: '',
    username: body.member.user.username,
    email: '',
    iconUrl: body.member.user.avatar,
    roles: body.member.roles,
    lv: 1,
    hp: 100,
    mp: 30,
    exp: 0,
    isMembership: false,
  }

  const user = await add<DiscordUser>(
    db,
    DiscordUserCN,
    userParams,
    body.member.user.id
  )
  if (!user) {
    await updateResponse(discordToken, body.application_id, body.token, {
      content: `⚠️ Failed to create user.Please try again in a few seconds.`,
      flags: 64,
    })
    return false
  }

  await updateResponse(discordToken, body.application_id, body.token, {
    content: `Hello, ${userParams.username} created!\nDocumentId:${user.id}`,
    flags: 64,
  })
  return true
}

ボタンを押したら Firestore のユーザーを表示する 🖲️👀

次に、viewAction.ts を以下のように追加します。

functions/skeet/src/lib/discord/actions/viewAction.ts

import { DiscordUser, DiscordUserCN } from '@/models/discordUserModels'
import {
  DiscordRouterParams,
  deferResponse,
  updateResponse,
} from '@skeet-framework/discord-utils'
import { add, get } from '@skeet-framework/firestore'
import { Response } from 'firebase-functions/v1'

export const viewAction = async (
  res: Response,
  db: FirebaseFirestore.Firestore,
  discordToken: string,
  body: DiscordRouterParams
) => {
  console.log('viewAction')
  await deferResponse(discordToken, body.id, body.token)

  const user = await get<DiscordUser>(db, DiscordUserCN, body.member.user.id)
  if (!user) {
    await updateResponse(discordToken, body.application_id, body.token, {
      content: `⚠️ Failed to get user.Please try again in a few seconds.`,
      flags: 64,
    })
    return false
  }

  // Discord Embed Message
  const emdbed = {
    title: 'あなたのステータス🛡️',
    description: `名前: ${user.username}\nLv:${user.lv}\nHP:${user.hp}\nMP:${user.mp}\nEXP:${user.exp}`,
    color: 0x0099ff,
    timestamp: new Date(),
    footer: {
      text: 'powered by Skeet Framework',
      icon_url:
        'https://storage.googleapis.com/skeet-assets/imgs/logo/SkeetLogoSquare.png',
    },
  }

  await updateResponse(discordToken, body.application_id, body.token, {
    embeds: [emdbed],
    flags: 64,
  })
  return true
}

レスポンスに Discord Embed Message を追加することで、ユーザーにより魅力的なメッセージを送信することができます。

メッセージの詳細については、以下のドキュメントを参照してください。

ボタンを押したら Firestore のユーザーを更新する 🖲️🔄

次に、bonusAction.ts を以下のように追加します。

functions/skeet/src/lib/discord/actions/bonusAction.ts

import { DiscordUser, DiscordUserCN } from '@/models/discordUserModels'
import {
  DiscordRouterParams,
  deferResponse,
  updateResponse,
} from '@skeet-framework/discord-utils'
import { get, update } from '@skeet-framework/firestore'
import { Response } from 'firebase-functions/v1'

export const bonusAction = async (
  res: Response,
  db: FirebaseFirestore.Firestore,
  discordToken: string,
  body: DiscordRouterParams
) => {
  console.log('bonusAction')
  await deferResponse(discordToken, body.id, body.token)

  const user = await get<DiscordUser>(db, DiscordUserCN, body.member.user.id)
  if (!user) {
    await updateResponse(discordToken, body.application_id, body.token, {
      content: `⚠️ Failed to get user.Please try again in a few seconds.`,
      flags: 64,
    })
    return false
  }

  const bonus = Math.floor(Math.random() * 100)
  const newExp = user.exp + bonus
  let lv = user.lv
  const expToNextLevel = 100 * lv

  if (newExp >= expToNextLevel) {
    lv++
  }

  await update<DiscordUser>(db, DiscordUserCN, body.member.user.id, {
    exp: newExp,
    lv,
  })

  let description = `あなたは${bonus}の EXP を獲得しました!`
  if (lv > user.lv) {
    description += `\nレベルが${lv}に上がりました!`
  }
  // Discord Embed Message
  const emdbed = {
    description,
    color: 0x0099ff,
  }

  await updateResponse(discordToken, body.application_id, body.token, {
    embeds: [emdbed],
    flags: 64,
  })
  return true
}

新しいボタンメッセージの送信 🖲️🔥

root ディレクトリに registerMessage を作成し、以下のように追加します。

import { onRequest } from 'firebase-functions/v2/https'
import { publicHttpOption } from '@/routings/options'
import { TypedRequestBody } from '@/types/http'
import { RootParams } from '@/types/http/rootParams'
import { messageChannel } from '@skeet-framework/discord-utils'
import { defineSecret } from 'firebase-functions/params'
import { registerMessage } from '@/lib/discord/messages/registerMessage'

const DISCORD_TOKEN = defineSecret('DISCORD_TOKEN')
export const root = onRequest(
  { ...publicHttpOption, secrets: [DISCORD_TOKEN] },
  async (req: TypedRequestBody<RootParams>, res) => {
    try {
      // Define your logic here
      const message = registerMessage()
      const channelId = 'your-channel-id'
      await messageChannel(DISCORD_TOKEN.value(), channelId, message)
      res.json({ status: 'success' })
    } catch (error) {
      res.status(500).json({ status: 'error', message: String(error) })
    }
  }
)

skeet s コマンドでローカルサーバーを起動し、

テスト用の root エンドポイント、

http://127.0.0.1:5001/your-project-id/asia-northeast1/root

にアクセスすると、ボタンメッセージが表示されます。

DiscordRouter にボタンアクションを追加する 🖲️🔥

functions/skeet/src/routings/discordRouter.ts にボタンアクションを追加します。

if 文の中に、ボタンの custom_id に対応するアクションを追加します。

// Import new actions
import { viewAction } from '@/lib/discord/actions/viewAction'
import { registerAction } from '@/lib/discord/actions/registerAction'
import { bonusAction } from '@/lib/discord/actions/bonusAction'

~中略~
...
} else {
  // Button action
  const { custom_id } = req.body.data as ActionData
  if (custom_id.match(/^register-button$/)) {
    await registerAction(res, db, DISCORD_TOKEN.value(), req.body)
  }
  if (custom_id.match(/^view-button$/)) {
    await viewAction(res, db, DISCORD_TOKEN.value(), req.body)
  }
  if (custom_id.match(/^bonus-button$/)) {
    await bonusAction(res, db, DISCORD_TOKEN.value(), req.body)
  }
}
...

これで、ボタンを押すと Firestore にユーザーが登録され、ユーザーのステータスを見ることができ、ログインボーナスをもらうことができるようになりました。

デプロイして Discord ボットを更新する 🖲️🔥

それでは、デプロイして Discord ボットを更新しましょう。

skeet deploy --function skeet:discordRouter

ボタンアクションを実行する 🖲️🔥

Discord ボットに追加したボタンを押してみましょう。

  • 登録ボタン

  • ステータスを見るボタン

  • ログインボーナスをもらうボタン

無事に Firestore とユーザーデータが連携されました🎉

続いて、何度かログインボーナスをもらうと

レベルがあがりました🎉

このように Discordのインタラクションと Firestoreのデータを連携させることができました。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?