Initial commit

This commit is contained in:
Finn 2024-10-10 10:00:44 -07:00
commit 86d912fd1f
7 changed files with 4165 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
config.json

3926
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

12
Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "matrix-meshtastic-bridge"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.89"
matrix-sdk = { version = "0.7.1", features = ["anyhow"] }
meshtastic = "0.1.6"
serde = { version = "1.0.210", features = ["derive"]}
serde_json = "1.0.128"
tokio = "1.40.0"

33
src/config.rs Normal file
View file

@ -0,0 +1,33 @@
use std::{fs::File, io::BufReader};
#[derive(serde::Deserialize, Debug)]
pub(crate) struct Config {
pub(crate) matrix: MatrixConfig,
pub(crate) meshtastic: MeshtasticConfig,
}
#[derive(serde::Deserialize, Debug)]
pub(crate) struct MatrixConfig {
pub(crate) username: String,
pub(crate) password: String,
#[serde(default = "get_device_name")]
pub(crate) device_name: String,
pub(crate) room: String,
}
#[derive(serde::Deserialize, Debug)]
pub(crate) struct MeshtasticConfig {
pub(crate) device: String,
}
fn get_device_name() -> String {
"meshtastic-bridge".to_string()
}
pub(crate) async fn read_config() -> Config {
let file = File::open("config.json").expect("failed to read config.json");
let reader = BufReader::new(file);
// Read the JSON contents of the file as an instance of `User`.
serde_json::from_reader(reader).expect("failed to parse config.json")
}

38
src/main.rs Normal file
View file

@ -0,0 +1,38 @@
use matrix_sdk::{config::SyncSettings, crypto::types::events::room, ruma::RoomId};
use tokio::sync::mpsc;
mod config;
mod matrix;
mod meshtastic;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = config::read_config().await;
let (matrix_tx, matrix_rx) = mpsc::channel(32);
let room_id = RoomId::parse(&config.matrix.room).expect("invalid room id");
println!("matrix room: {:?}", room_id);
let matrix_client = matrix::build(config.matrix)
.await
.expect("error logging into matrix");
matrix_client.sync_once(SyncSettings::default()).await?;
let meshtastic_client = meshtastic::build(config.meshtastic)
.await
.expect("error connecting to meshtastic");
tokio::spawn(async {
meshtastic_client.receive(matrix_tx).await.expect("error receiving message from meshtastic");
});
tokio::spawn(matrix::sender(matrix_client.clone(), matrix_rx, room_id));
// Syncing is important to synchronize the client state with the server.
// This method will never return unless there is an error.
matrix_client.sync(SyncSettings::default()).await?;
Ok(())
}

99
src/matrix.rs Normal file
View file

@ -0,0 +1,99 @@
use matrix_sdk::{
ruma::{
events::room::{
member::StrippedRoomMemberEvent,
message::{RoomMessageEventContent, SyncRoomMessageEvent},
}, OwnedRoomId, RoomId, TransactionId, UserId
},
Client, Room,
};
use tokio::time::{sleep, Duration};
use tokio::sync::mpsc::Receiver;
use crate::config;
pub(crate) async fn build(config: config::MatrixConfig) -> Result<matrix_sdk::Client, anyhow::Error> {
let user = UserId::parse(config.username)
.expect("invalid matrix username - should be a full mxid with server");
let client = Client::builder()
.server_name(user.server_name())
.build()
.await?;
client
.matrix_auth()
.login_username(user, &config.password)
.initial_device_display_name(&config.device_name)
.send()
.await?;
client.add_event_handler(|ev: SyncRoomMessageEvent| async move {
println!("[matrix] Received a message {:?}", ev);
});
client.add_event_handler(on_stripped_state_member);
println!("connected to matrix");
return Ok(client);
}
async fn on_stripped_state_member(
room_member: StrippedRoomMemberEvent,
client: Client,
room: Room,
) {
if room_member.state_key != client.user_id().unwrap() {
return;
}
tokio::spawn(async move {
println!("Joining room {}", room.room_id());
let mut delay = 2;
while let Err(err) = room.join().await {
// retry autojoin due to synapse sending invites, before the
// invited user can join for more information see
// https://github.com/matrix-org/synapse/issues/4345
eprintln!(
"Failed to join room {} ({err:?}), retrying in {delay}s",
room.room_id()
);
sleep(Duration::from_secs(delay)).await;
delay *= 2;
if delay > 3600 {
eprintln!("Can't join room {} ({err:?})", room.room_id());
break;
}
}
println!("Successfully joined room {}", room.room_id());
let content = RoomMessageEventContent::text_plain("fuck you");
let txn_id = TransactionId::new();
let result = room.send(content).with_transaction_id(&txn_id).await;
match result {
Ok(_) => eprintln!("sent hello message"),
Err(err) => eprintln!(
"Error sending hello message to room {}: {err:?}",
room.room_id()
),
}
});
}
pub(crate) async fn sender(client: Client, mut rx: Receiver<RoomMessageEventContent>, room_id: OwnedRoomId) {
let room = match client.get_room(&room_id) {
Some(room) => room,
None => panic!("requested matrix room not found")
};
while let Some(msg) = rx.recv().await {
match room.send(msg).await {
Err(err) => println!("error sending message to matrix: {:?}", err),
Ok(response) => println!("sent message to matrix: {:?}", response),
}
};
}

55
src/meshtastic.rs Normal file
View file

@ -0,0 +1,55 @@
use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
use meshtastic::api::state::Connected;
use meshtastic::api::{ConnectedStreamApi, StreamApi};
use meshtastic::protobufs::FromRadio;
use meshtastic::utils;
use tokio::sync::mpsc::{Sender, UnboundedReceiver};
use tokio::time::Duration;
use crate::config;
pub struct MeshtasticClient {
decoded_listener: UnboundedReceiver<FromRadio>,
stream_api: ConnectedStreamApi<Connected>,
}
pub(crate) async fn build(
config: config::MeshtasticConfig,
) -> Result<MeshtasticClient, Box<dyn std::error::Error>> {
let stream_api = StreamApi::new();
let serial_stream = utils::stream::build_serial_stream(config.device, None, None, None)?;
let (decoded_listener, stream_api) = stream_api.connect(serial_stream).await;
println!("connected to meshtastic device");
Ok(MeshtasticClient {
decoded_listener: decoded_listener,
stream_api: stream_api,
})
}
impl MeshtasticClient {
pub async fn receive(
mut self: MeshtasticClient,
matrix_sender: Sender<RoomMessageEventContent>,
) -> Result<(), Box<dyn std::error::Error>> {
// let config_id = utils::generate_rand_id();
// let stream_api = stream_api.configure(config_id).await?;
// This loop can be broken with ctrl+c, or by disconnecting
// the attached serial port.
println!("listening for messages from meshtastic");
while let Some(decoded) = self.decoded_listener.recv().await {
println!("Received: {:?}", decoded);
let msg = RoomMessageEventContent::text_plain(format!("{:?}", decoded));
matrix_sender
.send_timeout(msg, Duration::from_secs(10))
.await?;
}
Ok(())
}
}