カテゴリー: Discordボット開発

Discordボット開発#32サーバーログ・監査ログ(Audit Log)チャンネルを作ろう!メッセージの削除・編集・入退室・キックまで自動記録(Pythonサンプル) 🕵️

この記事は韓国語の原文から翻訳したものです。

ひと目でわかる要点

Discord標準の監査ログは「誰が何件削除したか」までしか教えてくれず、削除された内容は表示されません。rawイベントとon_audit_log_entry_createを組み合わせて、削除された内容・削除した人・入退室・ロール変更・キックまでを1つのチャンネルに埋め込みで記録するログボットを作ります。

  • Python 3.10.12 · discord.py 2.7.1 · python-dotenv 1.2.3 · 2026-09-25 実際に実行して確認
  • 削除されたメッセージの「内容」はボットのキャッシュにあるときだけ見られます → max_messagesを多めに、Message Content・Server Membersインテントは必須
  • 誰が削除したかはguild.audit_logs()からtarget・channel・時間(15秒)で探します。本人による削除は監査ログに記録が残りません
  • キック・BANはon_audit_log_entry_create(2.2+、Intents.moderation)で実行者・理由までリアルタイムに受け取れます
目次13項目

こんにちは!Mojangcamです 😎

前回の#31では、ロビーに入ると自動で作られ、空になると消える一時ボイスチャンネル(Temp Voice) ボットを作りました。今回は、サーバー運営者なら一度は必ず探すことになる機能です。「これ、誰が消したの?」

今日作るログボットは、こんなことをします。

  • 誰かがメッセージを削除すると、削除される前の内容がそのままログチャンネルに残ります
  • 管理者が他人のメッセージを削除した場合は、誰が削除したかまで記録されます
  • メンバーが参加するとアカウント作成日(サブ垢・ボットの見分けに!)、退出すると持っていたロールが残ります
  • キック・BANは実行した管理者と理由までリアルタイムで記録されます

なぜボットが必要なのでしょうか? 🧠 Discordにもサーバー設定 → 監査ログのメニューはありますが、削除されたメッセージの内容は表示されません。「誰が何件削除した」までしか出てこないんです。トラブルが起きたときに「さっきあの人が暴言を書いて消しました」を証明するには、ボットで前もって記録しておくしかありません。

#서버-로그 (server log) チャンネルにメッセージ削除(赤)・メンバー参加(緑)の埋め込みが積み重なった画面と、イベント別の色の凡例

今回のログボットは、#21 禁止ワードフィルター、#25 エラーハンドリングと一緒に、「コミュニティ統合管理ボット」の運営透明性モジュールの役割を担います。


📌 準備するもの

  • Python 3.9以上(3.10+推奨) — dict[str, int] の書き方を使っているので3.9から動きます
  • discord.py 2.7.1(2026-09-25時点のPyPI最新版) — on_audit_log_entry_create は2.2から追加されたので、古いバージョンなら必ずアップデートしてください
  • python-dotenv 1.x
  • Discord Developer Portalで作ったボット + サーバーへの招待済み(権限: 監査ログを表示、メッセージを送信、埋め込みリンク)
ターミナル
pip install -U discord.py python-dotenv

プロジェクトフォルダーに .env ファイルを作ってトークンを入れてください。トークンをコードに直接書くと、GitHubに上げたときに漏れる典型パターンです 😱

テキスト
token=여기에_봇_토큰

💡 .env は必ず .gitignore に追加しておきましょう。(echo .env >> .gitignore)

⚠️ 今回は特権インテント(Privileged Intents)が2つ必要です。Developer Portal → BotタブでMessage Content Intent(削除されたメッセージの内容を見るのに必須)とServer Members Intent(入退室・ロール変更の検知)をオンにしてください。これをオンにしないと、削除ログの内容が空っぽで出たり、入退室イベントがまったく呼ばれなかったりします。

🧩 ログボットが記録するもの

今日作るログは6種類です。色で区別して、スクロールするだけで何が起きたのかわかるようにします。

イベント 記録する内容 埋め込みの色
メッセージ削除 投稿者、チャンネル、削除される前の内容、添付ファイル名、削除した人 赤
メッセージ編集 編集前・後の内容、メッセージへのリンク オレンジ
参加・退出 アカウント作成日(新規アカウントの判別)、参加日、持っていたロール 緑 / グレー
ニックネーム・ロール変更 変更前・後、追加・削除されたロール 青
キック・BAN・解除 実行した管理者、対象、理由 濃い赤
チャンネル作成・削除 チャンネル名・種類 緑 / 赤

📌 ステップ1 - 基本設定とインテント3つ

Python
# discord.py 2.7.1 / Python 3.9+ / python-dotenv 1.x
import os
import json
import asyncio

import discord
from discord import app_commands
from discord.ext import commands
from dotenv import load_dotenv

load_dotenv()
token = os.getenv("token")

CONFIG_FILE = "log_config.json"   # {サーバーID(str): ログチャンネルID(int)}
MAX_CONTENT = 1000                # 埋め込みフィールドは最大1024文字 → 余裕をもって切る

intents = discord.Intents.default()
intents.message_content = True   # 削除されたメッセージの「内容」を見るには必須(特権インテント)
intents.members = True           # 入退室・ニックネーム・ロール変更の検知(特権インテント)
intents.moderation = True        # キック・BAN + 監査ログイベント

bot = commands.Bot(command_prefix="!", intents=intents, max_messages=5000)

log_channels: dict[str, int] = {}
lock = asyncio.Lock()

ひとつずつ見ていきましょう 💡

  • intents.message_content : 他の人が書いたメッセージの内容をボットが見られるようにします。これがオフだと、削除ログの content が空文字列で入ってきます。Portalでもオンにする必要がある特権インテントです。
  • intents.members : on_member_join / on_member_remove / on_member_update はこのインテントに紐づいています。これも特権インテントです。
  • intents.moderation : on_member_ban / on_member_unban、そして今日の主役 on_audit_log_entry_create がここに含まれます。これは特権ではないので、コードでオンにするだけでOKです。(以前の名前 intents.bans と同じものです。)
  • max_messages=5000 : ボットが覚えておくメッセージの数です。デフォルトは1000ですが、削除されたメッセージの内容はこのキャッシュにあるときだけ見られるので、多めに増やしました。

📌 ステップ2 - ログチャンネルの保存と埋め込みの共通関数

サーバーごとにログチャンネルが違うので、{서버ID: 채널ID} をJSONに保存します。#31とまったく同じパターンです。

Python
def save():
    with open(CONFIG_FILE, "w", encoding="utf-8") as f:
        json.dump(log_channels, f, ensure_ascii=False, indent=2)


def load():
    global log_channels
    if os.path.exists(CONFIG_FILE):
        with open(CONFIG_FILE, encoding="utf-8") as f:
            log_channels = json.load(f)


def get_log_channel(guild: discord.Guild):
    cid = log_channels.get(str(guild.id))
    return guild.get_channel(cid) if cid else None


def make_embed(title: str, color: discord.Color, user=None) -> discord.Embed:
    embed = discord.Embed(title=title, color=color, timestamp=discord.utils.utcnow())
    if user is not None:
        embed.set_author(name=f"{user} ({user.id})", icon_url=user.display_avatar.url)
    return embed


async def send_log(guild: discord.Guild, embed: discord.Embed):
    channel = get_log_channel(guild)
    if channel is None:
        return
    try:
        await channel.send(embed=embed)
    except discord.Forbidden:
        print(f"⚠️ {guild.name}: ログチャンネルにメッセージを送信する権限がありません。")


def cut(text: str) -> str:
    text = text or "(内容なし — 埋め込み/添付ファイルのみ、またはキャッシュにない)"
    return text if len(text) <= MAX_CONTENT else text[:MAX_CONTENT] + " …(省略)"
  • timestamp=discord.utils.utcnow() : 埋め込みの下に「今日 15:12」のような時刻が見る人のタイムゾーンで自動表示されます。ログでいちばん大事なのは時刻ですからね!
  • set_author(name, icon_url) : 埋め込みのいちばん上にプロフィール画像 + 이름 (ID) を入れておきます。IDも一緒に残すのは、あとでニックネームを変えたりサーバーを抜けたりしてもIDは変わらないからです。
  • send_logのtry/except : 誰かがログチャンネルの権限を変えてしまうと、ボットが Forbidden で落ちることがあります。#25で学んだとおりにキャッチして、コンソールに出すだけで先に進みます。
  • cut() : 埋め込みのフィールド値には1024文字の制限があります。超えると 400 Bad Request になるので、先に切っておきます。

📌 ステップ3 - ここが核心!メッセージの削除・編集をキャッチ

ここではrawイベントを使います。on_message_delete はキャッシュにあるメッセージしか知らせてくれませんが、on_raw_message_delete はキャッシュになくても「どのチャンネルのどのIDが削除されたか」までは教えてくれるからです。

メッセージ削除時のボットの処理フロー - 削除発生、キャッシュ参照、監査ログ参照の3段階と、状況別に「削除した人」欄に表示される値

Python
async def find_deleter(guild: discord.Guild, author_id: int, channel_id: int):
    """監査ログから「たった今この人のメッセージをこのチャンネルで削除した管理者」を探します。"""
    try:
        async for entry in guild.audit_logs(limit=5, action=discord.AuditLogAction.message_delete):
            same_target = entry.target is not None and entry.target.id == author_id
            same_channel = entry.extra.channel.id == channel_id
            recent = (discord.utils.utcnow() - entry.created_at).total_seconds() < 15
            if same_target and same_channel and recent:
                return entry.user
    except discord.Forbidden:
        return None
    return None


@bot.event
async def on_raw_message_delete(payload: discord.RawMessageDeleteEvent):
    if payload.guild_id is None:      # DMは無視
        return
    guild = bot.get_guild(payload.guild_id)
    msg = payload.cached_message

    embed = make_embed("🗑️ メッセージ削除", discord.Color.red(), msg.author if msg else None)
    embed.add_field(name="チャンネル", value=f"<#{payload.channel_id}>", inline=True)

    if msg is None:
        embed.add_field(name="内容", value="キャッシュにないメッセージです(ボット起動前のメッセージ)", inline=False)
        embed.set_footer(text=f"メッセージID: {payload.message_id}")
    else:
        if msg.author.bot:
            return
        deleter = await find_deleter(guild, msg.author.id, payload.channel_id)
        who = deleter.mention if deleter else "本人(または確認できず)"
        embed.add_field(name="削除した人", value=who, inline=True)
        embed.add_field(name="内容", value=cut(msg.content), inline=False)
        if msg.attachments:
            files = "\n".join(a.filename for a in msg.attachments)
            embed.add_field(name="添付ファイル", value=files, inline=False)
        embed.set_footer(text=f"メッセージID: {msg.id}")

    await send_log(guild, embed)


@bot.event
async def on_raw_message_edit(payload: discord.RawMessageUpdateEvent):
    if payload.guild_id is None:
        return
    before = payload.cached_message
    after = payload.message
    if before is None or after.author.bot:
        return
    if before.content == after.content:      # 埋め込みプレビュー生成などの「偽の編集」を除外
        return

    guild = bot.get_guild(payload.guild_id)
    embed = make_embed("✏️ メッセージ編集", discord.Color.orange(), after.author)
    embed.add_field(name="チャンネル", value=after.channel.mention, inline=True)
    embed.add_field(name="リンク", value=f"[メッセージへ移動]({after.jump_url})", inline=True)
    embed.add_field(name="編集前", value=cut(before.content), inline=False)
    embed.add_field(name="編集後", value=cut(after.content), inline=False)
    await send_log(guild, embed)


@bot.event
async def on_raw_bulk_message_delete(payload: discord.RawBulkMessageDeleteEvent):
    if payload.guild_id is None:
        return
    guild = bot.get_guild(payload.guild_id)
    embed = make_embed("🧹 メッセージ一括削除", discord.Color.dark_red())
    embed.add_field(name="チャンネル", value=f"<#{payload.channel_id}>", inline=True)
    embed.add_field(name="件数", value=f"{len(payload.message_ids)}件", inline=True)
    await send_log(guild, embed)

ひとつずつ見ていきましょう 💡

  • payload.cached_message : 削除されたメッセージがボットのキャッシュにあれば Message オブジェクト、なければ None です。ボットを起動する前に投稿されたメッセージはキャッシュにないので「内容は確認不可」として残しますが、チャンネルとメッセージIDは残します。それでも調査の役に立ちます。
  • guild.audit_logs(limit=5, action=...) : サーバーの監査ログを新しい順にたどる非同期イテレーターです。for ではなく async for で回す必要があります。ボットに監査ログを表示の権限がないと Forbidden になるので、tryで囲みました。
  • entry.target / entry.extra.channel / entry.created_at : message_delete の項目では、targetがメッセージを書いた人、extra.channel が削除されたチャンネル、extra.count が削除した件数です。この3つが一致し、15秒以内にできた項目なら、その entry.user(実行者)が削除した人です。
  • 「本人(または確認できず)」 : 自分のメッセージを自分で削除すると、監査ログにはまったく記録が残りません。なので監査ログで見つからなければ本人による削除とみなします。ただし、同じ管理者が同じ人のメッセージを続けて削除すると、Discordは既存の項目のcountを増やすだけで新しい項目を作らないため、15秒の条件に引っかかって取りこぼすことがあります。100%正確ではない、という点だけ覚えておいてください。
  • payload.message(編集イベント) : 編集後のメッセージです(2.5から追加)。編集前は cached_message から取ります。リンクを貼ったとき、Discordがプレビューの埋め込みを作る際に編集イベントがもう一度届くのですが、content が同じならスキップするようにして、ログが散らからないようにしました。
  • on_raw_bulk_message_delete : 管理者が「メッセージを100件削除」のような一括削除(purge)をすると、個別の削除イベントの代わりにこれが1回だけ届きます。内容を全部残すとログが爆発するので、件数だけ記録しました。

📌 ステップ4 - 参加・退出・ニックネーム・ロール変更

Python
@bot.event
async def on_member_join(member: discord.Member):
    embed = make_embed("📥 メンバー参加", discord.Color.green(), member)
    embed.add_field(name="メンバー", value=member.mention, inline=True)
    embed.add_field(name="アカウント作成日", value=discord.utils.format_dt(member.created_at, "R"), inline=True)
    embed.add_field(name="現在の人数", value=f"{member.guild.member_count}人", inline=True)
    await send_log(member.guild, embed)


@bot.event
async def on_member_remove(member: discord.Member):
    embed = make_embed("📤 メンバー退出", discord.Color.light_grey(), member)
    embed.add_field(name="メンバー", value=f"{member} ({member.mention})", inline=True)
    if member.joined_at:
        embed.add_field(name="参加日", value=discord.utils.format_dt(member.joined_at, "R"), inline=True)
    roles = [r.mention for r in member.roles if r != member.guild.default_role]
    if roles:
        embed.add_field(name="持っていたロール", value=" ".join(roles), inline=False)
    await send_log(member.guild, embed)


@bot.event
async def on_member_update(before: discord.Member, after: discord.Member):
    if before.nick != after.nick:
        embed = make_embed("🏷️ ニックネーム変更", discord.Color.blue(), after)
        embed.add_field(name="変更前", value=before.nick or "(なし)", inline=True)
        embed.add_field(name="変更後", value=after.nick or "(なし)", inline=True)
        await send_log(after.guild, embed)

    added = [r for r in after.roles if r not in before.roles]
    removed = [r for r in before.roles if r not in after.roles]
    if added or removed:
        embed = make_embed("🎭 ロール変更", discord.Color.blurple(), after)
        if added:
            embed.add_field(name="➕ 追加", value=" ".join(r.mention for r in added), inline=False)
        if removed:
            embed.add_field(name="➖ 削除", value=" ".join(r.mention for r in removed), inline=False)
        await send_log(after.guild, embed)
  • format_dt(時刻, "R") : <t:1790291186:R> の形式に変換してくれるので、Discordが「3日前」、「2年前」のような相対時間で表示してくれます。参加ログでアカウント作成日が「12分前」だったら?ボットアカウントやサブ垢の可能性が高いですよね。運営者がいちばん喜ぶフィールドです 👀
  • on_member_remove : 自分で抜けた場合も、キック・BANされた場合も、すべてこのイベントで届きます。なので「なぜ抜けたのか」は、次のステップの監査ログイベントで別に記録します。
  • before.roles / after.roles の比較 : on_member_update は、ニックネーム・ロール・タイムアウト・サーバープロフィール画像など、何か変わるたびに呼ばれます。なので中で「何が変わったのか」を自分で比較する必要があります。ロールのリストの差集合で追加・削除を区別しました。

📌 ステップ5 - キック・BANは監査ログイベントで(誰が・なぜ)

Python
MOD_ACTIONS = {
    discord.AuditLogAction.kick: ("👢 キック(Kick)", discord.Color.dark_orange()),
    discord.AuditLogAction.ban: ("🔨 BAN(Ban)", discord.Color.dark_red()),
    discord.AuditLogAction.unban: ("🕊️ BAN解除(Unban)", discord.Color.teal()),
}


@bot.event
async def on_audit_log_entry_create(entry: discord.AuditLogEntry):
    if entry.action not in MOD_ACTIONS:
        return
    title, color = MOD_ACTIONS[entry.action]
    embed = make_embed(title, color, entry.user)
    target = entry.target
    embed.add_field(name="対象", value=f"{target} (<@{target.id}>)" if target else "(不明)", inline=True)
    embed.add_field(name="実行者", value=entry.user.mention if entry.user else "(不明)", inline=True)
    embed.add_field(name="理由", value=entry.reason or "(理由なし)", inline=False)
    await send_log(entry.guild, embed)
  • on_audit_log_entry_create : サーバーの監査ログに新しい項目ができた瞬間、リアルタイムで呼ばれます(discord.py 2.2+、Intents.moderation が必要、ボットに監査ログを表示の権限が必須)。ステップ3のようにあとから探しにいくのではなく、Discordのほうから先に知らせてくれるので、ずっと正確です。
  • entry.action : 何の操作かを表す AuditLogAction 列挙型です。今日はキック・BAN・解除の3つだけを選びましたが、channel_create、role_update、member_role_update など数十種類あります。辞書に1行追加するだけでログの種類が増やせます。
  • entry.user / entry.target / entry.reason : それぞれ実行した管理者、操作された人、そして管理者がキックするときに書いた理由です。理由を書かないと None になるので、「(理由なし)」に置き換えました。

💡 実行者がNoneになる? ユーザー情報がキャッシュにないとそうなることがあります。entry.user_id(2.2+)には常に値が入っているので、f"<@{entry.user_id}>" でメンションだけ出してもOKです。

📌 ステップ6 - チャンネル作成・削除 + ログチャンネル設定コマンド

Python
@bot.event
async def on_guild_channel_create(channel: discord.abc.GuildChannel):
    embed = make_embed("📁 チャンネル作成", discord.Color.green())
    embed.add_field(name="チャンネル", value=f"{channel.mention} (`{channel.name}`)", inline=True)
    embed.add_field(name="種類", value=str(channel.type), inline=True)
    await send_log(channel.guild, embed)


@bot.event
async def on_guild_channel_delete(channel: discord.abc.GuildChannel):
    embed = make_embed("🗂️ チャンネル削除", discord.Color.red())
    embed.add_field(name="チャンネル", value=f"`{channel.name}`", inline=True)
    embed.add_field(name="種類", value=str(channel.type), inline=True)
    await send_log(channel.guild, embed)


@bot.tree.command(name="로그설정", description="[管理者] サーバーログを送るチャンネルを設定します")
@app_commands.describe(채널="ログが溜まっていくテキストチャンネル")
@app_commands.checks.has_permissions(administrator=True)
async def set_log(interaction: discord.Interaction, 채널: discord.TextChannel):
    me = interaction.guild.me
    perms = 채널.permissions_for(me)
    if not (perms.send_messages and perms.embed_links):
        return await interaction.response.send_message(
            f"❌ {채널.mention}に**メッセージを送信・埋め込みリンク**の権限がありません。", ephemeral=True)
    async with lock:
        log_channels[str(interaction.guild.id)] = 채널.id
        save()
    await interaction.response.send_message(f"✅ これからサーバーログを{채널.mention}に記録します!", ephemeral=True)
    await 채널.send(embed=make_embed("🟢 ログチャンネルに設定されました", discord.Color.green(), interaction.user))


@bot.tree.command(name="로그해제", description="[管理者] サーバーログの記録を停止します")
@app_commands.checks.has_permissions(administrator=True)
async def unset_log(interaction: discord.Interaction):
    async with lock:
        log_channels.pop(str(interaction.guild.id), None)
        save()
    await interaction.response.send_message("⏹️ ログの記録をオフにしました。", ephemeral=True)


@bot.tree.error
async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError):
    if isinstance(error, app_commands.MissingPermissions):
        await interaction.response.send_message("❌ 管理者だけが使えるコマンドです。", ephemeral=True)
    else:
        raise error


@bot.event
async def on_ready():
    load()
    await bot.tree.sync()
    print(f"✅ ログイン: {bot.user} / ログ設定済みのサーバー {len(log_channels)}個")


bot.run(token)

/로그설정 (ログ設定) コマンド実行後の自分だけに見える応答と、ログチャンネルに届いた設定完了の埋め込み、ボットが事前に確認する項目のチェックリスト

  • 채널: discord.TextChannel : スラッシュコマンドの引数の型をチャンネルにすると、Discordがチャンネル選択UIを出してくれます。#15 コマンドグループ編で見たあの機能です。
  • 채널.permissions_for(me) : ログチャンネルにボットが実際に書き込めるかどうかを、設定の時点で前もってチェックします。あとでイベントが起きたときに黙って失敗するより、今知らせるほうがずっと親切ですよね。
  • bot.tree.error : 管理者ではない人がコマンドを打つと MissingPermissions が発生しますが、キャッチしないと「インタラクションに失敗しました」という不親切なメッセージしか出ません。#25でキャッチしたパターンそのままです。

📎 ステップ1〜6のコードブロックを順番につなげれば、そのまま完成版になります。logbot.py として保存して、すぐに実行してください。

📌 実行結果

  1. python logbot.py を実行 → コンソールに ✅ ログイン: ボット名 / ログ設定済みのサーバー 0個
  2. 管理者が #서버-로그(サーバーログ)のような非公開チャンネルを作り、/로그설정 채널:#서버-로그(ログ設定コマンド)を実行 → 緑色の「🟢 ログチャンネルに設定されました」埋め込みが届きます。
  3. どこかのチャンネルでメッセージを書いて削除してみてください → 🗑️ 赤い埋め込みに削除した内容がそのまま残ります。管理者が他人のメッセージを削除すると、「削除した人」に管理者のメンションが表示されます。
  4. メッセージを編集すると ✏️ オレンジの埋め込みに編集前・後が並んで表示され、リンクを押すとそのメッセージにジャンプします。
  5. サブ垢でサーバーに参加して抜けてみると 📥 参加(アカウント作成日「N分前」!) → 📤 退出の順に積み重なり、キックすると 📤 退出のあとに 👢 キックの埋め込みが実行者・理由付きで続きます。

💡 ログチャンネルは必ず管理者だけが見られるように権限をロックしておきましょう。削除されたメッセージの内容がそのまま残るチャンネルなので、一般メンバーが見ると「消したのになんで残ってるの」という苦情が来ます。月に1回くらい古いログを整理するのもお忘れなく。

💡 ボーナス - ログの種類を増やす

MOD_ACTIONS の辞書に項目を追加するだけで、監査ログの種類を増やせます。よく使うものはこちらです。

Python
discord.AuditLogAction.member_update: ("⏳ タイムアウト/ニックネーム変更", discord.Color.gold()),
discord.AuditLogAction.role_create: ("🎨 ロール作成", discord.Color.green()),
discord.AuditLogAction.role_delete: ("🎨 ロール削除", discord.Color.red()),
discord.AuditLogAction.message_bulk_delete: ("🧹 一括削除(管理者)", discord.Color.dark_red()),

entry.changes.before / entry.changes.after を出力してみると、「ロール名がAからBに」「タイムアウトがいつまで」といった細かい変更内容まで取り出せます。ボイスチャンネルの入退室ログは、#31で使った on_voice_state_update に埋め込みを1行加えるだけで完成です。

📌 よくあるエラー

  1. 削除ログの内容が「(内容なし)」としか出ない → 原因: Developer PortalでMessage Content Intentをオンにしていないか、コードに intents.message_content = True が抜けている → 解決: 両方オンにしてボットを再起動。ボットを起動する前に投稿されたメッセージはキャッシュにないので、もともと見えないのが正常です。
  2. on_member_join / on_member_remove が呼ばれない → 原因: Server Members Intent が未設定 → 解決: Portalの Botタブでオンにして、intents.members = True を確認。これをオンにしないと、エラーも出ずに静かに呼ばれないので混乱しがちです。
  3. discord.errors.Forbidden: 403 Forbidden (error code: 50013): Missing Permissions → 原因: ボットのロールに監査ログを表示の権限がない → 解決: サーバー設定 → ロール → ボットのロールで「監査ログを表示」にチェック。on_audit_log_entry_create もこの権限がないと呼ばれません。
  4. HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body — In embeds.0.fields.2.value: Must be 1024 or fewer in length → 原因: 埋め込みのフィールドに1024文字を超える内容を入れた → 解決: サンプルの cut() のように切る。長いメッセージが多いなら、内容を discord.File でテキストファイルとして添付する方法もおすすめです。
  5. TypeError: 'async_generator' object is not iterable → 原因: guild.audit_logs() を for で回した → 解決: async for に変える。非同期イテレーターです。
  6. リンクを貼るたびに編集ログが2回出る → 原因: Discordがリンクのプレビュー(埋め込み)を付けるときに、編集イベントをもう一度送ってくる → 解決: サンプルのように before.content == after.content ならスキップする。
  7. discord.errors.PrivilegedIntentsRequired でボットが起動すらしない → 原因: コードでは特権インテントをオンにしたのに、Portalではオンにしていない状態 → 解決: Portalの Botタブ → Privileged Gateway Intents で2つともON。

🙌 まとめ

今日はrawイベント + 監査ログ(Audit Log)の参照 + on_audit_log_entry_create の3つを組み合わせて、メッセージの削除・編集から入退室・ロール変更・キック・BANまでを1つのチャンネルに埋め込みで記録するログボットを完成させました。まとめるとこうなります。

  • 削除されたメッセージの内容はキャッシュにあるときだけ見られる → max_messages を多めに、特権インテント2つをON
  • 誰が削除したかは guild.audit_logs() からtarget・channel・時間で探す(本人による削除は記録なし)
  • キック・BANは on_audit_log_entry_create で実行者・理由までリアルタイムに受け取る
  • 埋め込みフィールドは1024文字制限、ログチャンネルは管理者専用にロックする

次回は、#31で予告していた一時ボイスチャンネルの拡張 — オーナーの譲渡・キック・招待制(ホワイトリスト)の部屋や、テキストチャンネルが一緒に作られる「パーティールーム」を扱います。お楽しみに 🙌

みなさんのサーバーでは、どんなログがいちばん必要ですか?コメントで教えてもらえたら、ボーナスの項目に追加してみます 😎


📚 あわせて読みたい記事

連載 · 全32回中31回目

Discordボット開発

  1. #1Discordボット開発 - スラッシュコマンド(Slash Command)完全攻略!
  2. #2スラッシュコマンドにオプション(Arguments)を追加しよう!
  3. #3スラッシュコマンドにサブコマンド(Subcommand)を追加しよう!
  4. #4スラッシュコマンドで埋め込み(Embed)を送信しよう!
  5. #5ボタン(Button)のインタラクションを実装しよう!
  6. #6ドロップダウン(Select Menu)メニューを実装しよう!
  7. #7モーダル(Modal)で入力を受け取る方法 (Pythonサンプル付き)
  8. #8埋め込み(Embed)を画像・フィールドで飾る (Pythonサンプル付き)
  9. #9AI(Claude・ChatGPT API)と連携する! (Pythonサンプル付き)
  10. #10多言語(i18n)に対応する! (Pythonサンプル付き)
  11. #11FlaskでWebダッシュボードを作る! (Pythonサンプル付き)
  12. #12コマンドのクールダウン(Cooldown)・権限(Permission)を管理しよう! (Pythonサンプル付き)
  13. #13ロール・チャンネルの自動化でサーバーを管理しよう! (Pythonサンプル付き)
  14. #14データを保存しよう!JSON vs SQLite (Pythonサンプル付き)
  15. #15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제) (韓国語)
  16. #16리마인더·알림 기능 만들기! 봇이 먼저 말 걸게 하기 (파이썬 예제) (韓国語)
  17. #17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제) (韓国語)
  18. #18투표(Poll) 기능 만들기! 다수결로 정하는 서버 규칙 (파이썬 예제) (韓国語)
  19. #19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제) (韓国語)
  20. #20자동 환영 메시지·온보딩 만들기! 규칙 동의 버튼으로 자동 역할 부여 (파이썬 예제) (韓国語)
  21. #21키워드 자동응답·금지어 필터 만들기! 경고 누적으로 자동 제재까지 (파이썬 예제) (韓国語)
  22. #22슬래시 명령어 자동완성(Autocomplete) 만들기! 금지어를 골라서 추가·삭제 (파이썬 예제) (韓国語)
  23. #23aiosqlite로 비동기 DB 제대로 설계하기! JSON 탈출 (파이썬 예제) (韓国語)
  24. #24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제) (韓国語)
  25. #25봇이 죽지 않게! 에러 핸들링·로깅 체계 잡기 (파이썬 예제) (韓国語)
  26. #26티켓(Ticket) 문의 시스템 만들기! 버튼 하나로 1:1 상담 채널 자동 생성 (파이썬 예제) (韓国語)
  27. #27티켓 담당자 배정·우선순위 만들기! 스태프가 여러 명이어도 안 꼬이게 (파이썬 예제) (韓国語)
  28. #28레벨·경험치(XP) 시스템 만들기! 채팅할수록 레벨업, 랭킹판까지 (파이썬 예제) (韓国語)
  29. #29화폐·상점(Economy) 시스템 만들기! 코인 벌어서 아이템 사기 (파이썬 예제) (韓国語)
  30. #30投票・アンケート(Poll)ボットを作ろう!ネイティブ投票+ボタンでリアルタイム集計まで(Pythonサンプル) 🗳️
  31. #32サーバーログ・監査ログ(Audit Log)チャンネルを作ろう!メッセージの削除・編集・入退室・キックまで自動記録(Pythonサンプル) 🕵️現在の記事
  32. #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉 (韓国語)
全32回を表示閉じる

著者

Mojangcam

自分でコードを書き、サーバーを動かし、詰まったエラーを解決しながら学んだことを記録する開発・IT実践ブログ