discord.py - MongoDBをdiscord.pyで使う
はじめに
この記事はdiscord.pyでMongoDBを使ったプロフィールBotを作ることを目標にしています。
間違いを見つけたらどんどん指摘してください。
環境
- Python v3.9.2-final
- discord.py v1.7.1-final
- aiohttp v3.6.3
- system info: Windows 10 10.0.17134
(python -m discord -v
の情報)
1. MongoDBのアカウントを作る
もうCollectionを作成した人は3. ライブラリのインストールまで飛ばして下さい。
https://mongodb.com
にアクセスし、アカウントを作ります。
今回はGoogleアカウントで作ります。
Sign Inを押し、
ここのSign In with Google
からGoogleアカウントでログインします。
規約に同意し、以下のような画面になったら成功です。
エラーが発生した場合
ここにアクセスします。
それでもダメな場合はもう一度試してみて下さい。
2. Clusterを作る
Organizationを作る
Create an Organization
を押し、新しいOrganizationを作ります。
Organization Nameに名前を入力し、Nextを押します。
Select Cloud ServiceはMongoDB Atlasで大丈夫です。
メンバーを招待する画面に移ります。
もし他の開発者がいるなら招待しましょう。
今回は無視し、Create Organizationを押します。
Projectを作る
この画面になったらNew Projectで新しいProjectを作って下さい。
Project Nameで名前を指定します。
先ほどのように、他の開発者がいるなら招待しましょう。
今回は無視し、Create Projectを押します。
Clusterを作る
Build a ClusterでClusterを作ります。
プランが出てくるので、Shared Clustersを選択します。
ここで色々な設定が出来ます。
Google Cloud > Tokyoで日本のサーバーを使えます。
設定出来たら、Create ClusterでClusterを作成して下さい。
押すとClusterが作成されるので1-3分ほど待ちます。
接続を設定する
Clusterが出来たらConnectで接続設定を出します。
上でアクセスできるIPを制限できます。Herokuなどでホストしている場合はデプロイ毎にIPが変わるため、Allow Access from Anywhere(すべてのIPを許可)を使います。
今回はIPを制限しません。
下でユーザーを作成します。
今回はBotというユーザー名で作成します。パスワードはAutogenerate Secure Password(パスワードを自動生成)で作成した方が安全です。
生成したらパスワード欄のSHOWでパスワードを表示し、コピーします。
Connect Your Applicationを押します。
ウィンドウの下に書いてあるURL(Connection String)をコピーします。
mongodb+srv://<名前>:<パスワード>@hitoniyotte.kawaru.mongodb.net/<データベース名>?retryWrites=true&w=majority
(これは例です。)
<名前>
と<パスワード>
は設定した物に置き換えて下さい。データベース名はお好みで。
以上で準備は終わりです。お疲れ様でした。
3. ライブラリのインストール
MongoDBを使うには一般的にはpymongoを使いますが、これはブロッキング(discord.pyではBot全体のフリーズ、詳細はdiscord.pyのFAQにて)を引き起こしてしまいます。
そのため、今回はmotorを使います。
また、dnspythonも必要なのでインストールします。
py -3 -m pip install motor dnspython
または
python3 -m pip install motor dnspython
をしてください。
4. データベースを読み書きする
以下のコードから始めます:
import discord
from discord.ext import commands
intents = discord.Intents.default()
intents.members = True # Member Intentsを有効化する
allowed_mentions = discord.AllowedMentions(replied_user=False) # 返信のメンションをデフォルトで無効化
bot = commands.Bot(command_prefix="p ", intents=intents, allowed_mentions=allowed_mentions)
@bot.event
async def on_ready():
print(f"Logged in as {bot.user}")
bot.run("Th1sIsN0tT0k3n.B3cause.1fiShowB0tWillG3tH4cked")
クライアントを作成する
motor.motor_asyncio.AsyncIOMotorClientを作成します。
from discord.ext import commands
+ from motor import motor_asyncio as motor # motor_asyncioをインポート
================================================================
bot = commands.Bot(command_prefix="p ", intents=intents, allowed_mentions=allowed_mentions)
+ dbclient = motor.AsyncIOMotorClient("mongodb+srv://<名前>:<パスワード>@hitoniyotte.kawaru.mongodb.net/<データベース名>?retryWrites=true&w=majority") # AsyncIOMotorClientを作成
Connection Stringは置き換えて下さい。
また、流出を防ぐためにdotenvなどを使うことをお勧めします。
データベースオブジェクトを作成する
クライアント["データベース名"]
、またはクライアント.データベース名
でデータベースオブジェクトを作成できます。
dbclient = motor.AsyncIOMotorClient("mongodb+srv://<名前>:<パスワード>@hitoniyotte.kawaru.mongodb.net/<データベース名>?retryWrites=true&w=majority")
+ db = dbclient["ProfileBot"]
+ # または:db = dbclient.ProfileBot
コレクションオブジェクトを作成する
コレクションはテーブルのような物で、データを保存する場所です。
データベース["コレクション名"]
、またはデータベース.コレクション名
でコレクションオブジェクトを作成できます。
db = dbclient["ProfileBot"]
+ profiles_collection = db.profiles
+ # または:profiles_collection = db["profiles"]
コレクションを読み書きする
書き込み
insert_oneでデータを1つ、insert_manyで複数のデータを書き込むことが出来ます。
p set プロフィール
で設定出来るようにします。
@bot.command(name="set")
async def set_profile(ctx, *, text): # setだと変数名がかぶるので変更、*,で空白を無視
await profiles_collection.insert_one({
"userid": ctx.author.id,
"text": text
})
await ctx.reply("設定が完了しました。")
読み込み
find_oneで条件に一致するデータを1つ、findで複数のデータを検索できます。
p show ユーザー
で表示出来るようにします。
@bot.command(name="show")
async def show_profile(ctx, target: discord.User): # ユーザーを指定
profile = await profiles_collection.find_one({
"userid": target.id
}, {
"_id": False # 内部IDを取得しないように
})
if profile is None:
return await ctx.reply("見付かりませんでした。")
embed = discord.Embed(title=f"`{target}`のプロフィール", description=profile["text"]) # 埋め込みを作成
return await ctx.reply(embed=embed) # 埋め込みを送信
良い感じです。
削除
delete_oneで条件に合うデータを1つ、delete_manyで全て削除できます。
また、返り値のpymongo.results.DeleteResultのdeleted_countで削除した個数を取得できます。
p delete
、p del
で削除出来るようにします。
@bot.command(name="delete", aliases=["del"])
async def delete_profile(ctx): # ユーザーを指定
result = await profiles_collection.delete_one({
"userid": ctx.author.id # useridで条件を指定
})
if result.deleted_count == 0: # 削除できなかったら
return await ctx.reply("見付かりませんでした。")
return await ctx.reply("削除しました。")
しっかり削除できています。
置き換え
replace_oneで条件に合うデータを1つ指定の物に置き換えます。
upsert=Trueとすることで、合うデータがなかった場合は新規挿入ということができます(UPdate + inSERT)
async def set_profile(ctx, *, text):
+ new_data = {
+ "userid": ctx.author.id,
+ "text": text
+ }
+ await profiles_collection.replace_one({
+ "userid": ctx.author.id # useridで条件を指定
+ }, new_data, upsert=True)
- await profiles_collection.insert_one({
- "userid": ctx.author.id,
- "text": text
- })
しっかり置き換えられています。
他にも色々なことが出来ますが、それらについてはリファレンスを読んで下さい。
最終コード
import discord
from discord.ext import commands
from motor import motor_asyncio as motor
intents = discord.Intents.default()
intents.members = True
allowed_mentions = discord.AllowedMentions(replied_user=False)
bot = commands.Bot(command_prefix="p ", intents=intents, allowed_mentions=allowed_mentions)
dbclient = motor.AsyncIOMotorClient("mongodb+srv://<名前>:<パスワード>@hitoniyotte.kawaru.mongodb.net/<データベース名>?retryWrites=true&w=majority")
db = dbclient["ProfileBot"]
profiles_collection = db.profiles
@bot.event
async def on_ready():
print(f"Logged in as {bot.user}")
@bot.command(name="set")
async def set_profile(ctx, *, text):
new_data = {
"userid": ctx.author.id,
"text": text
}
result = await profiles_collection.replace_one({
"userid": ctx.author.id
}, new_data)
if result.matched_count == 0:
await profiles_collection.insert_one(new_data)
await ctx.reply("設定が完了しました。")
@bot.command(name="show")
async def show_profile(ctx, target: discord.User):
profile = await profiles_collection.find_one({
"userid": target.id
}, {
"_id": False
})
if profile is None:
return await ctx.reply("見付かりませんでした。")
embed = discord.Embed(title=f"`{target}`のプロフィール", description=profile["text"])
return await ctx.reply(embed=embed)
@bot.command(name="delete", aliases=["del"])
async def delete_profile(ctx):
result = await profiles_collection.delete_one({
"userid": ctx.author.id
})
if result.deleted_count == 0:
return await ctx.reply("見付かりませんでした。")
return await ctx.reply("削除しました。")
bot.run("Th1sIsN0tT0k3n.B3cause.1fiShowB0tWillG3tH4cked")
終わりに
この記事が役に立てば幸いです。
読んでいただきありがとうございました。
Discussion