219 lines
5.5 KiB
Rust
219 lines
5.5 KiB
Rust
use magnetar_core::web_model::acct::Acct;
|
|
use magnetar_sdk::mmm;
|
|
use magnetar_sdk::mmm::Token;
|
|
use percent_encoding::percent_decode_str;
|
|
use std::fmt::Display;
|
|
use std::str::FromStr;
|
|
use thiserror::Error;
|
|
use url::Host;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ValidName(String);
|
|
|
|
impl FromStr for ValidName {
|
|
type Err = FediverseTagParseError;
|
|
|
|
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
|
if name
|
|
.chars()
|
|
.any(|c| !c.is_alphanumeric() && c != '-' && c != '_' && c != '.')
|
|
{
|
|
return Err(FediverseTagParseError::InvalidChar(name.to_owned()));
|
|
}
|
|
|
|
Ok(Self(name.to_owned()))
|
|
}
|
|
}
|
|
|
|
impl AsRef<str> for ValidName {
|
|
fn as_ref(&self) -> &str {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl Display for ValidName {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct FediverseTag {
|
|
pub name: ValidName,
|
|
pub host: Option<Host>,
|
|
}
|
|
|
|
impl FediverseTag {
|
|
pub fn from_parts(parts: (&str, Option<&str>)) -> Result<Self, FediverseTagParseError> {
|
|
Self::try_from(FediverseTagDisplay::from_parts(parts)?)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<FediverseTagDisplay> for FediverseTag {
|
|
type Error = FediverseTagParseError;
|
|
|
|
fn try_from(
|
|
FediverseTagDisplay { name, host }: FediverseTagDisplay,
|
|
) -> Result<Self, FediverseTagParseError> {
|
|
Ok(FediverseTag {
|
|
name,
|
|
host: host.as_deref().map(Host::parse).transpose()?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&FediverseTagDisplay> for FediverseTag {
|
|
type Error = FediverseTagParseError;
|
|
|
|
fn try_from(
|
|
FediverseTagDisplay { name, host }: &FediverseTagDisplay,
|
|
) -> Result<Self, FediverseTagParseError> {
|
|
Ok(FediverseTag {
|
|
name: name.clone(),
|
|
host: host.as_deref().map(Host::parse).transpose()?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl From<&FediverseTag> for Acct {
|
|
fn from(tag: &FediverseTag) -> Self {
|
|
Acct::new(tag.to_string().into())
|
|
}
|
|
}
|
|
|
|
impl FromStr for FediverseTag {
|
|
type Err = FediverseTagParseError;
|
|
|
|
fn from_str(tag: &str) -> Result<Self, Self::Err> {
|
|
Self::try_from(FediverseTagDisplay::from_str(tag)?)
|
|
}
|
|
}
|
|
|
|
impl Display for FediverseTag {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
if let Some(ref host) = self.host {
|
|
write!(f, "{}@{}", self.name, host)
|
|
} else {
|
|
write!(f, "{}", self.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct FediverseTagDisplay {
|
|
pub name: ValidName,
|
|
pub host: Option<String>,
|
|
}
|
|
|
|
impl FediverseTagDisplay {
|
|
pub fn from_parts((name, host): (&str, Option<&str>)) -> Result<Self, FediverseTagParseError> {
|
|
Ok(FediverseTagDisplay {
|
|
name: name.parse()?,
|
|
host: host.map(str::to_owned),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl From<&FediverseTag> for FediverseTagDisplay {
|
|
fn from(FediverseTag { name, host }: &FediverseTag) -> Self {
|
|
FediverseTagDisplay {
|
|
name: name.clone(),
|
|
host: host
|
|
.as_ref()
|
|
.map(Host::to_string)
|
|
.as_deref()
|
|
.map(idna::domain_to_unicode)
|
|
.map(|v| v.0),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&Acct> for FediverseTagDisplay {
|
|
type Error = FediverseTagParseError;
|
|
|
|
fn try_from(acct: &Acct) -> Result<Self, Self::Error> {
|
|
acct.as_ref().parse()
|
|
}
|
|
}
|
|
|
|
impl FromStr for FediverseTagDisplay {
|
|
type Err = FediverseTagParseError;
|
|
|
|
fn from_str(tag: &str) -> Result<Self, Self::Err> {
|
|
Self::from_parts(split_tag_inner(tag))
|
|
}
|
|
}
|
|
|
|
impl Display for FediverseTagDisplay {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
if let Some(ref host) = self.host {
|
|
write!(f, "{}@{}", self.name, host)
|
|
} else {
|
|
write!(f, "{}", self.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum FediverseTagParseError {
|
|
#[error("Invalid char in tag: {0}")]
|
|
InvalidChar(String),
|
|
#[error("Invalid UTF-8 in tag: {0}")]
|
|
InvalidUtf8(#[from] std::str::Utf8Error),
|
|
#[error("Invalid host in tag: {0}")]
|
|
InvalidHost(#[from] url::ParseError),
|
|
}
|
|
|
|
impl From<&FediverseTagParseError> for &str {
|
|
fn from(_: &FediverseTagParseError) -> Self {
|
|
"FediverseTagParseError"
|
|
}
|
|
}
|
|
|
|
impl From<FediverseTag> for Acct {
|
|
fn from(value: FediverseTag) -> Self {
|
|
value.to_string().into()
|
|
}
|
|
}
|
|
|
|
fn split_tag_inner(tag: &str) -> (&str, Option<&str>) {
|
|
let tag = tag.strip_prefix('@').unwrap_or(tag.as_ref());
|
|
|
|
match tag.split_once('@') {
|
|
Some((name, "")) => (name, None),
|
|
Some((name, host)) => (name, Some(host)),
|
|
None => (tag, None),
|
|
}
|
|
}
|
|
|
|
pub fn lenient_parse_tag_decode(
|
|
tag: impl AsRef<str>,
|
|
) -> Result<FediverseTag, FediverseTagParseError> {
|
|
percent_decode_str(tag.as_ref()).decode_utf8()?.parse()
|
|
}
|
|
|
|
pub fn lenient_parse_acct_decode(acct: &Acct) -> Result<FediverseTag, FediverseTagParseError> {
|
|
lenient_parse_tag_decode(acct.as_ref())
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum RawReaction {
|
|
Unicode(String),
|
|
Shortcode {
|
|
shortcode: String,
|
|
host: Option<String>,
|
|
},
|
|
}
|
|
|
|
pub fn parse_reaction(tag: impl AsRef<str>) -> Option<RawReaction> {
|
|
let tok = mmm::Context::default().parse_ui(tag.as_ref());
|
|
|
|
match tok {
|
|
Token::UnicodeEmoji(text) => Some(RawReaction::Unicode(text)),
|
|
Token::ShortcodeEmoji { shortcode, host } => {
|
|
Some(RawReaction::Shortcode { shortcode, host })
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|