use serde::{Deserialize, Serialize};
use std::fmt;
use std::sync::Arc;
#[derive(Debug, thiserror::Error)]
pub enum SdkError {
#[error("WebSocket error: {0}")]
WebSocket(String),
#[error("HTTP error: {0}")]
Http(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Decryption error: {0}")]
Decryption(String),
#[error("Authentication failed: {0}")]
Authentication(String),
#[error("Timeout: {0}")]
Timeout(String),
#[error("SDK error: {0}")]
Internal(String),
}
impl From<tokio_tungstenite::tungstenite::Error> for SdkError {
fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
SdkError::WebSocket(err.to_string())
}
}
impl From<reqwest::Error> for SdkError {
fn from(err: reqwest::Error) -> Self {
SdkError::Http(err.to_string())
}
}
impl From<serde_json::Error> for SdkError {
fn from(err: serde_json::Error) -> Self {
SdkError::Serialization(err.to_string())
}
}
pub trait Logger: Send + Sync {
fn debug(&self, message: &str);
fn info(&self, message: &str);
fn warn(&self, message: &str);
fn error(&self, message: &str);
}
#[derive(Clone)]
pub struct WSClientOptions {
pub bot_id: String,
pub secret: String,
pub reconnect_interval: u64,
pub max_reconnect_attempts: i32,
pub heartbeat_interval: u64,
pub request_timeout: u64,
pub ws_url: Option<String>,
pub logger: Option<Arc<dyn Logger>>,
}
impl Default for WSClientOptions {
fn default() -> Self {
Self {
bot_id: String::new(),
secret: String::new(),
reconnect_interval: 1000,
max_reconnect_attempts: 10,
heartbeat_interval: 30000,
request_timeout: 10000,
ws_url: None,
logger: None,
}
}
}
impl WSClientOptions {
pub fn new(bot_id: impl Into<String>, secret: impl Into<String>) -> Self {
Self {
bot_id: bot_id.into(),
secret: secret.into(),
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WsCmd;
impl WsCmd {
pub const SUBSCRIBE: &'static str = "aibot_subscribe";
pub const HEARTBEAT: &'static str = "ping";
pub const RESPONSE: &'static str = "aibot_respond_msg";
pub const RESPONSE_WELCOME: &'static str = "aibot_respond_welcome_msg";
pub const RESPONSE_UPDATE: &'static str = "aibot_respond_update_msg";
pub const SEND_MSG: &'static str = "aibot_send_msg";
pub const CALLBACK: &'static str = "aibot_msg_callback";
pub const EVENT_CALLBACK: &'static str = "aibot_event_callback";
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MessageType {
#[serde(rename = "text")]
Text,
#[serde(rename = "image")]
Image,
#[serde(rename = "mixed")]
Mixed,
#[serde(rename = "voice")]
Voice,
#[serde(rename = "file")]
File,
}
impl fmt::Display for MessageType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MessageType::Text => write!(f, "text"),
MessageType::Image => write!(f, "image"),
MessageType::Mixed => write!(f, "mixed"),
MessageType::Voice => write!(f, "voice"),
MessageType::File => write!(f, "file"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventType {
#[serde(rename = "enter_chat")]
EnterChat,
#[serde(rename = "template_card_event")]
TemplateCardEvent,
#[serde(rename = "feedback_event")]
FeedbackEvent,
}
impl fmt::Display for EventType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EventType::EnterChat => write!(f, "enter_chat"),
EventType::TemplateCardEvent => write!(f, "template_card_event"),
EventType::FeedbackEvent => write!(f, "feedback_event"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TemplateCardType {
#[serde(rename = "text_notice")]
TextNotice,
#[serde(rename = "news_notice")]
NewsNotice,
#[serde(rename = "button_interaction")]
ButtonInteraction,
#[serde(rename = "vote_interaction")]
VoteInteraction,
#[serde(rename = "multiple_interaction")]
MultipleInteraction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsFrame {
#[serde(skip_serializing_if = "Option::is_none")]
pub cmd: Option<String>,
pub headers: WsFrameHeaders,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub errcode: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub errmsg: Option<String>,
}
impl WsFrame {
pub fn response_url(&self) -> Option<String> {
self.body.as_ref().and_then(|b| {
b.as_object()?.get("response_url")?.as_str().map(String::from)
})
}
}
impl WsFrame {
pub fn new(cmd: impl Into<String>, headers: WsFrameHeaders) -> Self {
Self {
cmd: Some(cmd.into()),
headers,
body: None,
errcode: None,
errmsg: None,
}
}
pub fn with_body(mut self, body: serde_json::Value) -> Self {
self.body = Some(body);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsFrameHeaders {
pub req_id: String,
}
impl WsFrameHeaders {
pub fn new(req_id: impl Into<String>) -> Self {
Self {
req_id: req_id.into(),
}
}
}