分类: Discord 机器人开发

写给 Discord 机器人开发者的 2026 年 9 月 API 变更汇总 - 文件 20MiB·斜杠命令多行输入·年龄验证(Age Assurance)·邀请目标用户·shard 必填,用 discord.py 2.7.1 应对(Python 示例)🗞️

本文译自韩语原文。

要点速览

把 2026 年 8 月底到 9 月 Discord 开发者更新日志里发布的 7 项变更,按机器人开发者的角度整理出来,并分成必须改代码的和不用改的。discord.py 2.7.1 里还没有的功能,用直接调用端点的示例来应对。

  • discord.py 2.7.1 · Python 3.13 · 基于 2026-09-30 官方更新日志确认(示例只做到语法·import 检查,在真实服务器上运行需要读者自行确认)
  • 必须改代码的只有两处:大型机器人分片应用的 shard 查询参数(9/15),以及开启 PRUNE_REQUIRES_ADMIN 的服务器上的清理权限(8/27)
  • 文件默认上限已提高到 20 MiB,但 discord.py 2.7.1 的 guild.filesize_limit 仍然返回 10 MiB → 请以 interaction.filesize_limit 为准
  • 字符串选项里可能出现换行,所以用 splitlines() 接收;邀请目标用户端点用 bot.http.request(Route(...)) 直接调用
目录15 项

大家好!我是 Mojangcam 😎

上一篇 第 24 篇 里,我们提前演练了 11 月 16 日就要生效的频道混淆。之后短短一个月,Discord 开发者更新日志又接连发布了 7 条分量不轻的公告。每一条都不长,可放在一起看,就会搞不清"我的机器人到底要改哪里?"

所以今天不做新功能,而是站在机器人开发者的角度,把 2026 年 8 月底到 9 月的变更一次性理清楚。每一项都先回答"会影响我的机器人吗?"和"需要改代码吗?",再附上基于 discord.py 2.7.1 的应对代码。库里还没有的功能,就用直接调用端点的方法补上 🛠️

本文你会学到

  • 用一张表看懂 9 月的 7 项变更(要改的 / 不用改的)
  • 文件上限已经涨到 20 MiB,库却还说是 10 MiB 的陷阱
  • 斜杠命令字符串选项开始出现换行之后该怎么处理
  • 用 Route 直接调用 discord.py 里没有的邀请目标用户(target user)端点
  • 大型机器人分片应用的 shard 查询参数、PRUNE_REQUIRES_ADMIN 权限检查
  • 因年龄验证(Age Assurance)和资料隐私设置看不到自定义状态时,如何安全地读取

📌 一览 - 9 月的 7 项变更

日期 变更 影响我的机器人吗? 需要改代码?
8/27 服务器功能 PRUNE_REQUIRES_ADMIN (Breaking) 仅限使用成员清理(prune)功能的机器人 添加权限检查
9/3 文件上传默认上限 10 MiB → 20 MiB 所有发送文件的机器人(往好的方向) 有计算上限的代码时
9/11 斜杠命令:字符串选项多行输入、命令模糊搜索、切换频道时记住命令 所有接收字符串选项的机器人 只需处理换行
9/15 大型机器人分片应用调用 GET /users/@me/guilds 时必须带 shard (Breaking) 仅限大型机器人分片应用 必须(符合条件时)
9/17 资料设为隐私时,自定义状态会从 Presence 中移除 读取状态的机器人 确认 None 处理
9/18 新增 4 个邀请目标用户端点 创建邀请链接的机器人(可选) 想用的话
9/22 年龄验证(Age Assurance)全球推行 无(只需观察行为变化) 无

先说结论:必须改的只有两处。使用大型机器人分片的应用要加 shard 查询参数,有成员清理功能的机器人要加权限检查。其余的要么知道就行,要么反而让机器人更好用 😌

📌 准备工作

  • Python 3.8 以上(示例在 Python 3.13 上做了语法检查)
  • discord.py 2.7.1 — 截至 2026-09-30 的最新版本。3 月之后没有发布新版本,所以今天讲的 9 月变更库里还没有跟进
  • python-dotenv — 用来把令牌分离到 .env 文件
  • 已经邀请了机器人的测试服务器(有管理员权限会更方便)
终端
pip install discord.py==2.7.1 python-dotenv

在项目文件夹里创建 .env 文件,填入令牌。

终端
DISCORD_TOKEN=여기에_봇_토큰

💡 .env 一定要加进 .gitignore。令牌一旦被传到 GitHub,Discord 会自动检测并作废这个令牌 😱

今天的示例因为要读取自定义状态,需要在开发者门户 → Bot 标签页里开启 Server Members Intent 和 Presence Intent。如果不需要读取状态,可以删掉 intents.presences = True 这一行,门户设置也可以跳过。

📌 1. 文件上传默认上限 20 MiB(9/3)

变更内容:默认文件上传上限从 10 MiB 提高到了 20 MiB。不只是用户,机器人、Webhook、交互响应也同样适用。

影响我的机器人吗?:如果你的机器人会发文件,这纯粹是好消息。在没有助力(Boost)的服务器上,15 MB 的日志文件或图片现在也能传上去了。

不过这里有个陷阱。打开 discord.py 2.7.1 的源码看看,默认上限还是旧的值。

Python
# discord/utils.py (2.7.1)
DEFAULT_FILE_SIZE_LIMIT_BYTES = 10485760   # 10 MiB

所以在没有助力的服务器上,guild.filesize_limit 和 ctx.filesize_limit 仍然返回 10 MiB。如果你用这个值写了"超过上限就不发送"之类的防御代码,那么机器人其实是在自己拒绝本来能发的文件 😅

正确做法是使用 Discord 在每次交互里一起发来的值。

Python
@bot.tree.command(name="파일한도", description="显示机器人在这个服务器能上传的文件大小")
async def file_limit(interaction: discord.Interaction):
    live = interaction.filesize_limit          # Discord 随这次交互一起发来的值
    cached = interaction.guild.filesize_limit if interaction.guild else None  # 库内置的表
    msg = f"Discord 告知的上限:**{live / MIB:.0f} MiB**"
    if cached is not None:
        msg += f"\ndiscord.py 表的值(guild.filesize_limit):{cached / MIB:.0f} MiB"
    await interaction.response.send_message(msg, ephemeral=True)

我们逐个拆开来看 💡

  • interaction.filesize_limit:discord.py 2.6 新增的属性。它原样保存交互载荷里的 attachment_size_limit 值,是 Discord 服务器此刻告诉你的上限,而不是库里的表。20 MiB 的变更会直接反映在这里。
  • guild.filesize_limit:查看库里硬编码的按助力等级划分的表。在 2.7.1 中,0·1 级是 10 MiB,2 级 50 MiB,3 级 100 MiB。在下个版本修复之前,只当参考就好。
  • MIB = 1024 * 1024:Discord 文档用的不是 MB,而是 MiB(1,048,576 字节)。按 20 MB(20,000,000 字节)算的话,会少大约 1 MB。

📌 2. 斜杠命令改进 - 多行输入·模糊搜索(9/11)

变更内容有三点。

  1. 字符串选项支持多行输入。用户可以用 Shift+Enter 换行后再发送。
  2. 命令选择窗口支持模糊搜索。只输入 /r a 也能出现 /role add。
  3. 切换频道后也会记住正在选的命令。

影响我的机器人吗?:第 1 点是关键。以前可以放心地认为字符串选项只有一行,现在会收到夹带 \n 的值。如果你的代码把选项值直接放进嵌入消息的标题、当作文件名或数据库键,或者记成单行日志,格式就可能被打乱。

处理起来很简单。只要养成按行拆分接收的习惯就行。

Python
@bot.tree.command(name="여러줄", description="把多行输入的文字按行拆开显示")
@app_commands.describe(text="试试用 Shift+Enter 换行输入")
async def multiline(interaction: discord.Interaction, text: app_commands.Range[str, 1, 1000]):
    lines = [line.strip() for line in text.splitlines() if line.strip()]
    if not lines:
        await interaction.response.send_message("内容是空的!", ephemeral=True)
        return
    body = "\n".join(f"{i}. {line}" for i, line in enumerate(lines, start=1))
    await interaction.response.send_message(f"一共收到 **{len(lines)} 行**。\n{body}")

我们逐个拆开来看 💡

  • text.splitlines():不管来的是 \n 还是 \r\n,都会按行切开。比 split("\n") 更安全。
  • if line.strip():丢掉空行。支持多行输入后,肯定会有用户连按两次 Enter。
  • app_commands.Range[str, 1, 1000]:字符串长度上限。变成多行之后更容易收到长文本,最好明确设定上限。Discord 一条消息最多 2,000 字,设成 1,000 字的话,加上编号再发回去也很安全。
  • 必须是一行的地方:要用作标题、文件名、键的值,请用 text.replace("\n", " ") 压成一行再用。

第 2 点模糊搜索是客户端选择命令名称时的功能。第 22 篇里做的选项自动补全回调并不会自动变成模糊搜索。不过用户今后会习惯"随便打几个字也能找到"的体验,所以建议不要把自动补全的过滤条件设得像 startswith 那么严格,而是像第 22 篇那样用 in(部分匹配)放宽一些 🔍

📌 3. 大型机器人分片应用必须带 shard 查询参数(9/15,Breaking)

变更内容:使用大型机器人分片(large bot sharding)的应用在调用 GET /users/@me/guilds(我的机器人所在的服务器列表)时,必须带上 shard 查询参数。不带就会收到 400 Bad Request。取值范围是 0 到 max_concurrency - 1,max_concurrency 在 Get Gateway Bot 响应的 session_start_limit 里。

影响我的机器人吗?:大型机器人分片是 Discord 专门为服务器数量非常多的机器人单独批准的方式。这个系列里做的这种规模的机器人不受影响。 官方文档也明确写了"不使用大型机器人分片的应用不受影响"。放心吧 😌

不过还是为符合条件的朋友写一下。discord.py 2.7.1 的 client.fetch_guilds() 没有 shard 参数(内部只发送 limit·before·after·with_counts),所以要直接调用端点。

Python
async def fetch_all_guilds_by_shard(client: discord.Client) -> list:
    """给使用大型机器人分片的应用用。普通机器人直接用 client.fetch_guilds() 就行。"""
    _, _, start_limit = await client.http.get_bot_gateway()
    max_concurrency = start_limit["max_concurrency"]
    guilds = []
    for shard in range(max_concurrency):
        route = Route("GET", "/users/@me/guilds")
        data = await client.http.request(route, params={"shard": shard, "limit": 200})
        guilds.extend(data)
    return guilds

我们逐个拆开来看 💡

  • client.http.get_bot_gateway():调用 GET /gateway/bot,返回 (shards, url, session_start_limit) 三个值。第三个字典里的 max_concurrency 就是需要的值。
  • Route("GET", "/users/@me/guilds"):discord.http.Route 是库在所有请求中都会用到的类。用 from discord.http import Route 导入。
  • client.http.request(route, params=...):查询字符串通过 params 传入。速率限制由库来处理。返回值是解析后的 JSON(服务器字典列表)。
  • 文档的说明:如果需要完整列表,要遍历所有 shard 值再合并。所以这里用 range(max_concurrency) 循环。

⚠️ bot.http 不是公开 API,而是库的内部对象。下个版本签名可能会变,等 discord.py 提供正式方法后,请换成正式方法。

📌 4. 自定义状态隐藏(9/17)+ 年龄验证 Age Assurance(9/22)

把两个放在一起,是因为结果一样。都是"机器人原本能看到的用户信息,从某天起看不到了"。

9/17 变更内容:如果用户的资料公开范围设为仅好友,或者设为好友和小型服务器而服务器成员超过 200 人,那么发往该服务器的 Presence Update 的 activities 里会去掉自定义状态。Playing·Streaming·Listening·Watching·Competing 等其他活动保持不变。

9/22 变更内容:年龄验证向全球推行。借用官方文档的原话,"应用会照常运行,但是"有四点可能看起来不一样。

  1. 资料设为隐私的用户,在 Presence 里可能看不到自定义状态(和上面 9/17 是同一件事)
  2. 带年龄限制的 Activity 可能会要求用户进行年龄组验证
  3. 由于父母设置的消费限额,部分购买可能会失败
  4. 通过 Social SDK 发送的私信会更常被归入消息请求

影响我的机器人吗?:不用改代码。但如果你的代码会从 member.activities 里取出自定义状态来用,就要把取不到的情况当作正常情况处理。以前可以假设"设置了状态的人就一定有",现在不行了。

Python
@bot.tree.command(name="상태보기", description="读取成员的自定义状态(可能看不到)")
async def show_status(interaction: discord.Interaction, member: discord.Member):
    custom = next((a for a in member.activities if isinstance(a, discord.CustomActivity)), None)
    if custom is None:
        text = "没有设置自定义状态,或者因为资料隐私设置,机器人看不到。"
    else:
        text = f"{custom.emoji or ''} {custom.name or ''}".strip() or "(只有表情的状态)"
    await interaction.response.send_message(f"{member.display_name}: {text}", ephemeral=True)

我们逐个拆开来看 💡

  • member.activities:是一个元组。自定义状态、游戏、Spotify 混在一起,所以用 isinstance(a, discord.CustomActivity) 挑出来。member.activity(单数)只给第一个,找自定义状态时不要用它。
  • next(..., None):没有就是 None。现在这个 None 同时代表"没设置"和"被隐藏"两种情况,而机器人没有办法区分。所以提示文字把两种情况都写进去了。
  • custom.name / custom.emoji:两者都是 Optional。也有只有表情的状态,所以用 or '' 包了一下。
  • Presence Intent:要让 member.activities 有内容,intents.presences = True 和开发者门户里的 Presence Intent 都必须开启。不开的话永远是空元组,就没法和"被隐藏"区分了。

📌 5. 邀请目标用户(target user)端点(9/18)

变更内容:新增了 4 个端点,可以给邀请链接加上"只有这些人能用"的名单。

操作 方法·路径 请求体
添加目标 PUT /invites/{invite.code}/target-users/{user.id} 无
移除目标 DELETE /invites/{invite.code}/target-users/{user.id} 无
批量添加 POST /invites/{invite.code}/target-users/bulk-add user_ids 数组(最多 1,000 人)
批量移除 POST /invites/{invite.code}/target-users/bulk-delete user_ids 数组(最多 1,000 人)

四个端点都要求是邀请的创建者,或者拥有 MANAGE_GUILD(管理服务器)权限,成功时返回 204(无响应体)。更新日志里还写到,创建邀请时就可以一并传入 target_user_ids 数组。

影响我的机器人吗?:不用也完全没问题。不过像第 33 篇的派对房间那样,要做"只有被邀请的人才能进来"的功能时,以前是靠频道权限覆盖来拦,现在可以把邀请链接本身做成特定用户专属了。

discord.py 2.7.1 的 create_invite() 没有这个选项。(虽然有个叫 target_user 的参数,但那指的是直播观看邀请里的主播,是另一个功能。特别容易搞混 😅)所以邀请照常创建,只有指定目标这一步直接调用。

Python
@bot.tree.command(name="초대대상", description="创建只有指定的人才能使用的邀请链接")
@app_commands.describe(member="允许使用邀请链接的成员")
async def invite_target(interaction: discord.Interaction, member: discord.Member):
    channel = interaction.channel
    if not isinstance(channel, discord.TextChannel):
        await interaction.response.send_message("请在文字频道里使用!", ephemeral=True)
        return

    await interaction.response.defer(ephemeral=True)
    invite = await channel.create_invite(max_age=3600, max_uses=1, unique=True,
                                         reason=f"{member} 专属邀请")

    # discord.py 2.7.1 还没有对应的方法,所以直接调用端点
    route = Route("PUT", "/invites/{code}/target-users/{user_id}",
                  code=invite.code, user_id=member.id)
    await bot.http.request(route)   # 成功时返回 204(无响应体)

    await interaction.followup.send(
        f"已创建 {member.mention} 专属邀请链接(1 小时,1 次):{invite.url}", ephemeral=True
    )

我们逐个拆开来看 💡

  • channel.create_invite(max_age=3600, max_uses=1, unique=True):1 小时有效、只能用 1 次的邀请。因为 unique=True,不会复用已有邀请,而是新建一个。这个邀请的创建者是我们的机器人,所以机器人就是"邀请的创建者",拥有指定目标的权限。
  • Route("PUT", "/invites/{code}/target-users/{user_id}", code=..., user_id=...):路径里的 {code}·{user_id} 由关键字参数填入。上面的代码生成的地址是 https://discord.com/api/v10/invites/<코드>/target-users/<유저ID>。(我亲自打印出来确认过。)
  • await bot.http.request(route):这是没有请求体的 PUT。要添加多个人的话,给 Route("POST", "/invites/{code}/target-users/bulk-add", code=...) 传入 json={"user_ids": [id1, id2]} 即可。
  • 先 defer:创建邀请 + 指定目标一共要发两次 HTTP 请求,可能超过 3 秒。先 defer(),再用 followup.send() 回复。

📌 6. 成员清理(prune)需要管理员权限(8/27,Breaking)

虽然是 9 月的文章,但这是 8 月最后一周的变更,所以一起讲。这是很容易漏掉的 Breaking Change。

变更内容:服务器所有者现在可以开启一个叫 PRUNE_REQUIRES_ADMIN 的服务器功能。开启后,查询预计清理人数(Get Guild Prune Count)和执行清理(Begin Guild Prune)都需要 ADMINISTRATOR 权限。没开启的服务器还是和现在一样,需要 MANAGE_GUILD + KICK_MEMBERS。

影响我的机器人吗?:只影响使用 guild.prune_members() 或 guild.estimate_pruned_members() 的机器人。如果某天突然开始报 403 Forbidden,那就是服务器所有者开启了这个功能。

Python
@bot.tree.command(name="정리예상", description="计算 30 天内没有活动的成员有多少(不会真的踢出)")
@app_commands.describe(days="多少天没有活动就算作清理对象 (1~30)")
async def prune_estimate(interaction: discord.Interaction, days: app_commands.Range[int, 1, 30] = 30):
    guild = interaction.guild
    if guild is None:
        await interaction.response.send_message("请在服务器里使用!", ephemeral=True)
        return

    perms = guild.me.guild_permissions
    if "PRUNE_REQUIRES_ADMIN" in guild.features:
        if not perms.administrator:
            await interaction.response.send_message(
                "这个服务器开启了 **PRUNE_REQUIRES_ADMIN**,机器人需要管理员权限。",
                ephemeral=True,
            )
            return
    elif not (perms.kick_members and perms.manage_guild):
        await interaction.response.send_message(
            "机器人需要 **踢出成员 + 管理服务器** 权限才能计算。", ephemeral=True
        )
        return

    await interaction.response.defer(ephemeral=True)
    count = await guild.estimate_pruned_members(days=days)
    await interaction.followup.send(f"按 {days} 天计算,清理对象:**{count} 人**(只做了计算)", ephemeral=True)

我们逐个拆开来看 💡

  • guild.features:服务器已开启功能名称的字符串列表。里面有 "COMMUNITY"、"NEWS" 这样的值,这次新增了 "PRUNE_REQUIRES_ADMIN"。即使库不认识新值,它也会以字符串原样传过来,所以在 2.7.1 里也能直接检查。
  • guild.me.guild_permissions:我们的机器人在服务器里的最终权限。administrator·kick_members·manage_guild 都在这里看。
  • estimate_pruned_members(days=...):不会真的踢人,只返回数字。示例里特意只用了这个。真正踢人的 prune_members() 无法撤销,建议再加一个确认按钮。
  • 为什么要提前检查:直接调用的话,也只是报 403 失败而已。但是告诉用户准确的原因,比如"请给机器人管理员权限",和只显示"出错了",体验完全不同。

💡 8 月可能错过的变更

  • 8/12 频道混淆 — 从 11/16 起,机器人看不到的频道会被隐藏。第 24 篇里有详细介绍。
  • 8/5 附件选项的 file_types — ATTACHMENT 选项和文件上传组件新增了"只接收这些扩展名"的过滤器。discord.py 2.7.1 里还没有(源码里找不到 file_types),只能等库更新。
  • 8/5 频道的 application_id 可能为 null — 如果你的代码假设频道对象的 application_id 一定是 ID,请加上 None 处理。

📌 完整代码(可直接复制)

只用 sept_check.py 一个文件,上面的 5 个命令就都能运行。直接复制使用吧!

Python
# discord.py 2.7.1 / Python 3.8+
# 2026 年 9 月 Discord API 变更检查机器人 (sept_check.py)
import os

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

load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")
if not TOKEN:
    raise SystemExit("❌ .env 文件里没有 DISCORD_TOKEN!")

intents = discord.Intents.default()
intents.members = True      # 成员信息(需要在开发者门户里开启)
intents.presences = True    # 用于查看自定义状态(需要在开发者门户里开启)
bot = commands.Bot(command_prefix="!", intents=intents)

MIB = 1024 * 1024


@bot.event
async def on_ready():
    await bot.tree.sync()
    print(f"✅ 已登录:{bot.user} / discord.py {discord.__version__}")


# ① 9/11 - 字符串选项多行输入
@bot.tree.command(name="여러줄", description="把多行输入的文字按行拆开显示")
@app_commands.describe(text="试试用 Shift+Enter 换行输入")
async def multiline(interaction: discord.Interaction, text: app_commands.Range[str, 1, 1000]):
    lines = [line.strip() for line in text.splitlines() if line.strip()]
    if not lines:
        await interaction.response.send_message("内容是空的!", ephemeral=True)
        return
    body = "\n".join(f"{i}. {line}" for i, line in enumerate(lines, start=1))
    await interaction.response.send_message(f"一共收到 **{len(lines)} 行**。\n{body}")


# ② 9/3 - 文件上传默认上限 20 MiB
@bot.tree.command(name="파일한도", description="显示机器人在这个服务器能上传的文件大小")
async def file_limit(interaction: discord.Interaction):
    live = interaction.filesize_limit          # Discord 随这次交互一起发来的值
    cached = interaction.guild.filesize_limit if interaction.guild else None  # 库内置的表
    msg = f"Discord 告知的上限:**{live / MIB:.0f} MiB**"
    if cached is not None:
        msg += f"\ndiscord.py 表的值(guild.filesize_limit):{cached / MIB:.0f} MiB"
    await interaction.response.send_message(msg, ephemeral=True)


# ③ 8/27 - PRUNE_REQUIRES_ADMIN 服务器功能
@bot.tree.command(name="정리예상", description="计算 30 天内没有活动的成员有多少(不会真的踢出)")
@app_commands.describe(days="多少天没有活动就算作清理对象 (1~30)")
async def prune_estimate(interaction: discord.Interaction, days: app_commands.Range[int, 1, 30] = 30):
    guild = interaction.guild
    if guild is None:
        await interaction.response.send_message("请在服务器里使用!", ephemeral=True)
        return

    perms = guild.me.guild_permissions
    if "PRUNE_REQUIRES_ADMIN" in guild.features:
        if not perms.administrator:
            await interaction.response.send_message(
                "这个服务器开启了 **PRUNE_REQUIRES_ADMIN**,机器人需要管理员权限。",
                ephemeral=True,
            )
            return
    elif not (perms.kick_members and perms.manage_guild):
        await interaction.response.send_message(
            "机器人需要 **踢出成员 + 管理服务器** 权限才能计算。", ephemeral=True
        )
        return

    await interaction.response.defer(ephemeral=True)
    count = await guild.estimate_pruned_members(days=days)
    await interaction.followup.send(f"按 {days} 天计算,清理对象:**{count} 人**(只做了计算)", ephemeral=True)


# ④ 9/18 - 邀请目标用户(target user)端点
@bot.tree.command(name="초대대상", description="创建只有指定的人才能使用的邀请链接")
@app_commands.describe(member="允许使用邀请链接的成员")
async def invite_target(interaction: discord.Interaction, member: discord.Member):
    channel = interaction.channel
    if not isinstance(channel, discord.TextChannel):
        await interaction.response.send_message("请在文字频道里使用!", ephemeral=True)
        return

    await interaction.response.defer(ephemeral=True)
    invite = await channel.create_invite(max_age=3600, max_uses=1, unique=True,
                                         reason=f"{member} 专属邀请")

    # discord.py 2.7.1 还没有对应的方法,所以直接调用端点
    route = Route("PUT", "/invites/{code}/target-users/{user_id}",
                  code=invite.code, user_id=member.id)
    await bot.http.request(route)   # 成功时返回 204(无响应体)

    await interaction.followup.send(
        f"已创建 {member.mention} 专属邀请链接(1 小时,1 次):{invite.url}", ephemeral=True
    )


# ⑤ 9/17·9/22 - 自定义状态可能看不到
@bot.tree.command(name="상태보기", description="读取成员的自定义状态(可能看不到)")
async def show_status(interaction: discord.Interaction, member: discord.Member):
    custom = next((a for a in member.activities if isinstance(a, discord.CustomActivity)), None)
    if custom is None:
        text = "没有设置自定义状态,或者因为资料隐私设置,机器人看不到。"
    else:
        text = f"{custom.emoji or ''} {custom.name or ''}".strip() or "(只有表情的状态)"
    await interaction.response.send_message(f"{member.display_name}: {text}", ephemeral=True)


# ⑥ 9/15 - 大型机器人分片应用必须带 shard 查询参数
async def fetch_all_guilds_by_shard(client: discord.Client) -> list:
    """给使用大型机器人分片的应用用。普通机器人直接用 client.fetch_guilds() 就行。"""
    _, _, start_limit = await client.http.get_bot_gateway()
    max_concurrency = start_limit["max_concurrency"]
    guilds = []
    for shard in range(max_concurrency):
        route = Route("GET", "/users/@me/guilds")
        data = await client.http.request(route, params={"shard": shard, "limit": 200})
        guilds.extend(data)
    return guilds


bot.run(TOKEN)

💡 如果你在用第 19 篇的 cogs/ 结构,只把命令部分拆出来移到 cogs/sept_check.py,也一样能运行。

📌 运行结果

终端
python sept_check.py

机器人启动后,在服务器里逐个输入命令试试。所有回复都是仅自己可见(ephemeral)的,不会把测试服务器弄乱。

示例画面:/파일한도 的运行结果 - 在没有助力的服务器上,"Discord 告知的上限:20 MiB / discord.py 表的值:10 MiB" 两行显示不同的画面

/파일한도 在没有助力的服务器上,像上面那样两个数字不同才是正常的。上面一行(20 MiB)是真正的上限,下面一行(10 MiB)说明库里的表已经过时了。

示例画面:在 /여러줄 命令里用 Shift+Enter 输入三行的画面,以及在"一共收到 3 行"后面加上 1. 2. 3. 编号返回的回复

/여러줄 请在输入框里按 Shift+Enter 换行试试。机器人会数出行数,加上编号发回来。

/초대대상 要用不在目标名单里的账号打开链接,才能真正确认。不是目标的话,应该会被禁止加入。

示例画面:/정리예상 的结果嵌入消息 - 回复"按 30 天计算,清理对象:N 人(只做了计算)"

⚠️ 老实说,本文的代码只在 Python 3.13 上做了语法·import 检查和 Route 地址拼接的确认,并没有在真实服务器上运行过命令。尤其是 /초대대상,用的是 9/18 新出的端点,请务必确认实际响应。如果不行,请不要留评论,而是通过联系页面告诉我,我会修改文章。

📌 常见错误

1. discord.errors.Forbidden: 403 Forbidden (error code: 50013): Missing Permissions — /정리예상

  • 原因:服务器开启了 PRUNE_REQUIRES_ADMIN,但机器人没有管理员权限;或者在没开启的服务器上,缺少踢出成员·管理服务器权限。
  • 解决:像上面的代码那样,先查看 guild.features 再提示所需权限。给机器人的身份组授予所需权限后再试一次。

2. discord.errors.Forbidden: 403 — /초대대상 的 PUT 请求

  • 原因:指定目标用户需要是邀请的创建者,或者拥有管理服务器权限。如果填的是别人创建的邀请码,或者机器人没有创建邀请(Create Invite)权限,就会失败。
  • 解决:像示例那样只给机器人自己创建的邀请添加目标,或者给机器人的身份组授予管理服务器权限。

3. discord.errors.HTTPException: 400 Bad Request — GET /users/@me/guilds

  • 原因:大型机器人分片应用在没有 shard 查询参数的情况下请求了服务器列表。从 9/15 起会返回 400。
  • 解决:像 fetch_all_guilds_by_shard() 那样加上 params={"shard": n},从 0 遍历到 max_concurrency - 1。普通机器人不会遇到这个错误。

4. /상태보기 总是只显示"没有设置自定义状态,或者..."

  • 原因:没有写 intents.presences = True,或者没有在开发者门户里开启 Presence Intent。这样 member.activities 永远是空元组。
  • 解决:代码和门户都开启后重启机器人。两边都开了还是看不到,那就是该用户设置了资料隐私(9/17),机器人看不到。这是正常的。

5. 发送文件时出现 413 Payload Too Large / Request entity too large

  • 原因:20 MiB 是没有助力的服务器的默认值。更大的文件依然不行,而且把 MB(1,000,000)和 MiB(1,048,576)混着算的话,在临界值附近会被拒。
  • 解决:发送前用 os.path.getsize() 和 interaction.filesize_limit 比较。如果用 guild.filesize_limit(2.7.1 中为 10 MiB)来比较,会把能发的文件也拒掉,所以别用它。

6. /초대대상 出现 discord.errors.NotFound: 404 (error code: 10062): Unknown interaction

  • 原因:没能在 3 秒内响应。要发两次 HTTP 请求,可能会比较慢。
  • 解决:在耗时操作之前先调用 await interaction.response.defer(),之后用 interaction.followup.send() 发送。

💬 今天一定要记住的 3 件事

  1. 要改的只有两处。 大型机器人分片应用的 shard 查询参数,成员清理机器人的 PRUNE_REQUIRES_ADMIN 权限检查。其余的知道就行。
  2. 文件上限看 interaction.filesize_limit。 库里的表(guild.filesize_limit)还停留在过时的 10 MiB。
  3. 字符串选项里会出现换行。 用 splitlines() 接收,必须是一行的地方压成一行再用。自定义状态也变成了"看不到"才是正常情况。

📌 结语

今天没有做新功能,而是一次性过了一遍 9 月整整一个月的 Discord API 变更 🗞️

总结一下:文件上限放宽到了 20 MiB(别相信库里的值),斜杠命令的字符串开始可以多行输入,还新增了把邀请链接做成特定用户专属的端点。因为年龄验证和资料隐私,看不到自定义状态的情况变多了;大型机器人分片应用要处理 shard 查询参数,成员清理机器人要注意 PRUNE_REQUIRES_ADMIN。discord.py 2.7.1 从 3 月之后就没有新版本,所以新端点都用 Route 直接调用的方法补上了 ✅

还有别忘了,11 月 16 日的频道混淆才是最大的变更。如果还没跑过第 24 篇里的 /채널점검,这个周末一定要跑一遍 💣

Discord 机器人开发系列继 #33 派对房间之后,下一篇预计会讲用 Docker 部署机器人(计划中)。敬请期待!🙌

📚 推荐一起阅读

作者

Mojangcam

亲手写代码、跑服务器、解决卡住的报错,把一路学到的东西记录下来的开发与 IT 实战博客