Compare commits

...

6 Commits

9 changed files with 1193 additions and 944 deletions

2
.envrc Normal file
View File

@ -0,0 +1,2 @@
use flake

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target
/.idea
/.env
/.direnv

1855
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,10 +7,10 @@ edition = "2021"
[dependencies]
# we use new features not quite yet in a released matrix rust SDK
#matrix-sdk = { version = "0.4.1", features = [ "encryption", "sled_cryptostore" ] }
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", rev = "a49a7fe1f93a4e8791dbbe4fd6876c587c33d2fa", features = ["encryption", "sled_cryptostore"] }
#monzo-lib = "0.4.0"
monzo-lib = { git = "https://github.com/danieleades/monzo-lib.git", rev = "e54ff827" }
matrix-sdk = "0.5.0"
#matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", rev = "a49a7fe1f93a4e8791dbbe4fd6876c587c33d2fa", features = ["encryption", "sled_cryptostore"] }
monzo-lib = "0.4.4"
#monzo-lib = { git = "https://github.com/danieleades/monzo-lib.git", rev = "e54ff827" }
tokio = { version = "1.13.0", features = [ "full" ] }
anyhow = "1.0.45"
warp = "0.3.1"

81
flake.lock Normal file
View File

@ -0,0 +1,81 @@
{
"nodes": {
"naersk": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1698420672,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
"owner": "nix-community",
"repo": "naersk",
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1714971268,
"narHash": "sha256-IKwMSwHj9+ec660l+I4tki/1NRoeGpyA2GdtdYpAgEw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "27c13997bf450a01219899f5a83bd6ffbfc70d3c",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-23.11",
"type": "indirect"
}
},
"root": {
"inputs": {
"naersk": "naersk",
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

47
flake.nix Normal file
View File

@ -0,0 +1,47 @@
{
description = "Matrix Monzo bot";
inputs = {
nixpkgs.url = "nixpkgs/nixos-23.11";
utils.url = "github:numtide/flake-utils";
naersk = {
url = "github:nix-community/naersk";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, utils, naersk }:
utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages."${system}";
naersk-lib = naersk.lib."${system}";
in rec {
# `nix build`
packages.mxmonzo = naersk-lib.buildPackage {
pname = "mxmonzo";
root = ./.;
buildInputs = with pkgs; [
openssl
pkg-config
];
};
defaultPackage = packages.mxmonzo;
# NixOS Modules
nixosModules = {
mxmonzo = import ./nixos_modules/mxmonzo.nix self;
};
# `nix run`
apps.mxmonzo = utils.lib.mkApp {
drv = packages.mxmonzo;
};
defaultApp = apps.mxmonzo;
# `nix develop`
devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ rustc cargo openssl pkg-config ];
};
});
}

114
nixos_modules/mxmonzo.nix Normal file
View File

@ -0,0 +1,114 @@
flake: {config, pkgs, lib, ...}:
let
cfg = config.services.mxmonzo;
inherit (flake.packages.${pkgs.stdenv.hostPlatform.system}) mxmonzo;
in
with lib;
{
options = {
services.mxmonzo = {
enable = mkOption {
default = false;
type = with types; bool;
description = ''
Start the Matrix Monzo bot.
'';
};
user = mkOption {
default = "mxmonzo";
type = with types; uniq str;
description = ''
Name of the user.
'';
};
matrixId = mkOption {
type = with types; str;
example = "@monzobot:librepush.net";
description = ''
Matrix ID of the Monzo bot user.
'';
};
bindAddress = mkOption {
default = "127.0.0.1:38320";
type = with types; str;
description = ''
Host:Port upon which to bind the web interface (used for OAuth + webhooks).
'';
};
externalBaseUri = mkOption {
example = "https://mxmonzo.my.librepush.net";
type = with types; str;
description = ''
External URL prefix to which this MxMonzo instance can be accessed (by webhooks).
It should be proxied to the HTTP interface listening on `bindAddress`.
'';
};
environmentFile = mkOption {
type = with types; path;
description = ''
File containing environment variables, especially:
- MATRIX_PASSWORD
- MONZO_CLIENT_ID
- MONZO_CLIENT_SECRET
'';
};
matrixRoom = mkOption {
type = with types; str;
description = ''
Room ID, like !roomid:librepush.net, of the bot's room to answer commands and emit notifications to.
'';
};
dataPath = mkOption {
type = with types; path;
description = ''
Path to where data can be kept.
'';
};
};
};
config = mkIf cfg.enable {
users.users."${cfg.user}" = {
description = "Matrix Monzo User";
isSystemUser = true;
group = "${cfg.user}";
};
users.groups."${cfg.user}" = {};
systemd.services.mxmonzo = {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
description = "Start the Matrix Monzo bot.";
environment = {
BIND_ADDRESS = cfg.bindAddress;
MATRIX_ID = cfg.matrixId;
MATRIX_ROOM = cfg.matrixRoom;
MATRIX_STORE = "${cfg.dataPath}/matrix-sdk";
MATRIX_PERSIST = "${cfg.dataPath}/matrix.json";
MONZO_PERSIST = "${cfg.dataPath}/monzo.json";
BASE_URI = cfg.externalBaseUri;
};
serviceConfig = {
Type = "simple";
User = "${cfg.user}";
ExecStart = ''${mxmonzo}/bin/mxmonzo'';
EnvironmentFile = [
cfg.environmentFile
];
};
};
};
}

View File

@ -1,11 +1,12 @@
use crate::monzo::monzo_client_freshened;
use crate::state::{Config, MonzoState, State, StateInner};
use crate::web::warp_main;
use matrix_sdk::config::{ClientConfig, SyncSettings};
use matrix_sdk::config::SyncSettings;
use matrix_sdk::room::Room;
use matrix_sdk::ruma::events::room::message::{MessageType, RoomMessageEventContent};
use matrix_sdk::ruma::events::SyncMessageEvent;
use matrix_sdk::ruma::events::OriginalSyncMessageLikeEvent;
use matrix_sdk::ruma::UserId;
use matrix_sdk::store::StateStore;
use matrix_sdk::{Client, Session};
use std::convert::TryFrom;
use std::fs::File;
@ -18,7 +19,7 @@ pub mod state;
pub mod web;
async fn on_room_message(
event: &SyncMessageEvent<RoomMessageEventContent>,
event: &OriginalSyncMessageLikeEvent<RoomMessageEventContent>,
room: Room,
state: &State,
) -> anyhow::Result<()> {
@ -66,12 +67,22 @@ async fn main() -> anyhow::Result<()> {
let state_inner = StateInner::default();
let mxid = UserId::try_from(config.matrix_id.clone())?;
let mxid = <&UserId>::try_from(config.matrix_id.as_str())?;
let store_path = config.matrix_store.clone();
let client_config = ClientConfig::new().store_path(store_path);
let state_store = Box::new(StateStore::open_with_path(store_path.join("state"))?);
let client = Client::new_from_user_id_with_config(&mxid, client_config).await?;
let crypto_store = Box::new(matrix_sdk::store::CryptoStore::open_with_passphrase(
store_path.join("crypto"),
None,
)?);
let client = Client::builder()
.crypto_store(crypto_store)
.state_store(state_store)
.user_id(&mxid)
.build()
.await?;
let state = State {
config: Arc::new(config.clone()),
@ -122,7 +133,7 @@ async fn main() -> anyhow::Result<()> {
client
.register_event_handler(
move |ev: SyncMessageEvent<RoomMessageEventContent>, room: Room| {
move |ev: OriginalSyncMessageLikeEvent<RoomMessageEventContent>, room: Room| {
let state_arc = state_arc.clone();
async move {
if let Err(error) = on_room_message(&ev, room, &state_arc).await {

View File

@ -55,7 +55,7 @@ async fn auth_done(query: AuthDone, state: State) -> anyhow::Result<impl warp::R
let _client = monzo_client_from_code(&state, &query.code).await?;
Ok("Success! Please authorise in Monzo, then click <a href='/auth_confirmed'>here</a>.")
Ok(warp::reply::html("Success! Please authorise in Monzo, then click <a href='/auth_confirmed'>here</a>."))
}
async fn auth_done_wrapped(
@ -140,7 +140,7 @@ async fn monzo_hook(hook: MonzoHook, state: State) -> anyhow::Result<impl warp::
let room = state
.matrix_client
.get_room(&RoomId::from_str(&state.config.matrix_room)?)
.get_room(<&RoomId>::try_from(state.config.matrix_room.as_str())?)
.ok_or_else(|| anyhow::anyhow!("Matrix room not found!"))?;
if let Room::Joined(room) = room {
let mut buf = String::new();