1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
//! Channel operations for DiscordUser
use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::{context::DiscordContext, error::Result, route::Route, types::*};
impl<T: DiscordContext + Send + Sync> ChannelOps for T {}
/// Response body for `POST /channels/{channel.id}/followers`.
///
/// Returned when an announcement channel is followed — contains the
/// announcement channel id and the webhook id created in the target channel
/// to forward crossposted messages.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FollowedChannel {
/// The announcement channel that was followed.
pub channel_id: String,
/// The id of the webhook created in the target channel that forwards
/// crossposted messages.
pub webhook_id: String,
}
/// Extension trait providing channel operations
#[allow(async_fn_in_trait)]
pub trait ChannelOps: DiscordContext {
/// Get or create a private channel (DM)
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
async fn get_my_private_channel(&self, recipients: Vec<UserId>) -> Result<Channel> {
let ids: Vec<String> = recipients.iter().map(|id| id.get().to_string()).collect();
self.http().post(Route::CreateDm, json!({ "recipients": ids })).await
}
/// Trigger a typing indicator in a channel (lasts ~10 seconds).
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
async fn broadcast_typing(&self, channel_id: &ChannelId) -> Result<()> {
self.http().post_empty(Route::TriggerTyping { channel_id: channel_id.get() }).await
}
/// Set a voice channel status message (visible in the channel header).
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
///
/// # Permissions
/// Requires being connected to the voice channel, or MANAGE_CHANNELS.
async fn set_channel_status(&self, channel_id: &ChannelId, status: &str) -> Result<()> {
self.http().put(Route::UpdateVoiceStatus { channel_id: channel_id.get() }, json!({ "status": status })).await
}
/// Fetch a channel's current settings and metadata.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure or if the channel is not
/// found.
async fn get_channel(&self, channel_id: &ChannelId) -> Result<Channel> {
self.http().get(Route::GetChannel { channel_id: channel_id.get() }).await
}
/// Create a new channel in a guild (POST /guilds/{id}/channels).
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
///
/// # Permissions
/// Requires MANAGE_CHANNELS permission in the guild.
async fn create_channel(&self, guild_id: &GuildId, req: CreateChannelRequest) -> Result<Channel> {
self.http().post(Route::CreateGuildChannel { guild_id: guild_id.get() }, req).await
}
/// Edit a channel's settings (PATCH /channels/{id}).
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
///
/// # Permissions
/// Requires MANAGE_CHANNELS permission.
async fn edit_channel(&self, channel_id: &ChannelId, req: EditChannelRequest) -> Result<Channel> {
self.http().patch(Route::EditChannel { channel_id: channel_id.get() }, req).await
}
/// Delete or close a channel (DELETE /channels/{id}).
///
/// For guild channels this permanently deletes the channel.
/// For DM channels it closes the conversation (can be re-opened).
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
///
/// # Permissions
/// Requires MANAGE_CHANNELS for guild channels.
async fn delete_channel(&self, channel_id: &ChannelId) -> Result<Channel> {
self.http().delete_with_response(Route::DeleteChannel { channel_id: channel_id.get() }).await
}
/// Get guild information
///
/// # Arguments
/// * `guild_id` - The guild ID
/// * `with_counts` - Whether to include approximate member and presence
/// counts
async fn get_guild(&self, guild_id: &GuildId, with_counts: bool) -> Result<Guild> {
self.http().get(Route::GetGuild { guild_id: guild_id.get(), with_counts }).await
}
/// Get user profile
///
/// # Arguments
/// * `user_id` - The user ID to get profile for
/// * `guild_id` - Optional guild ID for guild-specific profile data
///
/// # Returns
/// User profile data including bio, banner, connected accounts, etc.
async fn get_user_profile(&self, user_id: &UserId, guild_id: Option<&GuildId>) -> Result<UserProfile> {
self.http().get(Route::GetUserProfile { user_id: user_id.get(), guild_id: guild_id.map(|g| g.get()) }).await
}
/// List the invites (with metadata) for a channel
/// (`GET /channels/{channel.id}/invites`).
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
///
/// # Permissions
/// Requires MANAGE_CHANNELS permission.
async fn get_channel_invites(&self, channel_id: &ChannelId) -> Result<Vec<Invite>> {
self.http().get(Route::GetChannelInvites { channel_id: channel_id.get() }).await
}
/// Edit the permission overwrites for a user or role in a channel
/// (`PUT /channels/{channel.id}/permissions/{overwrite.id}`).
///
/// `allow` and `deny` are bitfields stringified as decimal numbers.
/// `overwrite_type` is `0` for a role overwrite or `1` for a member
/// overwrite. Returns 204 No Content on success.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
///
/// # Permissions
/// Requires MANAGE_ROLES permission.
async fn edit_channel_permissions(&self, channel_id: &ChannelId, overwrite_id: u64, allow: Option<&str>, deny: Option<&str>, overwrite_type: u8) -> Result<()> {
let body = EditChannelPermissionsRequest { allow: allow.map(|s| s.to_string()), deny: deny.map(|s| s.to_string()), overwrite_type };
self.http().put(Route::EditChannelPermissions { channel_id: channel_id.get(), overwrite_id }, body).await
}
/// Delete a channel permission overwrite for a user or role
/// (`DELETE /channels/{channel.id}/permissions/{overwrite.id}`).
///
/// Returns 204 No Content on success.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
///
/// # Permissions
/// Requires MANAGE_ROLES permission.
async fn delete_channel_permission(&self, channel_id: &ChannelId, overwrite_id: u64) -> Result<()> {
self.http().delete(Route::DeleteChannelPermission { channel_id: channel_id.get(), overwrite_id }).await
}
/// Follow an announcement channel and have its messages crossposted to
/// the target channel (`POST /channels/{channel.id}/followers`).
///
/// `webhook_channel_id` is the id of the target channel that will receive
/// crossposted messages.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
///
/// # Permissions
/// Requires MANAGE_WEBHOOKS permission in the target channel.
async fn follow_announcement_channel(&self, channel_id: &ChannelId, webhook_channel_id: u64) -> Result<FollowedChannel> {
self.http().post(Route::FollowAnnouncementChannel { channel_id: channel_id.get() }, json!({ "webhook_channel_id": webhook_channel_id.to_string() })).await
}
/// Add a recipient to a Group DM
/// (`PUT /channels/{channel.id}/recipients/{user.id}`).
///
/// Both `access_token` (an OAuth2 access token with the `gdm.join` scope)
/// and `nick` are optional in the wire format but typically required by
/// Discord. Returns 204 No Content on success.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
async fn group_dm_add_recipient(&self, channel_id: &ChannelId, user_id: &UserId, access_token: Option<&str>, nick: Option<&str>) -> Result<()> {
let mut body = serde_json::Map::new();
if let Some(tok) = access_token {
body.insert("access_token".to_string(), serde_json::Value::String(tok.to_string()));
}
if let Some(n) = nick {
body.insert("nick".to_string(), serde_json::Value::String(n.to_string()));
}
self.http().put(Route::GroupDmAddRecipient { channel_id: channel_id.get(), user_id: user_id.get() }, serde_json::Value::Object(body)).await
}
/// Remove a recipient from a Group DM
/// (`DELETE /channels/{channel.id}/recipients/{user.id}`).
///
/// Returns 204 No Content on success.
///
/// # Errors
/// Returns [`DiscordError::Http`] on HTTP failure.
async fn group_dm_remove_recipient(&self, channel_id: &ChannelId, user_id: &UserId) -> Result<()> {
self.http().delete(Route::GroupDmRemoveRecipient { channel_id: channel_id.get(), user_id: user_id.get() }).await
}
}