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

Discord BOTでのレジ及び売上管理について(GTA RP/FiveM)

1
Posted at

導入の背景について

FiveMで猫カフェのアルバイトをやっており、最初はスプレッドシートで管理していたが、猫カフェのオーナーになったことを機にDiscordでのレジを導入しました。

目的としては、計算のしやすさ、各人の売上の見える化、お店へのバック分の集計、人気商品の情報取得などを行えるようにしました。

実際のDiscord画面

【レジ画面】
スクリーンショット 2024-09-16 14.39.36.png

スクリーンショット 2024-09-16 14.47.09.png

【販売報告チャンネル】
スクリーンショット 2024-09-16 14.48.33.png

【キャッシュバック画面】
スクリーンショット 2024-09-16 14.45.11.png

【オーナー用メニュー】
スクリーンショット 2024-09-16 14.42.15.png

DiscordBotの概念図

構成のイメージ図は以下の通り。

image.png

[簡易版]魔法少女カフェBOT
image.png

実際のソースコード

猫カフェ版のすべてのソースコードを載せると長いので、簡易版で作成した魔法少女カフェ向けのコードを記載。
処理については、バック分の計算や売上個数管理がなく、ただのレジとしての機能になる。

35-magic-cafe-bot.py
import discord
from discord.ext import commands
import time
from discord.ui import View, Button, Modal, TextInput

#DiscordBOTのトークン 自身で作成したものを貼り付ける
TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxx"

# 必要なインテントを設定
intents = discord.Intents.default()
intents.message_content = True

# Botの設定
bot = commands.Bot(command_prefix='!', intents=intents)

# 商品と単価の辞書
items = {
    '魔法のサンドイッチ': 5000,
    '魔法のケーキ': 5000,
    '星のクッキー': 5000,
    '魔法のエリクサー': 5000,
    'プリズムソーダ': 5000,
    'ミスティックラテ': 5000
}

# ユーザーごとの購入履歴を保持する辞書
user_purchases = {}

# ボタンのビューを作成
class ButtonView(View):
    def __init__(self):
        super().__init__(timeout=None)  # タイムアウトを無効に設定

class QuantityModal(discord.ui.Modal):
    def __init__(self, item_name, item_price):
        super().__init__(title=f'{item_name}の数量入力')
        self.item_name = item_name
        self.item_price = item_price

        self.quantity = discord.ui.TextInput(
            label='数量を入力してください',
            placeholder='例: 3',
            min_length=1,
            max_length=3,
            required=True
        )
        self.add_item(self.quantity)

    async def on_submit(self, interaction: discord.Interaction):
        quantity = int(self.quantity.value)
        total_cost = self.item_price * quantity
        user_id = interaction.user.id
        
        if user_id not in user_purchases:
            user_purchases[user_id] = {}
        
        if self.item_name in user_purchases[user_id]:
            user_purchases[user_id][self.item_name]['quantity'] += quantity
            user_purchases[user_id][self.item_name]['total_cost'] += total_cost
        else:
            user_purchases[user_id][self.item_name] = {'quantity': quantity, 'total_cost': total_cost}

        # 変更を反映するための埋め込みメッセージを更新
        await update_embed(interaction)

        # 応答を遅延させることで、ユーザーにはメッセージを表示しない
        await interaction.response.defer()

class ItemButton(discord.ui.Button):
    def __init__(self, label, price):
        super().__init__(label=label, style=discord.ButtonStyle.primary)
        self.price = price

    async def callback(self, interaction: discord.Interaction):
        await interaction.response.send_modal(QuantityModal(self.label, self.price))

class CheckoutButton(discord.ui.Button):
    def __init__(self, report_channel_id: int):
        super().__init__(label='精算', style=discord.ButtonStyle.success)
        self.report_channel_id = report_channel_id 

    async def callback(self, interaction: discord.Interaction):
        user_id = interaction.user.id
        user_name = interaction.user.name
        total = sum(item['total_cost'] for item in user_purchases.get(user_id, {}).values())

        await interaction.response.send_message(f'合計金額は{total:,}円です。', ephemeral=True)

        # 報告先チャンネルにメッセージを送信
        report_channel = interaction.guild.get_channel(self.report_channel_id)
        if report_channel:
            await report_channel.send(f'{user_name} さんの売り上げ金額は {total:,} 円です。')
        else:
            print(f"チャンネルID {self.report_channel_id} が見つかりません。")

        # 報告後初期化
        time.sleep(5)
        user_purchases[interaction.user.id] = {}
        await update_embed(interaction)

class ResetButton(discord.ui.Button):
    def __init__(self):
        super().__init__(label='リセット', style=discord.ButtonStyle.danger)

    async def callback(self, interaction: discord.Interaction):
        user_purchases[interaction.user.id] = {}
        await interaction.response.send_message('合計金額がリセットされました。', ephemeral=True)
        await update_embed(interaction)

class ItemView(discord.ui.View):
    def __init__(self, items, report_channel_id: int):
        super().__init__(timeout=None)  # タイムアウトを無効に設定
        for item_name, item_price in items.items():
            self.add_item(ItemButton(label=item_name, price=item_price))
        self.add_item(CheckoutButton(report_channel_id=report_channel_id))
        self.add_item(ResetButton())

async def update_embed(interaction: discord.Interaction):
    user_id = interaction.user.id
    embed = discord.Embed(
        title='35マジカル レジ',
        description='商品の合計金額を計算してくれます!'
    )
    for item_name, item_price in items.items():
        quantity = user_purchases.get(user_id, {}).get(item_name, {}).get('quantity', 0)
        total_cost = user_purchases.get(user_id, {}).get(item_name, {}).get('total_cost', 0)
        embed.add_field(name=item_name, value=f'個数: {quantity}   合計金額: {total_cost:,}', inline=False)

    # 合計金額を埋め込みの最後に追加
    total_amount = sum(item['total_cost'] for item in user_purchases.get(user_id, {}).values())
    embed.add_field(name='総合計金額', value=f'{total_amount:,}', inline=False)
    
    await interaction.message.edit(embed=embed)

@bot.event
async def on_ready():
    print(f'{bot.user}としてログインしました')

@bot.command(name='start', help='35マジカルレジを開始します')
async def start(ctx):
    report_channel_id = 123456789012345  # 実際のチャンネルIDに置き換える 

    embed = discord.Embed(
        title='35マジカル レジ',
        description='商品の合計金額を計算してくれます!'
    )
    total_amount = 0  # 合計金額を計算するための変数
    for item_name, item_price in items.items():
        embed.add_field(name=item_name, value=f'個数: 0   合計金額: 0円', inline=False)

    # 合計金額を埋め込みの最後に追加
    embed.add_field(name='総合計金額', value=f'{total_amount:,}', inline=False)

    await ctx.send(embed=embed, view=ItemView(items, report_channel_id))

# Botの起動
bot.run(TOKEN)

上記のコードでDiscord Botのトークン及び、報告用のチャンネルIDを変更することで動作させることが可能です。

DiscordBotを動かすための事前準備(Discord側)

DisocrdBotの作成については、別記事にまとめているので、以下のリンクから参照してください。

チャンネルIDの確認方法

チャンネルIDは、投稿したいチャンネルを右クリックすると一番下に出てきます。

image.png

チャンネルIDが出てこない場合は、Discordのユーザー設定を開き、開発で検索して、開発者モードを有効にしてください。
image.png

DiscordBotを動かすための事前準備(Python側)

Pythonのインストールについては、省略します。(他の記事を参照にインストールしてください)

必要モジュールのインストール方法については、「discord.py」の「はじめに」に載っているので、以下のサイトを参照してインストールをしてください。(以下のサイトには載っていないですが、discord-componentsのインストールも必要です)

DiscordBotの起動方法

レジを起動させたいDsicordのチャンネルで以下のコマンドを入力して起動します。

!start

起動画面のイメージとしては、以下の通り
image.png

動作環境

作者の動作環境は、Windows10でpython3.11で動作している状況です。(基本的にPCが24時間稼働であり、常時Pythonを起動させれるため、PC上で動作させています。)
PCをちゃんとシャットダウンされている方は、外部サービスやFiveMサーバでの動作を検討が必要です。

今回のBot(Python)については、常時起動しておく必要があるため、FiveMサーバで稼働させることをおすすめします。

最後に

今後は、fivem server(Ubuntu 22.04)に移行を検討中です。

また、今回、公開したコードは、「35VILLAGE RP」というFiveMサーバの魔法少女カフェで実際に利用してもらっています。
また、猫カフェ版についても、「35VILLAGE RP」の猫カフェで利用しており、自身がオーナーということで紹介した機能以外にも、自動販売機(自作FievMスクリプト)の補充報告で利用したり、日々更新をしていっている状況です。
ぜひ、遊びに来てみてください。

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