154 lines
5.1 KiB
Rust
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)))
|
|
}
|
|
}
|