signal-irc-bridge/src/transform.rs
2024-05-18 20:46:29 +02:00

154 lines
5.1 KiB
Rust

use std::path::Path;
use bimap::BiMap;
use irc::client::prelude::*;
use serde_json::{Map, Value};
use tokio::fs::copy;
#[derive(Default)]
struct SignalMessageBuilder<'a> {
from: Option<String>,
message: Option<String>,
attachments_urls: Vec<String>,
target: Option<&'a String>,
}
impl<'b> SignalMessageBuilder<'b> {
pub fn message(self: &mut Self, message: String) {
self.message = Some(message);
}
pub fn from(self: &mut Self, from: String) {
self.from = Some(from);
}
pub fn attach(self: &mut Self, attach: Vec<String>) {
self.attachments_urls.extend_from_slice(&attach);
}
pub fn target(self: &mut Self, channel: Option<&'b String>) {
self.target = channel;
}
pub fn build(self: Self) -> Option<(&'b String, String)>
{
let attachments_text = self.attachments_urls.join(", ");
(match (&self.message, attachments_text.len()) {
(None, 0) => None,
(None, _) => self
.from
.as_ref()
.map(|from| format!("<{from}>: {attachments_text}")),
(Some(m), 0) => self.from.as_ref().map(|from| format!("<{from}>: {m}")),
(Some(m), _) => self
.from
.as_ref()
.map(|from| format!("<{from}>: {m} ({attachments_text})")),
}).and_then(|m| self.target.map(|e| (e, m)))
}
}
pub struct Bridge<'a> {
media_dir: &'a Path,
signalcli_dir: &'a Path,
url_root: &'a str,
mapping: &'a BiMap<String, String>,
}
impl<'a> Bridge<'a> {
pub fn new(
media_dir: &'a Path,
signalcli_dir: &'a Path,
url_root: &'a str,
mapping: &'a BiMap<String, String>,
) -> Self {
Self {
media_dir,
signalcli_dir,
url_root,
mapping,
}
}
async fn handle_attachment(self: &Self, attachment: &Value) -> Option<String> {
if let Value::Object(a) = attachment {
if let Some(Value::String(fname)) = a.get("id") {
match copy(
self.signalcli_dir.join("attachments").join(fname),
self.media_dir.join(fname),
)
.await
{
Err(e) => {
log::error!("dropped attachment because of {e}");
return None;
}
_ => {
let url_root = self.url_root;
return Some(url_root.to_owned() + fname);
}
}
}
}
None
}
async fn process_signal(
self: &Self,
message: &Map<String, Value>,
result: &mut SignalMessageBuilder<'a>,
) {
if let Some(Value::Array(attachments)) = message.get("attachments") {
let mut attachments_urls = Vec::with_capacity(attachments.len());
for a in attachments.iter() {
if let Some(url) = self.handle_attachment(a).await {
attachments_urls.push(url);
} else {
log::warn!("droped attachment: {a}");
}
}
result.attach(attachments_urls);
};
if let Some(Value::String(text)) = message.get("message") {
result.message(text.clone());
}
if let Some(Value::Object(group_info)) = message.get("groupInfo") {
if let Some(Value::String(groupid)) = group_info.get("groupId") {
result.target(self.mapping.get_by_right(groupid));
}
}
}
pub async fn signal2irc(self: &Self, signal: Value) -> Option<(&String, String)> {
let mut result: SignalMessageBuilder = SignalMessageBuilder::default();
if let Value::Object(map) = signal {
if let Some(Value::Object(envelope)) = map.get("envelope") {
if let Some(Value::String(from)) = envelope.get("sourceName") {
result.from(from.to_owned());
}
if let Some(Value::Object(message)) = envelope.get("dataMessage") {
self.process_signal(message, &mut result).await;
}
if let Some(Value::Object(m)) = envelope.get("syncMessage") {
if let Some(Value::Object(message)) = m.get("sentMessage") {
self.process_signal(message, &mut result).await;
}
}
}
}
result.build()
}
pub fn irc2signal(self: &Self, message: Message) -> Option<(&String, String)> {
match (message.prefix, message.command) {
(Some(Prefix::Nickname(from, _, _)), Command::PRIVMSG(channel, message)) => {
Some((channel, format!("<{from}>: {message}")))
}
(Some(Prefix::Nickname(from, _, _)), Command::NOTICE(channel, message)) => {
Some((channel, format!("📜 {from} {message}")))
}
_ => None,
}
.and_then(|(channel, e)| self.mapping.get_by_left(&channel).map(|c| (c, e)))
}
}