Skip to content

first gambling commit #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,7 @@ venv.bak/
# mypy
.mypy_cache/
/.vscode
token.txt
token.txt

# database
*.db
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RUN apt update -y \
RUN adduser --disabled-password --gecos "" bettercbot
USER bettercbot
WORKDIR /home/bettercbot
RUN mkdir persistent
ENV PATH="/home/bettercbot/.local/bin:$PATH"

COPY --chown=bettercbot:bettercbot src/cppref src/cppref
Expand Down
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ services:
bot:
build: .
restart: unless-stopped
volumes:
- bot-persistent:/home/bettercbot/persistent
volumes:
bot-persistent:
7 changes: 6 additions & 1 deletion src/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import discord
from discord.ext import commands
from src.util.blacklist import blacklist
from src.util.db import Database
from src import config as conf

def prefix(bot, message):
return [".", "++"]
Expand All @@ -13,6 +15,8 @@ def prefix(bot, message):

@bot.event
async def on_ready():
bot.db = Database(conf.database_path)

bot.user_cogs = [
# "src.cogs.verona",
"src.cogs.ping",
Expand All @@ -29,7 +33,8 @@ async def on_ready():
"src.cogs.admin",
"src.cogs.error_handler",
"src.cogs.snipe",
"src.cogs.modmail"
"src.cogs.modmail",
"src.cogs.gamble"
]
for cog in bot.user_cogs:
print(f"Loading {cog}...")
Expand Down
189 changes: 189 additions & 0 deletions src/cogs/gamble.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import random
import time
import datetime

from discord.ext import commands
import discord
from discord.ext.commands import Context
from discord import Member

from src import config as conf
from src.util.db import Database

class Users:
""" Helper class for managing the `users` table in the database. """
def __init__(self, db: Database):
self.db = db

def get_field(self, field_name: str, user_id: int):
cursor = self.db.cursor.execute(f"SELECT {field_name} FROM users WHERE id = ?", [user_id])
return cursor.fetchone()[0]

def get_leaderboard(self, max_entries: int):
cursor = self.db.cursor.execute(f"SELECT id, money FROM users ORDER BY money DESC LIMIT ?", [max_entries])
return cursor.fetchall()

def set_field(self, field_name: str, value, user_id: int, commit_changes: bool = True, add_value_instead: bool = False):
self.db.cursor.execute(f"UPDATE users SET {field_name} = {(field_name + ' +') if add_value_instead else ''} ? WHERE id = ?", [value, user_id])
if commit_changes:
self.db.connection.commit()

def check_user(self, user_id: int):
"""
NOTE: This function only checks if a user ID exists in the database, not if the user ID belongs to a valid server member.
"""
# Query number of times user id is found which should be 0 or 1.
cursor = self.db.cursor.execute("SELECT COUNT(*) FROM users WHERE id = ?", [user_id])
count = cursor.fetchone()[0]

# If no user ID was found in database, add user.
if count == 0:
self.db.cursor.execute("INSERT INTO users VALUES (?, 0, 0)", [user_id])
self.db.connection.commit()
elif count > 1:
raise Exception(f"Found multiple copies of user ID ({user_id}) in database.")

class Gamble(commands.Cog, name="Gamble"):
def __init__(self, bot):
self.bot = bot
self.db = bot.db
self.users = Users(self.db)

@commands.Cog.listener()
async def on_command_error(self, ctx: Context, error):
try:
if isinstance(error, commands.MissingRequiredArgument):
await ctx.send("Invalid input.")
if isinstance(error, commands.MissingPermissions):
await ctx.send("You do not have permission to use this command.")
if isinstance(error, commands.BadUnionArgument):
await ctx.send("Invalid input.")
except Exception as error:
print(error)

@commands.hybrid_command(with_app_command=True)
async def bet(self, ctx: Context, amount_to_bet: int):
"""
Place a bet (50/50). Enter the amount to bet.
"""
self.users.check_user(ctx.author.id)

if amount_to_bet == None:
await ctx.send("You need to enter the amount to bet.", ephemeral=True)
return

amount_in_wallet = self.users.get_field("money", ctx.author.id)

if amount_to_bet <= 0:
await ctx.send("You must enter a positive number greater than 0.", ephemeral=True)
return
if amount_to_bet > amount_in_wallet:
await ctx.send("You don't have enough money.", ephemeral=True)
return

won = random.randint(1, 2) == 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

random.choice([True, False]) or bool(random.getrandbits(1)) should be preferred


# Update wallet.
amount_result = amount_in_wallet + (amount_to_bet if won else -amount_to_bet)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really dislike (amount_to_bet if won else -amount_to_bet). I think it's better to be explicit here, even though it's a little more verbose

self.users.set_field("money", amount_result, ctx.author.id)

if won:
embed = discord.Embed(title="You won!", color=discord.Color.green())
embed.add_field(name=f"${amount_to_bet} has been added to your wallet", value=f"You have ${amount_result}")
await ctx.send(embed=embed)
else:
embed = discord.Embed(title="You lost.", color=discord.Color.red())
embed.add_field(name=f"${amount_to_bet} has been removed from your wallet", value=f"You have ${amount_result}")
await ctx.send(embed=embed)

@commands.hybrid_command(with_app_command=True)
async def wallet(self, ctx: Context, user: Member = None):
"""
Check your wallet. (Optional) Enter another user's ID to view their wallet.
"""
if user is None:
user = ctx.author

self.users.check_user(user.id)

sticker_url = f"https://cdn.discordapp.com/stickers/{conf.wallet_sticker_id}.png"

embed = discord.Embed(title=f"{user.display_name}'s wallet", color=discord.Color.blurple())
embed.set_thumbnail(url=sticker_url)
embed.add_field(name="Money", value=f"${self.users.get_field('money', user.id)}")

await ctx.send(embed=embed)

@commands.hybrid_command(with_app_command=True)
async def daily(self, ctx: Context):
"""
Get a small amount of money, works once per day.
"""
self.users.check_user(ctx.author.id)

last_daily_timestamp = int(self.users.get_field("last_daily_timestamp", ctx.author.id))
current_timestamp = int(time.time())
seconds_in_a_day = 86400
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true constants should be at module level and ALL_CAPS

seconds_since_last_daily = current_timestamp - last_daily_timestamp

if seconds_since_last_daily < seconds_in_a_day:
seconds_to_wait = seconds_in_a_day - seconds_since_last_daily

await ctx.send(f"You need to wait `{str(datetime.timedelta(seconds=seconds_to_wait))}` before you can use this again.", ephemeral=True)
return

# Update timestamp and add money to user"s wallet.
self.users.set_field("last_daily_timestamp", current_timestamp, ctx.author.id, commit_changes=False)
self.users.set_field("money", conf.daily_amount, ctx.author.id, commit_changes=True, add_value_instead=True)

await ctx.send(f"{ctx.author.mention} ${conf.daily_amount} has been added to your wallet.")

@commands.hybrid_command(with_app_command=True)
async def give(self, ctx: Context, user: Member, amount: int):
"""
Transfer money to another member. First argument is user ID. Second argument is amount.
"""
self.users.check_user(ctx.author.id)

if amount <= 0:
await ctx.send("You must enter a positive number greater than 0.", ephemeral=True)
return
if amount > self.users.get_field("money", ctx.author.id):
await ctx.send("You don't have enough money.", ephemeral=True)
return

if not user:
await ctx.send("User is not a valid user or not a member of this server.", ephemeral=True)
return

self.users.check_user(user.id)

self.users.set_field("money", -amount, ctx.author.id, commit_changes=False, add_value_instead=True)
self.users.set_field("money", amount, user.id, commit_changes=True, add_value_instead=True)

await ctx.send(f"Transferred ${amount} from {ctx.author.display_name} to {user.display_name}")

@commands.hybrid_command(with_app_command=True)
async def leaderboard(self, ctx: Context, max_entries: int = 10):
"""
Show the members with the most money.
"""
# Poor man"s clamp.
max_entries = max(1, min(max_entries, 50))

entries = self.users.get_leaderboard(max_entries)

member_list = ""

for index, entry in enumerate(entries):
user_id = entry[0]
user_money = entry[1]
member_list += f"{index + 1}. {self.bot.get_user(user_id).mention} ${user_money}\n"

embed = discord.Embed(title="Leaderboard", color=discord.Color.blurple())
embed.add_field(name=f"Showing top {max_entries} members", value=member_list)

await ctx.send(embed=embed)

async def setup(bot):
await bot.add_cog(Gamble(bot))
6 changes: 6 additions & 0 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,9 @@
help_channel : 'Done', # help
1055500115432972318 : 'Done' # code-review
}

database_path = "persistent/database.db"

daily_amount = 10

wallet_sticker_id = 1193982589556490440
5 changes: 5 additions & 0 deletions src/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS users(
id INTEGER NOT NULL PRIMARY KEY,
money INTEGER NOT NULL,
last_daily_timestamp INTEGER NOT NULL
);
10 changes: 10 additions & 0 deletions src/util/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import sqlite3
from pathlib import Path

class Database:
def __init__(self, path: Path):
self.connection = sqlite3.connect(path)
self.cursor = self.connection.cursor()

self.cursor.executescript((Path(__file__).parent.parent / "init.sql").read_text())
self.connection.commit()