use abi_stable::std_types::{RString, RVec};
use std::sync::Mutex;
pub fn expected_api_version() -> RString {
RString::from("0.3")
}
pub fn is_compatible_api_version(version: &str) -> bool {
version == "0.1" || version == "0.2" || version == "0.3"
}
pub const ACTION_IGNORE: i32 = 0;
pub const ACTION_REPLY: i32 = 1;
pub const ACTION_APPROVE: i32 = 2;
pub const ACTION_REJECT: i32 = 3;
#[repr(C)]
#[derive(Clone)]
pub struct DynamicActionResponse {
pub action_kind: i32,
pub message: RString,
pub segments_json: RString,
}
impl DynamicActionResponse {
pub fn text_reply(text: &str) -> Self {
Self {
action_kind: ACTION_REPLY,
message: RString::from(text),
segments_json: RString::new(),
}
}
pub fn rich_reply(segments_json: &str) -> Self {
Self {
action_kind: ACTION_REPLY,
message: RString::new(),
segments_json: RString::from(segments_json),
}
}
pub fn ignore() -> Self {
Self {
action_kind: ACTION_IGNORE,
message: RString::new(),
segments_json: RString::new(),
}
}
pub fn approve(remark: &str) -> Self {
Self {
action_kind: ACTION_APPROVE,
message: RString::from(remark),
segments_json: RString::new(),
}
}
pub fn reject(reason: &str) -> Self {
Self {
action_kind: ACTION_REJECT,
message: RString::from(reason),
segments_json: RString::new(),
}
}
}
#[repr(C)]
pub struct CommandRequest {
pub args: RString,
pub command_name: RString,
pub sender_id: RString,
pub group_id: RString,
pub raw_event_json: RString,
pub sender_nickname: RString,
pub message_id: RString,
pub timestamp: i64,
}
#[repr(C)]
pub struct CommandResponse {
pub action: DynamicActionResponse,
}
impl CommandResponse {
pub fn text(text: &str) -> Self {
Self {
action: DynamicActionResponse::text_reply(text),
}
}
pub fn builder() -> ReplyBuilder {
ReplyBuilder::new()
}
pub fn ignore() -> Self {
Self {
action: DynamicActionResponse::ignore(),
}
}
}
pub struct ReplyBuilder {
segments: Vec<String>,
}
impl ReplyBuilder {
pub fn new() -> Self {
Self {
segments: Vec::new(),
}
}
pub fn text(mut self, text: &str) -> Self {
let escaped = text
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
self.segments.push(format!(
r#"{{"type":"text","data":{{"text":"{}"}}}}"#,
escaped
));
self
}
pub fn at(mut self, user_id: &str) -> Self {
self.segments.push(format!(
r#"{{"type":"at","data":{{"qq":"{}"}}}}"#,
user_id
));
self
}
pub fn at_all(mut self) -> Self {
self.segments
.push(r#"{"type":"at","data":{"qq":"all"}}"#.to_string());
self
}
pub fn face(mut self, id: i32) -> Self {
self.segments.push(format!(
r#"{{"type":"face","data":{{"id":"{}"}}}}"#,
id
));
self
}
pub fn image_url(mut self, url: &str) -> Self {
let escaped = url.replace('\\', "\\\\").replace('"', "\\\"");
self.segments.push(format!(
r#"{{"type":"image","data":{{"file":"{}"}}}}"#,
escaped
));
self
}
pub fn image_base64(mut self, base64: &str) -> Self {
self.segments.push(format!(
r#"{{"type":"image","data":{{"file":"base64://{}"}}}}"#,
base64
));
self
}
pub fn record(mut self, file: &str) -> Self {
let escaped = file.replace('\\', "\\\\").replace('"', "\\\"");
self.segments.push(format!(
r#"{{"type":"record","data":{{"file":"{}"}}}}"#,
escaped
));
self
}
pub fn reply(mut self, message_id: &str) -> Self {
self.segments.push(format!(
r#"{{"type":"reply","data":{{"id":"{}"}}}}"#,
message_id
));
self
}
pub fn build(self) -> CommandResponse {
let json = format!("[{}]", self.segments.join(","));
CommandResponse {
action: DynamicActionResponse::rich_reply(&json),
}
}
pub fn build_auto(self) -> CommandResponse {
self.build()
}
}
impl Default for ReplyBuilder {
fn default() -> Self {
Self::new()
}
}
#[repr(C)]
pub struct InterceptorRequest {
pub bot_id: RString,
pub sender_id: RString,
pub group_id: RString,
pub message_text: RString,
pub raw_event_json: RString,
pub sender_nickname: RString,
pub message_id: RString,
pub timestamp: i64,
}
#[repr(C)]
pub struct InterceptorResponse {
pub allow: i32,
}
impl InterceptorResponse {
pub fn allow() -> Self {
Self { allow: 1 }
}
pub fn block() -> Self {
Self { allow: 0 }
}
}
#[repr(C)]
#[derive(Clone)]
pub struct InterceptorDescriptorEntry {
pub pre_handle_symbol: RString,
pub after_completion_symbol: RString,
}
#[repr(C)]
pub struct NoticeRequest {
pub route: RString,
pub raw_event_json: RString,
}
#[repr(C)]
pub struct NoticeResponse {
pub action: DynamicActionResponse,
}
#[repr(C)]
#[derive(Clone)]
pub struct CommandDescriptorEntry {
pub name: RString,
pub description: RString,
pub callback_symbol: RString,
pub aliases: RString,
pub category: RString,
pub required_role: RString,
pub scope: RString,
}
#[repr(C)]
#[derive(Clone)]
pub struct RouteDescriptorEntry {
pub kind: RString,
pub route: RString,
pub callback_symbol: RString,
}
#[repr(C)]
pub struct PluginDescriptor {
pub plugin_id: RString,
pub plugin_version: RString,
pub api_version: RString,
pub command_name: RString,
pub command_description: RString,
pub notice_route: RString,
pub request_route: RString,
pub meta_route: RString,
pub commands: RVec<CommandDescriptorEntry>,
pub routes: RVec<RouteDescriptorEntry>,
pub interceptors: RVec<InterceptorDescriptorEntry>,
}
impl PluginDescriptor {
pub fn new(id: &str, version: &str) -> Self {
Self {
plugin_id: RString::from(id),
plugin_version: RString::from(version),
api_version: RString::from("0.3"),
command_name: RString::new(),
command_description: RString::new(),
notice_route: RString::new(),
request_route: RString::new(),
meta_route: RString::new(),
commands: RVec::new(),
routes: RVec::new(),
interceptors: RVec::new(),
}
}
pub fn add_command(
mut self,
name: &str,
description: &str,
callback_symbol: &str,
) -> Self {
self.commands.push(CommandDescriptorEntry {
name: RString::from(name),
description: RString::from(description),
callback_symbol: RString::from(callback_symbol),
aliases: RString::new(),
category: RString::new(),
required_role: RString::new(),
scope: RString::new(),
});
self
}
pub fn add_command_full(mut self, entry: CommandDescriptorEntry) -> Self {
self.commands.push(entry);
self
}
pub fn add_interceptor(
mut self,
pre_handle_symbol: &str,
after_completion_symbol: &str,
) -> Self {
self.interceptors.push(InterceptorDescriptorEntry {
pre_handle_symbol: RString::from(pre_handle_symbol),
after_completion_symbol: RString::from(after_completion_symbol),
});
self
}
pub fn add_route(
mut self,
kind: &str,
route: &str,
callback_symbol: &str,
) -> Self {
self.routes.push(RouteDescriptorEntry {
kind: RString::from(kind),
route: RString::from(route),
callback_symbol: RString::from(callback_symbol),
});
self
}
}
#[repr(C)]
pub struct PluginInitConfig {
pub plugin_id: RString,
pub config_json: RString,
pub plugin_dir: RString,
pub data_dir: RString,
}
#[repr(C)]
pub struct PluginInitResult {
pub code: i32,
pub error_message: RString,
}
impl PluginInitResult {
pub fn ok() -> Self {
Self {
code: 0,
error_message: RString::new(),
}
}
pub fn err(message: &str) -> Self {
Self {
code: 1,
error_message: RString::from(message),
}
}
}
#[repr(C)]
#[derive(Clone)]
pub struct SendAction {
pub message_type: RString,
pub target_id: RString,
pub message: RString,
pub segments_json: RString,
}
static SEND_QUEUE: Mutex<Vec<SendAction>> = Mutex::new(Vec::new());
pub fn drain_send_queue() -> Vec<SendAction> {
SEND_QUEUE
.lock()
.map(|mut q| q.drain(..).collect())
.unwrap_or_default()
}
pub struct BotApi;
impl BotApi {
pub fn send_private_msg(user_id: &str, text: &str) {
Self::push(SendAction {
message_type: RString::from("private"),
target_id: RString::from(user_id),
message: RString::from(text),
segments_json: RString::new(),
});
}
pub fn send_group_msg(group_id: &str, text: &str) {
Self::push(SendAction {
message_type: RString::from("group"),
target_id: RString::from(group_id),
message: RString::from(text),
segments_json: RString::new(),
});
}
pub fn send_private_rich(user_id: &str, segments_json: &str) {
Self::push(SendAction {
message_type: RString::from("private"),
target_id: RString::from(user_id),
message: RString::new(),
segments_json: RString::from(segments_json),
});
}
pub fn send_group_rich(group_id: &str, segments_json: &str) {
Self::push(SendAction {
message_type: RString::from("group"),
target_id: RString::from(group_id),
message: RString::new(),
segments_json: RString::from(segments_json),
});
}
fn push(action: SendAction) {
if let Ok(mut q) = SEND_QUEUE.lock() {
q.push(action);
}
}
}
pub struct SendBuilder {
message_type: String,
target_id: String,
segments: Vec<String>,
}
impl SendBuilder {
pub fn group(group_id: &str) -> Self {
Self {
message_type: "group".to_string(),
target_id: group_id.to_string(),
segments: Vec::new(),
}
}
pub fn private(user_id: &str) -> Self {
Self {
message_type: "private".to_string(),
target_id: user_id.to_string(),
segments: Vec::new(),
}
}
pub fn text(mut self, text: &str) -> Self {
let escaped = text
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
self.segments.push(format!(
r#"{{"type":"text","data":{{"text":"{}"}}}}"#,
escaped
));
self
}
pub fn at(mut self, user_id: &str) -> Self {
self.segments.push(format!(
r#"{{"type":"at","data":{{"qq":"{}"}}}}"#,
user_id
));
self
}
pub fn at_all(mut self) -> Self {
self.segments
.push(r#"{"type":"at","data":{"qq":"all"}}"#.to_string());
self
}
pub fn face(mut self, id: i32) -> Self {
self.segments.push(format!(
r#"{{"type":"face","data":{{"id":"{}"}}}}"#,
id
));
self
}
pub fn image_url(mut self, url: &str) -> Self {
let escaped = url.replace('\\', "\\\\").replace('"', "\\\"");
self.segments.push(format!(
r#"{{"type":"image","data":{{"file":"{}"}}}}"#,
escaped
));
self
}
pub fn image_base64(mut self, base64: &str) -> Self {
self.segments.push(format!(
r#"{{"type":"image","data":{{"file":"base64://{}"}}}}"#,
base64
));
self
}
pub fn send(self) {
let json = format!("[{}]", self.segments.join(","));
BotApi::push(SendAction {
message_type: RString::from(self.message_type),
target_id: RString::from(self.target_id),
message: RString::new(),
segments_json: RString::from(json),
});
}
}