導入の背景について
FiveMで猫カフェのアルバイトをやっており、最初はスプレッドシートで管理していたが、猫カフェのオーナーになったことを機にDiscordでのレジを導入しました。
目的としては、計算のしやすさ、各人の売上の見える化、お店へのバック分の集計、人気商品の情報取得などを行えるようにしました。
実際のDiscord画面
DiscordBotの概念図
構成のイメージ図は以下の通り。
実際のソースコード
猫カフェ版のすべてのソースコードを載せると長いので、簡易版で作成した魔法少女カフェ向けのコードを記載。
処理については、バック分の計算や売上個数管理がなく、ただのレジとしての機能になる。
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は、投稿したいチャンネルを右クリックすると一番下に出てきます。
DiscordBotを動かすための事前準備(Python側)
Pythonのインストールについては、省略します。(他の記事を参照にインストールしてください)
必要モジュールのインストール方法については、「discord.py」の「はじめに」に載っているので、以下のサイトを参照してインストールをしてください。(以下のサイトには載っていないですが、discord-componentsのインストールも必要です)
DiscordBotの起動方法
レジを起動させたいDsicordのチャンネルで以下のコマンドを入力して起動します。
!start
動作環境
作者の動作環境は、Windows10でpython3.11で動作している状況です。(基本的にPCが24時間稼働であり、常時Pythonを起動させれるため、PC上で動作させています。)
PCをちゃんとシャットダウンされている方は、外部サービスやFiveMサーバでの動作を検討が必要です。
今回のBot(Python)については、常時起動しておく必要があるため、FiveMサーバで稼働させることをおすすめします。
最後に
今後は、fivem server(Ubuntu 22.04)に移行を検討中です。
また、今回、公開したコードは、「35VILLAGE RP」というFiveMサーバの魔法少女カフェで実際に利用してもらっています。
また、猫カフェ版についても、「35VILLAGE RP」の猫カフェで利用しており、自身がオーナーということで紹介した機能以外にも、自動販売機(自作FievMスクリプト)の補充報告で利用したり、日々更新をしていっている状況です。
ぜひ、遊びに来てみてください。









