インテント、プレゼンス、ゲートウェイ¶
ゲートウェイは WebSocket で、Discord はこれを通じてイベントを送り、Bot はプレゼンスやリクエストを送ります。bostie の Rust コアは、ハートビート、再開(resume)、再接続、圧縮、送信のレート制限を処理して、接続を維持します。このページでは、Bot がゲートウェイに何を求めるかを説明します。
インテント¶
Discord は、イベントのインテントを要求した Bot にだけそのイベントを送ります。Bot(intents=...) は Intents フラグを受け取ります。省略すると Intents.DEFAULT で、3 つの特権インテントを除くすべてのインテントです。
| 特権インテント | 必要になるもの |
|---|---|
GUILD_MEMBERS |
メンバーの参加・更新・退出のイベント、キャッシュ内のすべてのメンバー、全メンバーの要求 |
GUILD_PRESENCES |
プレゼンスの更新と、キャッシュ内のプレゼンス |
MESSAGE_CONTENT |
サーバー内のメッセージの内容、埋め込み、添付ファイル、コンポーネント。ただし Bot をメンションしたメッセージは除きます。DM ではインテントは不要です |
特権インテントは、Developer Portal の Bot ページでも有効にする必要があります。有効にしていないと、Discord はコード 4014 で接続を閉じ、start() は PrivilegedIntentsRequired を送出します。そのメッセージには、どこで有効にすればよいかが書かれています。インテントは | で追加します(Intents.DEFAULT | Intents.MESSAGE_CONTENT)。リスナーが Bot にないインテントを必要とする場合、bostie は警告を出し、オプションやリクエストがインテントを必要とする場合は MissingIntent を送出します。
ボイスには GUILD_VOICE_STATES が必要で、これは DEFAULT に含まれています。
import bostie
from bostie import Intents
bot = bostie.Bot(intents=Intents.ALL & ~Intents.GUILD_PRESENCES)
個々のインテントと並んで、3 つのプリセットがあります。Intents.DEFAULT(別名 Intents.UNPRIVILEGED)は特権インテント以外のすべてのインテント、Intents.PRIVILEGED は 3 つの特権インテント、Intents.ALL はその両方です。上の例のように、& ~ でプリセットからインテントを取り除けます。
プレゼンス¶
import bostie
from bostie import Context, Intents, Status, activity
bot = bostie.Bot(
intents=Intents.DEFAULT | Intents.GUILD_MEMBERS,
status=Status.IDLE,
activities=[activity.custom("Starting up")],
)
@bot.command
async def online(ctx: Context) -> None:
"""Switch to online and show what the bot is doing."""
await bot.update_presence(
status=Status.ONLINE, activities=[activity.listening("/help")]
)
await ctx.respond("Online.", ephemeral=True)
@bot.command
async def find(ctx: Context, name: str) -> None:
"""Find members whose name starts with the text."""
if ctx.guild_id is None:
await ctx.respond("Use this in a server.", ephemeral=True)
return
found = await bot.request_members(ctx.guild_id, query=name, limit=10)
names = ", ".join(member.display_name for member in found.members) or "nobody"
await ctx.respond(f"Found {names}.", ephemeral=True)
@bot.command
async def sounds(ctx: Context) -> None:
"""List this server's soundboard sounds."""
if ctx.guild_id is not None:
found = await bot.request_soundboard_sounds(ctx.guild_id)
await ctx.respond(", ".join(sound.name for sound in found) or "No sounds.")
Bot(status=..., activities=[...], afk=False, idle_since=None) は、Bot が接続したときに表示する内容を設定します。await bot.update_presence(...) は、すべてのシャードと、後から接続するシャードでそれを変更します。省略したオプションは値を保持します。
status は Status で、ONLINE、IDLE、DND、INVISIBLE のいずれかです。アクティビティは bostie.activity で作ります。
| ビルダー | 表示 |
|---|---|
activity.playing(name) |
name をプレイ中 |
activity.listening(name) |
name を再生中 |
activity.watching(name) |
name を視聴中 |
activity.competing(name) |
name に参戦中 |
activity.streaming(name, url) |
name を配信中(Discord が対応している配信サービスの場合) |
activity.custom(text) |
text を表示するカスタムステータス |
custom 以外はどれも、2 行目のテキストである state= を受け取り、activity.ActivityDict を返します。
リクエスト¶
一部のデータは、リクエストへの応答としてゲートウェイ経由でしか得られません。
await bot.request_members(guild_id, query="ab", limit=10)は、ユーザー名またはニックネームがqueryで始まるメンバーを最大limit人(0 から 100)返します。代わりにuser_ids=[...]を指定すると、ID で最大 100 人のメンバーを要求します。どちらも指定しないと全メンバーを要求し、これにはGUILD_MEMBERSが必要です。presences=Trueはプレゼンスも加え、これにはGUILD_PRESENCESが必要です。応答はRequestedMembersで、members、presences、そして Discord が見つけられなかった ID であるnot_foundを持ちます。Discord はこのリクエストをレート制限しており、拒否されるとretry_afterを持つMemberRequestRateLimitedが送出されます。await bot.request_soundboard_sounds(guild_id)は、サーバーのサウンドボードのサウンドを返します。await bot.request_channel_info(guild_id)は、サーバーのチャンネルのボイスステータスとボイスの開始時刻を返します。fields=["status"]を指定すると、要求する内容を減らせます。
どれも応答の各部分を最大 30 秒(メンバーの場合は timeout=)待ち、それを過ぎると TimeoutError を送出します。新しいセッションを開始中のシャードへのリクエストは StateError を送出します。準備ができてからリトライしてください。
生のゲートウェイコマンド¶
bot.gateway.send(op, d) は、任意のゲートウェイコマンドを送ります。オペコード 3(プレゼンス)はすべてのシャードに送られます。guild_id を持つペイロードはそのサーバーのシャードに、guild_ids を持つペイロードは各シャードにそれぞれの担当分の ID とともに送られ、それ以外はシャード 0 に送られます。Discord の応答はイベントとして届き、しかも何かが待ち受けているイベント名に限られます。そのため、生のコマンドは listen_raw リスナーや wait_for と組み合わせてください。
bot.gateway からは、ほかにも次のものが得られます。このプロセスが動かしているシャードを表す shard_ids、Discord があるサーバーのイベントを送るシャードを返す shard_for(guild_id)、独自の send、reconnect、latency を持つ shard(shard_id)、すべての接続を切断して新しい接続でセッションを再開する reconnect()、そしてハートビート往復時間の平均である latency です。このプロセスが動かしていないシャードへのコマンドは ShardNotRunning を送出し、ほかの多くのコマンドの後ろでキューに入りきらないコマンドは GatewayQueueFull を送出します。
バックプレッシャー¶
Rust コアは、Discord が送ってくる速さでイベントを読み取り、Python に渡します。イベントループが追いつかなくなると、シャードは Python が追いつくまで読み取りを止め、警告をログに出力し、待機中のバイト数を持つ ShardBackpressure を送ります。原因は、ハンドラーやリスナーの中での長いブロッキング処理です。そのような処理は asyncio.to_thread でスレッドに移してください。