Skip to content

Commit

Permalink
First pass at dynamic scripting through Rune (disabled by default) (#92)
Browse files Browse the repository at this point in the history
* Initial support for compile-time optional dynamic scripting
* Bump dependencies
  • Loading branch information
udoprog committed Nov 17, 2020
1 parent f960794 commit 272370c
Show file tree
Hide file tree
Showing 25 changed files with 1,613 additions and 365 deletions.
1,017 changes: 783 additions & 234 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ panic = "abort"
debug = true

[patch.crates-io]
tokio = { git = "https://github.com/tokio-rs/tokio", branch = "v0.2.x" }
runestick = { git = "https://github.com/rune-rs/rune" }
rune = { git = "https://github.com/rune-rs/rune" }
rune-modules = { git = "https://github.com/rune-rs/rune" }
95 changes: 51 additions & 44 deletions bot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,72 +8,79 @@ description = "High octane Twitch bot powered by Rust"

[dependencies]
eudex = "0.1.1"
chrono = { version = "0.4.11", features = ["serde"] }
chrono-tz = { version = "0.5.1", features = ["serde"] }
clap = "2.33.0"
mysql_async = "0.23.0"
diesel = { version = "1.4.4", features = ["sqlite", "chrono"] }
chrono = { version = "0.4.19", features = ["serde"] }
chrono-tz = { version = "0.5.3", features = ["serde"] }
clap = "2.33.3"
mysql_async = "0.25.0"
diesel = { version = "1.4.5", features = ["sqlite", "chrono"] }
diesel_migrations = "1.4.0"
warp = "0.2.2"
log = "0.4.8"
relative-path = { version = "1.0.0", features = ["serde"] }
reqwest = "0.10.4"
serde = { version = "1.0.106", features = ["rc"] }
serde_yaml = "0.8.11"
serde_json = "1.0.51"
# set the bundled feature to use the bundled libsqlite3
libsqlite3-sys = { version = "0.17.3", features = ["bundled", "unlock_notify"] }
warp = "0.2.5"
log = "0.4.11"
relative-path = { version = "1.3.2", features = ["serde"] }
reqwest = "0.10.8"
serde = { version = "1.0.117", features = ["rc"] }
serde_yaml = "0.8.14"
serde_json = "1.0.59"
serde_cbor = "0.11.1"
serde_urlencoded = "0.6.1"
tokio = { version = "0.2.0", features = ["full"] }
url = { version = "2.1.1", features = ["serde"] }
serde_urlencoded = "0.7.0"
tokio = { version = "0.2.23", features = ["full"] }
url = { version = "2.2.0", features = ["serde"] }
Inflector = "0.11.4"
base64 = "0.12.0"
base64 = "0.13.0"
rand = "0.7.3"
fixed-map = "0.7.1"
log4rs = "0.11.0"
handlebars = "3.0.1"
log4rs = "0.13.0"
handlebars = "3.5.1"
htmlescape = "0.3.1"
lazy_static = "1.4.0"
# set the bundled feature to use the bundled libsqlite3
libsqlite3-sys = { version = "0.17.3", features = ["bundled", "unlock_notify"] }
webbrowser = "0.5.2"
parking_lot = "0.10.2"
webbrowser = "0.5.5"
parking_lot = "0.11.0"
percent-encoding = "2.1.0"
bytes = "0.5.4"
bytes = "0.5.6"
uuid = { version = "0.8.1", features = ["serde", "v4"] }
pin-utils = "0.1.0-alpha.4"
smallvec = { version = "1.3.0", features = ["serde"] }
dirs = "2.0.2"
backoff = "0.1.6"
rust-embed = { version = "5.5.1", features = ["interpolate-folder-path"] }
pin-utils = "0.1.0"
smallvec = { version = "1.4.2", features = ["serde"] }
dirs = "3.0.1"
backoff = "0.2.1"
rust-embed = { version = "5.6.0", features = ["interpolate-folder-path"] }
mime = "0.3.16"
mime_guess = "2.0.3"
uom = "0.27.0"
crossbeam = "0.7.3"
uom = "0.30.0"
crossbeam = "0.8.0"
hex = "0.4.2"
graphql_client = { version = "0.9.0", default-features = false }
futures-option = "0.2.0"
futures-cache = "0.9.0"
anyhow = "1.0.28"
thiserror = "1.0.15"
anyhow = "1.0.34"
thiserror = "1.0.22"
async-injector = "0.10.0"
async-trait = "0.1.30"
leaky-bucket = "0.7.3"
regex = "1.3.7"
backtrace = "0.3.46"
futures = { version = "0.3.4", features = ["async-await"] }
tracing = "0.1.13"
tracing-core = "0.1.10"
tracing-futures = { version = "0.2.3", default-features = false, features = ["std-future"] }
async-trait = "0.1.41"
leaky-bucket = "0.8.0"
regex = "1.4.2"
backtrace = "0.3.54"
futures = { version = "0.3.8", features = ["async-await"] }
tracing = "0.1.21"
tracing-core = "0.1.17"
tracing-futures = { version = "0.2.4", default-features = false, features = ["std-future"] }
slab = "0.4.2"
irc = "0.14.0"
irc = "0.14.1"
ignore = "0.4.16"
notify = "5.0.0-pre.4"

runestick = { version = "0.7.0", optional = true }
rune = { version = "0.7.0", features = ["diagnostics"], optional = true }
rune-modules = { version = "0.7.0", features = ["full"], optional = true }

[build-dependencies]
winres = "0.1.11"
anyhow = "1.0.28"
anyhow = "1.0.34"

[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3.8", features = ["shellapi", "impl-default"] }
winapi = { version = "0.3.9", features = ["shellapi", "impl-default"] }

[features]
default = []
windows = []
windows = []
scripting = ["runestick", "rune", "rune-modules"]
1 change: 1 addition & 0 deletions bot/migrations/2020-07-24-144205_script_storage/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE script_keys;
6 changes: 6 additions & 0 deletions bot/migrations/2020-07-24-144205_script_storage/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE script_keys (
channel VARCHAR NOT NULL,
key BLOB NOT NULL,
value BLOB NOT NULL,
PRIMARY KEY (channel, key)
);
2 changes: 1 addition & 1 deletion bot/src/api/setbac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ pub async fn run(
event = remote.rx.select_next_some() => {
let event = event?;

/// Only update on switches to current song.
// Only update on switches to current song.
match event {
bus::Global::SongModified => (),
_ => continue,
Expand Down
1 change: 1 addition & 0 deletions bot/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub(crate) struct ContextInner {
}

/// Context for a single command invocation.
#[derive(Clone)]
pub struct Context {
pub(crate) api_url: Arc<Option<String>>,
pub(crate) user: irc::User,
Expand Down
4 changes: 2 additions & 2 deletions bot/src/currency/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,8 @@ impl From<std::num::TryFromIntError> for BalanceTransferError {
}
}

impl From<mysql_async::error::Error> for BalanceTransferError {
fn from(value: mysql_async::error::Error) -> Self {
impl From<mysql_async::Error> for BalanceTransferError {
fn from(value: mysql_async::Error) -> Self {
BalanceTransferError::Other(value.into())
}
}
90 changes: 43 additions & 47 deletions bot/src/currency/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct Queries {

impl Queries {
/// Select all balances.
async fn select_balances<Tx>(&self, tx: Tx) -> Result<(Tx, Vec<(String, i32)>)>
async fn select_balances<Tx>(&self, tx: &mut Tx) -> Result<Vec<(String, i32)>>
where
Tx: Queryable,
{
Expand All @@ -52,16 +52,14 @@ impl Queries {
);

log::trace!("select_balances: {}", query);

let rows = tx.prep_exec(query, ()).await?;

let (tx, result) = rows.map_and_drop(mysql::from_row::<(String, i32)>).await?;

Ok((tx, result))
let results = tx
.exec_map(query.as_str(), (), mysql::from_row::<(String, i32)>)
.await?;
Ok(results)
}

/// Select the given balance.
async fn select_balance<Tx>(&self, tx: Tx, user: &str) -> Result<(Tx, Option<i32>)>
async fn select_balance<Tx>(&self, tx: &mut Tx, user: &str) -> Result<Option<i32>>
where
Tx: Queryable,
{
Expand All @@ -80,25 +78,22 @@ impl Queries {
};

log::trace!("select_balance: {} {:?}", query, params);
let result = tx.prep_exec(query, params).await?;

let (tx, results) = result.map_and_drop(mysql::from_row::<(i32,)>).await?;

Ok((tx, results.into_iter().map(|(c, ..)| c).next()))
Ok(tx.exec_first(query.as_str(), params).await?)
}

/// Helper to insert or update a single balance.
async fn modify_balance<Tx>(&self, tx: Tx, user: &str, amount: i32) -> Result<Tx>
async fn modify_balance<Tx>(&self, tx: &mut Tx, user: &str, amount: i32) -> Result<()>
where
Tx: Queryable,
{
// TODO: when mysql_async moves to async/await we can probably remove this budged ownership.
self.upsert_balances(tx, vec![user.to_string()], amount)
.await
.await?;
Ok(())
}

/// Update or insert a batch of balances.
async fn upsert_balances<Tx, I>(&self, tx: Tx, users: I, amount: i32) -> Result<Tx>
async fn upsert_balances<Tx, I>(&self, tx: &mut Tx, users: I, amount: i32) -> Result<()>
where
Tx: Queryable,
I: IntoIterator<Item = String> + Send + 'static,
Expand All @@ -121,11 +116,12 @@ impl Queries {
});

log::trace!("upsert_balances: {}", query);
Ok(tx.batch_exec(query, params).await?)
tx.exec_batch(query.as_str(), params).await?;
Ok(())
}

/// Insert the given balance.
async fn insert_balance<Tx>(&self, tx: Tx, user: &str, balance: i32) -> Result<Tx>
async fn insert_balance<Tx>(&self, tx: &mut Tx, user: &str, balance: i32) -> Result<()>
where
Tx: Queryable,
{
Expand All @@ -143,11 +139,12 @@ impl Queries {
};

log::trace!("insert_balance: {} {:?}", query, params);
Ok(tx.drop_exec(query, params).await?)
tx.exec_drop(query.as_str(), params).await?;
Ok(())
}

/// Update the given balance.
async fn update_balance<Tx>(&self, tx: Tx, user: &str, balance: i32) -> Result<Tx>
async fn update_balance<Tx>(&self, tx: &mut Tx, user: &str, balance: i32) -> Result<()>
where
Tx: Queryable,
{
Expand All @@ -164,7 +161,8 @@ impl Queries {
};

log::trace!("update_balance: {} {:?}", query, params);
Ok(tx.drop_exec(query, params).await?)
tx.exec_drop(query.as_str(), params).await?;
Ok(())
}
}

Expand Down Expand Up @@ -201,19 +199,21 @@ impl Backend {
let taker = user_id(taker);
let giver = user_id(giver);

let opts = mysql::TransactionOptions::new();
let tx = self.pool.start_transaction(opts).await?;
let opts = mysql::TxOpts::new();
let mut tx = self.pool.start_transaction(opts).await?;

let (tx, balance) = self.queries.select_balance(tx, &giver).await?;
let balance = self.queries.select_balance(&mut tx, &giver).await?;

let balance = balance.unwrap_or_default();

if balance < amount && !override_balance {
return Err(BalanceTransferError::NoBalance);
}

let tx = self.queries.modify_balance(tx, &taker, amount).await?;
let tx = self.queries.modify_balance(tx, &giver, -amount).await?;
self.queries.modify_balance(&mut tx, &taker, amount).await?;
self.queries
.modify_balance(&mut tx, &giver, -amount)
.await?;

tx.commit().await?;
Ok(())
Expand All @@ -224,10 +224,10 @@ impl Backend {
let channel = self.channel.to_string();
let mut output = Vec::new();

let opts = mysql::TransactionOptions::new();
let tx = self.pool.start_transaction(opts).await?;
let opts = mysql::TxOpts::new();
let mut tx = self.pool.start_transaction(opts).await?;

let (_, balances) = self.queries.select_balances(tx).await?;
let balances = self.queries.select_balances(&mut tx).await?;

for (user, balance) in balances {
output.push(Balance {
Expand All @@ -243,18 +243,18 @@ impl Backend {

/// Import balances for all users.
pub async fn import_balances(&self, balances: Vec<Balance>) -> Result<()> {
let opts = mysql::TransactionOptions::new();
let opts = mysql::TxOpts::new();
let mut tx = self.pool.start_transaction(opts).await?;

for balance in balances {
let amount: i32 = balance.amount.try_into()?;
let user = user_id(&balance.user);

let (new_tx, results) = self.queries.select_balance(tx, &user).await?;
let balance = self.queries.select_balance(&mut tx, &user).await?;

tx = match results {
None => self.queries.insert_balance(new_tx, &user, 0).await?,
Some(_) => self.queries.update_balance(new_tx, &user, amount).await?,
match balance {
None => self.queries.insert_balance(&mut tx, &user, 0).await?,
Some(_) => self.queries.update_balance(&mut tx, &user, amount).await?,
}
}

Expand All @@ -265,10 +265,10 @@ impl Backend {
/// Find user balance.
pub async fn balance_of(&self, _channel: &str, user: &str) -> Result<Option<BalanceOf>> {
let user = user_id(&user);
let opts = mysql::TransactionOptions::new();
let tx = self.pool.start_transaction(opts).await?;
let opts = mysql::TxOpts::new();
let mut tx = self.pool.start_transaction(opts).await?;

let (_, balance) = self.queries.select_balance(tx, &user).await?;
let balance = self.queries.select_balance(&mut tx, &user).await?;

let balance = match balance {
Some(b) => b.try_into()?,
Expand All @@ -286,11 +286,9 @@ impl Backend {
let user = user_id(&user);
let amount = amount.try_into()?;

let opts = mysql::TransactionOptions::new();
let tx = self.pool.start_transaction(opts).await?;

let tx = self.queries.modify_balance(tx, &user, amount).await?;

let opts = mysql::TxOpts::new();
let mut tx = self.pool.start_transaction(opts).await?;
self.queries.modify_balance(&mut tx, &user, amount).await?;
tx.commit().await?;
Ok(())
}
Expand All @@ -302,12 +300,10 @@ impl Backend {
I::IntoIter: Send + 'static,
{
let amount = amount.try_into()?;
let opts = mysql::TransactionOptions::new();
let tx = self.pool.start_transaction(opts).await?;

let opts = mysql::TxOpts::new();
let mut tx = self.pool.start_transaction(opts).await?;
let users = users.into_iter().map(|u| user_id(&u));
let tx = self.queries.upsert_balances(tx, users, amount).await?;

self.queries.upsert_balances(&mut tx, users, amount).await?;
tx.commit().await?;
Ok(())
}
Expand Down
Loading

0 comments on commit 272370c

Please sign in to comment.