Compare commits
11 Commits
614b912dc9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f5996467d9 | |||
| a1cd0e6a25 | |||
| c054ef1c1c | |||
| 4509359c0e | |||
| 3b741835bd | |||
| d35426ec07 | |||
| e798e82395 | |||
| e5ca4473bc | |||
| 1874058c45 | |||
| 6f1adcee5e | |||
| f75c0aa057 |
64
.github/workflows/docker-image.yml
vendored
64
.github/workflows/docker-image.yml
vendored
@@ -1,64 +0,0 @@
|
||||
name: Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
repository_dispatch:
|
||||
types: [ lamm-updated ]
|
||||
|
||||
jobs:
|
||||
build_amd64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build and push the Docker image
|
||||
run: docker build . --file Dockerfile --tag ${{ secrets.DOCKERHUB_USERNAME }}/discord-bot:amd64
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- name: Push the Docker image
|
||||
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/discord-bot:amd64
|
||||
|
||||
build_arm64:
|
||||
runs-on: self-hosted
|
||||
|
||||
steps:
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file Dockerfile --tag ${{ secrets.DOCKERHUB_USERNAME }}/discord-bot:arm64
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- name: Push the Docker image
|
||||
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/discord-bot:arm64
|
||||
|
||||
create_manifest:
|
||||
needs: [build_amd64, build_arm64]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Create and push multi-arch manifest
|
||||
run: |
|
||||
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/discord-bot:amd64
|
||||
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/discord-bot:arm64
|
||||
docker manifest create ${{ secrets.DOCKERHUB_USERNAME }}/discord-bot:latest \
|
||||
--amend ${{ secrets.DOCKERHUB_USERNAME }}/discord-bot:amd64 \
|
||||
--amend ${{ secrets.DOCKERHUB_USERNAME }}/discord-bot:arm64
|
||||
docker manifest push ${{ secrets.DOCKERHUB_USERNAME }}/discord-bot:latest
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/target
|
||||
.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 poise::serenity_prelude as serenity;
|
||||
@@ -10,13 +10,7 @@ pub async fn ban(ctx: Context<'_>,
|
||||
#[rest]
|
||||
reason: Option<String>) -> Result<(), Error>
|
||||
{
|
||||
let guild = match ctx.guild_id() {
|
||||
Some(g) => g,
|
||||
None => {
|
||||
ctx.reply("This command must be ran within a guild.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||
|
||||
if let Some(role) = settings::get_banrole(ctx, guild).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)]
|
||||
pub async fn unban(ctx: Context<'_>, user: serenity::User) -> Result<(), Error>
|
||||
{
|
||||
let guild = match ctx.guild_id() {
|
||||
Some(g) => g,
|
||||
None => {
|
||||
ctx.reply("This command must be ran within a guild.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||
|
||||
if let Some(role) = settings::get_banrole(ctx, guild).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"))]
|
||||
pub async fn balance(ctx: Context<'_>, user: Option<serenity::User>) -> Result<(), Error> {
|
||||
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).",
|
||||
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 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() {
|
||||
"all" => balance,
|
||||
"half" => balance / 2,
|
||||
@@ -165,7 +168,7 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
||||
"**Dealer's hand**: {} ({})\n",
|
||||
"**Your hand**: {} ({})\n\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_count,
|
||||
@@ -194,13 +197,15 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
||||
concat!(
|
||||
"**Dealer's hand**: {} ({})\n",
|
||||
"**Your hand**: {} ({})\n\n",
|
||||
"**Bet**: {}"
|
||||
"**Bet**: {}",
|
||||
"{}"
|
||||
),
|
||||
format!("`{}`, `XX`", dealers_hand[0]),
|
||||
dealers_hand[0].value(matches!(dealers_hand[0], Card { rank: Rank::Ace, .. })),
|
||||
players_hand.iter().map(|card| format!("`{card}`")).collect::<Vec<String>>().join(", "),
|
||||
players_count,
|
||||
amount,
|
||||
if timeout { "\n*You have 60 seconds to make a decision.*" } else { "" }
|
||||
)
|
||||
)
|
||||
.components(components)
|
||||
@@ -209,10 +214,36 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
||||
msg.edit(ctx, reply).await?;
|
||||
|
||||
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 {
|
||||
ctx.reply("failed interaction!").await?;
|
||||
return Ok(());
|
||||
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(());
|
||||
} else {
|
||||
timeout = true;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Reset timeout after player interacts
|
||||
timeout = false;
|
||||
|
||||
mci.create_response(ctx, serenity::CreateInteractionResponse::Acknowledge).await?;
|
||||
|
||||
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 {
|
||||
let amount = amount * 3 / 2;
|
||||
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 {
|
||||
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 => {
|
||||
if players_count == 21 && players_hand.len() == 2 {
|
||||
let amount = amount * 3 / 2;
|
||||
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 {
|
||||
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 => {
|
||||
@@ -272,7 +306,7 @@ pub async fn blackjack(ctx: Context<'_>, amount: String) -> Result<(), Error>
|
||||
}
|
||||
Ordering::Greater => {
|
||||
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 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;
|
||||
|
||||
async fn get_streak<'a, E>(db: E, user: UserId) -> Result<Option<i32>, Error>
|
||||
where
|
||||
E: PgExecutor<'a>,
|
||||
{
|
||||
match sqlx::query(
|
||||
"SELECT streak FROM dailies WHERE userid = $1"
|
||||
).bind(user.get() as i64).fetch_one(db).await
|
||||
{
|
||||
Ok(row) => Ok(Some(row.get(0))),
|
||||
Err(sqlx::Error::RowNotFound) => Ok(None),
|
||||
Err(e) => Err(Box::new(e)),
|
||||
}
|
||||
pub async fn get_streak(conn: &mut PgConnection, user: UserId) -> Result<Option<i32>, Error> {
|
||||
let result = sqlx::query!(
|
||||
"SELECT streak FROM dailies WHERE userid = $1",
|
||||
user.get() as i64
|
||||
)
|
||||
.fetch_optional(conn)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| r.streak).unwrap_or(None))
|
||||
}
|
||||
|
||||
async fn set_streak<'a, E>(db: E, user: UserId, streak: i32) -> Result<(), Error>
|
||||
where
|
||||
E: PgExecutor<'a>,
|
||||
{
|
||||
sqlx::query("INSERT INTO dailies (userid, streak) VALUES ($1, $2) ON CONFLICT (userid) DO UPDATE SET streak = EXCLUDED.streak")
|
||||
.bind(user.get() as i64)
|
||||
.bind(streak)
|
||||
.execute(db).await?;
|
||||
pub async fn set_streak(conn: &mut PgConnection, user: UserId, streak: i32) -> Result<(), Error> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO dailies (userid, streak) VALUES ($1, $2)
|
||||
ON CONFLICT (userid) DO UPDATE SET streak = EXCLUDED.streak",
|
||||
user.get() as i64,
|
||||
streak
|
||||
)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_last<'a, E>(db: E, user: UserId) -> Result<Option<DateTime<Utc>>, Error>
|
||||
where
|
||||
E: PgExecutor<'a>,
|
||||
{
|
||||
match sqlx::query(
|
||||
"SELECT last FROM dailies WHERE userid = $1"
|
||||
).bind(user.get() as i64).fetch_one(db).await
|
||||
{
|
||||
Ok(row) => Ok(Some(row.get(0))),
|
||||
Err(sqlx::Error::RowNotFound) => Ok(None),
|
||||
Err(e) => Err(Box::new(e)),
|
||||
}
|
||||
pub async fn get_last(conn: &mut PgConnection, user: UserId) -> Result<Option<DateTime<Utc>>, Error> {
|
||||
let result = sqlx::query!(
|
||||
"SELECT last FROM dailies WHERE userid = $1",
|
||||
user.get() as i64
|
||||
)
|
||||
.fetch_optional(conn)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| r.last).unwrap_or(None))
|
||||
}
|
||||
|
||||
async fn set_last<'a, E>(db: E, user: UserId, last: DateTime<Utc>) -> Result<(), Error>
|
||||
where
|
||||
E: PgExecutor<'a>,
|
||||
{
|
||||
sqlx::query("INSERT INTO dailies (userid, last) VALUES ($1, $2) ON CONFLICT (userid) DO UPDATE SET last = EXCLUDED.last")
|
||||
.bind(user.get() as i64)
|
||||
.bind(last)
|
||||
.execute(db).await?;
|
||||
pub async fn set_last(conn: &mut PgConnection, user: UserId, last: DateTime<Utc>) -> Result<(), Error> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO dailies (userid, last) VALUES ($1, $2)
|
||||
ON CONFLICT (userid) DO UPDATE SET last = EXCLUDED.last",
|
||||
user.get() as i64,
|
||||
last
|
||||
)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -60,13 +56,14 @@ where
|
||||
/// Tells you what your current daily streak is
|
||||
#[poise::command(slash_command, prefix_command)]
|
||||
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 {
|
||||
Some(user) => (user.id, format!("{} has", user.display_name())),
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
use crate::common::{Context, Error};
|
||||
use poise::serenity_prelude::UserId;
|
||||
use sqlx::Row;
|
||||
|
||||
enum LeaderboardType {
|
||||
Tokens(usize),
|
||||
@@ -13,15 +12,16 @@ async fn display_leaderboard(ctx: Context<'_>, t: LeaderboardType) -> Result<(),
|
||||
|
||||
match t {
|
||||
LeaderboardType::Tokens(count) => {
|
||||
let rows = sqlx::query(
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
SELECT id, balance FROM bank
|
||||
ORDER BY balance DESC
|
||||
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();
|
||||
|
||||
for (id, balance) in users {
|
||||
@@ -32,15 +32,16 @@ async fn display_leaderboard(ctx: Context<'_>, t: LeaderboardType) -> Result<(),
|
||||
ctx.reply(format!("```\n{output}```")).await?;
|
||||
}
|
||||
LeaderboardType::Dailies(count) => {
|
||||
let rows = sqlx::query(
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
SELECT userid, streak FROM dailies
|
||||
ORDER BY streak DESC
|
||||
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();
|
||||
|
||||
for (id, streak) in users {
|
||||
|
||||
@@ -9,7 +9,7 @@ pub mod blackjack;
|
||||
|
||||
use crate::{inventory::{self, Inventory}, common::{Context, Error}};
|
||||
use poise::serenity_prelude::{self as serenity, futures::StreamExt, UserId};
|
||||
use sqlx::{Row, PgExecutor};
|
||||
use sqlx::PgConnection;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -79,16 +79,15 @@ mod items {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_balance<'a, E>(id: UserId, db: E) -> Result<i32, Error>
|
||||
where
|
||||
E: PgExecutor<'a>,
|
||||
pub async fn get_balance(id: UserId, db: &mut PgConnection) -> Result<i32, Error>
|
||||
{
|
||||
let row = sqlx::query("SELECT balance FROM bank WHERE id = $1")
|
||||
.bind(id.get() as i64)
|
||||
.fetch_one(db).await.ok();
|
||||
let row = sqlx::query!(
|
||||
"SELECT balance FROM bank WHERE id = $1",
|
||||
id.get() as i64
|
||||
).fetch_one(db).await.ok();
|
||||
|
||||
let balance = if let Some(row) = row {
|
||||
row.try_get("balance")?
|
||||
row.balance.unwrap_or(100)
|
||||
} else {
|
||||
100
|
||||
};
|
||||
@@ -96,14 +95,13 @@ where
|
||||
Ok(balance)
|
||||
}
|
||||
|
||||
pub async fn change_balance<'a, E>(id: UserId, balance: i32, db: E) -> Result<(), Error>
|
||||
where
|
||||
E: PgExecutor<'a>,
|
||||
pub async fn change_balance(id: UserId, balance: i32, db: &mut PgConnection) -> Result<(), Error>
|
||||
{
|
||||
sqlx::query("INSERT INTO bank (id, balance) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET balance = EXCLUDED.balance")
|
||||
.bind(id.get() as i64)
|
||||
.bind(balance)
|
||||
.execute(db).await?;
|
||||
sqlx::query!(
|
||||
r#"INSERT INTO bank (id, balance) VALUES ($1, $2)
|
||||
ON CONFLICT (id) DO UPDATE SET balance = EXCLUDED.balance"#,
|
||||
id.get() as i64, balance
|
||||
).execute(db).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ async fn autocomplete_shop<'a>(
|
||||
ctx: Context<'_>,
|
||||
partial: &'a str,
|
||||
) -> impl Iterator<Item = serenity::AutocompleteChoice> + use<'a> {
|
||||
let db = &ctx.data().database;
|
||||
let balance = super::get_balance(ctx.author().id, db).await;
|
||||
let mut tx = ctx.data().database.begin().await.unwrap();
|
||||
let balance = super::get_balance(ctx.author().id, &mut *tx).await;
|
||||
|
||||
ITEMS.values()
|
||||
.filter(move |(_, item)| item.name.contains(partial))
|
||||
|
||||
@@ -38,6 +38,7 @@ pub fn commands() -> Vec<Command<Data, Error>> {
|
||||
gambling::blackjack::blackjack(),
|
||||
eval::eval(),
|
||||
self_roles::role(),
|
||||
self_roles::editrole(),
|
||||
settings::setting(),
|
||||
administration::ban::ban(),
|
||||
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 std::collections::HashMap;
|
||||
|
||||
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(|| {
|
||||
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)))
|
||||
}
|
||||
|
||||
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
|
||||
#[poise::command(slash_command, prefix_command)]
|
||||
pub async fn color(ctx: Context<'_>,
|
||||
@@ -66,27 +85,12 @@ pub async fn color(ctx: Context<'_>,
|
||||
#[rest]
|
||||
color: String) -> Result<(), Error>
|
||||
{
|
||||
let color = if let Some(named) = COLORS.get(color.as_str()) {
|
||||
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 guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||
let user = ctx.author();
|
||||
let color = parse_color(&color)?;
|
||||
|
||||
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?;
|
||||
|
||||
common::no_ping_reply(&ctx, format!("{}'s color has been updated.", guild.role(ctx, role).await?)).await?;
|
||||
let role = guild.role(ctx, change_user_role_color(ctx, &user, guild, color).await?).await?;
|
||||
common::no_ping_reply(&ctx, format!("{role}'s color has been updated.")).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
|
||||
use crate::common::{Context, Error};
|
||||
use crate::common::{Context, Error, BigBirbError};
|
||||
|
||||
/// Remove and delete your personal role
|
||||
#[poise::command(slash_command, prefix_command)]
|
||||
pub async fn disown(ctx: Context<'_>) -> 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 guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||
let user = ctx.author();
|
||||
|
||||
let mut tx = ctx.data().database.begin().await?;
|
||||
|
||||
@@ -1,31 +1,47 @@
|
||||
|
||||
use crate::common::{Context, Error};
|
||||
use sqlx::{PgConnection, Row};
|
||||
use sqlx::PgConnection;
|
||||
use poise::serenity_prelude::{EditRole, GuildId, Permissions, RoleId, UserId};
|
||||
|
||||
mod register;
|
||||
mod whois;
|
||||
mod color;
|
||||
mod name;
|
||||
pub mod color;
|
||||
pub mod name;
|
||||
mod disown;
|
||||
mod remove;
|
||||
|
||||
mod admin;
|
||||
|
||||
#[poise::command(
|
||||
prefix_command,
|
||||
slash_command,
|
||||
subcommands(
|
||||
"register::register",
|
||||
"whois::whois",
|
||||
"color::color",
|
||||
"name::name",
|
||||
"color::color",
|
||||
"disown::disown",
|
||||
"remove::remove",
|
||||
"whois::whois",
|
||||
)
|
||||
)]
|
||||
pub async fn role(_ctx: Context<'_>) -> Result<(), Error> {
|
||||
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.
|
||||
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
|
||||
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")
|
||||
.bind(user.get() as i64)
|
||||
.bind(guild.get() as i64)
|
||||
sqlx::query!("DELETE FROM selfroles WHERE userid = $1 AND guildid = $2", user.get() as i64, guild.get() as i64)
|
||||
.execute(db).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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")
|
||||
.bind(role.get() as i64)
|
||||
.bind(guild.get() as i64)
|
||||
sqlx::query!("DELETE FROM selfroles WHERE roleid = $1 AND guildid = $2", role.get() as i64, guild.get() as i64)
|
||||
.execute(db).await?;
|
||||
|
||||
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
|
||||
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")
|
||||
.bind(user.get() as i64)
|
||||
.bind(guild.get() as i64)
|
||||
.bind(role.get() as i64)
|
||||
sqlx::query!(
|
||||
r#"INSERT INTO selfroles (userid, guildid, roleid) VALUES($1, $2, $3)
|
||||
ON CONFLICT (userid, guildid) DO UPDATE SET roleid = EXCLUDED.roleid"#,
|
||||
user.get() as i64, guild.get() as i64, role.get() as i64)
|
||||
.execute(db).await?;
|
||||
|
||||
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
|
||||
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")
|
||||
.bind(user.get() as i64)
|
||||
.bind(guild.get() as i64)
|
||||
match sqlx::query!("SELECT roleid FROM selfroles WHERE userid = $1 AND guildid = $2", user.get() as i64, guild.get() as i64)
|
||||
.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(e) => return Err(Box::new(e)),
|
||||
}
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
|
||||
use crate::common::{self, Context, Error};
|
||||
|
||||
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();
|
||||
use crate::common::{self, Context, Error, BigBirbError};
|
||||
use poise::serenity_prelude::{EditRole, User, GuildId, RoleId};
|
||||
|
||||
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 role = super::edit_role(ctx, user.id, guild, EditRole::new().name(name), &mut *tx).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(())
|
||||
}
|
||||
|
||||
@@ -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 serenity::UserId;
|
||||
use sqlx::Row;
|
||||
|
||||
/// Let you know who is the owner of a role.
|
||||
#[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;
|
||||
|
||||
if let Some(guild) = ctx.guild_id() {
|
||||
let user = match sqlx::query("SELECT userid FROM selfroles WHERE roleid = $1")
|
||||
.bind(role.id.get() as i64)
|
||||
let user = match sqlx::query!("SELECT userid FROM selfroles WHERE roleid = $1", role.id.get() as i64)
|
||||
.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) => {
|
||||
ctx.reply("This role is not owned by anyone.").await?;
|
||||
return Ok(());
|
||||
@@ -25,7 +23,7 @@ pub async fn whois(ctx: Context<'_>, role: serenity::Role) -> Result<(), Error>
|
||||
|
||||
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 {
|
||||
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 sqlx::Row;
|
||||
@@ -20,13 +20,7 @@ async fn get_prefix(ctx: Context<'_>, guild: GuildId) -> Result<Option<String>,
|
||||
|
||||
#[poise::command(prefix_command, slash_command)]
|
||||
async fn prefix(ctx: Context<'_>, prefix: Option<String>) -> Result<(), Error> {
|
||||
let guild = match ctx.guild_id() {
|
||||
Some(g) => g,
|
||||
None => {
|
||||
ctx.reply("This command must be ran within a guild.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||
|
||||
match 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)]
|
||||
pub async fn position(ctx: Context<'_>, role: Option<Role>) -> Result<(), Error> {
|
||||
let guild = match ctx.guild_id() {
|
||||
Some(g) => g,
|
||||
None => {
|
||||
ctx.reply("This command must be ran within a guild.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||
let member = ctx.author_member().await.unwrap();
|
||||
|
||||
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)]
|
||||
pub async fn hoist(ctx: Context<'_>, hoist: Option<bool>) -> Result<(), Error> {
|
||||
let guild = match ctx.guild_id() {
|
||||
Some(g) => g,
|
||||
None => {
|
||||
ctx.reply("This command must be ran within a guild.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||
|
||||
match 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)]
|
||||
pub async fn banrole(ctx: Context<'_>, role: Option<Role>) -> Result<(), Error> {
|
||||
let guild = match ctx.guild_id() {
|
||||
Some(g) => g,
|
||||
None => {
|
||||
ctx.reply("This command must be ran within a guild.").await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let guild = ctx.guild_id().ok_or(BigBirbError::GuildOnly)?;
|
||||
let member = ctx.author_member().await.unwrap();
|
||||
|
||||
if !member.permissions(ctx).iter().any(|p| p.manage_guild()) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::{error, fmt};
|
||||
use poise::ReplyHandle;
|
||||
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())
|
||||
).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> {
|
||||
let guild = match ctx.guild_id {
|
||||
Some(guild) => guild,
|
||||
None => return Ok(ctx.data.prefix.clone()),
|
||||
let Some(guild) = ctx.guild_id else {
|
||||
return Ok(ctx.data.prefix.clone());
|
||||
};
|
||||
|
||||
let db = &ctx.data.database;
|
||||
@@ -112,69 +111,9 @@ async fn main() -> Result<(), Error> {
|
||||
.max_connections(5)
|
||||
.connect(&database_url).await?;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS bank (
|
||||
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?;
|
||||
sqlx::migrate!()
|
||||
.run(&database)
|
||||
.await?;
|
||||
|
||||
println!("Bot is ready!");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user