matrix-ipfs-bot

git clone git://archive.git.mtrnord.blog/MTRNord/matrix-ipfs-bot.git
Log | Files | Refs | LICENSE

commit 7e83bbb93ab45956318523ed83509fcab05e7489
parent 49a644d83b1eb5fcdff002614fe781367c45c50f
Author: Marcel <mtrnord1@gmail.com>
Date:   Sat, 27 Jun 2020 16:19:37 +0200

Fix some issues

Took 1 hour 4 minutes

Diffstat:
M.gitignore | 1+
MCargo.lock | 29+++++++++++++++++++++++++++++
MCargo.toml | 2++
AReadme.md | 2++
Aconfig_example.yml | 2++
Asrc/config.rs | 16++++++++++++++++
Msrc/main.rs | 132++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/utils.rs | 11+++++++++++
8 files changed, 167 insertions(+), 28 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1 +1,2 @@ /target +config.yml diff --git a/Cargo.lock b/Cargo.lock @@ -909,6 +909,12 @@ dependencies = [ ] [[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + +[[package]] name = "log" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -963,6 +969,8 @@ dependencies = [ "reqwest", "ruma-api", "serde", + "serde_json", + "serde_yaml", "tokio", "tracing", "tracing-subscriber", @@ -1803,6 +1811,18 @@ dependencies = [ ] [[package]] +name = "serde_yaml" +version = "0.8.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + +[[package]] name = "sha-1" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2480,6 +2500,15 @@ dependencies = [ ] [[package]] +name = "yaml-rust" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" +dependencies = [ + "linked-hash-map", +] + +[[package]] name = "zeroize" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -19,5 +19,7 @@ async-trait = "0.1.31" tracing = "0.1" ruma-api = "0.16.1" serde = "1.0.114" +serde_json = "1.0.48" +serde_yaml = "0.8" reqwest = { version = "0.10", features = ["json"] } bytes = "0.5" diff --git a/Readme.md b/Readme.md @@ -0,0 +1,2 @@ +## Best practice +Make your upload ipfs gateway public and use it for links for best performance diff --git a/config_example.yml b/config_example.yml @@ -0,0 +1,2 @@ +ipfs_gateway: "https://cloudflare-ipfs.com" +ipfs_api: "http://localhost:5001" diff --git a/src/config.rs b/src/config.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; +use std::fs::OpenOptions; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Config { + pub ipfs_gateway: String, + pub ipfs_api: String, +} + +impl Config { + pub fn load() -> Self { + let f = OpenOptions::new().read(true).open("./config.yml").unwrap(); + let json: Self = serde_yaml::from_reader(f).expect("config should be proper YAML"); + json + } +} diff --git a/src/main.rs b/src/main.rs @@ -1,43 +1,48 @@ -use std::fs::File; +use std::convert::TryFrom; +use std::fs::{File, OpenOptions}; use std::io::Write; use std::path::PathBuf; use std::{env, fs, process::exit}; use bytes::buf::Buf; -use ipfs_api::response::AddResponse; use ipfs_api::{IpfsClient, TryFromUri}; use matrix_sdk::{ self, events::collections::all::RoomEvent, - events::room::message::{ - MessageEvent, MessageEventContent, NoticeMessageEventContent, RelatesTo, + events::room::{ + member::MemberEventContent, + message::{MessageEvent, MessageEventContent, NoticeMessageEventContent, RelatesTo}, }, - identifiers::RoomId, - Client, ClientConfig, EventEmitter, SyncRoom, SyncSettings, + events::stripped::StrippedRoomMember, + identifiers::{RoomId, UserId}, + Client, ClientConfig, EventEmitter, Session as SDKSession, SyncRoom, SyncSettings, }; -use tracing::{debug, info, warn, Level}; +use tracing::{info, warn, Level}; use tracing_subscriber::FmtSubscriber; use url::Url; -use crate::utils::get_media_download_url; - -mod utils; +use crate::config::Config; +use crate::utils::{get_media_download_url, Session}; +mod config; mod get_room_event; +mod utils; struct CommandBot { /// This clone of the `Client` will send requests to the server, /// while the other keeps us in sync with the server using `sync_forever`. client: Client, ipfs_client: IpfsClient, + config: Config, } impl CommandBot { - pub fn new(client: Client) -> Self { + pub fn new(client: Client, config: Config) -> Self { + let ipfs_client = IpfsClient::from_str(&config.ipfs_api).unwrap(); Self { client, - ipfs_client: IpfsClient::from_str("http://172.27.0.1:5001").unwrap(), - //ipfs_client: Default::default(), + ipfs_client, + config, } } @@ -67,7 +72,10 @@ impl CommandBot { related_event_original: Option<RelatesTo>, ) { let content = MessageEventContent::Notice(NoticeMessageEventContent { - body: format!("https://ipfs.io/ipfs/{}?filename={}", hash, filename), + body: format!( + "{}/ipfs/{}?filename={}", + self.config.ipfs_gateway, hash, filename + ), format: None, formatted_body: None, relates_to: related_event_original, @@ -95,7 +103,7 @@ impl CommandBot { self.remove_file(raw_filename); let hash = ipfs_resp.first().unwrap().hash.clone(); - self.ipfs_client.pin_add(&hash, true).await; + self.ipfs_client.pin_add(&hash, true).await.unwrap(); hash } @@ -103,6 +111,20 @@ impl CommandBot { #[matrix_sdk_common_macros::async_trait] impl EventEmitter for CommandBot { + async fn on_stripped_state_member( + &self, + room: SyncRoom, + _: &StrippedRoomMember, + _: Option<MemberEventContent>, + ) { + println!("room: {:?}", room); + if let SyncRoom::Invited(room) = room { + let room_id = room.read().await.room_id.clone(); + println!("room_id: {:?}", &room_id); + let resp = self.client.join_room_by_id(&room_id).await.unwrap(); + println!("JoinResp: {:?}", resp); + } + } async fn on_room_message(&self, room: SyncRoom, event: &MessageEvent) { if let SyncRoom::Joined(room) = room { if let MessageEventContent::Text(text_event) = event.clone().content { @@ -152,16 +174,28 @@ impl EventEmitter for CommandBot { match resp { Ok(mut resp) => { + println!("{:?}", resp.event.deserialize()); let (event, _updated) = self .client .base_client .receive_joined_timeline_event(&room_id, &mut resp.event) .await .unwrap(); - if let Ok(RoomEvent::RoomMessage(msg_event)) = - event.unwrap().deserialize() - { - related_events.push(msg_event); + match event { + Some(event) => { + if let Ok(RoomEvent::RoomMessage(msg_event)) = + event.deserialize() + { + related_events.push(msg_event); + } + } + None => { + if let Ok(RoomEvent::RoomMessage(msg_event)) = + resp.event.deserialize() + { + related_events.push(msg_event); + } + } } } Err(e) => { @@ -307,6 +341,20 @@ impl EventEmitter for CommandBot { } } } else { + let content = MessageEventContent::Notice(NoticeMessageEventContent { + body: "Unable to find related event!".to_string(), + format: None, + formatted_body: None, + relates_to: related_event_original.clone(), + }); + + self.client + // send our message to the room we found the "!party" command in + // the last parameter is an optional Uuid which we don't care about. + .room_send(&room_id, content, None) + .await + .unwrap(); + warn!("Unable to find related_event"); } } @@ -333,21 +381,49 @@ async fn login_and_sync( // create a new Client with the given homeserver url and config let mut client = Client::new_with_config(homeserver_url, client_config).unwrap(); - client - .login( - username.clone(), - password, - None, - Some("ipfs bot".to_string()), - ) - .await?; + let mut session = home.clone(); + session.push("session.json"); + if session.exists() { + let f = OpenOptions::new().read(true).open(&session).unwrap(); + let json: Session = serde_json::from_reader(f).expect("file should be proper JSON"); + let session = SDKSession { + access_token: json.access_token, + user_id: UserId::try_from(json.user_id).unwrap(), + device_id: json.device_id, + }; + client.restore_login(session).await.unwrap(); + } else { + let f = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&session) + .unwrap(); + + let login_response = client + .login( + username.clone(), + password, + None, + Some("ipfs bot".to_string()), + ) + .await?; + + let session = Session { + access_token: login_response.access_token, + user_id: login_response.user_id.to_string(), + device_id: login_response.device_id, + }; + + serde_json::to_writer(&f, &session).unwrap(); + } println!("logged in as {}", username); // add our CommandBot to be notified of incoming messages, we do this after the initial // sync to avoid responding to messages before the bot was running. client - .add_event_emitter(Box::new(CommandBot::new(client.clone()))) + .add_event_emitter(Box::new(CommandBot::new(client.clone(), Config::load()))) .await; // since we called sync before we `sync_forever` we must pass that sync token to diff --git a/src/utils.rs b/src/utils.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use url::Url; pub fn get_media_download_url(homeserver: &Url, mxc_url: String) -> String { @@ -10,3 +11,13 @@ pub fn get_media_download_url(homeserver: &Url, mxc_url: String) -> String { new_url.set_path(new_path.as_str()); new_url.to_string() } + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Session { + /// The access token used for this session. + pub access_token: String, + /// The user the access token was issued for. + pub user_id: String, + /// The ID of the client device + pub device_id: String, +}