初めに
この記事は,discord botでメッセージを転送しようという目的の記事です.また,discord botでminecraft serverを操作したいアドベントカレンダーの記事ですが,ネタ切れで趣旨とは若干それています
また,
- WSL, Ubuntu22.04.1
- Python 3.10.6
- discord.py 2.1.0
という環境にて実施しています.
やりたいこと
誰かのメッセージをどこかのチャンネルへと転送したいです.また,そのときに誰がそのメッセージを転送したかを表示させたいです.
作る
転送に利用するコマンドは,特定のメッセージに対してコマンドを利用するためcontext_menuを利用します.
import os
from discord import (
Interaction,
Intents,
Client,
Message,
)
from discord.app_commands import CommandTree
import pprint
class MyClient(Client):
def __init__(self, *, intents: Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
await super().setup_hook()
synced_commands = await self.tree.sync()
pprint.pprint(synced_commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = Intents.default()
client = MyClient(intents=intents)
@client.tree.context_menu()
async def message_forward(interaction: Interaction, message: Message):
await interaction.response.send_message(
f"this message is: {message.content}", ephemeral=True
)
client.run(os.getenv("TOKEN"))
転送というメッセージに対してコマンドを実行して,中身を送信してもらうようにしました.
メッセージの埋め込み
次に誰がどんなことを言ったのかを表示させる必要があります.その表示にはEmbed
というクラスを使います.
Embedにはtitleやfooter,authorやtimestampなどを設定できます.ただしtitleは初期化時のみ設定可能です.
今回は転送したいメッセージにおいて,その送信者,内容,時間,および転送する人を埋め込みます.titleは特に必要ないと思ったので入れていません.
画像だけ貼り付けた場合などはメッセージのcontentがありませんので確認しています.1そしてメッセージの送信時刻をtimestamp
に,メッセージの送信者の名前とアイコンの画像をset_author
で,誰が転送しようとしているのかをset_footer
で設定しています.
あとはそれを送信時に指定してあげます.
import os
from discord import (
Interaction,
Intents,
Client,
Message,
+ Embed,
)
from discord.app_commands import CommandTree
import pprint
class MyClient(Client):
def __init__(self, *, intents: Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
await super().setup_hook()
synced_commands = await self.tree.sync()
pprint.pprint(synced_commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = Intents.default()
client = MyClient(intents=intents)
@client.tree.context_menu()
async def message_forward(interaction: Interaction, message: Message):
- await interaction.response.send_message(
- f"this message is: {message.content}", ephemeral=True
- )
+ embed = discord.Embed(title=None)
+ if message.content:
+ embed.description = message.content
+ embed.timestamp = message.created_at
+
+ embed.set_author(
+ name=message.author.display_name,
+ icon_url=message.author.display_avatar.url,
+ ).set_footer(
+ text=f"forwarded from {interaction.user.display_name}",
+ icon_url=interaction.user.display_avatar.url,
+ )
+
+ await interaction.response.send_message("embed", embed=embed, ephemeral=True)
client.run(os.getenv("TOKEN"))
無事に埋め込めました.
メッセージの場所に移動
今度はメッセージの場所まで移動します.つまり,どんな文脈からそのメッセージが出てきたのかも見れる感じです.
ボタンを使って飛べるようにしましょう.つまりViewとButtonです.
import os
from discord import (
Interaction,
Intents,
Client,
Message,
Embed,
+ ui,
+ ButtonStyle
)
from discord.app_commands import CommandTree
import pprint
class MyClient(Client):
def __init__(self, *, intents: Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
await super().setup_hook()
synced_commands = await self.tree.sync()
pprint.pprint(synced_commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = Intents.default()
client = MyClient(intents=intents)
@client.tree.context_menu()
async def message_forward(interaction: Interaction, message: Message):
embed = Embed(title=None)
if message.content:
embed.description = message.content
embed.timestamp = message.created_at
embed.set_author(
name=message.author.display_name, icon_url=message.author.display_avatar.url
).set_footer(
text=f"forwarded from {interaction.user.display_name}",
icon_url=interaction.user.display_avatar.url,
)
+ url_view = ui.View()
+ url_view.add_item(
+ ui.Button(
+ label="Go to Message", style=ButtonStyle.url, url=message.jump_url
+ )
+ )
- await interaction.response.send_message("embed", embed=embed, ephemeral=True)
+ await interaction.response.send_message("embed", embed=embed, view=url_view)
client.run(os.getenv("TOKEN"))
使ってみるとわかりますが,ちゃんとそのメッセージの場所に移動してくれます.
チャンネルの選択
続いてどのチャンネルに送信するかのドロップダウンメニューを作ります.これにはViewのサブクラスでdiscord.ui.select
デコレータを利用します.これのcls
にui.ChannelSelect
のクラスを指定することでチャンネルのドロップダウンメニューを作ってくれます.しかし,そのままだとチャンネルのカテゴリやボイスチャンネルも含まれるのでchannel_types=[ChannelType.text]
としてテキストチャンネルだけ表示させるようにしています.
import os
from discord import (
Interaction,
Intents,
Client,
Message,
Embed,
ui,
ButtonStyle,
+ ChannelType,
)
from discord.app_commands import CommandTree
import pprint
class MyClient(Client):
def __init__(self, *, intents: Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
await super().setup_hook()
synced_commands = await self.tree.sync()
pprint.pprint(synced_commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = Intents.default()
client = MyClient(intents=intents)
+ class SendChannelView(ui.View):
+ @ui.select(
+ cls=ui.ChannelSelect,
+ placeholder="select channel",
+ channel_types=[ChannelType.text],
+ )
+ async def set_channel(self, interaction: Interaction, select: ui.ChannelSelect):
+ await interaction.response.send_message(f"you select {select.values[0]}")
@client.tree.context_menu()
async def message_forward(interaction: Interaction, message: Message):
embed = Embed(title=None)
if message.content:
embed.description = message.content
embed.timestamp = message.created_at
embed.set_author(
name=message.author.display_name, icon_url=message.author.display_avatar.url
).set_footer(
text=f"forwarded from {interaction.user.display_name}",
icon_url=interaction.user.display_avatar.url,
)
url_view = ui.View()
url_view.add_item(
ui.Button(label="Go to Message", style=ButtonStyle.url, url=message.jump_url)
)
+ send_channel_view = SendChannelView()
await interaction.response.send_message("embed", embed=embed, view=url_view)
+ await interaction.followup.send(view=send_channel_view)
client.run(os.getenv("TOKEN"))
ボタンを押して送信
続いて,ボタンを押したときに選択されたチャンネルに送信するように変更します.先ほど作ったSendChannelView
にボタンを追加するbuttonデコレータを追加します.また,埋め込みはこのときに送信したいのでSendChannelView
の引数に渡しておきます.
import os
from discord import (
Interaction,
Intents,
Client,
Message,
Embed,
ui,
ButtonStyle,
ChannelType,
)
from discord.app_commands import CommandTree
import pprint
class MyClient(Client):
def __init__(self, *, intents: Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
await super().setup_hook()
synced_commands = await self.tree.sync()
pprint.pprint(synced_commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = Intents.default()
client = MyClient(intents=intents)
class SendChannelView(ui.View):
+ def __init__(self, embed: Embed, url_view: ui.View):
+ super().__init__()
+ self.send_embed = embed
+ self.send_url_view = url_view
@ui.select(
cls=ui.ChannelSelect,
placeholder="select channel",
channel_types=[ChannelType.text],
)
async def set_channel(self, interaction: Interaction, select: ui.ChannelSelect):
await interaction.response.send_message(f"you select {select.values[0]}")
+ @ui.button(label="Send")
+ async def send_button(self, interaction: Interaction, button: ui.Button):
+ await interaction.response.send_message("send!")
+ await self.fav_channel.send(embed=self.send_embed, view=self.send_url_view)
@client.tree.context_menu()
async def message_forward(interaction: Interaction, message: Message):
embed = Embed(title=None)
if message.content:
embed.description = message.content
embed.timestamp = message.created_at
embed.set_author(
name=message.author.display_name, icon_url=message.author.display_avatar.url
).set_footer(
text=f"forwarded from {interaction.user.display_name}",
icon_url=interaction.user.display_avatar.url,
)
url_view = ui.View()
url_view.add_item(
ui.Button(label="Go to Message", style=ButtonStyle.url, url=message.jump_url)
)
- send_channel_view = SendChannelView()
+ send_channel_view = SendChannelView(embed=embed, url_view=url_view)
client.run(os.getenv("TOKEN"))
転送されましたね.
細かいところ
転送自体は現段階でできています.しかし問題があります.
- チャンネルが指定されていないときにボタンを押すとエラーが発生する
- 何回でも転送できる
- 邪魔
というわけでこれらを片付けます.
チャンネルの指定を必須化,および一度のみの転送
チャンネルの指定を必須にするためには,そもそも送信できなくしてしまえばいいです.Buttonにはdisabled
というプロパティがあります.このプロパティを最初はTrueにして,一度でも選択されたらFalseに変更させます.このとき,見た目を変えると押せるようになった感が出ると思いますので見た目の変更もします.
また,転送したらButtonやSelectを使えなくしましょう.Viewにはchildren
というプロパティがありますので,Buttonが押されたときの処理でfor文を回してdisabledにしましょう.ただし,childreの返り値はItemのリストです.これはButtonやSelectなどのスーパークラスですが,disabledというプロパティを持っていません.そのため,vscodeなどを使って入力補完が効かないです.まあButtonやSelectは持っていますから,一応大丈夫です.2
import os
from discord import (
Interaction,
Intents,
Client,
Message,
Embed,
ui,
ButtonStyle,
ChannelType,
)
from discord.app_commands import CommandTree
import pprint
class MyClient(Client):
def __init__(self, *, intents: Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
await super().setup_hook()
synced_commands = await self.tree.sync()
pprint.pprint(synced_commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = Intents.default()
client = MyClient(intents=intents)
class SendChannelView(ui.View):
def __init__(self, embed: Embed, url_view: ui.View):
super().__init__()
self.send_embed = embed
self.send_url_view = url_view
@ui.select(
cls=ui.ChannelSelect,
placeholder="select channel",
channel_types=[ChannelType.text],
)
async def set_channel(self, interaction: Interaction, select: ui.ChannelSelect):
- await interaction.response.send_message(f"you select {select.values[0]}")
+ self.send_button.disabled = False
+ self.send_button.style = ButtonStyle.green
+
+ await interaction.response.edit_message(view=self)
self.fav_channel = await select.values[0].fetch()
@ui.button(label="Send", disabled=True, style=ButtonStyle.gray)
async def send_button(self, interaction: Interaction, button: ui.Button):
- await interaction.response.send_message("send!")
+ for item in self.children:
+ item.disabled = True
+ self.send_button.style = ButtonStyle.gray
+ await interaction.response.edit_message(view=self)
await self.fav_channel.send(embed=self.send_embed, view=self.send_url_view)
@client.tree.context_menu()
async def message_forward(interaction: Interaction, message: Message):
embed = Embed(title=None)
if message.content:
embed.description = message.content
embed.timestamp = message.created_at
embed.set_author(
name=message.author.display_name, icon_url=message.author.display_avatar.url
).set_footer(
text=f"forwarded from {interaction.user.display_name}",
icon_url=interaction.user.display_avatar.url,
)
url_view = ui.View()
url_view.add_item(
ui.Button(label="Go to Message", style=ButtonStyle.url, url=message.jump_url)
)
send_channel_view = SendChannelView(embed=embed, url_view=url_view)
await interaction.response.send_message("where?", view=send_channel_view)
client.run(os.getenv("TOKEN"))
こんな感じでボタンの色が変わったり,使えなくなったりしました.使えなくなる話の詳しいことは,書いた記事で触れているので見ていただけるとありがたいです.
邪魔
さて,次の問題です.
履歴に残るのが邪魔です.残す理由もないですし消しましょう.また,そのままメッセージを送信すると通知も来るので自分だけにしか見えなくしましょう.
削除するメッセージはチャンネルの選択などをするメッセージですから,ボタンを押したときのインタラクションにあります.このインタラクションに対してdelete_original_response()
メソッドを利用します.
import os
from discord import (
Interaction,
Intents,
Client,
Message,
Embed,
ui,
ButtonStyle,
ChannelType,
)
from discord.app_commands import CommandTree
import pprint
class MyClient(Client):
def __init__(self, *, intents: Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
await super().setup_hook()
synced_commands = await self.tree.sync()
pprint.pprint(synced_commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = Intents.default()
client = MyClient(intents=intents)
class SendChannelView(ui.View):
def __init__(self, embed: Embed, url_view: ui.View):
super().__init__()
self.send_embed = embed
self.send_url_view = url_view
@ui.select(
cls=ui.ChannelSelect,
placeholder="select channel",
channel_types=[ChannelType.text],
)
async def set_channel(self, interaction: Interaction, select: ui.ChannelSelect):
self.send_button.disabled = False
self.send_button.style = ButtonStyle.green
await interaction.response.edit_message(view=self)
self.fav_channel = await select.values[0].fetch()
@ui.button(label="Send", disabled=True, style=ButtonStyle.gray)
async def send_button(self, interaction: Interaction, button: ui.Button):
for item in self.children:
item.disabled = True
self.send_button.style = ButtonStyle.gray
await interaction.response.edit_message(view=self)
await self.fav_channel.send(embed=self.send_embed, view=self.send_url_view)
+ await interaction.delete_original_response()
@client.tree.context_menu()
async def message_forward(interaction: Interaction, message: Message):
embed = Embed(title=None)
if message.content:
embed.description = message.content
embed.timestamp = message.created_at
embed.set_author(
name=message.author.display_name, icon_url=message.author.display_avatar.url
).set_footer(
text=f"forwarded from {interaction.user.display_name}",
icon_url=interaction.user.display_avatar.url,
)
url_view = ui.View()
url_view.add_item(
ui.Button(label="Go to Message", style=ButtonStyle.url, url=message.jump_url)
)
send_channel_view = SendChannelView(embed=embed, url_view=url_view)
- await interaction.response.send_message("where?", view=send_channel_view)
+ await interaction.response.send_message(
+ "where?", view=send_channel_view, ephemeral=True
+ )
client.run(os.getenv("TOKEN"))
自分だけ見えているやつですね.送信した後は消えちゃうので載せられません~~し,gifを載せるにしてもgifにするとき毎回謎画質になる3~~ので,ご自身で試してみてください.
以上となります.discord botでメッセージを転送することができました.
お疲れ様でした.
-
画像も転送したい場合は,メッセージの
embeds
からEmbedを複数取得します.その後取得したものと自身で作成した埋め込みをlistとして,送信の際にemveds
に指定するとできると思います.message_contentの権限が必要なので注意してください. ↩ -
TextInputはdisabledを持っていませんが,なんかViewでは使えなかったです.Modalだと使えるんですけどね.要検証 ↩
-
多分軽量化のためにいろいろしてくれてるんでしょうけど,そのせいで変になります.いい感じの動画の載せ方ありますかね... ↩