use acter_core::events::{
rsvp::RsvpStatus, ColorizeBuilder, DisplayBuilder, ObjRefBuilder, Position,
};
use anyhow::{Context, Result};
use core::time::Duration;
use matrix_sdk::{HttpError, RumaApiError};
use matrix_sdk_base::{
media::{MediaFormat, MediaThumbnailSettings},
ruma::{
api::{client::error::ErrorBody, error::FromHttpResponseError},
events::room::{
message::UrlPreview as RumaUrlPreview, MediaSource as SdkMediaSource,
ThumbnailInfo as SdkThumbnailInfo,
},
MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedUserId, UInt,
},
ComposerDraft, ComposerDraftType,
};
use serde::{Deserialize, Serialize};
use std::{ops::Deref, str::FromStr};
use tracing::error;
use super::api::FfiBuffer;
use super::RefDetails;
pub fn duration_from_secs(secs: u64) -> Duration {
Duration::from_secs(secs)
}
pub struct OptionString {
text: Option<String>,
}
impl OptionString {
pub(crate) fn new(text: Option<String>) -> Self {
OptionString { text }
}
pub fn text(&self) -> Option<String> {
self.text.clone()
}
}
impl From<Option<String>> for OptionString {
fn from(text: Option<String>) -> Self {
OptionString { text }
}
}
pub struct OptionBuffer {
pub(crate) data: Option<Vec<u8>>,
}
impl OptionBuffer {
pub(crate) fn new(data: Option<Vec<u8>>) -> Self {
OptionBuffer { data }
}
pub fn data(&self) -> Option<FfiBuffer<u8>> {
self.data.clone().map(FfiBuffer::new)
}
}
pub struct OptionRsvpStatus {
pub(crate) status: Option<RsvpStatus>,
}
impl OptionRsvpStatus {
pub(crate) fn new(status: Option<RsvpStatus>) -> Self {
OptionRsvpStatus { status }
}
pub fn status(&self) -> Option<RsvpStatus> {
self.status.clone()
}
pub fn status_str(&self) -> Option<String> {
self.status.as_ref().map(ToString::to_string)
}
}
#[derive(Clone)]
pub struct OptionComposeDraft {
draft: Option<ComposeDraft>,
}
impl OptionComposeDraft {
pub(crate) fn new(draft: Option<ComposeDraft>) -> Self {
OptionComposeDraft { draft }
}
pub fn draft(&self) -> Option<ComposeDraft> {
self.draft.clone()
}
}
pub struct MediaSource {
pub(crate) inner: SdkMediaSource,
}
impl From<&SdkMediaSource> for MediaSource {
fn from(value: &SdkMediaSource) -> Self {
MediaSource {
inner: value.clone(),
}
}
}
impl MediaSource {
pub fn url(&self) -> String {
match self.inner.clone() {
SdkMediaSource::Plain(url) => url.to_string(),
SdkMediaSource::Encrypted(file) => file.url.to_string(),
}
}
}
#[derive(Clone)]
pub struct ThumbnailInfo {
pub(crate) inner: SdkThumbnailInfo,
}
impl From<&SdkThumbnailInfo> for ThumbnailInfo {
fn from(value: &SdkThumbnailInfo) -> Self {
ThumbnailInfo {
inner: value.clone(),
}
}
}
impl ThumbnailInfo {
pub fn mimetype(&self) -> Option<String> {
self.inner.mimetype.clone()
}
pub fn size(&self) -> Option<u64> {
self.inner.size.map(Into::into)
}
pub fn width(&self) -> Option<u64> {
self.inner.width.map(Into::into)
}
pub fn height(&self) -> Option<u64> {
self.inner.height.map(Into::into)
}
}
pub struct UrlPreview(pub(crate) RumaUrlPreview);
impl UrlPreview {
pub fn from(prev: &RumaUrlPreview) -> Self {
Self(prev.clone())
}
pub fn new(prev: RumaUrlPreview) -> Self {
Self(prev)
}
pub fn url(&self) -> Option<String> {
self.0.url.clone()
}
pub fn title(&self) -> Option<String> {
self.0.title.clone()
}
pub fn description(&self) -> Option<String> {
self.0.description.clone()
}
pub fn has_image(&self) -> bool {
false }
pub fn image_source(&self) -> Option<MediaSource> {
None }
}
#[derive(Clone)]
pub struct ComposeDraft {
inner: ComposerDraft,
}
impl ComposeDraft {
pub fn new(
plain_text: String,
html_text: Option<String>,
msg_type: String,
event_id: Option<OwnedEventId>,
) -> Self {
let m_type = msg_type.clone();
let draft_type = match (m_type.as_str(), event_id) {
("new", None) => ComposerDraftType::NewMessage,
("edit", Some(id)) => ComposerDraftType::Edit { event_id: id },
("reply", Some(id)) => ComposerDraftType::Reply { event_id: id },
_ => ComposerDraftType::NewMessage,
};
ComposeDraft {
inner: ComposerDraft {
plain_text,
html_text,
draft_type,
},
}
}
pub fn inner(&self) -> ComposerDraft {
self.inner.clone()
}
pub fn plain_text(&self) -> String {
self.inner.plain_text.clone()
}
pub fn html_text(&self) -> Option<String> {
self.inner.html_text.clone()
}
pub fn event_id(&self) -> Option<String> {
match &(self.inner.draft_type) {
ComposerDraftType::Edit { event_id } => Some(event_id.to_string()),
ComposerDraftType::Reply { event_id } => Some(event_id.to_string()),
ComposerDraftType::NewMessage => None,
}
}
pub fn draft_type(&self) -> String {
match &(self.inner.draft_type) {
ComposerDraftType::NewMessage => "new".to_string(),
ComposerDraftType::Edit { event_id } => "edit".to_string(),
ComposerDraftType::Reply { event_id } => "reply".to_string(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ReactionRecord {
sender_id: OwnedUserId,
timestamp: MilliSecondsSinceUnixEpoch,
sent_by_me: bool,
}
impl ReactionRecord {
pub(crate) fn new(
sender_id: OwnedUserId,
timestamp: MilliSecondsSinceUnixEpoch,
sent_by_me: bool,
) -> Self {
ReactionRecord {
sender_id,
timestamp,
sent_by_me,
}
}
pub fn sender_id(&self) -> OwnedUserId {
self.sender_id.clone()
}
pub fn sent_by_me(&self) -> bool {
self.sent_by_me
}
pub fn timestamp(&self) -> u64 {
self.timestamp.get().into()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DeviceRecord {
device_id: OwnedDeviceId,
display_name: Option<String>,
last_seen_ts: Option<MilliSecondsSinceUnixEpoch>,
last_seen_ip: Option<String>,
is_verified: bool,
is_active: bool,
is_me: bool,
}
impl DeviceRecord {
pub(crate) fn new(
device_id: OwnedDeviceId,
display_name: Option<String>,
last_seen_ts: Option<MilliSecondsSinceUnixEpoch>,
last_seen_ip: Option<String>,
is_verified: bool,
is_active: bool,
is_me: bool,
) -> Self {
DeviceRecord {
device_id,
display_name,
last_seen_ts,
last_seen_ip,
is_verified,
is_active,
is_me,
}
}
pub fn device_id(&self) -> OwnedDeviceId {
self.device_id.clone()
}
pub fn display_name(&self) -> Option<String> {
self.display_name.clone()
}
pub fn last_seen_ts(&self) -> Option<u64> {
self.last_seen_ts.map(|x| x.get().into())
}
pub fn last_seen_ip(&self) -> Option<String> {
self.last_seen_ip.clone()
}
pub fn is_verified(&self) -> bool {
self.is_verified
}
pub fn is_me(&self) -> bool {
self.is_me
}
pub fn is_active(&self) -> bool {
self.is_active
}
}
#[derive(Clone, Debug)]
pub struct ThumbnailSize {
width: UInt,
height: UInt,
}
impl ThumbnailSize {
pub(crate) fn new(width: u64, height: u64) -> Result<Self> {
let width = UInt::new(width).context("invalid thumbnail width")?;
let height = UInt::new(height).context("invalid thumbnail height")?;
Ok(ThumbnailSize { width, height })
}
pub(crate) fn width(&self) -> UInt {
self.width
}
pub(crate) fn height(&self) -> UInt {
self.height
}
pub fn parse_into_media_format(thumb_size: Option<Box<ThumbnailSize>>) -> MediaFormat {
match thumb_size {
Some(thumb_size) => MediaFormat::from(thumb_size),
None => MediaFormat::File,
}
}
}
impl From<Box<ThumbnailSize>> for MediaFormat {
fn from(val: Box<ThumbnailSize>) -> Self {
MediaFormat::Thumbnail(MediaThumbnailSettings::new(val.width, val.height))
}
}
pub fn new_thumb_size(width: u64, height: u64) -> Result<ThumbnailSize> {
ThumbnailSize::new(width, height)
}
pub fn new_colorize_builder(
color: Option<u32>,
background: Option<u32>,
link: Option<u32>,
) -> Result<ColorizeBuilder> {
let mut builder = ColorizeBuilder::default();
if let Some(color) = color {
builder.color(color);
}
if let Some(background) = background {
builder.background(background);
}
if let Some(link) = link {
builder.link(link);
}
Ok(builder)
}
pub fn new_obj_ref_builder(
position: Option<String>,
reference: Box<RefDetails>,
) -> Result<ObjRefBuilder> {
if let Some(p) = position {
let p = Position::from_str(&p)?;
Ok(ObjRefBuilder::new(Some(p), (*reference).deref().clone()))
} else {
Ok(ObjRefBuilder::new(None, (*reference).deref().clone()))
}
}
pub fn clearify_error(err: matrix_sdk::Error) -> anyhow::Error {
if let matrix_sdk::Error::Http(HttpError::Api(api_error)) = &err {
match api_error {
FromHttpResponseError::Deserialization(des) => {
return anyhow::anyhow!("Deserialization failed: {des}");
}
FromHttpResponseError::Server(inner) => match inner {
RumaApiError::ClientApi(error) => {
if let ErrorBody::Standard { kind, message } = &error.body {
return anyhow::anyhow!("{message:?} [{kind:?}]");
}
return anyhow::anyhow!("{0:?} [{1}]", error.body, error.status_code);
}
RumaApiError::Uiaa(uiaa_error) => {
if let Some(err) = &uiaa_error.auth_error {
return anyhow::anyhow!("{:?} [{:?}]", err.message, err.kind);
}
error!(?uiaa_error, "Other UIAA response");
return anyhow::anyhow!("Unsupported User Interaction needed.");
}
RumaApiError::Other(err) => {
return anyhow::anyhow!("{:?} [{:?}]", err.body, err.status_code);
}
},
_ => {}
}
}
err.into()
}
pub fn new_display_builder() -> DisplayBuilder {
DisplayBuilder::default()
}