Matrix notification
This commit is contained in:
parent
86495543ce
commit
2de26f5ffc
7 changed files with 1090 additions and 17 deletions
949
Cargo.lock
generated
949
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -9,20 +9,20 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
argon2 = "0.4.1"
|
||||
async-std = { version = "1.12.0", features = ["attributes"] }
|
||||
base64 = "0.13.0"
|
||||
clap = { version = "4.0.15", default-features = false, features = ["derive", "std"] }
|
||||
crossbeam-channel = "0.5.6"
|
||||
directories = "4.0.1"
|
||||
log = "0.4.17"
|
||||
#matrix-sdk = { version = "0.5.0", default-features = false }
|
||||
matrix-sdk = { version = "0.6.0", default-features = false, features = ["rustls-tls"] }
|
||||
rand = "0.8.5"
|
||||
rand_core = { version = "0.6.4", features = ["std"] }
|
||||
rpassword = "7.0.0"
|
||||
serde = { version = "1.0.145", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.86"
|
||||
sha2 = "0.10.6"
|
||||
sled = "0.34.7"
|
||||
tera = { version = "1.17.1", features = ["builtins", "date-locale"] }
|
||||
tide = { version = "0.16.0", default-features = false, features = ["h1-server", "cookies", "logger"] }
|
||||
tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread"] }
|
||||
toml_edit = { version = "0.14.4", features = ["easy"] }
|
||||
typed-sled = "0.2.0"
|
||||
|
|
38
README.md
Normal file
38
README.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
# webcomment
|
||||
|
||||
Rust webserver for comments, that you can easily embed in a website.
|
||||
|
||||
**Early development, not safe for production yet**
|
||||
|
||||
## Features
|
||||
|
||||
* List and post comments by topic (e.g. each article in your blog is a topic)
|
||||
* Admin approval
|
||||
* Admin notification on new comment via Matrix
|
||||
* Embedded one-file webserver
|
||||
* [Tera](https://github.com/Keats/tera) templates
|
||||
|
||||
## Use
|
||||
|
||||
webcomment init
|
||||
|
||||
# This adds an admin password to the config (password are hashed)
|
||||
webcomment psw
|
||||
|
||||
# edit ~/.config/webcomment/config.toml
|
||||
|
||||
webcomment start
|
||||
|
||||
Each topic is accessible at `/t/<topic_name>`.
|
||||
|
||||
Admin login is accessible at `/admin`. Once authenticated, you can see the pending comments on the topic pages.
|
||||
|
||||
## License
|
||||
|
||||
CopyLeft 2022 Pascal Engélibert [(why copyleft?)](//txmn.tk/blog/why-copyleft/)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.
|
|
@ -32,6 +32,17 @@ pub struct Config {
|
|||
pub lang: String,
|
||||
#[serde(default = "Config::default_listen")]
|
||||
pub listen: SocketAddr,
|
||||
/// Send a matrix message on new comment
|
||||
#[serde(default = "Config::default_matrix_notify")]
|
||||
pub matrix_notify: bool,
|
||||
#[serde(default = "Config::default_matrix_password")]
|
||||
pub matrix_password: String,
|
||||
#[serde(default = "Config::default_matrix_room")]
|
||||
pub matrix_room: String,
|
||||
#[serde(default = "Config::default_matrix_server")]
|
||||
pub matrix_server: String,
|
||||
#[serde(default = "Config::default_matrix_user")]
|
||||
pub matrix_user: String,
|
||||
#[serde(default = "Config::default_root_url")]
|
||||
pub root_url: String,
|
||||
}
|
||||
|
@ -70,6 +81,21 @@ impl Config {
|
|||
fn default_listen() -> SocketAddr {
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 31720)
|
||||
}
|
||||
fn default_matrix_notify() -> bool {
|
||||
false
|
||||
}
|
||||
fn default_matrix_password() -> String {
|
||||
"".into()
|
||||
}
|
||||
fn default_matrix_room() -> String {
|
||||
"#maintenance:matrix.txmn.tk".into()
|
||||
}
|
||||
fn default_matrix_server() -> String {
|
||||
"https://matrix.txmn.tk".into()
|
||||
}
|
||||
fn default_matrix_user() -> String {
|
||||
"@tuxmain:matrix.txmn.tk".into()
|
||||
}
|
||||
fn default_root_url() -> String {
|
||||
"/".into()
|
||||
}
|
||||
|
@ -89,6 +115,11 @@ impl Default for Config {
|
|||
cookies_domain: Self::default_cookies_domain(),
|
||||
lang: Self::default_lang(),
|
||||
listen: Self::default_listen(),
|
||||
matrix_notify: Self::default_matrix_notify(),
|
||||
matrix_password: Self::default_matrix_password(),
|
||||
matrix_room: Self::default_matrix_room(),
|
||||
matrix_server: Self::default_matrix_server(),
|
||||
matrix_user: Self::default_matrix_user(),
|
||||
root_url: Self::default_root_url(),
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +138,7 @@ pub fn read_config(dir: &Path) -> Config {
|
|||
std::str::from_utf8(&std::fs::read(path).expect("Cannot read config file"))
|
||||
.expect("Bad encoding in config file"),
|
||||
)
|
||||
.expect("Bad JSON in config file")
|
||||
.expect("Bad TOML in config file")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ mod cli;
|
|||
mod config;
|
||||
mod db;
|
||||
mod helpers;
|
||||
mod notify;
|
||||
mod queries;
|
||||
mod server;
|
||||
mod templates;
|
||||
|
@ -12,7 +13,7 @@ use argon2::{
|
|||
};
|
||||
use clap::Parser;
|
||||
|
||||
#[async_std::main]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let opt = cli::MainOpt::parse();
|
||||
|
||||
|
|
64
src/notify.rs
Normal file
64
src/notify.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use crate::config::Config;
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use matrix_sdk::ruma;
|
||||
use std::sync::Arc;
|
||||
|
||||
struct Notifier {
|
||||
matrix: Option<(matrix_sdk::Client, matrix_sdk::room::Joined)>,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
async fn new(config: &Config) -> Self {
|
||||
Self {
|
||||
matrix: if config.matrix_notify {
|
||||
let user = ruma::UserId::parse(&config.matrix_user).unwrap();
|
||||
let client = matrix_sdk::Client::builder()
|
||||
.homeserver_url(&config.matrix_server)
|
||||
.user_agent("Webcomment")
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
client
|
||||
.login_username(&user, &config.matrix_password)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
client
|
||||
.sync_once(matrix_sdk::config::SyncSettings::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let room_id = <&ruma::RoomId>::try_from(config.matrix_room.as_str()).unwrap();
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
|
||||
if let matrix_sdk::room::Room::Joined(room) = room {
|
||||
Some((client, room))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn notify(&self) {
|
||||
if let Some((_client, room)) = &self.matrix {
|
||||
room.send(
|
||||
ruma::events::room::message::RoomMessageEventContent::text_plain("New comment."),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_notifier(config: Arc<Config>, recv: Receiver<()>) {
|
||||
let notifier = Notifier::new(&config).await;
|
||||
for () in recv {
|
||||
notifier.notify().await;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{config::*, db::*, helpers, queries::*, templates::*};
|
||||
|
||||
use argon2::{Argon2, PasswordHash, PasswordVerifier};
|
||||
use crossbeam_channel::Sender;
|
||||
use log::error;
|
||||
use std::sync::Arc;
|
||||
use tera::Context;
|
||||
|
@ -11,6 +12,9 @@ pub async fn start_server(config: Config, dbs: Dbs, templates: Templates) {
|
|||
let templates = Arc::new(templates);
|
||||
let config = Arc::new(config);
|
||||
|
||||
let (notify_send, notify_recv) = crossbeam_channel::bounded(10);
|
||||
tokio::spawn(crate::notify::run_notifier(config.clone(), notify_recv));
|
||||
|
||||
let mut app = tide::new();
|
||||
app.at(&format!("{}t/:topic", config.root_url)).get({
|
||||
let config = config.clone();
|
||||
|
@ -25,7 +29,13 @@ pub async fn start_server(config: Config, dbs: Dbs, templates: Templates) {
|
|||
let templates = templates.clone();
|
||||
let dbs = dbs.clone();
|
||||
move |req: tide::Request<()>| {
|
||||
handle_post_comments(req, config.clone(), templates.clone(), dbs.clone())
|
||||
handle_post_comments(
|
||||
req,
|
||||
config.clone(),
|
||||
templates.clone(),
|
||||
dbs.clone(),
|
||||
notify_send.clone(),
|
||||
)
|
||||
}
|
||||
});
|
||||
app.at(&format!("{}admin", config.root_url)).get({
|
||||
|
@ -176,6 +186,7 @@ async fn handle_post_comments(
|
|||
config: Arc<Config>,
|
||||
templates: Arc<Templates>,
|
||||
dbs: Dbs,
|
||||
notify_send: Sender<()>,
|
||||
) -> tide::Result<tide::Response> {
|
||||
match req.body_form::<CommentQuery>().await? {
|
||||
CommentQuery::NewComment(query) => {
|
||||
|
@ -215,6 +226,7 @@ async fn handle_post_comments(
|
|||
helpers::new_pending_comment(&comment, &dbs)
|
||||
.map_err(|e| error!("Adding pending comment: {:?}", e))
|
||||
.ok();
|
||||
notify_send.send(()).ok();
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue