Persist configuration with rusqlite

This commit is contained in:
bluepython508
2024-11-03 22:18:10 +00:00
parent 23169ef19c
commit 0cdd2735a6
6 changed files with 229 additions and 30 deletions

1
.envrc
View File

@@ -4,3 +4,4 @@ export DISCORD_TOKEN_FILE=$(mktemp)
cat >$DISCORD_TOKEN_FILE <<END cat >$DISCORD_TOKEN_FILE <<END
MTMwMjQyMTcwMjU5NjIzMTE5OA.G3qd1e.5LqVmTtO0ZxR-j1ueO537IvKHF8YfHdnd-R4zo MTMwMjQyMTcwMjU5NjIzMTE5OA.G3qd1e.5LqVmTtO0ZxR-j1ueO537IvKHF8YfHdnd-R4zo
END END
export DB_FILE=db.sqlite

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/target /target
/.direnv/ /.direnv/
db.sqlite

163
Cargo.lock generated
View File

@@ -23,6 +23,18 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.3" version = "1.1.3"
@@ -130,6 +142,7 @@ dependencies = [
"futures", "futures",
"poise", "poise",
"rand", "rand",
"rusqlite",
"serenity", "serenity",
"tokio", "tokio",
] ]
@@ -444,6 +457,18 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.1.1" version = "2.1.1"
@@ -630,6 +655,9 @@ name = "hashbrown"
version = "0.14.5" version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
@@ -637,6 +665,15 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
[[package]]
name = "hashlink"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [
"hashbrown 0.14.5",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.9" version = "0.3.9"
@@ -820,12 +857,28 @@ version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "libsqlite3-sys"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.12"
@@ -974,6 +1027,45 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
"uncased",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.15" version = "0.2.15"
@@ -986,6 +1078,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]] [[package]]
name = "poise" name = "poise"
version = "0.6.1" version = "0.6.1"
@@ -1186,6 +1284,32 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rusqlite"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
dependencies = [
"bitflags 2.6.0",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"rusqlite-macros",
"smallvec",
]
[[package]]
name = "rusqlite-macros"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecdc5e5d64f172916dfc8a0b0f7876de19b899e7a5f1d5b2c04c722cc78e0e45"
dependencies = [
"fallible-iterator",
"litrs",
"sqlite3-parser",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.24"
@@ -1433,6 +1557,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]] [[package]]
name = "skeptic" name = "skeptic"
version = "0.13.7" version = "0.13.7"
@@ -1479,6 +1609,24 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "sqlite3-parser"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb5307dad6cb84730ce8bdefde56ff4cf95fe516972d52e2bbdc8a8cd8f2520b"
dependencies = [
"bitflags 2.6.0",
"cc",
"fallible-iterator",
"indexmap",
"log",
"memchr",
"phf",
"phf_codegen",
"phf_shared",
"uncased",
]
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
@@ -1852,6 +2000,15 @@ dependencies = [
"syn 2.0.87", "syn 2.0.87",
] ]
[[package]]
name = "uncased"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "2.8.0" version = "2.8.0"
@@ -1915,6 +2072,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"

View File

@@ -11,5 +11,6 @@ eyre = "0.6.12"
futures = "0.3.31" futures = "0.3.31"
poise = "0.6.1" poise = "0.6.1"
rand = "0.8.5" rand = "0.8.5"
rusqlite = { version = "0.32.1", features = ["rusqlite-macros"] }
serenity = "0.12.2" serenity = "0.12.2"
tokio = { version = "1.41.0", features = ["rt", "net"] } tokio = { version = "1.41.0", features = ["rt", "net"] }

View File

@@ -21,5 +21,9 @@
]; ];
systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ]; systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ];
crane.source = ./.; crane.source = ./.;
perSystem = {pkgs, ...}: {
crane.packages.default.buildInputs = [pkgs.sqlite];
crane.shell.args.packages = [pkgs.sqlite-interactive pkgs.sqlx-cli];
};
}; };
} }

View File

@@ -1,24 +1,66 @@
use std::{ use std::path::Path;
collections::BTreeMap,
sync::{Arc, RwLock},
};
use eyre::{bail, Context as _, Error, OptionExt, Result}; use eyre::{bail, Context as _, Error, OptionExt, Result};
use futures::FutureExt; use futures::FutureExt;
use poise::serenity_prelude as serenity; use poise::serenity_prelude as serenity;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use rusqlite::OptionalExtension;
// TODO: persist configuration
// TODO: buttons? // TODO: buttons?
// TODO: shuffle channels as well as users/instead of users?
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct GuildData { struct GuildData {
guild: serenity::GuildId,
town_square: serenity::ChannelId, town_square: serenity::ChannelId,
cottages: serenity::ChannelId, cottages: serenity::ChannelId,
} }
#[derive(Default)] struct Data(tokio::sync::Mutex<rusqlite::Connection>);
struct Data(Arc<RwLock<BTreeMap<serenity::GuildId, GuildData>>>);
impl Data {
fn open(path: impl AsRef<Path>) -> Result<Self> {
let conn = rusqlite::Connection::open(path)?;
conn.execute_batch("CREATE TABLE IF NOT EXISTS guilds(guild TEXT PRIMARY KEY NOT NULL, town_square TEXT NOT NULL, cottages TEXT NOT NULL);")?;
Ok(Self(tokio::sync::Mutex::new(conn)))
}
async fn get(&self, guild: serenity::GuildId) -> Result<GuildData> {
self.0
.lock()
.await
.prepare_cached("SELECT guild, town_square, cottages FROM guilds WHERE guild = ?;")?
.query_row(rusqlite::params![guild.to_string()], |row| {
Ok(GuildData {
guild: row.get_unwrap::<_, String>("guild").parse().unwrap(),
town_square: row.get_unwrap::<_, String>("town_square").parse().unwrap(),
cottages: row.get_unwrap::<_, String>("cottages").parse().unwrap(),
})
})
.optional()?
.ok_or_eyre("This bot has not been configured in this server!")
}
async fn insert(
&self,
GuildData {
guild,
town_square,
cottages,
}: GuildData,
) -> Result<()> {
self.0
.lock()
.await
.prepare_cached("INSERT INTO guilds (guild, town_square, cottages) VALUES (?, ?, ?)")?
.execute(rusqlite::params![
guild.to_string(),
town_square.to_string(),
cottages.to_string()
])?;
Ok(())
}
}
type Context<'a> = poise::Context<'a, Data, Error>; type Context<'a> = poise::Context<'a, Data, Error>;
@@ -75,14 +117,8 @@ async fn dusk(ctx: Context<'_>) -> Result<()> {
let GuildData { let GuildData {
town_square, town_square,
cottages, cottages,
} = ctx ..
.data() } = ctx.data().get(guild).await?;
.0
.read()
.expect("No poisoned locks")
.get(&guild)
.copied()
.ok_or_eyre("This bot hasn't been configured for this server")?;
run(ctx, town_square, cottages).await?; run(ctx, town_square, cottages).await?;
@@ -97,14 +133,8 @@ async fn dawn(ctx: Context<'_>) -> Result<()> {
let GuildData { let GuildData {
town_square, town_square,
cottages, cottages,
} = ctx ..
.data() } = ctx.data().get(guild).await?;
.0
.read()
.expect("No poisoned locks")
.get(&guild)
.copied()
.ok_or_eyre("This bot hasn't been configured for this server")?;
run(ctx, cottages, town_square).await?; run(ctx, cottages, town_square).await?;
@@ -121,13 +151,11 @@ async fn configure(
.guild_id() .guild_id()
.ok_or_eyre("This bot only works in servers")?; .ok_or_eyre("This bot only works in servers")?;
ctx.data().0.write().expect("No poisoned locks").insert( ctx.data().insert(GuildData {
guild, guild,
GuildData { town_square,
town_square, cottages,
cottages, }).await?;
},
);
ctx.reply("Configured successfully!").await?; ctx.reply("Configured successfully!").await?;
Ok(()) Ok(())
} }
@@ -160,6 +188,7 @@ async fn main() -> Result<()> {
std::env::var_os("DISCORD_TOKEN_FILE").ok_or_eyre("A Discord token is required")?, std::env::var_os("DISCORD_TOKEN_FILE").ok_or_eyre("A Discord token is required")?,
) )
.wrap_err("A Discord token is required")?; .wrap_err("A Discord token is required")?;
let dbfile = std::env::var_os("DB_FILE").ok_or_eyre("A database file is required")?;
let intents = serenity::GatewayIntents::non_privileged(); let intents = serenity::GatewayIntents::non_privileged();
let framework = poise::Framework::builder() let framework = poise::Framework::builder()
@@ -173,7 +202,7 @@ async fn main() -> Result<()> {
for guild in &ready.guilds { for guild in &ready.guilds {
register_commands(ctx, framework.options(), guild.id).await?; register_commands(ctx, framework.options(), guild.id).await?;
} }
Ok(Data::default()) Data::open(dbfile)
}) })
}) })
.build(); .build();