Browse Source

More emoji reactions

master
Stephen 4 months ago
parent
commit
455b87be1a
8 changed files with 193 additions and 93 deletions
  1. +1
    -0
      .gitignore
  2. +62
    -1
      Cargo.lock
  3. +2
    -1
      Cargo.toml
  4. +2
    -0
      rustfmt.toml
  5. +11
    -11
      src/config.rs
  6. +19
    -13
      src/db.rs
  7. +72
    -45
      src/main.rs
  8. +24
    -22
      src/relation.rs

+ 1
- 0
.gitignore View File

@ -1 +1,2 @@
/target
.idea

+ 62
- 1
Cargo.lock View File

@ -121,11 +121,12 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
[[package]]
name = "cat_disruptor_7000"
version = "0.5.1"
version = "0.6.0"
dependencies = [
"async-trait",
"matrix-sdk",
"matrix-sdk-common-macros",
"phf",
"ruma",
"rusqlite",
"serde",
@ -1010,6 +1011,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros",
"phf_shared",
"proc-macro-hack",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"rand 0.7.3",
]
[[package]]
name = "phf_macros"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -1117,6 +1162,7 @@ dependencies = [
"rand_chacha",
"rand_core 0.5.1",
"rand_hc",
"rand_pcg",
]
[[package]]
@ -1163,6 +1209,15 @@ dependencies = [
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -1484,6 +1539,12 @@ dependencies = [
]
[[package]]
name = "siphasher"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
[[package]]
name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"


+ 2
- 1
Cargo.toml View File

@ -1,6 +1,6 @@
[package]
name = "cat_disruptor_7000"
version = "0.5.1"
version = "0.6.0"
authors = ["Stephen D <@stephen:m.scd31.com>"]
edition = "2018"
@ -17,6 +17,7 @@ tracing-subscriber = "0.2"
uuid = { version = "0.8.1", features = ["v4"] }
statsd = "^0.13.1"
rusqlite = "0.22"
phf = { version = "0.8", features = ["macros"] }
[dependencies.ruma]
git = "https://github.com/ruma/ruma"


+ 2
- 0
rustfmt.toml View File

@ -0,0 +1,2 @@
tab_spaces = 4
hard_tabs = true

+ 11
- 11
src/config.rs View File

@ -1,4 +1,4 @@
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
use std::fs::File;
@ -8,7 +8,7 @@ pub struct Config {
pub username: String,
pub password: String,
pub store_path: String,
pub statsd_server: String
pub statsd_server: String,
}
pub fn get_config() -> Config {
@ -18,13 +18,13 @@ pub fn get_config() -> Config {
}
fn get_conf_file() -> File {
let filenames = ["config.json", "/etc/cat_disruptor_7000/config.json"];
for filename in filenames.iter() {
match File::open(filename) {
Ok(x) => return x,
Err(_) => continue
}
}
panic!("Could not open config file");
let filenames = ["config.json", "/etc/cat_disruptor_7000/config.json"];
for filename in filenames.iter() {
match File::open(filename) {
Ok(x) => return x,
Err(_) => continue,
}
}
panic!("Could not open config file");
}

+ 19
- 13
src/db.rs View File

@ -3,36 +3,42 @@ use std::path::PathBuf;
use rusqlite::{params, Connection};
pub struct DataStore {
conn: Connection
conn: Connection,
}
impl DataStore {
pub fn open(filename: PathBuf) -> Self {
let conn = Connection::open(filename).unwrap();
conn.execute(
"CREATE TABLE IF NOT EXISTS device_ids (
id INTEGER PRIMARY KEY,
device_id VARCHAR(10) NOT NULL UNIQUE
)",
params![]
).unwrap();
params![],
)
.unwrap();
DataStore { conn }
}
pub fn get_device_id(&self) -> Option<String> {
match self.conn.query_row("SELECT device_id FROM device_ids ORDER BY id DESC LIMIT 1",
params![],
|x| { x.get(0)}
match self.conn.query_row(
"SELECT device_id FROM device_ids ORDER BY id DESC LIMIT 1",
params![],
|x| x.get(0),
) {
Ok(x) => Some(x),
Err(_) => None
Err(_) => None,
}
}
pub fn save_device_id(&self, device_id: String) {
self.conn.execute("INSERT OR IGNORE INTO device_ids (device_id) VALUES (?1)",
params![device_id]).unwrap();
self.conn
.execute(
"INSERT OR IGNORE INTO device_ids (device_id) VALUES (?1)",
params![device_id],
)
.unwrap();
}
}

+ 72
- 45
src/main.rs View File

@ -1,8 +1,4 @@
use std::{
path::Path,
fs,
time::Duration
};
use std::{fs, path::Path, time::Duration};
use url::Url;
@ -10,32 +6,43 @@ use matrix_sdk::{
self,
events::{
room::{
member::MemberEventContent,
message::{MessageEventContent, TextMessageEventContent},
member::MemberEventContent
},
StrippedStateEventStub,
MessageEventStub
MessageEventStub, StrippedStateEventStub,
},
Client, ClientConfig, EventEmitter, SyncRoom, SyncSettings, RoomState,
identifiers::UserId
identifiers::UserId,
Client, ClientConfig, EventEmitter, RoomState, SyncRoom, SyncSettings,
};
use phf::phf_map;
use async_trait::async_trait;
use db::DataStore;
mod relation;
mod config;
mod db;
mod relation;
const REACTION_MAP: phf::Map<&'static str, &'static str> = phf_map! {
"cat" => "🐈",
"spook" => "👀",
"glasses" => "👓"
};
struct EventCallback {
client: Client,
user_id: UserId,
statsd_client: statsd::Client
statsd_client: statsd::Client,
}
impl EventCallback {
pub fn new(client: Client, user_id: UserId, statsd_client: statsd::Client) -> Self {
Self { client, user_id, statsd_client }
Self {
client,
user_id,
statsd_client,
}
}
}
@ -48,18 +55,25 @@ impl EventEmitter for EventCallback {
content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }),
event_id,
..
} = event {
if msg_body.to_lowercase().contains("cat") {
// Cat in message. React with emoji
match relation::react(&self.client,
event_id.clone(),
room.read().await.room_id.clone(),
"🐈".to_string()).await { // Cat emoji
Ok(_) => self.statsd_client.incr("reacted.cats"),
Err(x) => {
println!("Error while reacting: {}", x);
self.statsd_client.incr("reacted.error");
} = event
{
for (k, v) in REACTION_MAP.entries() {
if msg_body.to_lowercase().contains(k) {
// key in message. React with value
match relation::react(
&self.client,
event_id.clone(),
room.read().await.room_id.clone(),
v.to_string(),
)
.await
{
Ok(_) => self.statsd_client.incr(&format!("reacted.{}s", k)),
Err(x) => {
println!("Error while reacting: {}", x);
self.statsd_client.incr("reacted.error");
}
}
}
}
@ -68,10 +82,12 @@ impl EventEmitter for EventCallback {
}
//Handles incoming invitations
async fn on_stripped_state_member(&self,
sync_room: SyncRoom,
room_member: &StrippedStateEventStub<MemberEventContent>,
_: Option<MemberEventContent>) {
async fn on_stripped_state_member(
&self,
sync_room: SyncRoom,
room_member: &StrippedStateEventStub<MemberEventContent>,
_: Option<MemberEventContent>,
) {
if room_member.state_key == self.user_id {
if let RoomState::Invited(x) = sync_room {
let room = x.read().await;
@ -80,7 +96,7 @@ impl EventEmitter for EventCallback {
Ok(_) => {
println!("Joined {}", room.display_name());
self.statsd_client.incr("joined.success");
},
}
Err(_) => {
println!("There was an error while joining {}", room.display_name());
self.statsd_client.incr("joined.error");
@ -97,17 +113,19 @@ async fn login(
password: String,
config: ClientConfig,
statsd_client: statsd::Client,
datastore: DataStore
datastore: DataStore,
) -> Result<(), matrix_sdk::Error> {
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
let mut client = Client::new_with_config(homeserver_url, config).unwrap();
let login_response = client.login(
username,
password,
datastore.get_device_id(),
Some("Cat Disruptor".to_string())
).await?;
let login_response = client
.login(
username,
password,
datastore.get_device_id(),
Some("Cat Disruptor".to_string()),
)
.await?;
println!("Logged in as {}", login_response.user_id);
println!("Device id: {}", login_response.device_id);
@ -121,12 +139,14 @@ async fn login(
#[tokio::main]
async fn main() -> Result<(), matrix_sdk::Error> {
tracing_subscriber::fmt().with_env_filter(tracing_subscriber::EnvFilter::from_default_env()).init();
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
let config = config::get_config();
if config.homeserver_url == "changeme" &&
config.username == "changeme" &&
config.password == "changeme"
if config.homeserver_url == "changeme"
&& config.username == "changeme"
&& config.password == "changeme"
{
panic!("Please update the values in the config file!");
}
@ -137,7 +157,7 @@ async fn main() -> Result<(), matrix_sdk::Error> {
if !store_path.exists() {
fs::create_dir(config.store_path.clone()).unwrap();
}
let matrix_config = ClientConfig::new()
.store_path(store_path.clone())
.timeout(Duration::from_secs(60)); // Auto-reconnect if we lose connection
@ -146,7 +166,14 @@ async fn main() -> Result<(), matrix_sdk::Error> {
// In the future we will probably store other things in the database.
// Sqlite would be overkill otherwise.
let datastore = DataStore::open(store_path.join("cat_disruptor.sqlite"));
login(config.homeserver_url, config.username, config.password,
matrix_config, statsd_client, datastore).await
login(
config.homeserver_url,
config.username,
config.password,
matrix_config,
statsd_client,
datastore,
)
.await
}

+ 24
- 22
src/relation.rs View File

@ -1,43 +1,45 @@
use std::collections::HashMap;
use matrix_sdk::{
identifiers::{ EventId, RoomId },
events::EventType,
Client
identifiers::{EventId, RoomId},
Client,
};
use serde::{Serialize, Deserialize};
use ruma::api::client::r0::message::create_message_event;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Clone, Serialize, Deserialize, Debug)]
struct ContentStruct {
pub rel_type: String,
pub event_id: EventId,
pub key: String
pub key: String,
}
pub async fn react(client: &Client,
event_id: EventId,
room_id: RoomId,
emoji: String) -> Result<(), String> {
let mut content_hm: HashMap<String, ContentStruct>
= HashMap::new();
content_hm.insert("m.relates_to".to_string(),
ContentStruct {
rel_type: "m.annotation".to_string(),
event_id,
key: emoji
});
pub async fn react(
client: &Client,
event_id: EventId,
room_id: RoomId,
emoji: String,
) -> Result<(), String> {
let mut content_hm: HashMap<String, ContentStruct> = HashMap::new();
content_hm.insert(
"m.relates_to".to_string(),
ContentStruct {
rel_type: "m.annotation".to_string(),
event_id,
key: emoji,
},
);
let request = create_message_event::Request {
event_type: EventType::Custom("m.reaction".into()),
room_id,
txn_id: Uuid::new_v4().to_string(),
data: serde_json::value::to_raw_value(&content_hm).unwrap()
data: serde_json::value::to_raw_value(&content_hm).unwrap(),
};
match client.send(request).await {
Ok(_) => Ok(()),
Err(x) => Err(format!("{}", x))
Err(x) => Err(format!("{}", x)),
}
}

Loading…
Cancel
Save