# Forum and threads
Forum events arrive over the gateway when the `Intents::FORUMS` (private) or `Intents::OPEN_FORUM_EVENT` (public) bits are set. The framework decodes the gateway payload into typed structs and dispatches to your `EventHandler`. There is currently no REST API on `BotApi` for creating or deleting threads — bots are notification consumers, not authors.
## Private forum callbacks
With `Intents::FORUMS`, the following `EventHandler` methods become live:
- `forum_thread_create(&self, ctx, thread: Thread)`
- `forum_thread_update(&self, ctx, thread: Thread)`
- `forum_thread_delete(&self, ctx, thread: Thread)`
- `forum_post_create(&self, ctx, post: Post)`
- `forum_post_delete(&self, ctx, post: Post)`
- `forum_reply_create(&self, ctx, reply: Reply)`
- `forum_reply_delete(&self, ctx, reply: Reply)`
- `forum_publish_audit_result(&self, ctx, result: ForumAuditResult)`
## Public forum callbacks
`Intents::OPEN_FORUM_EVENT` enables the same lifecycle on a single payload type, `OpenThread`, which carries optional `thread_info`, `post_info`, and `reply_info` blocks depending on which sub-event fired:
- `open_forum_thread_create` / `_update` / `_delete`
- `open_forum_post_create` / `_delete`
- `open_forum_reply_create` / `_delete`
## Payload shapes
`Thread` carries `channel_id`, `guild_id`, `author_id`, `event_id` (`Option<String>`), and a `thread_info: ThreadInfo`. `ThreadInfo` exposes the title and rich-content blocks the forum editor produced.
`Post` and `Reply` follow the same shape but with `post_info: PostInfo` / `reply_info: ReplyInfo` instead. All three structs cache the `BotApi` they were built with; you can grab it via `thread.api()` and similar accessors.
`ForumAuditResult` mirrors the audit payload directly:
```rust
pub struct ForumAuditResult {
pub task_id: String,
pub guild_id: String,
pub channel_id: String,
pub author_id: String,
pub thread_id: String,
pub post_id: String,
pub reply_id: String,
pub publish_type: u32,
pub result: u32, // 0 = pass, non-zero = reject
pub err_msg: String,
pub date_time: String,
pub event_id: Option<String>,
}
```
`forum_publish_audit_result` fires once per audit decision. The framework's gateway layer ACKs the dispatch automatically — your handler does not need to send anything back. `result == 0` indicates the publish went through; otherwise `err_msg` carries the reason supplied by the platform.
## Reading rich content
`ThreadInfo`, `PostInfo`, and `ReplyInfo` deserialize the QQ "paragraph" structure:
- `Content { paragraphs: Vec<Paragraph> }`
- `Paragraph { elems: Vec<Elem>, props }`
- `Elem` is a tagged union (`Text`, `Image`, `Video`, `Url`).
The exact field names and types are in `botrs::forum`; pattern-matching `Elem` is the typical way to extract text or attachments from a thread body.
## Minimal handler
```rust
async fn forum_publish_audit_result(&self, _ctx: Context, result: ForumAuditResult) {
if result.result != 0 {
tracing::warn!(
task = result.task_id,
err = result.err_msg,
"forum publish rejected"
);
}
}
async fn forum_thread_create(&self, _ctx: Context, thread: Thread) {
tracing::info!(
guild = ?thread.guild_id,
channel = ?thread.channel_id,
"new forum thread"
);
}
```