use crate::capability::Protocol;
use crate::id::{MessageId, Uin};
use crate::resource::{Media, ResourceSource};
use serde_json::{Map, Value};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ImageSubType {
Normal,
Sticker,
Flash,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ContactKind {
Friend,
Group,
}
#[derive(Clone, Debug)]
pub enum MusicShare {
Platform { ty: String, id: String },
Custom { url: String, audio: String, title: String, content: Option<String>, image: Option<String> },
}
#[derive(Clone, Debug)]
pub enum Forward {
Ref {
id: String,
title: Option<String>,
preview: Vec<String>,
summary: Option<String>,
},
Nodes {
nodes: Vec<ForwardNode>,
title: Option<String>,
summary: Option<String>,
prompt: Option<String>,
news: Vec<String>,
source: Option<String>,
},
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MediaSendHints {
pub cache: Option<bool>,
pub proxy: Option<bool>,
pub timeout: Option<i32>,
}
#[derive(Clone, Debug)]
pub struct ForwardNode {
pub user: Uin,
pub name: String,
pub content: Vec<Segment>,
pub time: Option<i64>,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Segment {
Text(String),
Mention {
user: Uin,
name: Option<String>,
},
MentionAll,
Face {
id: String,
large: bool,
result_id: Option<String>,
chain_count: Option<i32>,
sub_type: Option<i32>,
},
Reply {
id: MessageId,
sender: Option<Uin>,
time: Option<i64>,
quoted: Vec<Segment>,
},
Image {
res: Media,
sub_type: ImageSubType,
hints: MediaSendHints,
},
Record {
res: Media,
magic: Option<i32>,
hints: MediaSendHints,
},
Video {
res: Media,
hints: MediaSendHints,
thumb: Option<ResourceSource>,
},
File {
id: String,
name: String,
size: u64,
hash: Option<String>,
url: Option<String>,
},
Forward(Forward),
MarketFace {
package_id: i32,
emoji_id: String,
key: String,
summary: Option<String>,
url: Option<String>,
},
LightApp {
app_name: Option<String>,
payload: String,
},
Xml {
service_id: Option<i32>,
payload: String,
},
Poke {
kind: i32,
id: i32,
strength: Option<i32>,
name: Option<String>,
},
Contact {
kind: ContactKind,
id: Uin,
},
Location {
lat: f64,
lon: f64,
title: Option<String>,
content: Option<String>,
},
Music(MusicShare),
Share {
url: String,
title: String,
content: Option<String>,
image: Option<String>,
},
Rps {
result: Option<i32>,
},
Dice {
result: Option<i32>,
},
Shake,
Anonymous {
ignore: Option<bool>,
},
Keyboard {
content: Value,
},
Markdown {
content: String,
},
LongMsg {
id: String,
},
FlashFile {
title: Option<String>,
file_set_id: String,
scene_type: i32,
},
MiniApp {
payload: String,
},
OnlineFile {
msg_id: String,
element_id: String,
file_name: String,
file_size: String,
is_dir: bool,
},
FlashTransfer {
file_set_id: String,
},
Raw {
protocol: Protocol,
kind: String,
data: Map<String, Value>,
},
}
impl Segment {
pub fn text(s: impl Into<String>) -> Self {
Segment::Text(s.into())
}
pub fn at(user: impl Into<Uin>) -> Self {
Segment::Mention { user: user.into(), name: None }
}
pub fn at_all() -> Self {
Segment::MentionAll
}
pub fn face(id: impl Into<String>) -> Self {
Segment::Face { id: id.into(), large: false, result_id: None, chain_count: None, sub_type: None }
}
pub fn image_bytes(b: impl Into<bytes::Bytes>) -> Self {
Segment::Image {
res: Media::from_source(ResourceSource::bytes(b)),
sub_type: ImageSubType::Normal,
hints: MediaSendHints::default(),
}
}
pub fn image_url(u: impl Into<String>) -> Self {
Segment::Image {
res: Media::from_source(ResourceSource::url(u)),
sub_type: ImageSubType::Normal,
hints: MediaSendHints::default(),
}
}
pub fn image_path(p: impl Into<std::path::PathBuf>) -> Self {
Segment::Image {
res: Media::from_source(ResourceSource::path(p)),
sub_type: ImageSubType::Normal,
hints: MediaSendHints::default(),
}
}
pub fn reply(id: MessageId) -> Self {
Segment::Reply { id, sender: None, time: None, quoted: Vec::new() }
}
pub fn xml(payload: impl Into<String>) -> Self {
Segment::Xml { service_id: None, payload: payload.into() }
}
pub fn poke(kind: i32, id: i32) -> Self {
Segment::Poke { kind, id, strength: None, name: None }
}
pub fn location(lat: f64, lon: f64) -> Self {
Segment::Location { lat, lon, title: None, content: None }
}
pub fn music(platform: impl Into<String>, id: impl Into<String>) -> Self {
Segment::Music(MusicShare::Platform { ty: platform.into(), id: id.into() })
}
pub fn share(url: impl Into<String>, title: impl Into<String>) -> Self {
Segment::Share { url: url.into(), title: title.into(), content: None, image: None }
}
pub fn anonymous(ignore: bool) -> Self {
Segment::Anonymous { ignore: Some(ignore) }
}
pub fn image_url_with_summary(u: impl Into<String>, summary: impl Into<String>) -> Self {
let mut res = Media::from_source(ResourceSource::url(u));
res.summary = Some(summary.into());
Segment::Image { res, sub_type: ImageSubType::Normal, hints: MediaSendHints::default() }
}
pub fn video_url(u: impl Into<String>) -> Self {
Segment::Video {
res: Media::from_source(ResourceSource::url(u)),
hints: MediaSendHints::default(),
thumb: None,
}
}
pub fn as_text(&self) -> Option<&str> {
match self {
Segment::Text(t) => Some(t),
_ => None,
}
}
pub fn forward(nodes: Vec<ForwardNode>) -> Self {
Segment::Forward(Forward::nodes(nodes))
}
}
impl Forward {
pub fn nodes(nodes: Vec<ForwardNode>) -> Self {
Forward::Nodes { nodes, title: None, summary: None, prompt: None, news: Vec::new(), source: None }
}
pub fn title(mut self, t: impl Into<String>) -> Self {
if let Forward::Nodes { title, .. } = &mut self {
*title = Some(t.into());
}
self
}
}
impl ForwardNode {
pub fn new(user: impl Into<Uin>, name: impl Into<String>, content: Vec<Segment>) -> Self {
ForwardNode { user: user.into(), name: name.into(), content, time: None }
}
pub fn text(user: impl Into<Uin>, name: impl Into<String>, text: impl Into<String>) -> Self {
ForwardNode::new(user, name, vec![Segment::text(text)])
}
pub fn at_time(mut self, t: i64) -> Self {
self.time = Some(t);
self
}
pub fn chunk_items<I, S>(
user: impl Into<Uin>,
name: impl Into<String>,
items: I,
sep: &str,
max_chars: usize,
) -> Vec<ForwardNode>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let user = user.into();
let name = name.into();
let sep_len = sep.chars().count();
let mut nodes = Vec::new();
let mut buf = String::new();
let mut len = 0usize;
for item in items {
let item: String = item.into();
let il = item.chars().count();
if !buf.is_empty() && len + sep_len + il > max_chars {
nodes.push(ForwardNode::text(user, name.clone(), std::mem::take(&mut buf)));
len = 0;
}
if !buf.is_empty() {
buf.push_str(sep);
len += sep_len;
}
buf.push_str(&item);
len += il;
}
if !buf.is_empty() {
nodes.push(ForwardNode::text(user, name, buf));
}
nodes
}
pub fn chunk_text(
user: impl Into<Uin>,
name: impl Into<String>,
text: impl Into<String>,
max_chars: usize,
) -> Vec<ForwardNode> {
let text = text.into();
let lines: Vec<&str> = text.lines().collect();
Self::chunk_items(user, name, lines, "\n", max_chars)
}
}