use crate::models::{HasId, HasName, Snowflake};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct Channel {
#[serde(default)]
pub id: Snowflake,
#[serde(default)]
pub guild_id: Snowflake,
#[serde(default)]
pub name: String,
#[serde(default, rename = "type")]
pub channel_type: ChannelType,
#[serde(default)]
pub sub_type: ChannelSubType,
#[serde(default)]
pub position: i64,
#[serde(default)]
pub parent_id: Snowflake,
#[serde(default)]
pub owner_id: Snowflake,
#[serde(default)]
pub private_type: PrivateType,
#[serde(default)]
pub private_user_ids: Vec<String>,
#[serde(default)]
pub speak_permission: SpeakPermission,
#[serde(default)]
pub application_id: Snowflake,
#[serde(default)]
pub permissions: String,
#[serde(default)]
pub op_user_id: Snowflake,
}
impl Channel {
pub fn new() -> Self {
Self::default()
}
pub fn from_data(_api: crate::api::BotApi, id: String, data: serde_json::Value) -> Self {
let mut channel = serde_json::from_value::<Self>(data).unwrap_or_default();
channel.id = id;
channel
}
pub fn mention(&self) -> String {
format!("<#{}>", self.id)
}
pub fn is_text(&self) -> bool {
self.channel_type == ChannelType::Text
}
pub fn is_voice(&self) -> bool {
self.channel_type == ChannelType::Voice
}
pub fn is_group(&self) -> bool {
self.channel_type == ChannelType::Category
}
pub fn is_live(&self) -> bool {
self.channel_type == ChannelType::Live
}
pub fn is_application(&self) -> bool {
self.channel_type == ChannelType::Application
}
pub fn is_discussion(&self) -> bool {
self.channel_type == ChannelType::Forum
}
pub fn is_public(&self) -> bool {
self.private_type == PrivateType::Public
}
pub fn is_admin_only(&self) -> bool {
self.private_type == PrivateType::OnlyAdmin
}
pub fn is_specified_users_only(&self) -> bool {
self.private_type == PrivateType::AdminAndMember
}
pub fn everyone_can_speak(&self) -> bool {
self.speak_permission == SpeakPermission::Public
}
pub fn admin_only_speak(&self) -> bool {
self.speak_permission == SpeakPermission::AdminAndMember
}
pub fn display_name(&self) -> Option<&str> {
(!self.name.is_empty()).then_some(self.name.as_str())
}
}
impl HasId for Channel {
fn id(&self) -> Option<&Snowflake> {
(!self.id.is_empty()).then_some(&self.id)
}
}
impl HasName for Channel {
fn name(&self) -> &str {
&self.name
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct ChannelValueObject {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub channel_type: Option<ChannelType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub position: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_id: Option<Snowflake>,
#[serde(skip_serializing_if = "Option::is_none")]
pub owner_id: Option<Snowflake>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sub_type: Option<ChannelSubType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub private_type: Option<PrivateType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub private_user_ids: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub speak_permission: Option<SpeakPermission>,
#[serde(skip_serializing_if = "Option::is_none")]
pub application_id: Option<Snowflake>,
#[serde(skip_serializing_if = "Option::is_none")]
pub permissions: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub op_user_id: Option<Snowflake>,
}
impl ChannelValueObject {
pub fn new(
name: impl Into<String>,
channel_type: ChannelType,
sub_type: ChannelSubType,
) -> Self {
Self {
name: Some(name.into()),
channel_type: Some(channel_type),
sub_type: Some(sub_type),
..Default::default()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(from = "u32", into = "u32")]
#[repr(u32)]
pub enum ChannelType {
#[default]
Text = 0,
Voice = 2,
Category = 4,
Live = 10005,
Application = 10006,
Forum = 10007,
Unknown(u32),
}
pub const CHANNEL_TYPE_TEXT: ChannelType = ChannelType::Text;
pub const CHANNEL_TYPE_VOICE: ChannelType = ChannelType::Voice;
pub const CHANNEL_TYPE_CATEGORY: ChannelType = ChannelType::Category;
pub const CHANNEL_TYPE_LIVE: ChannelType = ChannelType::Live;
pub const CHANNEL_TYPE_APPLICATION: ChannelType = ChannelType::Application;
pub const CHANNEL_TYPE_FORUM: ChannelType = ChannelType::Forum;
#[allow(non_upper_case_globals)]
pub const ChannelTypeText: ChannelType = CHANNEL_TYPE_TEXT;
#[allow(non_upper_case_globals)]
pub const ChannelTypeVoice: ChannelType = CHANNEL_TYPE_VOICE;
#[allow(non_upper_case_globals)]
pub const ChannelTypeCategory: ChannelType = CHANNEL_TYPE_CATEGORY;
#[allow(non_upper_case_globals)]
pub const ChannelTypeLive: ChannelType = CHANNEL_TYPE_LIVE;
#[allow(non_upper_case_globals)]
pub const ChannelTypeApplication: ChannelType = CHANNEL_TYPE_APPLICATION;
#[allow(non_upper_case_globals)]
pub const ChannelTypeForum: ChannelType = CHANNEL_TYPE_FORUM;
impl From<u32> for ChannelType {
fn from(value: u32) -> Self {
match value {
0 => Self::Text,
2 => Self::Voice,
4 => Self::Category,
10005 => Self::Live,
10006 => Self::Application,
10007 => Self::Forum,
other => Self::Unknown(other),
}
}
}
impl ChannelType {
pub fn from_u8(value: u8) -> Option<Self> {
Some(Self::from(value as u32))
}
pub fn from_u32(value: u32) -> Self {
Self::from(value)
}
}
impl From<ChannelType> for u32 {
fn from(channel_type: ChannelType) -> Self {
match channel_type {
ChannelType::Text => 0,
ChannelType::Voice => 2,
ChannelType::Category => 4,
ChannelType::Live => 10005,
ChannelType::Application => 10006,
ChannelType::Forum => 10007,
ChannelType::Unknown(value) => value,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(from = "u32", into = "u32")]
#[repr(u32)]
pub enum ChannelSubType {
#[default]
Chat = 0,
Notice = 1,
Guide = 2,
TeamGame = 3,
Unknown(u32),
}
pub const CHANNEL_SUB_TYPE_CHAT: ChannelSubType = ChannelSubType::Chat;
pub const CHANNEL_SUB_TYPE_NOTICE: ChannelSubType = ChannelSubType::Notice;
pub const CHANNEL_SUB_TYPE_GUIDE: ChannelSubType = ChannelSubType::Guide;
pub const CHANNEL_SUB_TYPE_TEAM_GAME: ChannelSubType = ChannelSubType::TeamGame;
#[allow(non_upper_case_globals)]
pub const ChannelSubTypeChat: ChannelSubType = CHANNEL_SUB_TYPE_CHAT;
#[allow(non_upper_case_globals)]
pub const ChannelSubTypeNotice: ChannelSubType = CHANNEL_SUB_TYPE_NOTICE;
#[allow(non_upper_case_globals)]
pub const ChannelSubTypeGuide: ChannelSubType = CHANNEL_SUB_TYPE_GUIDE;
#[allow(non_upper_case_globals)]
pub const ChannelSubTypeTeamGame: ChannelSubType = CHANNEL_SUB_TYPE_TEAM_GAME;
impl From<u32> for ChannelSubType {
fn from(value: u32) -> Self {
match value {
0 => Self::Chat,
1 => Self::Notice,
2 => Self::Guide,
3 => Self::TeamGame,
other => Self::Unknown(other),
}
}
}
impl ChannelSubType {
pub fn from_u8(value: u8) -> Option<Self> {
Some(Self::from(value as u32))
}
}
impl From<ChannelSubType> for u32 {
fn from(subtype: ChannelSubType) -> Self {
match subtype {
ChannelSubType::Chat => 0,
ChannelSubType::Notice => 1,
ChannelSubType::Guide => 2,
ChannelSubType::TeamGame => 3,
ChannelSubType::Unknown(value) => value,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(from = "u8", into = "u8")]
#[repr(u8)]
pub enum PrivateType {
#[default]
Public = 0,
OnlyAdmin = 1,
AdminAndMember = 2,
Unknown(u8),
}
pub type ChannelPrivateType = PrivateType;
pub const CHANNEL_PRIVATE_TYPE_PUBLIC: ChannelPrivateType = PrivateType::Public;
pub const CHANNEL_PRIVATE_TYPE_ONLY_ADMIN: ChannelPrivateType = PrivateType::OnlyAdmin;
pub const CHANNEL_PRIVATE_TYPE_ADMIN_AND_MEMBER: ChannelPrivateType = PrivateType::AdminAndMember;
#[allow(non_upper_case_globals)]
pub const ChannelPrivateTypePublic: ChannelPrivateType = CHANNEL_PRIVATE_TYPE_PUBLIC;
#[allow(non_upper_case_globals)]
pub const ChannelPrivateTypeOnlyAdmin: ChannelPrivateType = CHANNEL_PRIVATE_TYPE_ONLY_ADMIN;
#[allow(non_upper_case_globals)]
pub const ChannelPrivateTypeAdminAndMember: ChannelPrivateType =
CHANNEL_PRIVATE_TYPE_ADMIN_AND_MEMBER;
impl From<u8> for PrivateType {
fn from(value: u8) -> Self {
match value {
0 => Self::Public,
1 => Self::OnlyAdmin,
2 => Self::AdminAndMember,
other => Self::Unknown(other),
}
}
}
impl From<PrivateType> for u8 {
fn from(private_type: PrivateType) -> Self {
match private_type {
PrivateType::Public => 0,
PrivateType::OnlyAdmin => 1,
PrivateType::AdminAndMember => 2,
PrivateType::Unknown(other) => other,
}
}
}
impl PrivateType {
pub fn from_u8(value: u8) -> Option<Self> {
Some(Self::from(value))
}
}
impl From<PrivateType> for u32 {
fn from(private_type: PrivateType) -> Self {
match private_type {
PrivateType::Public => 0,
PrivateType::OnlyAdmin => 1,
PrivateType::AdminAndMember => 2,
PrivateType::Unknown(value) => value as u32,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(from = "u8", into = "u8")]
#[repr(u8)]
pub enum SpeakPermission {
#[default]
Invalid = 0,
Public = 1,
AdminAndMember = 2,
Unknown(u8),
}
pub type SpeakPermissionType = SpeakPermission;
pub const SPEAK_PERMISSION_TYPE_PUBLIC: SpeakPermissionType = SpeakPermission::Public;
pub const SPEAK_PERMISSION_TYPE_ADMIN_AND_MEMBER: SpeakPermissionType =
SpeakPermission::AdminAndMember;
#[allow(non_upper_case_globals)]
pub const SpeakPermissionTypePublic: SpeakPermissionType = SPEAK_PERMISSION_TYPE_PUBLIC;
#[allow(non_upper_case_globals)]
pub const SpeakPermissionTypeAdminAndMember: SpeakPermissionType =
SPEAK_PERMISSION_TYPE_ADMIN_AND_MEMBER;
impl From<u8> for SpeakPermission {
fn from(value: u8) -> Self {
match value {
0 => Self::Invalid,
1 => Self::Public,
2 => Self::AdminAndMember,
other => Self::Unknown(other),
}
}
}
impl From<SpeakPermission> for u8 {
fn from(speak_permission: SpeakPermission) -> Self {
match speak_permission {
SpeakPermission::Invalid => 0,
SpeakPermission::Public => 1,
SpeakPermission::AdminAndMember => 2,
SpeakPermission::Unknown(other) => other,
}
}
}
impl SpeakPermission {
pub fn from_u8(value: u8) -> Option<Self> {
Some(Self::from(value))
}
}
impl From<SpeakPermission> for u32 {
fn from(speak_permission: SpeakPermission) -> Self {
match speak_permission {
SpeakPermission::Invalid => 0,
SpeakPermission::Public => 1,
SpeakPermission::AdminAndMember => 2,
SpeakPermission::Unknown(value) => value as u32,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct ChannelPermissions {
#[serde(default)]
pub channel_id: Snowflake,
#[serde(default)]
pub user_id: Snowflake,
#[serde(default)]
pub permissions: String,
}
impl ChannelPermissions {
pub fn new() -> Self {
Self::default()
}
pub fn is_user_permission(&self) -> bool {
!self.user_id.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct ChannelRolesPermissions {
#[serde(default)]
pub channel_id: Snowflake,
#[serde(default)]
pub role_id: Snowflake,
#[serde(default)]
pub permissions: String,
}
impl ChannelRolesPermissions {
pub fn is_role_permission(&self) -> bool {
!self.role_id.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct UpdateChannelPermissions {
#[serde(skip_serializing_if = "Option::is_none")]
pub add: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub remove: Option<String>,
}
impl UpdateChannelPermissions {
pub fn new(add: Option<impl ToString>, remove: Option<impl ToString>) -> Self {
Self {
add: add.map(|value| value.to_string()),
remove: remove.map(|value| value.to_string()),
}
}
pub fn validate(&self) -> crate::error::Result<()> {
if let Some(add) = self.add.as_deref() {
add.parse::<u64>().map_err(|err| {
crate::error::BotError::invalid_data(format!("invalid parameter add: {err}"))
})?;
}
if let Some(remove) = self.remove.as_deref() {
remove.parse::<u64>().map_err(|err| {
crate::error::BotError::invalid_data(format!("invalid parameter remove: {err}"))
})?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_channel_creation() {
let channel = Channel::new();
assert!(channel.id.is_empty());
assert!(channel.name.is_empty());
assert!(channel.is_public()); }
#[test]
fn test_channel_types() {
let mut channel = Channel::new();
channel.channel_type = ChannelType::Text;
assert!(channel.is_text());
assert!(!channel.is_voice());
channel.channel_type = ChannelType::Voice;
assert!(channel.is_voice());
assert!(!channel.is_text());
channel.channel_type = ChannelType::Category;
assert!(channel.is_group());
}
#[test]
fn test_channel_type_conversion() {
assert_eq!(ChannelType::from(0), ChannelType::Text);
assert_eq!(u32::from(ChannelType::Text), 0);
assert_eq!(ChannelType::from(10005), ChannelType::Live);
assert_eq!(u32::from(ChannelType::Live), 10005);
assert_eq!(ChannelType::from(99999), ChannelType::Unknown(99999));
assert_eq!(u32::from(ChannelType::Unknown(99999)), 99999);
}
#[test]
fn test_private_types() {
let mut channel = Channel::new();
channel.private_type = PrivateType::Public;
assert!(channel.is_public());
assert!(!channel.is_admin_only());
channel.private_type = PrivateType::OnlyAdmin;
assert!(!channel.is_public());
assert!(channel.is_admin_only());
channel.private_type = PrivateType::AdminAndMember;
assert!(channel.is_specified_users_only());
}
#[test]
fn test_speak_permissions() {
let mut channel = Channel::new();
channel.speak_permission = SpeakPermission::Public;
assert!(channel.everyone_can_speak());
assert!(!channel.admin_only_speak());
channel.speak_permission = SpeakPermission::AdminAndMember;
assert!(!channel.everyone_can_speak());
assert!(channel.admin_only_speak());
}
#[test]
fn test_channel_mention() {
let mut channel = Channel::new();
channel.id = "123456789".to_string();
assert_eq!(channel.mention(), "<#123456789>");
}
#[test]
fn test_channel_permissions() {
let mut perms = ChannelPermissions::new();
assert!(!perms.is_user_permission());
perms.user_id = "user123".to_string();
assert!(perms.is_user_permission());
let role_perms = ChannelRolesPermissions {
role_id: "role123".to_string(),
..Default::default()
};
assert!(role_perms.is_role_permission());
}
#[test]
fn botgo_channel_uses_zero_values_for_missing_fields() {
let channel: Channel = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(channel.id, "");
assert_eq!(channel.guild_id, "");
assert_eq!(channel.name, "");
assert_eq!(channel.channel_type, ChannelType::Text);
assert_eq!(channel.sub_type, ChannelSubType::Chat);
assert_eq!(channel.private_type, PrivateType::Public);
assert_eq!(channel.speak_permission, SpeakPermission::Invalid);
assert!(channel.private_user_ids.is_empty());
}
#[test]
fn botgo_channel_decodes_large_type_values() {
let channel: Channel = serde_json::from_value(serde_json::json!({
"id": "channel-1",
"guild_id": "guild-1",
"name": "live",
"type": 10005,
"sub_type": 3,
"private_type": 2,
"speak_permission": 1,
"private_user_ids": ["user-1"],
"permissions": "2048"
}))
.unwrap();
assert_eq!(channel.id, "channel-1");
assert_eq!(channel.guild_id, "guild-1");
assert_eq!(channel.channel_type, ChannelType::Live);
assert_eq!(channel.sub_type, ChannelSubType::TeamGame);
assert_eq!(channel.private_type, PrivateType::AdminAndMember);
assert_eq!(channel.speak_permission, SpeakPermission::Public);
assert_eq!(channel.private_user_ids, ["user-1"]);
assert_eq!(channel.permissions, "2048");
}
#[test]
fn botgo_channel_permissions_are_separate_dtos() {
let user_permissions: ChannelPermissions = serde_json::from_value(serde_json::json!({
"channel_id": "channel-1",
"user_id": "user-1",
"permissions": "1024"
}))
.unwrap();
let role_permissions: ChannelRolesPermissions = serde_json::from_value(serde_json::json!({
"channel_id": "channel-1",
"role_id": "role-1",
"permissions": "2048"
}))
.unwrap();
assert_eq!(user_permissions.user_id, "user-1");
assert_eq!(user_permissions.permissions, "1024");
assert_eq!(role_permissions.role_id, "role-1");
assert_eq!(role_permissions.permissions, "2048");
}
}