Persist configuration with rusqlite
This commit is contained in:
1
.envrc
1
.envrc
@@ -4,3 +4,4 @@ export DISCORD_TOKEN_FILE=$(mktemp)
|
||||
cat >$DISCORD_TOKEN_FILE <<END
|
||||
MTMwMjQyMTcwMjU5NjIzMTE5OA.G3qd1e.5LqVmTtO0ZxR-j1ueO537IvKHF8YfHdnd-R4zo
|
||||
END
|
||||
export DB_FILE=db.sqlite
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/target
|
||||
/.direnv/
|
||||
db.sqlite
|
||||
|
||||
163
Cargo.lock
generated
163
Cargo.lock
generated
@@ -23,6 +23,18 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
@@ -130,6 +142,7 @@ dependencies = [
|
||||
"futures",
|
||||
"poise",
|
||||
"rand",
|
||||
"rusqlite",
|
||||
"serenity",
|
||||
"tokio",
|
||||
]
|
||||
@@ -444,6 +457,18 @@ dependencies = [
|
||||
"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]]
|
||||
name = "fastrand"
|
||||
version = "2.1.1"
|
||||
@@ -630,6 +655,9 @@ name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
@@ -637,6 +665,15 @@ version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
@@ -820,12 +857,28 @@ version = "0.2.161"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
@@ -974,6 +1027,45 @@ version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.15"
|
||||
@@ -986,6 +1078,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
|
||||
[[package]]
|
||||
name = "poise"
|
||||
version = "0.6.1"
|
||||
@@ -1186,6 +1284,32 @@ dependencies = [
|
||||
"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]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
@@ -1433,6 +1557,12 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "skeptic"
|
||||
version = "0.13.7"
|
||||
@@ -1479,6 +1609,24 @@ version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
@@ -1852,6 +2000,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "unicase"
|
||||
version = "2.8.0"
|
||||
@@ -1915,6 +2072,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
||||
@@ -11,5 +11,6 @@ eyre = "0.6.12"
|
||||
futures = "0.3.31"
|
||||
poise = "0.6.1"
|
||||
rand = "0.8.5"
|
||||
rusqlite = { version = "0.32.1", features = ["rusqlite-macros"] }
|
||||
serenity = "0.12.2"
|
||||
tokio = { version = "1.41.0", features = ["rt", "net"] }
|
||||
|
||||
@@ -21,5 +21,9 @@
|
||||
];
|
||||
systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ];
|
||||
crane.source = ./.;
|
||||
perSystem = {pkgs, ...}: {
|
||||
crane.packages.default.buildInputs = [pkgs.sqlite];
|
||||
crane.shell.args.packages = [pkgs.sqlite-interactive pkgs.sqlx-cli];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
89
src/main.rs
89
src/main.rs
@@ -1,24 +1,66 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
use eyre::{bail, Context as _, Error, OptionExt, Result};
|
||||
use futures::FutureExt;
|
||||
use poise::serenity_prelude as serenity;
|
||||
use rand::seq::SliceRandom;
|
||||
use rusqlite::OptionalExtension;
|
||||
|
||||
// TODO: persist configuration
|
||||
// TODO: buttons?
|
||||
// TODO: shuffle channels as well as users/instead of users?
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct GuildData {
|
||||
guild: serenity::GuildId,
|
||||
town_square: serenity::ChannelId,
|
||||
cottages: serenity::ChannelId,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Data(Arc<RwLock<BTreeMap<serenity::GuildId, GuildData>>>);
|
||||
struct Data(tokio::sync::Mutex<rusqlite::Connection>);
|
||||
|
||||
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>;
|
||||
|
||||
@@ -75,14 +117,8 @@ async fn dusk(ctx: Context<'_>) -> Result<()> {
|
||||
let GuildData {
|
||||
town_square,
|
||||
cottages,
|
||||
} = ctx
|
||||
.data()
|
||||
.0
|
||||
.read()
|
||||
.expect("No poisoned locks")
|
||||
.get(&guild)
|
||||
.copied()
|
||||
.ok_or_eyre("This bot hasn't been configured for this server")?;
|
||||
..
|
||||
} = ctx.data().get(guild).await?;
|
||||
|
||||
run(ctx, town_square, cottages).await?;
|
||||
|
||||
@@ -97,14 +133,8 @@ async fn dawn(ctx: Context<'_>) -> Result<()> {
|
||||
let GuildData {
|
||||
town_square,
|
||||
cottages,
|
||||
} = ctx
|
||||
.data()
|
||||
.0
|
||||
.read()
|
||||
.expect("No poisoned locks")
|
||||
.get(&guild)
|
||||
.copied()
|
||||
.ok_or_eyre("This bot hasn't been configured for this server")?;
|
||||
..
|
||||
} = ctx.data().get(guild).await?;
|
||||
|
||||
run(ctx, cottages, town_square).await?;
|
||||
|
||||
@@ -121,13 +151,11 @@ async fn configure(
|
||||
.guild_id()
|
||||
.ok_or_eyre("This bot only works in servers")?;
|
||||
|
||||
ctx.data().0.write().expect("No poisoned locks").insert(
|
||||
ctx.data().insert(GuildData {
|
||||
guild,
|
||||
GuildData {
|
||||
town_square,
|
||||
cottages,
|
||||
},
|
||||
);
|
||||
town_square,
|
||||
cottages,
|
||||
}).await?;
|
||||
ctx.reply("Configured successfully!").await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -160,6 +188,7 @@ async fn main() -> Result<()> {
|
||||
std::env::var_os("DISCORD_TOKEN_FILE").ok_or_eyre("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 framework = poise::Framework::builder()
|
||||
@@ -173,7 +202,7 @@ async fn main() -> Result<()> {
|
||||
for guild in &ready.guilds {
|
||||
register_commands(ctx, framework.options(), guild.id).await?;
|
||||
}
|
||||
Ok(Data::default())
|
||||
Data::open(dbfile)
|
||||
})
|
||||
})
|
||||
.build();
|
||||
|
||||
Reference in New Issue
Block a user