use super::Api;
#[cfg(feature = "html")] use html_parser::{Dom, Element};
use s2rs_derive::Forwarder;
use serde_json::json;
use super::utils::RequestBuilderUtils;
#[derive(Debug)]
pub enum CommentContentFragment {
Link {
to: String,
content: String,
},
Text(String),
Emoji(String)
}
#[derive(Debug)]
pub struct CommentContent(pub Vec<CommentContentFragment>);
impl CommentContent {
#[cfg(feature = "html")]
fn try_from_html(element: &Element) -> Option<Self> {
use crate::html::ElementUtils;
fn get_link_data(element: &Element) -> Option<(String, String)> {
if let Some(text) = element.get_text() {
Some((text.to_owned(), element.get_attribute("href")?))
} else {
get_link_data(element.children.first()?.element()?)
}
}
let mut fragments = Vec::new();
for node in &element.children {
if let Some(text) = node.text() {
fragments.push(CommentContentFragment::Text(text.to_owned()));
} else if let Some(element) = node.element() {
match element.name.as_str() {
"a" => {
let (content, to) = get_link_data(element)?;
fragments.push(CommentContentFragment::Link { content, to })
}
"img" => fragments.push(CommentContentFragment::Emoji(element.get_attribute("src")?)),
_ => None?
}
}
}
Some(Self(fragments))
}
}
#[derive(Debug)]
pub struct UserReply {
pub id: u64,
pub profile_name: String,
pub author_name: String,
pub author_id: u64,
pub avatar_url: String,
pub content: CommentContent,
pub created_at: String,
}
impl UserReply {
#[cfg(feature = "html")]
fn try_from_html(element: &Element, profile_name: String) -> Option<Self> {
use crate::html::ElementUtils;
let info = element.child_by_class("comment")?;
let inner_info = info.child_by_class("info")?;
let extra_info = inner_info.children.get(2)?.element()?;
Some(Self {
avatar_url: info.child_by_id("comment-user")?.child_by_class("avatar")?.get_attribute("src")?,
author_name: inner_info.child_by_class("name")?.child_by_name("a")?.get_text()?.to_owned(),
content: CommentContent::try_from_html(inner_info.child_by_class("content")?)?,
created_at: extra_info.child_by_class("time")?.get_attribute("title")?,
author_id: extra_info.child_by_name("a")?.get_attribute("data-commentee-id")?.parse().ok()?,
id: info.get_attribute("data-comment-id")?.parse().ok()?,
profile_name
})
}
}
#[derive(Debug)]
pub struct UserComment {
pub id: u64,
pub profile_name: String,
pub author_name: String,
pub author_id: u64,
pub avatar_url: String,
pub content: CommentContent,
pub created_at: String,
pub replies: Vec<UserReply>,
}
impl UserComment {
#[cfg(feature = "html")]
fn try_from_html(element: &Element, profile_name: String) -> Option<Self> {
use crate::html::ElementUtils;
let info = element.child_by_class("comment")?;
let inner_info = info.child_by_class("info")?;
let extra_info = inner_info.children.get(2)?.element()?;
let mut replies = Vec::new();
for node in &element.child_by_class("replies")?.children {
if let Some(element) = node.element() {
if element.classes.contains(&"reply".to_string()) {
replies.push(UserReply::try_from_html(element, profile_name.clone())?);
}
}
}
Some(Self {
avatar_url: info.child_by_id("comment-user")?.child_by_class("avatar")?.get_attribute("src")?,
author_name: inner_info.child_by_class("name")?.child_by_name("a")?.get_text()?.to_owned(),
content: CommentContent::try_from_html(inner_info.child_by_class("content")?)?,
created_at: extra_info.child_by_class("time")?.get_attribute("title")?,
author_id: extra_info.child_by_name("a")?.get_attribute("data-commentee-id")?.parse().ok()?,
id: info.get_attribute("data-comment-id")?.parse().ok()?,
profile_name,
replies
})
}
}
#[derive(Debug, Forwarder)]
pub enum GetUserCommentsError {
Parsing,
#[forward(reqwest::Error)]
This(super::Error)
}
impl Api {
#[cfg(feature = "html")]
pub async fn user_comments(&self, name: &str, page: Option<u8>) -> Result<Vec<UserComment>, GetUserCommentsError> {
let response = self.get_site_api(&format!["comments/user/{name}/"]).query(&[("page", page)]).send_success().await?;
let data = response.text().await?;
let dom = Dom::parse(&data).ok().ok_or(GetUserCommentsError::Parsing)?;
let mut result = Vec::new();
for node in &dom.children {
if let Some(element) = node.element() {
if element.classes.contains(&"top-level-reply".to_string()) {
result.push(UserComment::try_from_html(element, name.to_owned()).ok_or(GetUserCommentsError::Parsing)?);
}
}
}
Ok(result)
}
}
impl Api {
pub async fn report_user_comment(&self, id: u64) -> super::Result<()> {
self.post_site_api(&format!["comments/user/{}/rep/", &self.name]).json(&json!({
"id": id.to_string()
})).send_success().await?;
Ok(())
}
}