如何从特定频道狙击消息(discord.py)

Posted

技术标签:

【中文标题】如何从特定频道狙击消息(discord.py)【英文标题】:How to snipe messages from a specific channel (discord.py) 【发布时间】:2021-07-30 20:45:36 【问题描述】:

结果

狙击在 X 频道而不是 Discord 公会内的所有频道中发送的消息。也就是说,它应该只跟踪该通道中的消息删除(由其 ID 标识),并且只响应同一通道中的!snipe 命令。我这里的当前代码会狙击 Discord 公会中发送的每条消息。

问题

如何狙击X频道而不是整个公会发送的消息?

我主要打算在一个公会中运行这个机器人。不过,如果需要的话,如果它可以扩展到多个公会,那就太好了。

我目前的代码如下。

import discord
from discord.ext import commands
from tokens import token

client = commands.Bot(command_prefix="!", self_bot=False)
client.sniped_messages = 


@client.event
async def on_ready():
    print("Your bot is ready.")


@client.event
async def on_message_delete(message):
    print(f'sniped message message')
    client.sniped_messages[message.guild.id] = (
        message.content, message.author, message.channel.name, message.created_at)


@client.command()
async def snipe(ctx):
    try:
        contents, author, channel_name, time = client.sniped_messages[ctx.guild.id]

    except:
        await ctx.channel.send("Couldn't find a message to snipe!")
        return

    embed = discord.Embed(description=contents,
                          color=discord.Color.purple(), timestamp=time)
    embed.set_author(
        name=f"author.name#author.discriminator", icon_url=author.avatar_url)
    embed.set_footer(text=f"Deleted in : #channel_name")

    await ctx.channel.send(embed=embed)

client.run(token, bot=True)

【问题讨论】:

【参考方案1】:

我将建议两种略有不同的解决方案,因为如果您只在一个公会上运行此机器人,代码会更简单。两者的共同点是您需要检查哪些频道消息被删除,!snipe 命令在哪个频道发送。

单公会版

如果你只监视/狙击一个公会的一个频道,那么你只能有 一个 删除的消息来跟踪。因此,您不需要像您发布的代码中那样的字典;你可以只保留一条消息或None

您已经从一个单独的文件中导入了您的令牌,因此为了方便起见,您不妨也将频道 ID(它是一个 int,与机器人令牌不同)放在那里。请注意,按照惯例,常量(您不打算更改的变量)通常在 Python 中全部大写。 tokens.py 看起来像这样:

TOKEN = 'string of characters here'
CHANNEL_ID = 123456789 # actually a 17- or 18-digit integer

还有机器人本身:

import discord
from discord.ext import commands
from tokens import TOKEN, CHANNEL_ID

client = commands.Bot(command_prefix='!')
client.sniped_message = None

@client.event
async def on_ready():
    print("Your bot is ready.")

@client.event
async def on_message_delete(message):
    # Make sure it's in the watched channel, and not one of the bot's own
    # messages.
    if message.channel.id == CHANNEL_ID and message.author != client.user:
        print(f'sniped message: message')
        client.sniped_message = message

@client.command()
async def snipe(ctx):
    # Only respond to the command in the watched channel.
    if ctx.channel.id != CHANNEL_ID:
        return

    if client.sniped_message is None:
        await ctx.channel.send("Couldn't find a message to snipe!")
        return

    message = client.sniped_message

    embed = discord.Embed(
        description=message.content,
        color=discord.Color.purple(),
        timestamp=message.created_at
    )
    embed.set_author(
        name=f"message.author.name#message.author.discriminator",
        icon_url=message.author.avatar_url
    )
    embed.set_footer(text=f"Deleted in: #message.channel.name")

    await ctx.channel.send(embed=embed)

client.run(TOKEN)

多公会版本

如果您在多个公会中分别监控一个频道,那么您需要分别跟踪它们。方便地,频道 ID 是 globally unique,而不仅仅是在一个公会中。因此,您可以仅通过 ID 跟踪他们,而不必同时包含公会 ID。

您可以将它们保存在一个列表中,但我建议使用set,因为检查某物是否在集合中更快。帮助自己记住哪个是哪个可能也是一个好主意的评论。

TOKEN = 'string of characters here'
# Not a dictionary, even though it uses 
CHANNEL_IDS = 
    # That one guild
    123456789,
    # The other guild
    987654322,

然后,不是检查单个频道 ID,而是检查它是否在 多个 ID 的集合中。

import discord
from discord.ext import commands
from tokens import TOKEN, CHANNEL_IDS

client = commands.Bot(command_prefix='!')
client.sniped_messages = 

@client.event
async def on_ready():
    print("Your bot is ready.")

@client.event
async def on_message_delete(message):
    # Make sure it's in a watched channel, and not one of the bot's own
    # messages.
    if message.channel.id in CHANNEL_IDS and message.author != client.user:
        print(f'sniped message: message')
        client.sniped_messages[message.channel.id] = message

@client.command()
async def snipe(ctx):
    # Only respond to the command in a watched channel.
    if ctx.channel.id not in CHANNEL_IDS:
        return

    try:
        message = client.sniped_messages[ctx.channel.id]
    # See note below
    except KeyError:
        await ctx.channel.send("Couldn't find a message to snipe!")
        return

    embed = discord.Embed(
        description=message.content,
        color=discord.Color.purple(),
        timestamp=message.created_at
    )
    embed.set_author(
        name=f"message.author.name#message.author.discriminator",
        icon_url=message.author.avatar_url
    )
    embed.set_footer(text=f"Deleted in: #message.channel.name")

    await ctx.channel.send(embed=embed)

client.run(TOKEN)

注意:bare except clauses,就像在您的原始代码中一样,通常不是一个好主意。在这里,我们只想捕获KeyError,如果请求的键不在字典中,则会引发此问题。

您可以选择以不同的方式实现相同的逻辑:

message = client.sniped_messages.get(ctx.channel.id)
if message is None:
    await ctx.channel.send("Couldn't find a message to snipe!")
    return

字典的.get() 方法返回相应的项目,就像正常的索引一样。但是,如果没有这样的键,它不会引发异常,而是返回一个默认值(如果您在对 get 的调用中没有指定一个默认值,则为 None)。

如果您使用的是 Python 3.8+,前两行也可以使用 assignment expression(使用“海象运算符”)组合在一起,它会同时分配和检查所有内容:

if (message := client.sniped_messages.get(ctx.channel.id)) is None:
    await ctx.channel.send("Couldn't find a message to snipe!")
    return

为了完整起见,提到了这些替代选项;所有这些方法都很好。

【讨论】:

以上是关于如何从特定频道狙击消息(discord.py)的主要内容,如果未能解决你的问题,请参考以下文章

如何制作不狙击跨服务器的狙击命令(discord.py)

Discord py 将在一个频道上发送的消息(带有嵌入)转发到另一个频道以及如何确定服务器中的成员数量?

Discord.py 狙击命令

如何从 discord.py 中的文本通道获取所有消息的数组?

如何查看特定用户在语音频道上花费的时间?[Discord.py]

Discord py 向频道发送消息