Add dicebot-roll dice expression parser
This commit is contained in:
parent
b4ccae581a
commit
59cb407eae
|
@ -1,5 +1,14 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
@ -16,6 +25,7 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||||
name = "axfive-matrix-dicebot"
|
name = "axfive-matrix-dicebot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -581,6 +591,24 @@ version = "0.1.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
"thread_local",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -754,6 +782,15 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.42"
|
version = "0.1.42"
|
||||||
|
|
|
@ -9,7 +9,8 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
reqwest = "^0.10"
|
reqwest = "^0.10"
|
||||||
serde_json = "^1"
|
serde_json = "^1"
|
||||||
toml = "0.5"
|
toml = "^0.5"
|
||||||
|
regex = "^1.3"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "^1"
|
version = "^1"
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
use regex::Regex;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ParseError {
|
||||||
|
reason: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "ParseError: {}", self.reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for ParseError {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Dice {
|
||||||
|
count: u32,
|
||||||
|
sides: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Roll {
|
||||||
|
Dice(Dice),
|
||||||
|
Bonus(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Roll {
|
||||||
|
fn from(value: u32) -> Roll {
|
||||||
|
Roll::Bonus(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Dice> for Roll {
|
||||||
|
fn from(value: Dice) -> Roll {
|
||||||
|
Roll::Dice(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Roll {
|
||||||
|
type Err = Box<dyn Error>;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let s = s.trim();
|
||||||
|
let regex = Regex::new(r"(?i)^(\d+)\s*(d\s*(\d+))?$")?;
|
||||||
|
let captures = match regex.captures(s) {
|
||||||
|
Some(captures) => captures,
|
||||||
|
None => {
|
||||||
|
return Err(ParseError {
|
||||||
|
reason: format!("{:?} is not a legal Roll Part expression", s),
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match captures.get(2) {
|
||||||
|
Some(_) => Ok(Dice {
|
||||||
|
count: captures.get(1).unwrap().as_str().parse()?,
|
||||||
|
sides: captures.get(3).unwrap().as_str().parse()?,
|
||||||
|
}
|
||||||
|
.into()),
|
||||||
|
None => Ok(Roll::Bonus(captures.get(1).unwrap().as_str().parse()?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Part {
|
||||||
|
Plus(Roll),
|
||||||
|
Minus(Roll),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Part {
|
||||||
|
type Err = Box<dyn Error>;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let s = s.trim();
|
||||||
|
|
||||||
|
match s.chars().next() {
|
||||||
|
Some('+') => Ok(Part::Plus(s[1..].parse()?)),
|
||||||
|
Some('-') => Ok(Part::Minus(s[1..].parse()?)),
|
||||||
|
Some(_) => Ok(Part::Plus(s.parse()?)),
|
||||||
|
None => Err(ParseError {
|
||||||
|
reason: format!("{:?} is not a legal Roll Part expression", s),
|
||||||
|
}
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Expression(Vec<Part>);
|
||||||
|
|
||||||
|
impl FromStr for Expression {
|
||||||
|
type Err = Box<dyn Error>;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let s = s.trim();
|
||||||
|
let validation_regex = Regex::new(
|
||||||
|
r"(?xi)^
|
||||||
|
([-\+])?\s*(\d+)\s*(d\s*(\d+))?
|
||||||
|
(\s*([-\+])\s*(\d+)\s*(d\s*(\d+))?)*
|
||||||
|
$",
|
||||||
|
)?;
|
||||||
|
if !validation_regex.is_match(s) {
|
||||||
|
return Err(ParseError {
|
||||||
|
reason: format!("{:?} is not a legal dice expression", s),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let part = Regex::new(r"(?i)[-\+]?\s*\d+\s*(d\s*(\d+))?")?;
|
||||||
|
|
||||||
|
let results: Result<_, _> = part.find_iter(s).map(|p| p.as_str().parse()).collect();
|
||||||
|
Ok(Expression(results?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let roll_string = std::env::args().skip(1).collect::<Vec<String>>().join(" ");
|
||||||
|
// first regex needs to be different because the sign is mandatory for the rest
|
||||||
|
let expression: Expression = roll_string.parse()?;
|
||||||
|
println!("{:?}", expression);
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
|
use axfive_matrix_dicebot::bot::DiceBot;
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::signal::unix::{signal, SignalKind};
|
use tokio::signal::unix::{signal, SignalKind};
|
||||||
use axfive_matrix_dicebot::bot::DiceBot;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
33
src/bot.rs
33
src/bot.rs
|
@ -1,10 +1,11 @@
|
||||||
use serde::{self, Deserialize, Serialize};
|
|
||||||
use reqwest::{Client, Url};
|
|
||||||
use crate::matrix::SyncCommand;
|
use crate::matrix::SyncCommand;
|
||||||
use std::path::PathBuf;
|
use reqwest::{Client, Url};
|
||||||
|
use serde::{self, Deserialize, Serialize};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const USER_AGENT: &str = "AxFive Matrix DiceBot/0.1.0 (+https://gitlab.com/Taywee/axfive-matrix-dicebot)";
|
const USER_AGENT: &str =
|
||||||
|
"AxFive Matrix DiceBot/0.1.0 (+https://gitlab.com/Taywee/axfive-matrix-dicebot)";
|
||||||
|
|
||||||
/// The "matrix" section of the config, which gives home server, login information, and etc.
|
/// The "matrix" section of the config, which gives home server, login information, and etc.
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
@ -42,20 +43,24 @@ struct LoginResponse {
|
||||||
|
|
||||||
impl DiceBot {
|
impl DiceBot {
|
||||||
/// Create a new dicebot from the given config path and config
|
/// Create a new dicebot from the given config path and config
|
||||||
pub async fn new(config_path: Option<PathBuf>, config: Config) -> Result<Self, Box<dyn std::error::Error>> {
|
pub async fn new(
|
||||||
|
config_path: Option<PathBuf>,
|
||||||
|
config: Config,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let home_server: Url = format!("https://{}", config.matrix.home_server).parse()?;
|
let home_server: Url = format!("https://{}", config.matrix.home_server).parse()?;
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let request = serde_json::to_string(&config.matrix.login)?;
|
let request = serde_json::to_string(&config.matrix.login)?;
|
||||||
let mut login_url = home_server.clone();
|
let mut login_url = home_server.clone();
|
||||||
login_url.set_path("/_matrix/client/r0/login");
|
login_url.set_path("/_matrix/client/r0/login");
|
||||||
let response = client.post(login_url)
|
let response = client
|
||||||
|
.post(login_url)
|
||||||
.header("user-agent", USER_AGENT)
|
.header("user-agent", USER_AGENT)
|
||||||
.body(request)
|
.body(request)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
let body: LoginResponse = serde_json::from_str(&response.text().await?)?;
|
let body: LoginResponse = serde_json::from_str(&response.text().await?)?;
|
||||||
let next_batch = config.matrix.next_batch.clone();
|
let next_batch = config.matrix.next_batch.clone();
|
||||||
Ok(DiceBot{
|
Ok(DiceBot {
|
||||||
home_server,
|
home_server,
|
||||||
config_path,
|
config_path,
|
||||||
client,
|
client,
|
||||||
|
@ -66,7 +71,9 @@ impl DiceBot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new dicebot, storing the config path to write it out
|
/// Create a new dicebot, storing the config path to write it out
|
||||||
pub async fn from_path<P: Into<PathBuf>>(config_path: P) -> Result<Self, Box<dyn std::error::Error>> {
|
pub async fn from_path<P: Into<PathBuf>>(
|
||||||
|
config_path: P,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let config_path = config_path.into();
|
let config_path = config_path.into();
|
||||||
let config = {
|
let config = {
|
||||||
let contents = fs::read_to_string(&config_path)?;
|
let contents = fs::read_to_string(&config_path)?;
|
||||||
|
@ -98,10 +105,11 @@ impl DiceBot {
|
||||||
|
|
||||||
// TODO: handle http 429
|
// TODO: handle http 429
|
||||||
if let Some(since) = &self.next_batch {
|
if let Some(since) = &self.next_batch {
|
||||||
sync_url.query_pairs_mut()
|
sync_url.query_pairs_mut().append_pair("since", since);
|
||||||
.append_pair("since", since);
|
|
||||||
}
|
}
|
||||||
let body = self.client.get(sync_url)
|
let body = self
|
||||||
|
.client
|
||||||
|
.get(sync_url)
|
||||||
.header("user-agent", USER_AGENT)
|
.header("user-agent", USER_AGENT)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
|
@ -117,7 +125,8 @@ impl DiceBot {
|
||||||
/// construction
|
/// construction
|
||||||
pub async fn logout(mut self) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn logout(mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let logout_url = self.url("/_matrix/client/r0/logout", &[]);
|
let logout_url = self.url("/_matrix/client/r0/logout", &[]);
|
||||||
self.client.post(logout_url)
|
self.client
|
||||||
|
.post(logout_url)
|
||||||
.header("user-agent", USER_AGENT)
|
.header("user-agent", USER_AGENT)
|
||||||
.body("{}")
|
.body("{}")
|
||||||
.send()
|
.send()
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
pub mod matrix;
|
|
||||||
pub mod bot;
|
pub mod bot;
|
||||||
|
pub mod matrix;
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::collections::HashMap;
|
||||||
#[serde(tag = "msgtype")]
|
#[serde(tag = "msgtype")]
|
||||||
#[serde(rename = "m.text")]
|
#[serde(rename = "m.text")]
|
||||||
pub struct TextMessage {
|
pub struct TextMessage {
|
||||||
body: String
|
body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need untagged because redactions are blank
|
// Need untagged because redactions are blank
|
||||||
|
|
Loading…
Reference in New Issue