Sending Messages
Passive Messages
Passive messages refer to the bot responding to messages reactively.
@filter.command("helloworld")
async def helloworld(self, event: AstrMessageEvent):
yield event.plain_result("Hello!")
yield event.plain_result("你好!")
yield event.image_result("path/to/image.jpg") # Send an image
yield event.image_result(
"https://example.com/image.jpg"
) # Send an image from URL, must start with http or httpsActive Messages
Active messages refer to the bot proactively pushing messages. Some platforms may not support active message sending.
For scheduled tasks or when you don't want to send messages immediately, you can use event.unified_msg_origin to get a string and store it, then use self.context.send_message(unified_msg_origin, chains) to send messages when needed.
from astrbot.api.event import MessageChain
@filter.command("helloworld")
async def helloworld(self, event: AstrMessageEvent):
umo = event.unified_msg_origin
message_chain = MessageChain().message("Hello!").file_image("path/to/image.jpg")
await self.context.send_message(event.unified_msg_origin, message_chain)With this feature, you can store the unified_msg_origin and send messages when needed.
TIP
About unified_msg_origin. unified_msg_origin is a string that records the unique ID of a session. AstrBot uses it to identify which messaging platform and which session it belongs to. This allows messages to be sent to the correct session when using send_message. For more about MessageChain, see the next section.
Rich Media Messages
AstrBot supports sending rich media messages such as images, audio, videos, etc. Use MessageChain to construct messages.
import astrbot.api.message_components as Comp
@filter.command("helloworld")
async def helloworld(self, event: AstrMessageEvent):
chain = [
Comp.At(qq=event.get_sender_id()), # Mention the message sender
Comp.Plain("Check out this image:"),
Comp.Image.fromURL("https://example.com/image.jpg"), # Send image from URL
Comp.Image.fromFileSystem(
"path/to/image.jpg"
), # Send image from local file system
Comp.Plain("This is an image."),
]
yield event.chain_result(chain)The above constructs a message chain, which will ultimately send a message containing both images and text while preserving the order.
TIP
In the aiocqhttp message adapter, for messages of type plain, the strip() method is used during sending to remove spaces and line breaks. You can add zero-width spaces \u200b before and after the message to resolve this issue.
Similarly,
File
Comp.File(file="path/to/file.txt", name="file.txt") # Not supported by some platformsAudio Record
path = "path/to/record.wav" # Currently only accepts wav format, please convert other formats yourself
Comp.Record(file=path, url=path)Video
path = "path/to/video.mp4"
Comp.Video.fromFileSystem(path=path)
Comp.Video.fromURL(url="https://example.com/video.mp4")Interactive Buttons
Create button actions and styles with Button.CallbackAction, Button.UrlAction, and Button.Style. ActionRow is a separate message component.
Wrap Button components in an ActionRow and handle clicks with @filter.button_action("button ID"). Add these methods to an existing Star plugin class.
Current platform support:
| Support | Platforms |
|---|---|
| Callback and URL buttons | QQ Official Bot, Telegram, Lark, DingTalk, Discord, Slack, LINE, KOOK, Satori, WeCom (including WeChat Customer Service), WeCom AI Bot, WebChat |
| Callback buttons; URL actions appear as regular links | Mattermost |
| Text or link fallback without button callbacks | OneBot v11 (aiocqhttp), Misskey, Weixin Official Account, Weixin Personal (weixin_oc) |
Satori button support depends on the connected platform and client. Mattermost callback buttons require a callback URL reachable by the Mattermost server. For platforms using text fallbacks, set fallback_text to explain the available text commands.
from astrbot.api.event import AstrMessageEvent, filter
import astrbot.api.message_components as Comp
@filter.command("buttons")
async def buttons(self, event: AstrMessageEvent):
yield event.chain_result([
Comp.Plain("Choose an action:"),
Comp.ActionRow(
buttons=[
Comp.Button(
id="my_plugin:confirm",
label="Confirm",
action=Comp.Button.CallbackAction(data={"item_id": 42}),
style=Comp.Button.Style.PRIMARY,
),
Comp.Button(
id="my_plugin:docs",
label="Docs",
action=Comp.Button.UrlAction(url="https://docs.astrbot.app"),
),
],
action_timeout=60,
fallback_text="Buttons are unavailable here. Use text commands instead.",
),
])
@filter.button_action("my_plugin:confirm")
async def confirm(self, event: AstrMessageEvent):
interaction = event.get_button_interaction()
if interaction is None:
return
yield event.plain_result(f"Confirmed item: {interaction.data['item_id']}")action_timeoutis the lifetime of the row's callback buttons in seconds. It must be a finite positive number; the defaultNonemeans unlimited. The shared deadline starts when the row is first rendered for sending. Reusing the sameActionRowinstance preserves its deadline; create a new instance to start a new lifetime.- The deadline is persisted with the callback data and survives restarts. Expired clicks receive “按钮已过期,请重新发起操作。 / This button has expired. Please start again.” without running plugin handlers. Telegram uses an alert; Discord and Slack use replies visible only to the clicking user. Other platforms send a message, with WeCom AI Bot updating its card. Repeated expired clicks also receive the notice. Operations already accepted can finish, and buttons may remain visible in the original message.
Button.CallbackAction.datamust be JSON-compatible and is returned asinteraction.data. The button ID becomesinteraction.action_id; prefix IDs with your plugin name to avoid collisions.Button.UrlActionopens a link without invoking a plugin callback and is unaffected byaction_timeout.fallback_textis used on platforms without button support, rather than as an expiry notice.@filter.button_action()without an ID matches all button callbacks. Useevent.get_button_interaction()to access theButtonInteractionevent component.
Sending Video Messages
from astrbot.api.event import filter, AstrMessageEvent
@filter.command("test")
async def test(self, event: AstrMessageEvent):
from astrbot.api.message_components import Video
# fromFileSystem requires the user's protocol client and bot to be on the same system.
video = Video.fromFileSystem(path="test.mp4")
# More universal approach
video = Video.fromURL(url="https://example.com/video.mp4")
yield event.chain_result([video])
Sending Group Forward Messages
Most platforms do not support this message type. Current support: OneBot v11
You can send group forward messages as follows.
from astrbot.api.event import filter, AstrMessageEvent
@filter.command("test")
async def test(self, event: AstrMessageEvent):
from astrbot.api.message_components import Node, Plain, Image
node = Node(
uin=905617992,
name="Soulter",
content=[Plain("hi"), Image.fromFileSystem("test.jpg")],
)
yield event.chain_result([node])