use crate::api::BotApi;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum Format {
PlainText = 1,
Html = 2,
Markdown = 3,
Json = 4,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Text {
pub text: Option<String>,
}
impl Text {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PlatImage {
pub url: Option<String>,
pub width: Option<u32>,
pub height: Option<u32>,
pub image_id: Option<String>,
}
impl PlatImage {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Image {
#[serde(default)]
pub plat_image: PlatImage,
}
impl Image {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Cover {
pub url: Option<String>,
pub width: Option<u32>,
pub height: Option<u32>,
}
impl Cover {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PlatVideo {
pub url: Option<String>,
pub width: Option<u32>,
pub height: Option<u32>,
pub video_id: Option<String>,
#[serde(default)]
pub cover: Cover,
}
impl PlatVideo {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Video {
#[serde(default)]
pub plat_video: PlatVideo,
}
impl Video {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Url {
pub url: Option<String>,
pub desc: Option<String>,
}
impl Url {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Elem {
#[serde(rename = "type", default)]
pub element_type: Option<u8>,
#[serde(default)]
pub text: Option<Text>,
#[serde(default)]
pub image: Option<Image>,
#[serde(default)]
pub video: Option<Video>,
#[serde(default)]
pub url: Option<Url>,
}
impl Elem {
pub fn new(data: &Value) -> Self {
let mut elem: Self = serde_json::from_value(data.clone()).unwrap_or_default();
match elem.element_type {
Some(1) => {
elem.image = None;
elem.video = None;
elem.url = None;
}
Some(2) => {
elem.text = None;
elem.video = None;
elem.url = None;
}
Some(3) => {
elem.text = None;
elem.image = None;
elem.url = None;
}
Some(4) => {
elem.text = None;
elem.image = None;
elem.video = None;
}
_ => {
elem.text = None;
elem.image = None;
elem.video = None;
elem.url = None;
}
}
elem
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Paragraph {
#[serde(default)]
pub elems: Vec<Elem>,
pub props: Option<Value>,
}
impl Paragraph {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Title {
#[serde(default)]
pub paragraphs: Vec<Paragraph>,
}
impl Title {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Content {
#[serde(default)]
pub paragraphs: Vec<Paragraph>,
}
impl Content {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ThreadInfo {
pub title: Option<String>,
pub content: Option<String>,
pub thread_id: Option<String>,
pub date_time: Option<String>,
}
impl ThreadInfo {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PostInfo {
pub thread_id: Option<String>,
pub post_id: Option<String>,
pub content: Option<String>,
pub date_time: Option<String>,
}
impl PostInfo {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ReplyInfo {
pub thread_id: Option<String>,
pub post_id: Option<String>,
pub reply_id: Option<String>,
pub content: Option<String>,
pub date_time: Option<String>,
}
impl ReplyInfo {
pub fn new(data: &Value) -> Self {
serde_json::from_value(data.clone()).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Thread {
#[serde(skip)]
api: BotApi,
pub thread_info: ThreadInfo,
pub channel_id: Option<String>,
pub guild_id: Option<String>,
pub author_id: Option<String>,
pub event_id: Option<String>,
}
#[derive(Debug, Default, Deserialize)]
struct ThreadWire {
#[serde(default)]
thread_info: ThreadInfo,
#[serde(default)]
channel_id: Option<String>,
#[serde(default)]
guild_id: Option<String>,
#[serde(default)]
author_id: Option<String>,
}
impl Thread {
pub fn new(api: BotApi, event_id: Option<String>, data: &Value) -> Self {
let wire: ThreadWire = serde_json::from_value(data.clone()).unwrap_or_default();
Self {
api,
thread_info: wire.thread_info,
channel_id: wire.channel_id,
guild_id: wire.guild_id,
author_id: wire.author_id,
event_id,
}
}
pub fn api(&self) -> &BotApi {
&self.api
}
}
impl std::fmt::Display for Thread {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Thread {{ channel_id: {:?}, guild_id: {:?}, author_id: {:?}, event_id: {:?} }}",
self.channel_id, self.guild_id, self.author_id, self.event_id
)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Post {
#[serde(skip)]
api: BotApi,
pub guild_id: Option<String>,
pub channel_id: Option<String>,
pub author_id: Option<String>,
pub post_info: PostInfo,
pub event_id: Option<String>,
}
#[derive(Debug, Default, Deserialize)]
struct PostWire {
#[serde(default)]
guild_id: Option<String>,
#[serde(default)]
channel_id: Option<String>,
#[serde(default)]
author_id: Option<String>,
#[serde(default)]
post_info: PostInfo,
}
impl Post {
pub fn new(api: BotApi, event_id: Option<String>, data: &Value) -> Self {
let wire: PostWire = serde_json::from_value(data.clone()).unwrap_or_default();
Self {
api,
guild_id: wire.guild_id,
channel_id: wire.channel_id,
author_id: wire.author_id,
post_info: wire.post_info,
event_id,
}
}
pub fn api(&self) -> &BotApi {
&self.api
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Reply {
#[serde(skip)]
api: BotApi,
pub guild_id: Option<String>,
pub channel_id: Option<String>,
pub author_id: Option<String>,
pub reply_info: ReplyInfo,
pub event_id: Option<String>,
}
#[derive(Debug, Default, Deserialize)]
struct ReplyWire {
#[serde(default)]
guild_id: Option<String>,
#[serde(default)]
channel_id: Option<String>,
#[serde(default)]
author_id: Option<String>,
#[serde(default)]
reply_info: ReplyInfo,
}
impl Reply {
pub fn new(api: BotApi, event_id: Option<String>, data: &Value) -> Self {
let wire: ReplyWire = serde_json::from_value(data.clone()).unwrap_or_default();
Self {
api,
guild_id: wire.guild_id,
channel_id: wire.channel_id,
author_id: wire.author_id,
reply_info: wire.reply_info,
event_id,
}
}
pub fn api(&self) -> &BotApi {
&self.api
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct ForumAuditResult {
#[serde(default)]
pub task_id: String,
#[serde(default)]
pub guild_id: String,
#[serde(default)]
pub channel_id: String,
#[serde(default)]
pub author_id: String,
#[serde(default)]
pub thread_id: String,
#[serde(default)]
pub post_id: String,
#[serde(default)]
pub reply_id: String,
#[serde(default, rename = "type")]
pub publish_type: u32,
#[serde(default)]
pub result: u32,
#[serde(default)]
pub err_msg: String,
#[serde(default)]
pub date_time: String,
#[serde(skip)]
pub event_id: Option<String>,
}
impl ForumAuditResult {
pub fn new(event_id: Option<String>, data: &Value) -> Self {
let mut result = serde_json::from_value::<Self>(data.clone()).unwrap_or_default();
result.event_id = event_id;
result
}
}
#[derive(Debug, Clone, Serialize)]
pub struct OpenThread {
#[serde(skip)]
api: BotApi,
pub channel_id: Option<String>,
pub guild_id: Option<String>,
pub author_id: Option<String>,
pub thread_info: Option<ThreadInfo>,
pub post_info: Option<PostInfo>,
pub reply_info: Option<ReplyInfo>,
pub event_id: Option<String>,
}
#[derive(Debug, Default, Deserialize)]
struct OpenThreadWire {
#[serde(default)]
guild_id: Option<String>,
#[serde(default)]
channel_id: Option<String>,
#[serde(default)]
author_id: Option<String>,
#[serde(default)]
thread_info: Option<ThreadInfo>,
#[serde(default)]
post_info: Option<PostInfo>,
#[serde(default)]
reply_info: Option<ReplyInfo>,
}
impl OpenThread {
pub fn new(api: BotApi, data: &Value) -> Self {
let wire: OpenThreadWire = serde_json::from_value(data.clone()).unwrap_or_default();
Self {
api,
event_id: None,
guild_id: wire.guild_id,
channel_id: wire.channel_id,
author_id: wire.author_id,
thread_info: wire.thread_info,
post_info: wire.post_info,
reply_info: wire.reply_info,
}
}
pub fn api(&self) -> &BotApi {
&self.api
}
}
impl std::fmt::Display for OpenThread {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"OpenThread {{ channel_id: {:?}, guild_id: {:?}, author_id: {:?}, event_id: {:?} }}",
self.channel_id, self.guild_id, self.author_id, self.event_id
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format() {
assert_eq!(Format::PlainText as u8, 1);
assert_eq!(Format::Html as u8, 2);
assert_eq!(Format::Markdown as u8, 3);
assert_eq!(Format::Json as u8, 4);
}
#[test]
fn test_text_creation() {
let data = serde_json::json!({
"text": "Hello, world!"
});
let text = Text::new(&data);
assert_eq!(text.text, Some("Hello, world!".to_string()));
}
#[test]
fn thread_info_keeps_title_and_content_as_strings() {
let data = serde_json::json!({
"thread_id": "thread-1",
"title": "{\"paragraphs\":[]}",
"content": "{\"paragraphs\":[{\"elems\":[]}]}",
"date_time": "2024-01-02T03:04:05+08:00"
});
let thread_info = ThreadInfo::new(&data);
assert_eq!(thread_info.thread_id.as_deref(), Some("thread-1"));
assert_eq!(thread_info.title.as_deref(), Some("{\"paragraphs\":[]}"));
assert_eq!(
thread_info.content.as_deref(),
Some("{\"paragraphs\":[{\"elems\":[]}]}")
);
assert_eq!(
thread_info.date_time.as_deref(),
Some("2024-01-02T03:04:05+08:00")
);
let value = serde_json::to_value(&thread_info).unwrap();
assert_eq!(value["title"], serde_json::json!("{\"paragraphs\":[]}"));
assert_eq!(
value["content"],
serde_json::json!("{\"paragraphs\":[{\"elems\":[]}]}")
);
}
#[test]
fn forum_audit_result_serializes_zero_value_strings() {
let data = serde_json::json!({
"task_id": "task-1",
"guild_id": "guild-1",
"channel_id": "channel-1",
"author_id": "author-1",
"thread_id": "thread-1",
"post_id": "",
"reply_id": "",
"type": 1,
"result": 2,
"err_msg": "",
"date_time": "2024-01-02T03:04:05+08:00"
});
let parsed = ForumAuditResult::new(Some("event-1".into()), &data);
assert_eq!(parsed.task_id, "task-1");
assert_eq!(parsed.publish_type, 1);
assert_eq!(parsed.result, 2);
assert_eq!(parsed.event_id.as_deref(), Some("event-1"));
let value = serde_json::to_value(ForumAuditResult::default()).unwrap();
assert_eq!(value["task_id"], "");
assert_eq!(value["guild_id"], "");
assert_eq!(value["type"], 0);
assert_eq!(value["result"], 0);
assert_eq!(value["date_time"], "");
assert!(value.get("event_id").is_none());
}
}