Compare commits
10 Commits
614b912dc9
...
a1cd0e6a25
| Author | SHA1 | Date | |
|---|---|---|---|
| a1cd0e6a25 | |||
| c054ef1c1c | |||
| 4509359c0e | |||
| 3b741835bd | |||
| d35426ec07 | |||
| e798e82395 | |||
| e5ca4473bc | |||
| 1874058c45 | |||
| 6f1adcee5e | |||
| f75c0aa057 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
.env
|
.env
|
||||||
|
.vscode
|
||||||
|
|||||||
428
Cargo.lock
generated
428
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
4
migrations/20250626163902_bank.sql
Normal file
4
migrations/20250626163902_bank.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS bank (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
balance INT
|
||||||
|
)
|
||||||
8
migrations/20250626164104_selfroles.sql
Normal file
8
migrations/20250626164104_selfroles.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS selfroles (
|
||||||
|
userid BIGINT NOT NULL,
|
||||||
|
guildid BIGINT NOT NULL,
|
||||||
|
roleid BIGINT,
|
||||||
|
UNIQUE (userid, guildid)
|
||||||
|
)
|
||||||
|
|
||||||
6
migrations/20250626164333_games.sql
Normal file
6
migrations/20250626164333_games.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS games (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
name CHAR[255]
|
||||||
|
)
|
||||||
|
|
||||||
8
migrations/20250626164633_items.sql
Normal file
8
migrations/20250626164633_items.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS items (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
owner BIGINT NOT NULL,
|
||||||
|
game BIGINT NOT NULL,
|
||||||
|
item BIGINT NOT NULL,
|
||||||
|
data JSON NOT NULL,
|
||||||
|
name TEXT
|
||||||
|
)
|
||||||
5
migrations/20250626164737_dailies.sql
Normal file
5
migrations/20250626164737_dailies.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS dailies (
|
||||||
|
userid BIGINT NOT NULL PRIMARY KEY,
|
||||||
|
last TIMESTAMPTZ,
|
||||||
|
streak INT
|
||||||
|
)
|
||||||
7
migrations/20250626164817_settings.sql
Normal file
7
migrations/20250626164817_settings.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
guildid BIGINT NOT NULL PRIMARY KEY,
|
||||||
|
positional_role BIGINT,
|
||||||
|
banrole BIGINT,
|
||||||
|
hoist_selfroles BOOLEAN,
|
||||||
|
prefix TEXT
|
||||||
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::common::{Context, Error};
|
use crate::common::{Context, Error, BigBirbError};
|
||||||
use crate::commands::settings;
|
use crate::commands::settings;
|
||||||
|
|
||||||
use poise::serenity_prelude as serenity;
|
use poise::serenity_prelude as serenity;
|
||||||
@@ -10,13 +10,7 @@ pub async fn ban(ctx: Context<'_>,
|
|||||||
#[rest]
|
#[rest]
|
||||||
reason: Option<String>) -> Result<(), Error>
|
reason: Option<String>) -> Result<(), Error>
|
||||||
{
|
{
|
||||||
let guild = match ctx.guild_id() {
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
Some(g) => g,
|
|
||||||
None => {
|
|
||||||
ctx.reply("This command must be ran within a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(role) = settings::get_banrole(ctx, guild).await? {
|
if let Some(role) = settings::get_banrole(ctx, guild).await? {
|
||||||
let member = guild.member(&ctx, user.id).await?;
|
let member = guild.member(&ctx, user.id).await?;
|
||||||
@@ -35,13 +29,7 @@ pub async fn ban(ctx: Context<'_>,
|
|||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn unban(ctx: Context<'_>, user: serenity::User) -> Result<(), Error>
|
pub async fn unban(ctx: Context<'_>, user: serenity::User) -> Result<(), Error>
|
||||||
{
|
{
|
||||||
let guild = match ctx.guild_id() {
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
Some(g) => g,
|
|
||||||
None => {
|
|
||||||
ctx.reply("This command must be ran within a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(role) = settings::get_banrole(ctx, guild).await? {
|
if let Some(role) = settings::get_banrole(ctx, guild).await? {
|
||||||
let member = guild.member(&ctx, user.id).await?;
|
let member = guild.member(&ctx, user.id).await?;
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use poise::serenity_prelude as serenity;
|
|||||||
#[poise::command(slash_command, prefix_command, aliases("bal", "b"))]
|
#[poise::command(slash_command, prefix_command, aliases("bal", "b"))]
|
||||||
pub async fn balance(ctx: Context<'_>, user: Option<serenity::User>) -> Result<(), Error> {
|
pub async fn balance(ctx: Context<'_>, user: Option<serenity::User>) -> Result<(), Error> {
|
||||||
let user = user.as_ref().unwrap_or(ctx.author());
|
let user = user.as_ref().unwrap_or(ctx.author());
|
||||||
let db = &ctx.data().database;
|
let mut tx = ctx.data().database.begin().await?;
|
||||||
|
|
||||||
let wealth = super::get_balance(user.id, db).await?;
|
let wealth = super::get_balance(user.id, &mut tx).await?;
|
||||||
|
|
||||||
common::no_ping_reply(&ctx, format!("{} **{}** token(s).",
|
common::no_ping_reply(&ctx, format!("{} **{}** token(s).",
|
||||||
if user.id == ctx.author().id {
|
if user.id == ctx.author().id {
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
|||||||
let mut tx = ctx.data().database.begin().await?;
|
let mut tx = ctx.data().database.begin().await?;
|
||||||
let mut balance = super::get_balance(ctx.author().id, &mut *tx).await?;
|
let mut balance = super::get_balance(ctx.author().id, &mut *tx).await?;
|
||||||
|
|
||||||
|
// whether the player is going to time out in the next 60 seconds
|
||||||
|
let mut timeout = false;
|
||||||
|
|
||||||
let amount = match amount.to_lowercase().as_str() {
|
let amount = match amount.to_lowercase().as_str() {
|
||||||
"all" => balance,
|
"all" => balance,
|
||||||
"half" => balance / 2,
|
"half" => balance / 2,
|
||||||
@@ -165,7 +168,7 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
|||||||
"**Dealer's hand**: {} ({})\n",
|
"**Dealer's hand**: {} ({})\n",
|
||||||
"**Your hand**: {} ({})\n\n",
|
"**Your hand**: {} ({})\n\n",
|
||||||
"**Bet**: {}\n",
|
"**Bet**: {}\n",
|
||||||
"Bust! You've lost **{}** tokens."
|
"Bust! You've lost **{}** token(s)."
|
||||||
),
|
),
|
||||||
dealers_hand.iter().map(|card| format!("`{card}`")).collect::<Vec<String>>().join(", "),
|
dealers_hand.iter().map(|card| format!("`{card}`")).collect::<Vec<String>>().join(", "),
|
||||||
dealers_count,
|
dealers_count,
|
||||||
@@ -194,13 +197,15 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
|||||||
concat!(
|
concat!(
|
||||||
"**Dealer's hand**: {} ({})\n",
|
"**Dealer's hand**: {} ({})\n",
|
||||||
"**Your hand**: {} ({})\n\n",
|
"**Your hand**: {} ({})\n\n",
|
||||||
"**Bet**: {}"
|
"**Bet**: {}",
|
||||||
|
"{}"
|
||||||
),
|
),
|
||||||
format!("`{}`, `XX`", dealers_hand[0]),
|
format!("`{}`, `XX`", dealers_hand[0]),
|
||||||
dealers_hand[0].value(matches!(dealers_hand[0], Card { rank: Rank::Ace, .. })),
|
dealers_hand[0].value(matches!(dealers_hand[0], Card { rank: Rank::Ace, .. })),
|
||||||
players_hand.iter().map(|card| format!("`{card}`")).collect::<Vec<String>>().join(", "),
|
players_hand.iter().map(|card| format!("`{card}`")).collect::<Vec<String>>().join(", "),
|
||||||
players_count,
|
players_count,
|
||||||
amount,
|
amount,
|
||||||
|
if timeout { "\n*You have 60 seconds to make a decision.*" } else { "" }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.components(components)
|
.components(components)
|
||||||
@@ -209,10 +214,36 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
|||||||
msg.edit(ctx, reply).await?;
|
msg.edit(ctx, reply).await?;
|
||||||
|
|
||||||
let Some(mci) = serenity::ComponentInteractionCollector::new(ctx.serenity_context())
|
let Some(mci) = serenity::ComponentInteractionCollector::new(ctx.serenity_context())
|
||||||
.timeout(Duration::from_secs(120))
|
.timeout(Duration::from_secs(60))
|
||||||
.filter(move |mci| mci.data.custom_id.starts_with("blackjack")).await else {
|
.filter(move |mci| mci.data.custom_id.starts_with("blackjack")).await else {
|
||||||
ctx.reply("failed interaction!").await?;
|
if timeout {
|
||||||
|
msg.edit(ctx, poise::CreateReply::default()
|
||||||
|
.components(vec![])
|
||||||
|
.content(
|
||||||
|
format!(
|
||||||
|
concat!(
|
||||||
|
"**Dealer's hand**: {} ({})\n",
|
||||||
|
"**Your hand**: {} ({})\n\n",
|
||||||
|
"**Bet**: {}\n",
|
||||||
|
"{}"
|
||||||
|
),
|
||||||
|
dealers_hand.iter().map(|card| format!("`{card}`")).collect::<Vec<String>>().join(", "),
|
||||||
|
dealers_count,
|
||||||
|
players_hand.iter().map(|card| format!("`{card}`")).collect::<Vec<String>>().join(", "),
|
||||||
|
players_count,
|
||||||
|
amount, format!("No bets go without a game! You've lost **{amount}** token(s).")
|
||||||
|
)
|
||||||
|
)).await?;
|
||||||
|
|
||||||
|
balance -= amount;
|
||||||
|
super::change_balance(ctx.author().id, balance, &mut *tx).await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
timeout = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if mci.member.clone().unwrap().user.id != ctx.author().id {
|
if mci.member.clone().unwrap().user.id != ctx.author().id {
|
||||||
@@ -225,6 +256,9 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset timeout after player interacts
|
||||||
|
timeout = false;
|
||||||
|
|
||||||
mci.create_response(ctx, serenity::CreateInteractionResponse::Acknowledge).await?;
|
mci.create_response(ctx, serenity::CreateInteractionResponse::Acknowledge).await?;
|
||||||
|
|
||||||
match &mci.data.custom_id[..] {
|
match &mci.data.custom_id[..] {
|
||||||
@@ -251,20 +285,20 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
|||||||
if players_count == 21 && players_hand.len() == 2 {
|
if players_count == 21 && players_hand.len() == 2 {
|
||||||
let amount = amount * 3 / 2;
|
let amount = amount * 3 / 2;
|
||||||
balance += amount;
|
balance += amount;
|
||||||
format!("You've won with a Blackjack! You've gained **{amount}** tokens.")
|
format!("You've won with a Blackjack! You've gained **{amount}** token(s).")
|
||||||
} else {
|
} else {
|
||||||
balance += amount;
|
balance += amount;
|
||||||
format!("You've won! **{amount}** tokens have been added to your account.")
|
format!("You've won! **{amount}** token(s) have been added to your account.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ordering::Greater if dealers_count > 21 => {
|
Ordering::Greater if dealers_count > 21 => {
|
||||||
if players_count == 21 && players_hand.len() == 2 {
|
if players_count == 21 && players_hand.len() == 2 {
|
||||||
let amount = amount * 3 / 2;
|
let amount = amount * 3 / 2;
|
||||||
balance += amount;
|
balance += amount;
|
||||||
format!("You've won with a Blackjack! You've gained **{amount}** tokens.")
|
format!("You've won with a Blackjack! You've gained **{amount}** token(s).")
|
||||||
} else {
|
} else {
|
||||||
balance += amount;
|
balance += amount;
|
||||||
format!("You've won! **{amount}** tokens have been added to your account.")
|
format!("You've won! **{amount}** token(s) have been added to your account.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
@@ -272,7 +306,7 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
|||||||
}
|
}
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
balance -= amount;
|
balance -= amount;
|
||||||
format!("You've lost. **{amount}** tokens to the dealer.")
|
format!("You've lost. **{amount}** token(s) to the dealer.")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +1,54 @@
|
|||||||
use crate::{Context, Error};
|
use crate::{Context, Error};
|
||||||
|
|
||||||
use poise::serenity_prelude::{UserId, User};
|
use poise::serenity_prelude::{UserId, User};
|
||||||
use sqlx::{types::chrono::{DateTime, Utc, TimeZone}, PgExecutor, Row};
|
use sqlx::{types::chrono::{DateTime, TimeZone, Utc}, PgConnection};
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
async fn get_streak<'a, E>(db: E, user: UserId) -> Result<Option<i32>, Error>
|
pub async fn get_streak(conn: &mut PgConnection, user: UserId) -> Result<Option<i32>, Error> {
|
||||||
where
|
let result = sqlx::query!(
|
||||||
E: PgExecutor<'a>,
|
"SELECT streak FROM dailies WHERE userid = $1",
|
||||||
{
|
user.get() as i64
|
||||||
match sqlx::query(
|
)
|
||||||
"SELECT streak FROM dailies WHERE userid = $1"
|
.fetch_optional(conn)
|
||||||
).bind(user.get() as i64).fetch_one(db).await
|
.await?;
|
||||||
{
|
|
||||||
Ok(row) => Ok(Some(row.get(0))),
|
Ok(result.map(|r| r.streak).unwrap_or(None))
|
||||||
Err(sqlx::Error::RowNotFound) => Ok(None),
|
|
||||||
Err(e) => Err(Box::new(e)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_streak<'a, E>(db: E, user: UserId, streak: i32) -> Result<(), Error>
|
pub async fn set_streak(conn: &mut PgConnection, user: UserId, streak: i32) -> Result<(), Error> {
|
||||||
where
|
sqlx::query!(
|
||||||
E: PgExecutor<'a>,
|
"INSERT INTO dailies (userid, streak) VALUES ($1, $2)
|
||||||
{
|
ON CONFLICT (userid) DO UPDATE SET streak = EXCLUDED.streak",
|
||||||
sqlx::query("INSERT INTO dailies (userid, streak) VALUES ($1, $2) ON CONFLICT (userid) DO UPDATE SET streak = EXCLUDED.streak")
|
user.get() as i64,
|
||||||
.bind(user.get() as i64)
|
streak
|
||||||
.bind(streak)
|
)
|
||||||
.execute(db).await?;
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_last<'a, E>(db: E, user: UserId) -> Result<Option<DateTime<Utc>>, Error>
|
pub async fn get_last(conn: &mut PgConnection, user: UserId) -> Result<Option<DateTime<Utc>>, Error> {
|
||||||
where
|
let result = sqlx::query!(
|
||||||
E: PgExecutor<'a>,
|
"SELECT last FROM dailies WHERE userid = $1",
|
||||||
{
|
user.get() as i64
|
||||||
match sqlx::query(
|
)
|
||||||
"SELECT last FROM dailies WHERE userid = $1"
|
.fetch_optional(conn)
|
||||||
).bind(user.get() as i64).fetch_one(db).await
|
.await?;
|
||||||
{
|
|
||||||
Ok(row) => Ok(Some(row.get(0))),
|
Ok(result.map(|r| r.last).unwrap_or(None))
|
||||||
Err(sqlx::Error::RowNotFound) => Ok(None),
|
|
||||||
Err(e) => Err(Box::new(e)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_last<'a, E>(db: E, user: UserId, last: DateTime<Utc>) -> Result<(), Error>
|
pub async fn set_last(conn: &mut PgConnection, user: UserId, last: DateTime<Utc>) -> Result<(), Error> {
|
||||||
where
|
sqlx::query!(
|
||||||
E: PgExecutor<'a>,
|
"INSERT INTO dailies (userid, last) VALUES ($1, $2)
|
||||||
{
|
ON CONFLICT (userid) DO UPDATE SET last = EXCLUDED.last",
|
||||||
sqlx::query("INSERT INTO dailies (userid, last) VALUES ($1, $2) ON CONFLICT (userid) DO UPDATE SET last = EXCLUDED.last")
|
user.get() as i64,
|
||||||
.bind(user.get() as i64)
|
last
|
||||||
.bind(last)
|
)
|
||||||
.execute(db).await?;
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -60,13 +56,14 @@ where
|
|||||||
/// Tells you what your current daily streak is
|
/// Tells you what your current daily streak is
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn streak(ctx: Context<'_>, user: Option<User>) -> Result<(), Error> {
|
pub async fn streak(ctx: Context<'_>, user: Option<User>) -> Result<(), Error> {
|
||||||
let db = &ctx.data().database;
|
let mut tx = ctx.data().database.begin().await?;
|
||||||
|
|
||||||
let (user, who) = match user {
|
let (user, who) = match user {
|
||||||
Some(user) => (user.id, format!("{} has", user.display_name())),
|
Some(user) => (user.id, format!("{} has", user.display_name())),
|
||||||
None => (ctx.author().id, "You have".to_string()),
|
None => (ctx.author().id, "You have".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.reply(format!("{who} a daily streak of **{}**", get_streak(db, user).await?.unwrap_or(0))).await?;
|
ctx.reply(format!("{who} a daily streak of **{}**", get_streak(&mut tx, user).await?.unwrap_or(0))).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
use crate::common::{Context, Error};
|
use crate::common::{Context, Error};
|
||||||
use poise::serenity_prelude::UserId;
|
use poise::serenity_prelude::UserId;
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
enum LeaderboardType {
|
enum LeaderboardType {
|
||||||
Tokens(usize),
|
Tokens(usize),
|
||||||
@@ -13,15 +12,16 @@ async fn display_leaderboard(ctx: Context<'_>, t: LeaderboardType) -> Result<(),
|
|||||||
|
|
||||||
match t {
|
match t {
|
||||||
LeaderboardType::Tokens(count) => {
|
LeaderboardType::Tokens(count) => {
|
||||||
let rows = sqlx::query(
|
let rows = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
SELECT id, balance FROM bank
|
SELECT id, balance FROM bank
|
||||||
ORDER BY balance DESC
|
ORDER BY balance DESC
|
||||||
LIMIT $1
|
LIMIT $1
|
||||||
"#
|
"#,
|
||||||
).bind(count as i32).fetch_all(db).await?;
|
count as i32
|
||||||
|
).fetch_all(db).await?;
|
||||||
|
|
||||||
let users: Vec<(_, i32)> = rows.iter().map(|row| (UserId::new(row.get::<i64, _>(0) as u64), row.get(1))).collect();
|
let users: Vec<(_, i32)> = rows.iter().map(|row| (UserId::new(row.id as u64), row.balance.unwrap_or(100))).collect();
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
|
|
||||||
for (id, balance) in users {
|
for (id, balance) in users {
|
||||||
@@ -32,15 +32,16 @@ async fn display_leaderboard(ctx: Context<'_>, t: LeaderboardType) -> Result<(),
|
|||||||
ctx.reply(format!("```\n{output}```")).await?;
|
ctx.reply(format!("```\n{output}```")).await?;
|
||||||
}
|
}
|
||||||
LeaderboardType::Dailies(count) => {
|
LeaderboardType::Dailies(count) => {
|
||||||
let rows = sqlx::query(
|
let rows = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
SELECT userid, streak FROM dailies
|
SELECT userid, streak FROM dailies
|
||||||
ORDER BY streak DESC
|
ORDER BY streak DESC
|
||||||
LIMIT $1
|
LIMIT $1
|
||||||
"#
|
"#,
|
||||||
).bind(count as i32).fetch_all(db).await?;
|
count as i32
|
||||||
|
).fetch_all(db).await?;
|
||||||
|
|
||||||
let users: Vec<(_, i32)> = rows.iter().map(|row| (UserId::new(row.get::<i64, _>(0) as u64), row.get(1))).collect();
|
let users: Vec<(_, i32)> = rows.iter().map(|row| (UserId::new(row.userid as u64), row.streak.unwrap_or(0))).collect();
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
|
|
||||||
for (id, streak) in users {
|
for (id, streak) in users {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub mod blackjack;
|
|||||||
|
|
||||||
use crate::{inventory::{self, Inventory}, common::{Context, Error}};
|
use crate::{inventory::{self, Inventory}, common::{Context, Error}};
|
||||||
use poise::serenity_prelude::{self as serenity, futures::StreamExt, UserId};
|
use poise::serenity_prelude::{self as serenity, futures::StreamExt, UserId};
|
||||||
use sqlx::{Row, PgExecutor};
|
use sqlx::PgConnection;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -79,16 +79,15 @@ mod items {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_balance<'a, E>(id: UserId, db: E) -> Result<i32, Error>
|
pub async fn get_balance(id: UserId, db: &mut PgConnection) -> Result<i32, Error>
|
||||||
where
|
|
||||||
E: PgExecutor<'a>,
|
|
||||||
{
|
{
|
||||||
let row = sqlx::query("SELECT balance FROM bank WHERE id = $1")
|
let row = sqlx::query!(
|
||||||
.bind(id.get() as i64)
|
"SELECT balance FROM bank WHERE id = $1",
|
||||||
.fetch_one(db).await.ok();
|
id.get() as i64
|
||||||
|
).fetch_one(db).await.ok();
|
||||||
|
|
||||||
let balance = if let Some(row) = row {
|
let balance = if let Some(row) = row {
|
||||||
row.try_get("balance")?
|
row.balance.unwrap_or(100)
|
||||||
} else {
|
} else {
|
||||||
100
|
100
|
||||||
};
|
};
|
||||||
@@ -96,14 +95,13 @@ where
|
|||||||
Ok(balance)
|
Ok(balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn change_balance<'a, E>(id: UserId, balance: i32, db: E) -> Result<(), Error>
|
pub async fn change_balance(id: UserId, balance: i32, db: &mut PgConnection) -> Result<(), Error>
|
||||||
where
|
|
||||||
E: PgExecutor<'a>,
|
|
||||||
{
|
{
|
||||||
sqlx::query("INSERT INTO bank (id, balance) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET balance = EXCLUDED.balance")
|
sqlx::query!(
|
||||||
.bind(id.get() as i64)
|
r#"INSERT INTO bank (id, balance) VALUES ($1, $2)
|
||||||
.bind(balance)
|
ON CONFLICT (id) DO UPDATE SET balance = EXCLUDED.balance"#,
|
||||||
.execute(db).await?;
|
id.get() as i64, balance
|
||||||
|
).execute(db).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ async fn autocomplete_shop<'a>(
|
|||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
partial: &'a str,
|
partial: &'a str,
|
||||||
) -> impl Iterator<Item = serenity::AutocompleteChoice> + use<'a> {
|
) -> impl Iterator<Item = serenity::AutocompleteChoice> + use<'a> {
|
||||||
let db = &ctx.data().database;
|
let mut tx = ctx.data().database.begin().await.unwrap();
|
||||||
let balance = super::get_balance(ctx.author().id, db).await;
|
let balance = super::get_balance(ctx.author().id, &mut *tx).await;
|
||||||
|
|
||||||
ITEMS.values()
|
ITEMS.values()
|
||||||
.filter(move |(_, item)| item.name.contains(partial))
|
.filter(move |(_, item)| item.name.contains(partial))
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ pub fn commands() -> Vec<Command<Data, Error>> {
|
|||||||
gambling::blackjack::blackjack(),
|
gambling::blackjack::blackjack(),
|
||||||
eval::eval(),
|
eval::eval(),
|
||||||
self_roles::role(),
|
self_roles::role(),
|
||||||
|
self_roles::editrole(),
|
||||||
settings::setting(),
|
settings::setting(),
|
||||||
administration::ban::ban(),
|
administration::ban::ban(),
|
||||||
administration::ban::unban(),
|
administration::ban::unban(),
|
||||||
|
|||||||
125
src/commands/self_roles/admin.rs
Normal file
125
src/commands/self_roles/admin.rs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
|
||||||
|
use crate::common::{self, Context, Error, BigBirbError};
|
||||||
|
|
||||||
|
use poise::serenity_prelude::{User, Role};
|
||||||
|
|
||||||
|
/// Change the name of a user's personal role
|
||||||
|
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")]
|
||||||
|
pub async fn name(ctx: Context<'_>, user: User, #[rest] name: String) -> Result<(), Error> {
|
||||||
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
|
|
||||||
|
let role = guild.role(ctx, super::name::change_user_role_name(ctx, &user, guild, name).await?).await?;
|
||||||
|
common::no_ping_reply(&ctx, format!("{role} has been updated.")).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the name of a user's personal role
|
||||||
|
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")]
|
||||||
|
pub async fn color(ctx: Context<'_>, user: User, color: String) -> Result<(), Error> {
|
||||||
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
|
let color = super::color::parse_color(&color)?;
|
||||||
|
|
||||||
|
let role = guild.role(ctx, super::color::change_user_role_color(ctx, &user, guild, color).await?).await?;
|
||||||
|
common::no_ping_reply(&ctx, format!("{role}'s color has been updated.")).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change a user's role name and color at once
|
||||||
|
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")]
|
||||||
|
pub async fn set(ctx: Context<'_>, user: User, color: String, #[rest] name: String) -> Result<(), Error> {
|
||||||
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
|
let color = super::color::parse_color(&color)?;
|
||||||
|
|
||||||
|
super::color::change_user_role_color(ctx, &user, guild, color).await?;
|
||||||
|
let role = guild.role(ctx, super::name::change_user_role_name(ctx, &user, guild, name).await?).await?;
|
||||||
|
|
||||||
|
common::no_ping_reply(&ctx, format!("{role} has been updated.")).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove and delete user's self role
|
||||||
|
#[poise::command(slash_command, prefix_command, aliases("disown"), required_permissions = "MANAGE_ROLES")]
|
||||||
|
pub async fn remove(ctx: Context<'_>, user: User) -> Result<(), Error> {
|
||||||
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
|
let mut tx = ctx.data().database.begin().await?;
|
||||||
|
|
||||||
|
if let Some(role) = super::get_user_role(user.id, guild, &mut *tx).await? {
|
||||||
|
guild.delete_role(ctx, role).await?;
|
||||||
|
super::remove_role(role, guild, &mut *tx).await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
common::no_ping_reply(&ctx, format!("{user}'s self role has been deleted.")).await?;
|
||||||
|
} else {
|
||||||
|
common::no_ping_reply(&ctx, format!("{user} has no self role.")).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a selfrole from the database without deleting it or attempting to take it from the user
|
||||||
|
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")]
|
||||||
|
pub async fn forget(ctx: Context<'_>, user: User) -> Result<(), Error> {
|
||||||
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
|
let mut tx = ctx.data().database.begin().await?;
|
||||||
|
|
||||||
|
if let Some(role) = super::get_user_role(user.id, guild, &mut *tx).await? {
|
||||||
|
super::remove_role(role, guild, &mut *tx).await?;
|
||||||
|
common::no_ping_reply(&ctx, format!("{user}'s selfrole has been forgotten.")).await?;
|
||||||
|
} else {
|
||||||
|
common::no_ping_reply(&ctx, format!("{user} has no selfrole set.")).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Give a user an existing role as their self role
|
||||||
|
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")]
|
||||||
|
pub async fn give(ctx: Context<'_>, user: User, role: Role, force: Option<bool>) -> Result<(), Error> {
|
||||||
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
|
let force = force.unwrap_or(false);
|
||||||
|
let member = guild.member(ctx, user).await?;
|
||||||
|
|
||||||
|
let mut tx = ctx.data().database.begin().await?;
|
||||||
|
|
||||||
|
if force {
|
||||||
|
// delete existing self role for user
|
||||||
|
if let Some(original) = super::get_user_role(member.user.id, guild, &mut *tx).await? {
|
||||||
|
guild.delete_role(ctx, original).await?;
|
||||||
|
super::remove_role(role.id, guild, &mut *tx).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove role from another user if it is already registered as their self role
|
||||||
|
if let Some(user) = super::get_user_by_role(role.id, guild, &mut *tx).await? {
|
||||||
|
let m = guild.member(ctx, user).await?;
|
||||||
|
m.remove_role(ctx, role.id).await?;
|
||||||
|
super::remove_role(role.id, guild, &mut *tx).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
super::update_user_role(member.user.id, guild, role.id, &mut *tx).await?;
|
||||||
|
member.add_role(ctx, role.id).await?;
|
||||||
|
} else {
|
||||||
|
if let Some(original) = super::get_user_role(member.user.id, guild, &mut *tx).await? {
|
||||||
|
let original = guild.role(ctx, original).await?;
|
||||||
|
common::no_ping_reply(&ctx, format!("{original} is already set as this user's self role, enable force to overwrite.")).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(owner) = super::get_user_by_role(role.id, guild, &mut *tx).await? {
|
||||||
|
let owner = owner.to_user(ctx).await?;
|
||||||
|
common::no_ping_reply(&ctx, format!("{role} is already owned by {owner}, enable force to overwrite.")).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
super::update_user_role(member.user.id, guild, role.id, &mut *tx).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
common::no_ping_reply(&ctx, format!("{member} has been given the self role {role}.")).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
|
|
||||||
use crate::common::{self, Context, Error};
|
use crate::common::{self, Context, Error, BigBirbError};
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use hex_color::HexColor;
|
use hex_color::HexColor;
|
||||||
use poise::serenity_prelude::{colours, Color, EditRole};
|
use poise::serenity_prelude::{colours, Color, User, GuildId, RoleId, EditRole};
|
||||||
|
|
||||||
static COLORS: Lazy<HashMap<&'static str, Color>> = Lazy::new(|| {
|
static COLORS: Lazy<HashMap<&'static str, Color>> = Lazy::new(|| {
|
||||||
HashMap::from([
|
HashMap::from([
|
||||||
@@ -59,6 +59,25 @@ async fn autocomplete_colors<'a>(
|
|||||||
COLORS.clone().into_keys().filter(move |x| x.split_whitespace().any(|x| x.starts_with(partial)))
|
COLORS.clone().into_keys().filter(move |x| x.split_whitespace().any(|x| x.starts_with(partial)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_color(s: &str) -> Result<Color, Error> {
|
||||||
|
let color = if let Some(named) = COLORS.get(s) {
|
||||||
|
named.clone()
|
||||||
|
} else {
|
||||||
|
let rgb = HexColor::parse_rgb(&s)?;
|
||||||
|
Color::from_rgb(rgb.r, rgb.g, rgb.b)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn change_user_role_color(ctx: Context<'_>, user: &User, guild: GuildId, color: Color) -> Result<RoleId, Error> {
|
||||||
|
let mut tx = ctx.data().database.begin().await?;
|
||||||
|
let role = super::edit_role(ctx, user.id, guild, EditRole::new().colour(color), &mut *tx).await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
Ok(role)
|
||||||
|
}
|
||||||
|
|
||||||
/// Change the color of your personal role
|
/// Change the color of your personal role
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn color(ctx: Context<'_>,
|
pub async fn color(ctx: Context<'_>,
|
||||||
@@ -66,27 +85,12 @@ pub async fn color(ctx: Context<'_>,
|
|||||||
#[rest]
|
#[rest]
|
||||||
color: String) -> Result<(), Error>
|
color: String) -> Result<(), Error>
|
||||||
{
|
{
|
||||||
let color = if let Some(named) = COLORS.get(color.as_str()) {
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
named.clone()
|
|
||||||
} else {
|
|
||||||
let rgb = HexColor::parse_rgb(&color)?;
|
|
||||||
Color::from_rgb(rgb.r, rgb.g, rgb.b)
|
|
||||||
};
|
|
||||||
|
|
||||||
let guild = if let Some(guild) = ctx.guild_id() {
|
|
||||||
guild
|
|
||||||
} else {
|
|
||||||
ctx.reply("This command can only be run inside of a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let user = ctx.author();
|
let user = ctx.author();
|
||||||
|
let color = parse_color(&color)?;
|
||||||
|
|
||||||
let mut tx = ctx.data().database.begin().await?;
|
let role = guild.role(ctx, change_user_role_color(ctx, &user, guild, color).await?).await?;
|
||||||
let role = super::edit_role(ctx, user.id, guild, EditRole::new().colour(color), &mut *tx).await?;
|
common::no_ping_reply(&ctx, format!("{role}'s color has been updated.")).await?;
|
||||||
tx.commit().await?;
|
|
||||||
|
|
||||||
common::no_ping_reply(&ctx, format!("{}'s color has been updated.", guild.role(ctx, role).await?)).await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
|
|
||||||
use crate::common::{Context, Error};
|
use crate::common::{Context, Error, BigBirbError};
|
||||||
|
|
||||||
/// Remove and delete your personal role
|
/// Remove and delete your personal role
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
pub async fn disown(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn disown(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
let guild = if let Some(guild) = ctx.guild_id() {
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
guild
|
|
||||||
} else {
|
|
||||||
ctx.reply("This command can only be run inside of a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let user = ctx.author();
|
let user = ctx.author();
|
||||||
|
|
||||||
let mut tx = ctx.data().database.begin().await?;
|
let mut tx = ctx.data().database.begin().await?;
|
||||||
|
|||||||
@@ -1,31 +1,47 @@
|
|||||||
|
|
||||||
use crate::common::{Context, Error};
|
use crate::common::{Context, Error};
|
||||||
use sqlx::{PgConnection, Row};
|
use sqlx::PgConnection;
|
||||||
use poise::serenity_prelude::{EditRole, GuildId, Permissions, RoleId, UserId};
|
use poise::serenity_prelude::{EditRole, GuildId, Permissions, RoleId, UserId};
|
||||||
|
|
||||||
mod register;
|
|
||||||
mod whois;
|
mod whois;
|
||||||
mod color;
|
pub mod color;
|
||||||
mod name;
|
pub mod name;
|
||||||
mod disown;
|
mod disown;
|
||||||
mod remove;
|
|
||||||
|
mod admin;
|
||||||
|
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
prefix_command,
|
prefix_command,
|
||||||
slash_command,
|
slash_command,
|
||||||
subcommands(
|
subcommands(
|
||||||
"register::register",
|
|
||||||
"whois::whois",
|
|
||||||
"color::color",
|
|
||||||
"name::name",
|
"name::name",
|
||||||
|
"color::color",
|
||||||
"disown::disown",
|
"disown::disown",
|
||||||
"remove::remove",
|
"whois::whois",
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub async fn role(_ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn role(_ctx: Context<'_>) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[poise::command(
|
||||||
|
prefix_command,
|
||||||
|
slash_command,
|
||||||
|
subcommands(
|
||||||
|
"admin::name",
|
||||||
|
"admin::color",
|
||||||
|
"admin::remove",
|
||||||
|
"admin::forget",
|
||||||
|
"admin::set",
|
||||||
|
"admin::give",
|
||||||
|
"whois::whois",
|
||||||
|
),
|
||||||
|
required_permissions = "MANAGE_ROLES",
|
||||||
|
)]
|
||||||
|
pub async fn editrole(_ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Edit a user's personal role, creates it with some default values if it doesn't exist.
|
/// Edit a user's personal role, creates it with some default values if it doesn't exist.
|
||||||
pub async fn edit_role(ctx: Context<'_>, user: UserId, guild: GuildId, edit: EditRole<'_>, db: &mut PgConnection) -> Result<RoleId, Error>
|
pub async fn edit_role(ctx: Context<'_>, user: UserId, guild: GuildId, edit: EditRole<'_>, db: &mut PgConnection) -> Result<RoleId, Error>
|
||||||
{
|
{
|
||||||
@@ -67,18 +83,14 @@ async fn create_role(
|
|||||||
|
|
||||||
/// Remove a row concerning a user's self role from the database
|
/// Remove a row concerning a user's self role from the database
|
||||||
pub async fn remove_user_role(user: UserId, guild: GuildId, db: &mut PgConnection) -> Result<(), Error> {
|
pub async fn remove_user_role(user: UserId, guild: GuildId, db: &mut PgConnection) -> Result<(), Error> {
|
||||||
sqlx::query("DELETE FROM selfroles WHERE userid = $1 AND guildid = $2")
|
sqlx::query!("DELETE FROM selfroles WHERE userid = $1 AND guildid = $2", user.get() as i64, guild.get() as i64)
|
||||||
.bind(user.get() as i64)
|
|
||||||
.bind(guild.get() as i64)
|
|
||||||
.execute(db).await?;
|
.execute(db).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_role(role: RoleId, guild: GuildId, db: &mut PgConnection) -> Result<(), Error> {
|
pub async fn remove_role(role: RoleId, guild: GuildId, db: &mut PgConnection) -> Result<(), Error> {
|
||||||
sqlx::query("DELETE FROM selfroles WHERE roleid = $1 AND guildid = $2")
|
sqlx::query!("DELETE FROM selfroles WHERE roleid = $1 AND guildid = $2", role.get() as i64, guild.get() as i64)
|
||||||
.bind(role.get() as i64)
|
|
||||||
.bind(guild.get() as i64)
|
|
||||||
.execute(db).await?;
|
.execute(db).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -86,10 +98,10 @@ pub async fn remove_role(role: RoleId, guild: GuildId, db: &mut PgConnection) ->
|
|||||||
|
|
||||||
/// Replace a user's custom role with a new one
|
/// Replace a user's custom role with a new one
|
||||||
pub async fn update_user_role(user: UserId, guild: GuildId, role: RoleId, db: &mut PgConnection) -> Result<(), Error> {
|
pub async fn update_user_role(user: UserId, guild: GuildId, role: RoleId, db: &mut PgConnection) -> Result<(), Error> {
|
||||||
sqlx::query("INSERT INTO selfroles (userid, guildid, roleid) VALUES($1, $2, $3) ON CONFLICT (userid, guildid) DO UPDATE SET roleid = EXCLUDED.roleid")
|
sqlx::query!(
|
||||||
.bind(user.get() as i64)
|
r#"INSERT INTO selfroles (userid, guildid, roleid) VALUES($1, $2, $3)
|
||||||
.bind(guild.get() as i64)
|
ON CONFLICT (userid, guildid) DO UPDATE SET roleid = EXCLUDED.roleid"#,
|
||||||
.bind(role.get() as i64)
|
user.get() as i64, guild.get() as i64, role.get() as i64)
|
||||||
.execute(db).await?;
|
.execute(db).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -97,12 +109,21 @@ pub async fn update_user_role(user: UserId, guild: GuildId, role: RoleId, db: &m
|
|||||||
|
|
||||||
/// Get a user's personal role id from the database
|
/// Get a user's personal role id from the database
|
||||||
pub async fn get_user_role(user: UserId, guild: GuildId, db: &mut PgConnection) -> Result<Option<RoleId>, Error> {
|
pub async fn get_user_role(user: UserId, guild: GuildId, db: &mut PgConnection) -> Result<Option<RoleId>, Error> {
|
||||||
match sqlx::query("SELECT roleid FROM selfroles WHERE userid = $1 AND guildid = $2")
|
match sqlx::query!("SELECT roleid FROM selfroles WHERE userid = $1 AND guildid = $2", user.get() as i64, guild.get() as i64)
|
||||||
.bind(user.get() as i64)
|
|
||||||
.bind(guild.get() as i64)
|
|
||||||
.fetch_one(db).await
|
.fetch_one(db).await
|
||||||
{
|
{
|
||||||
Ok(row) => Ok(Some(RoleId::new(row.try_get::<i64, usize>(0)? as u64))),
|
Ok(row) => Ok(Some(RoleId::new(row.roleid.unwrap() as u64))),
|
||||||
|
Err(sqlx::Error::RowNotFound) => Ok(None),
|
||||||
|
Err(e) => return Err(Box::new(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a user from the role id
|
||||||
|
pub async fn get_user_by_role(role: RoleId, guild: GuildId, db: &mut PgConnection) -> Result<Option<UserId>, Error> {
|
||||||
|
match sqlx::query!("SELECT userid FROM selfroles WHERE roleid = $1 AND guildid = $2", role.get() as i64, guild.get() as i64)
|
||||||
|
.fetch_one(db).await
|
||||||
|
{
|
||||||
|
Ok(row) => Ok(Some(UserId::new(row.userid as u64))),
|
||||||
Err(sqlx::Error::RowNotFound) => Ok(None),
|
Err(sqlx::Error::RowNotFound) => Ok(None),
|
||||||
Err(e) => return Err(Box::new(e)),
|
Err(e) => return Err(Box::new(e)),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,23 @@
|
|||||||
|
|
||||||
use crate::common::{self, Context, Error};
|
use crate::common::{self, Context, Error, BigBirbError};
|
||||||
|
use poise::serenity_prelude::{EditRole, User, GuildId, RoleId};
|
||||||
use poise::serenity_prelude::EditRole;
|
|
||||||
|
|
||||||
/// Change the name of your personal role
|
|
||||||
#[poise::command(slash_command, prefix_command)]
|
|
||||||
pub async fn name(ctx: Context<'_>, #[rest] name: String) -> Result<(), Error> {
|
|
||||||
let guild = if let Some(guild) = ctx.guild_id() {
|
|
||||||
guild
|
|
||||||
} else {
|
|
||||||
ctx.reply("This command can only be run inside of a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let user = ctx.author();
|
|
||||||
|
|
||||||
|
pub async fn change_user_role_name(ctx: Context<'_>, user: &User, guild: GuildId, name: String) -> Result<RoleId, Error> {
|
||||||
let mut tx = ctx.data().database.begin().await?;
|
let mut tx = ctx.data().database.begin().await?;
|
||||||
let role = super::edit_role(ctx, user.id, guild, EditRole::new().name(name), &mut *tx).await?;
|
let role = super::edit_role(ctx, user.id, guild, EditRole::new().name(name), &mut *tx).await?;
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
|
||||||
common::no_ping_reply(&ctx, format!("{} has been updated.", guild.role(ctx, role).await?)).await?;
|
Ok(role)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the name of your personal role
|
||||||
|
#[poise::command(slash_command, prefix_command, )]
|
||||||
|
pub async fn name(ctx: Context<'_>, #[rest] name: String) -> Result<(), Error> {
|
||||||
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
|
let user = ctx.author();
|
||||||
|
|
||||||
|
let role = guild.role(ctx, change_user_role_name(ctx, user, guild, name).await?).await?;
|
||||||
|
common::no_ping_reply(&ctx, format!("{role} has been updated.")).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
|
|
||||||
use crate::common::{self, Context, Error};
|
|
||||||
|
|
||||||
use poise::serenity_prelude as serenity;
|
|
||||||
|
|
||||||
/// Register an existing role as a user's custom role. This deletes their current self role.
|
|
||||||
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")]
|
|
||||||
pub async fn register(ctx: Context<'_>, user: serenity::User, role: serenity::Role) -> Result<(), Error> {
|
|
||||||
let guild = if let Some(guild) = ctx.guild_id() {
|
|
||||||
guild
|
|
||||||
} else {
|
|
||||||
ctx.reply("This command can only be run inside of a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tx = ctx.data().database.begin().await?;
|
|
||||||
|
|
||||||
if let Some(role) = super::get_user_role(user.id, guild, &mut *tx).await? {
|
|
||||||
guild.delete_role(ctx, role).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let member = guild.member(ctx, user).await?;
|
|
||||||
member.add_role(ctx, role.id).await?;
|
|
||||||
|
|
||||||
super::update_user_role(member.user.id, guild, role.id, &mut *tx).await?;
|
|
||||||
tx.commit().await?;
|
|
||||||
|
|
||||||
common::no_ping_reply(&ctx, format!("{} has been set as {}'s self role.", role, member.user)).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
|
|
||||||
use crate::common::{self, Context, Error};
|
|
||||||
|
|
||||||
use poise::serenity_prelude as serenity;
|
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_ROLES")]
|
|
||||||
pub async fn remove(ctx: Context<'_>, user: serenity::User) -> Result<(), Error> {
|
|
||||||
let guild = if let Some(guild) = ctx.guild_id() {
|
|
||||||
guild
|
|
||||||
} else {
|
|
||||||
ctx.reply("This command can only be run inside of a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tx = ctx.data().database.begin().await?;
|
|
||||||
|
|
||||||
if let Some(role) = super::get_user_role(user.id, guild, &mut *tx).await? {
|
|
||||||
guild.delete_role(ctx, role).await?;
|
|
||||||
super::remove_role(role, guild, &mut *tx).await?;
|
|
||||||
tx.commit().await?;
|
|
||||||
common::no_ping_reply(&ctx, format!("{}'s self role has been deleted.", user)).await?;
|
|
||||||
} else {
|
|
||||||
common::no_ping_reply(&ctx, format!("{} has no self role.", user)).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ use crate::common::{self, Context, Error};
|
|||||||
|
|
||||||
use poise::serenity_prelude as serenity;
|
use poise::serenity_prelude as serenity;
|
||||||
use serenity::UserId;
|
use serenity::UserId;
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
/// Let you know who is the owner of a role.
|
/// Let you know who is the owner of a role.
|
||||||
#[poise::command(slash_command, prefix_command)]
|
#[poise::command(slash_command, prefix_command)]
|
||||||
@@ -11,11 +10,10 @@ pub async fn whois(ctx: Context<'_>, role: serenity::Role) -> Result<(), Error>
|
|||||||
let db = &ctx.data().database;
|
let db = &ctx.data().database;
|
||||||
|
|
||||||
if let Some(guild) = ctx.guild_id() {
|
if let Some(guild) = ctx.guild_id() {
|
||||||
let user = match sqlx::query("SELECT userid FROM selfroles WHERE roleid = $1")
|
let user = match sqlx::query!("SELECT userid FROM selfroles WHERE roleid = $1", role.id.get() as i64)
|
||||||
.bind(role.id.get() as i64)
|
|
||||||
.fetch_one(db).await
|
.fetch_one(db).await
|
||||||
{
|
{
|
||||||
Ok(row) => UserId::new(row.try_get::<i64, usize>(0)? as u64),
|
Ok(row) => UserId::new(row.userid as u64),
|
||||||
Err(sqlx::Error::RowNotFound) => {
|
Err(sqlx::Error::RowNotFound) => {
|
||||||
ctx.reply("This role is not owned by anyone.").await?;
|
ctx.reply("This role is not owned by anyone.").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -25,7 +23,7 @@ pub async fn whois(ctx: Context<'_>, role: serenity::Role) -> Result<(), Error>
|
|||||||
|
|
||||||
let member = guild.member(ctx, user).await?;
|
let member = guild.member(ctx, user).await?;
|
||||||
|
|
||||||
common::no_ping_reply(&ctx, format!("{} owns this role.", member)).await?;
|
common::no_ping_reply(&ctx, format!("{member} owns this role.")).await?;
|
||||||
} else {
|
} else {
|
||||||
ctx.reply("This command must be used within a server!").await?;
|
ctx.reply("This command must be used within a server!").await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::common::{self, Context, Error};
|
use crate::common::{self, Context, Error, BigBirbError};
|
||||||
|
|
||||||
use poise::serenity_prelude::{Role, RoleId, GuildId};
|
use poise::serenity_prelude::{Role, RoleId, GuildId};
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
@@ -20,13 +20,7 @@ async fn get_prefix(ctx: Context<'_>, guild: GuildId) -> Result<Option<String>,
|
|||||||
|
|
||||||
#[poise::command(prefix_command, slash_command)]
|
#[poise::command(prefix_command, slash_command)]
|
||||||
async fn prefix(ctx: Context<'_>, prefix: Option<String>) -> Result<(), Error> {
|
async fn prefix(ctx: Context<'_>, prefix: Option<String>) -> Result<(), Error> {
|
||||||
let guild = match ctx.guild_id() {
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
Some(g) => g,
|
|
||||||
None => {
|
|
||||||
ctx.reply("This command must be ran within a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match prefix {
|
match prefix {
|
||||||
Some(prefix) => {
|
Some(prefix) => {
|
||||||
@@ -74,14 +68,7 @@ pub async fn get_positional_role(ctx: Context<'_>, guild: GuildId) -> Result<Opt
|
|||||||
|
|
||||||
#[poise::command(prefix_command, slash_command)]
|
#[poise::command(prefix_command, slash_command)]
|
||||||
pub async fn position(ctx: Context<'_>, role: Option<Role>) -> Result<(), Error> {
|
pub async fn position(ctx: Context<'_>, role: Option<Role>) -> Result<(), Error> {
|
||||||
let guild = match ctx.guild_id() {
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
Some(g) => g,
|
|
||||||
None => {
|
|
||||||
ctx.reply("This command must be ran within a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let member = ctx.author_member().await.unwrap();
|
let member = ctx.author_member().await.unwrap();
|
||||||
|
|
||||||
if !member.permissions(ctx).iter().any(|p| p.manage_guild()) {
|
if !member.permissions(ctx).iter().any(|p| p.manage_guild()) {
|
||||||
@@ -132,13 +119,7 @@ pub async fn get_hoist_selfroles(ctx: Context<'_>, guild: GuildId) -> Result<boo
|
|||||||
|
|
||||||
#[poise::command(prefix_command, slash_command)]
|
#[poise::command(prefix_command, slash_command)]
|
||||||
pub async fn hoist(ctx: Context<'_>, hoist: Option<bool>) -> Result<(), Error> {
|
pub async fn hoist(ctx: Context<'_>, hoist: Option<bool>) -> Result<(), Error> {
|
||||||
let guild = match ctx.guild_id() {
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
Some(g) => g,
|
|
||||||
None => {
|
|
||||||
ctx.reply("This command must be ran within a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match hoist {
|
match hoist {
|
||||||
Some(hoist) => {
|
Some(hoist) => {
|
||||||
@@ -195,14 +176,7 @@ pub async fn get_banrole(ctx: Context<'_>, guild: GuildId) -> Result<Option<Role
|
|||||||
|
|
||||||
#[poise::command(prefix_command, slash_command)]
|
#[poise::command(prefix_command, slash_command)]
|
||||||
pub async fn banrole(ctx: Context<'_>, role: Option<Role>) -> Result<(), Error> {
|
pub async fn banrole(ctx: Context<'_>, role: Option<Role>) -> Result<(), Error> {
|
||||||
let guild = match ctx.guild_id() {
|
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||||
Some(g) => g,
|
|
||||||
None => {
|
|
||||||
ctx.reply("This command must be ran within a guild.").await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let member = ctx.author_member().await.unwrap();
|
let member = ctx.author_member().await.unwrap();
|
||||||
|
|
||||||
if !member.permissions(ctx).iter().any(|p| p.manage_guild()) {
|
if !member.permissions(ctx).iter().any(|p| p.manage_guild()) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::{error, fmt};
|
||||||
use poise::ReplyHandle;
|
use poise::ReplyHandle;
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
@@ -20,3 +21,20 @@ pub async fn no_ping_reply<'a>(ctx: &'a Context<'_>, text: impl Into<String>) ->
|
|||||||
.allowed_mentions(CreateAllowedMentions::new())
|
.allowed_mentions(CreateAllowedMentions::new())
|
||||||
).await?)
|
).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum BigBirbError {
|
||||||
|
GuildOnly,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for BigBirbError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
Self::GuildOnly => "This command must be run inside of a guild.",
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{s}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for BigBirbError {}
|
||||||
|
|||||||
71
src/main.rs
71
src/main.rs
@@ -55,9 +55,8 @@ async fn event_handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_prefix(ctx: PartialContext<'_, Data, Error>) -> Result<Option<String>, Error> {
|
async fn get_prefix(ctx: PartialContext<'_, Data, Error>) -> Result<Option<String>, Error> {
|
||||||
let guild = match ctx.guild_id {
|
let Some(guild) = ctx.guild_id else {
|
||||||
Some(guild) => guild,
|
return Ok(ctx.data.prefix.clone());
|
||||||
None => return Ok(ctx.data.prefix.clone()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = &ctx.data.database;
|
let db = &ctx.data.database;
|
||||||
@@ -112,69 +111,9 @@ async fn main() -> Result<(), Error> {
|
|||||||
.max_connections(5)
|
.max_connections(5)
|
||||||
.connect(&database_url).await?;
|
.connect(&database_url).await?;
|
||||||
|
|
||||||
sqlx::query(
|
sqlx::migrate!()
|
||||||
r#"
|
.run(&database)
|
||||||
CREATE TABLE IF NOT EXISTS bank (
|
.await?;
|
||||||
id BIGINT PRIMARY KEY,
|
|
||||||
balance INT
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
).execute(&database).await?;
|
|
||||||
|
|
||||||
sqlx::query(
|
|
||||||
r#"
|
|
||||||
CREATE TABLE IF NOT EXISTS selfroles (
|
|
||||||
userid BIGINT NOT NULL,
|
|
||||||
guildid BIGINT NOT NULL,
|
|
||||||
roleid BIGINT,
|
|
||||||
UNIQUE (userid, guildid)
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
).execute(&database).await?;
|
|
||||||
|
|
||||||
sqlx::query(
|
|
||||||
r#"
|
|
||||||
CREATE TABLE IF NOT EXISTS games (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
name CHAR[255]
|
|
||||||
)
|
|
||||||
"#
|
|
||||||
).execute(&database).await?;
|
|
||||||
|
|
||||||
sqlx::query(
|
|
||||||
r#"
|
|
||||||
CREATE TABLE IF NOT EXISTS items (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
owner BIGINT NOT NULL,
|
|
||||||
game BIGINT NOT NULL,
|
|
||||||
item BIGINT NOT NULL,
|
|
||||||
data JSON NOT NULL,
|
|
||||||
name TEXT
|
|
||||||
)
|
|
||||||
"#
|
|
||||||
).execute(&database).await?;
|
|
||||||
|
|
||||||
sqlx::query(
|
|
||||||
r#"
|
|
||||||
CREATE TABLE IF NOT EXISTS dailies (
|
|
||||||
userid BIGINT NOT NULL PRIMARY KEY,
|
|
||||||
last TIMESTAMPTZ,
|
|
||||||
streak INT
|
|
||||||
)
|
|
||||||
"#
|
|
||||||
).execute(&database).await?;
|
|
||||||
|
|
||||||
sqlx::query(
|
|
||||||
r#"
|
|
||||||
CREATE TABLE IF NOT EXISTS settings (
|
|
||||||
guildid BIGINT NOT NULL PRIMARY KEY,
|
|
||||||
positional_role BIGINT,
|
|
||||||
banrole BIGINT,
|
|
||||||
hoist_selfroles BOOLEAN,
|
|
||||||
prefix TEXT
|
|
||||||
)
|
|
||||||
"#
|
|
||||||
).execute(&database).await?;
|
|
||||||
|
|
||||||
println!("Bot is ready!");
|
println!("Bot is ready!");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user