async def create_transaction_external(sending_user: usr.User, amount: float, destination: str) -> 'Transaction': # Create transaction tx = None async with in_transaction() as conn: tx = Transaction(sending_user=sending_user, amount=str(Env.amount_to_raw(amount)), destination=destination, receiving_user=None) await tx.save(using_db=conn) return tx
async def tipauthor_cmd(self, ctx: Context): if ctx.error: await Messages.add_x_reaction(ctx.message) return msg = ctx.message user = ctx.user send_amount = ctx.send_amount # See how much they need to make this tip. available_balance = Env.raw_to_amount(await user.get_available_balance()) if send_amount > available_balance: await Messages.add_x_reaction(ctx.message) await Messages.send_error_dm( msg.author, f"Your balance isn't high enough to complete this tip. You have **{available_balance} {Env.currency_symbol()}**, but this tip would cost you **{send_amount} {Env.currency_symbol()}**" ) return # Make the transactions in the database tx_list = [] task_list = [] tx = await Transaction.create_transaction_external( sending_user=user, amount=send_amount, destination=Env.donation_address()) # Add reactions await msg.add_reaction('\U00002611') await msg.add_reaction('\U0001F618') await msg.add_reaction('\u2764') await msg.add_reaction('\U0001F499') await msg.add_reaction('\U0001F49B') # Queue the actual send await TransactionQueue.instance().put(tx) # Update stats stats: Stats = await user.get_stats(server_id=msg.guild.id) if msg.channel.id not in config.Config.instance( ).get_no_stats_channels(): await stats.update_tip_stats(send_amount)
def __init__(self, bot: Bot, host: str, port: int): self.bot = bot self.app = web.Application() self.app.add_routes([ web.post('/callback', self.callback), web.get('/ufw/{wallet}', self.ufw), web.get('/wfu/{user}', self.wfu), web.get('/users', self.users) ]) self.logger = logging.getLogger() self.host = host self.port = port self.min_amount = 10 if Env.banano() else 0.1
async def create_transaction_internal_dbuser( sending_user: usr.User, amount: float, receiving_user: usr.User) -> 'Transaction': """Create a transaction in the database, among discord users""" # Create transaction tx = None async with in_transaction() as conn: tx = Transaction(sending_user=sending_user, amount=str(Env.amount_to_raw(amount)), destination=await receiving_user.get_address(), receiving_user=receiving_user) await tx.save(using_db=conn) return tx
def get_help_pages(self, cmd_dict: dict, adminhelp: bool = False) -> list: """Builds paginated help menu""" pages = [] # Overview author = f"Graham v{__version__} ({'BANANO' if Env.banano() else 'Nano'}) edition" title = "Command Overview" description = ( "Use `{0}help command` for more information about a specific command " + " or go to the next page").format( config.Config.instance().command_prefix) entries = [] for k, cmd_list in cmd_dict.items(): for cmd in cmd_dict[k]['cmd_list']: entries.append( Entry( f"{config.Config.instance().command_prefix}{cmd.triggers[0]}", cmd.overview)) if adminhelp: entries.append( Entry(f"{config.Config.instance().command_prefix}adminhelp", "View the full list of admin commands")) pages.append( Page(entries=entries, title=title, author=author, description=description)) # Build detail pages for group, details in cmd_dict.items(): author = cmd_dict[group]['header'] description = cmd_dict[group]['info'] entries = self.get_entries(cmd_dict[group]['cmd_list']) pages.append( Page(entries=entries, author=author, description=description)) # Info entries = [ Entry( f"{config.Config.instance().command_prefix}{tips.TIPAUTHOR_INFO.triggers[0]}", tips.TIPAUTHOR_INFO.details) ] author = f"Graham v{__version__} for {Env.currency_name()}" heart = '\U0001F49B' if Env.banano() else '\U0001F499' description = "This bot is completely free, open source, and MIT licnesed" description += f"\n\nMade with {heart} for the **BANANO** and **NANO** communities" description += f"\nHangout with some awesome people at https://chat.banano.cc" description += f"\nMy Discord: **@bbedward#9246**" description += f"\nMy Reddit: **/u/bbedward**" description += f"\nMy Twitter: **@theRealBbedward**" description += f"\n\nGraham GitHub: https://github.com/bbedward/graham_discord_bot" pages.append( Page(entries=entries, author=author, description=description)) return pages
def format_balance_message(self, balance_raw: int, pending_raw: int, pending_send_db: int, pending_receive_db: int) -> discord.Embed: embed = discord.Embed( colour=0xFBDD11 if Env.banano() else discord.Colour.dark_blue()) embed.set_author( name="Balance", icon_url= "https://github.com/bbedward/graham_discord_bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://github.com/bbedward/graham_discord_bot/raw/master/assets/nano_logo.png" ) embed.description = "**Available:**\n" embed.description += f"```{Env.commafy(Env.format_float(Env.raw_to_amount(balance_raw - pending_send_db)))} {Env.currency_symbol()}\n" pending_receive_str = f"+ {Env.commafy(Env.format_float(Env.raw_to_amount(pending_raw + pending_receive_db)))} {Env.currency_symbol()}" pending_send_str = f"- {Env.commafy(Env.format_float(Env.raw_to_amount(pending_send_db)))} {Env.currency_symbol()}" rjust_size = max(len(pending_send_str), len(pending_receive_str)) embed.description += f"{pending_receive_str.ljust(rjust_size)} (Pending Receipt)\n{pending_send_str.ljust(rjust_size)} (Pending Send)```\n" embed.set_footer( text= "Pending balances are in queue and will become available after processing." ) return embed
async def sendmax_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message user: User = ctx.user destination: str = ctx.destination bal = await user.get_available_balance_dec() if (bal < 0.01 and Env.banano()) or (bal < 0.000001 and not Env.banano()): await Messages.send_error_dm(msg.author, "You balance is 0, so I can't make any withdraw") return # Create transaction tx = await Transaction.create_transaction_external( sending_user=user, amount=await user.get_available_balance_dec(), destination=destination ) # Queue the actual send await TransactionQueue.instance().put(tx) # Send user message await Messages.send_success_dm(msg.author, "I've queued your transaction! I'll let you know once I broadcast it to the network.")
async def create_transaction_giveaway( sending_user: usr.User, amount: float, giveaway: gway.Giveaway, conn = None) -> 'Transaction': """Create a transaction in the database, among discord users""" # Create transaction tx = None tx = Transaction( sending_user = sending_user, amount = str(Env.amount_to_raw(amount)), giveaway=giveaway ) await tx.save(using_db=conn) return tx
async def callback(self, request: web.Request): """Route for handling HTTP callback""" request_json = await request.json() hash = request_json['hash'] self.logger.debug(f"callback received {hash}") # cache if not await RedisDB.instance().exists(f"callback:{hash}"): await RedisDB.instance().set(f"callback:{hash}", "val", expires=300) else: return web.HTTPOk() # De-serialize block request_json['block'] = json.loads(request_json['block']) # only consider sends if 'is_send' in request_json and (request_json['is_send'] or request_json['is_send'] == 'true'): if 'amount' in request_json: # only consider self.min_amount or larger converted_amount = Env.raw_to_amount( int(request_json['amount'])) if converted_amount >= self.min_amount: # Figure out of this is one of our users link = request_json['block']['link_as_account'] account = await Account.filter( address=link).prefetch_related('user').first() if account is None: return web.HTTPOk() # See if this is an internal TX transaction = await Transaction.filter( block_hash=hash ).prefetch_related('receiving_user').first() if transaction is not None and transaction.receiving_user is not None: return web.HTTPOk() self.logger.debug( f'Deposit received: {request_json["amount"]} for {account.user.id}' ) amount_string = f"{Env.raw_to_amount(int(request_json['amount']))} {Env.currency_symbol()}" discord_user = await self.bot.fetch_user(account.user.id) if discord_user is not None: await Messages.send_success_dm( discord_user, f"Your deposit of **{amount_string}** has been received. It will be in your available balance shortly!", header="Deposit Success", footer= f"I only notify you of deposits that are {self.min_amount} {Env.currency_symbol()} or greater." ) return web.HTTPOk()
async def create_transaction_internal( sending_user: usr.User, amount: float, receiving_user: discord.User) -> 'Transaction': """Create a transaction in the database, among discord users""" # See if receiving user exists in our database receiving_user_db: usr.User = await usr.User.create_or_fetch_user( receiving_user) if receiving_user_db.tip_banned: return None # Create transaction tx = None async with in_transaction() as conn: tx = Transaction(sending_user=sending_user, amount=str(Env.amount_to_raw(amount)), destination=await receiving_user_db.get_address(), receiving_user=receiving_user_db) await tx.save(using_db=conn) return tx
def __init__(self, bot: Bot, host: str, port: int): self.bot = bot self.app = web.Application(middlewares=[web.normalize_path_middleware()]) self.app.add_routes([ web.post('/callback', self.callback) ]) cors = aiohttp_cors.setup(self.app, defaults={ "*": aiohttp_cors.ResourceOptions( allow_credentials=True, expose_headers="*", allow_headers="*", ) }) ufw_resource = cors.add(self.app.router.add_resource("/ufw/{wallet}")) cors.add(ufw_resource.add_route("GET", self.ufw)) wfu_resource = cors.add(self.app.router.add_resource("/wfu/{user}")) cors.add(wfu_resource.add_route("GET", self.wfu)) users_resource = cors.add(self.app.router.add_resource("/users")) cors.add(users_resource.add_route("GET", self.users)) self.logger = logging.getLogger() self.host = host self.port = port self.min_amount = 10 if Env.banano() else 0.1
def test_format_float(self): self.assertEqual(Env.format_float(9.90000), "9.9") self.assertEqual(Env.format_float(9.0), "9") self.assertEqual(Env.format_float(9), "9") self.assertEqual(Env.format_float(9.9000010), "9.900001")
import logging from util.regex import AmountAmbiguousException, AmountMissingException, RegexUtil from util.validators import Validators from util.util import Utils from util.discord.channel import ChannelUtil from util.discord.messages import Messages from db.models.user import User from db.redis import RedisDB from typing import List from models.constants import Constants from db.models.transaction import Transaction from tasks.transaction_queue import TransactionQueue # Commands Documentation RAIN_INFO = CommandInfo( triggers=["brain" if Env.banano() else "nrain", "brian", "nrian"], overview="Distribute a tip amount amongst active users", details="Distribute amount amongst active users." + f"\nExample: `{config.Config.instance().command_prefix}{'b' if Env.banano() else 'n'}rain 1000` will distribute 1000 {Env.currency_symbol()} between everyone who is active." + f"\n **minimum amount to rain: {config.Config.instance().get_rain_minimum()} {Env.currency_symbol()}**" ) class RainCog(commands.Cog): def __init__(self, bot: Bot): self.bot = bot self.logger = logging.getLogger() @commands.Cog.listener() async def on_message(self, message: discord.Message):
async def ticketstatus_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message user = ctx.user author = msg.author content = msg.content # If private, see what servers they are part of guilds = None if ChannelUtil.is_private(msg.channel): guilds = [] for g in self.bot.guilds: if g.get_member(msg.author.id) is not None: guilds.append(g) if len(guilds) == 0: return # See if they've been spamming redis_key = f"ticketspam:{msg.author.id}" if not ctx.god: spam = await RedisDB.instance().get(redis_key) if spam is not None: spam = int(spam) if spam >= 3: await Messages.send_error_dm(msg.author, "You're temporarily banned from entering giveaways") await Messages.delete_message(msg) return else: spam = 0 else: spam = 0 # Get active giveaway(s) - public channel if guilds == None: gw = await Giveaway.get_active_giveaway(server_id=msg.guild.id) if gw is None: await Messages.send_error_dm(msg.author, "There aren't any active giveaways.") await Messages.delete_message(msg) # Block ticket spam await RedisDB.instance().set(f"ticketspam:{msg.author.id}", str(spam + 1), expires=3600) return # Get their active giveaway transaction active_tx = await Transaction.filter(giveaway__id=gw.id, sending_user__id=user.id).first() response = None if active_tx is None: if int(gw.entry_fee) > 0: fee_converted = Env.raw_to_amount(int(gw.entry_fee)) response = f"There is a fee of **{fee_converted} {Env.currency_symbol()}**!\n" response+= f"Use `{config.Config.instance().command_prefix}ticket {fee_converted}` to pay the fee and enter" else: response = f"This giveaway is free to enter\n" response+= f"Use `{config.Config.instance().command_prefix}ticket` to enter." else: needed = int(gw.entry_fee) - int(active_tx.amount) if needed <= 0: response = f"You're already entered into this giveaway" else: fee_converted = Env.raw_to_amount(int(gw.entry_fee)) paid_converted = Env.raw_to_amount(int(active_tx.amount)) response = f"There is a fee of **{fee_converted} {Env.currency_symbol()}**! You've donated **{paid_converted} {Env.currency_symbol()}** but that's not enough to enter!\n" response+= f"Use `{config.Config.instance().command_prefix}ticket {NumberUtil.format_float(fee_converted - paid_converted)}` to pay the fee and enter" # Build response embed = discord.Embed(colour=0xFBDD11 if Env.banano() else discord.Colour.dark_blue()) embed.set_author(name=f"Giveaway #{gw.id} is active!", icon_url="https://github.com/bbedward/graham_discord_bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://github.com/bbedward/graham_discord_bot/raw/master/assets/nano_logo.png") embed.description = response await msg.author.send(embed=embed) await Messages.delete_message(msg) return # Get active giveaways (private channel) gws = await Giveaway.get_active_giveaways(server_ids=[g.id for g in guilds]) if gws is None or len(gws) == 0: await Messages.send_error_dm(msg.author, "There aren't any active giveaways.") await Messages.delete_message(msg) # Block ticket spam await RedisDB.instance().set(f"ticketspam:{msg.author.id}", str(spam + 1), expires=3600) return # Get their active giveaway transaction response = None for gw in gws: active_tx = await Transaction.filter(giveaway__id=gw.id, sending_user__id=user.id).first() response = f"**Giveaway #{gw.id}**\n" if response is None else f"**Giveaway #{gw.id}**:\n" if active_tx is None: if int(gw.entry_fee) > 0: fee_converted = Env.raw_to_amount(int(gw.entry_fee)) response+= f"There is a fee of **{fee_converted} {Env.currency_symbol()}**!\n" response+= f"Use `{config.Config.instance().command_prefix}ticket {fee_converted} id={gw.id}` to pay the fee and enter\n" else: response+= f"This giveaway is free to enter\n" response+= f"Use `{config.Config.instance().command_prefix}ticket id={gw.id}` to enter.\n" else: needed = int(gw.entry_fee) - int(active_tx.amount) if needed <= 0: response+= f"You're already entered into this giveaway" else: fee_converted = Env.raw_to_amount(int(gw.entry_fee)) paid_converted = Env.raw_to_amount(int(active_tx.amount)) response+= f"There is a fee of **{fee_converted} {Env.currency_symbol()}**! You've donated **{paid_converted} {Env.currency_symbol()}** but that's not enough to enter!\n" response+= f"Use `{config.Config.instance().command_prefix}ticket {NumberUtil.format_float(fee_converted - paid_converted)} id={gw.id}` to pay the fee and enter\n" # Build response embed = discord.Embed(colour=0xFBDD11 if Env.banano() else discord.Colour.dark_blue()) embed.set_author(name=f"Here are the active giveaways!", icon_url="https://github.com/bbedward/graham_discord_bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://github.com/bbedward/graham_discord_bot/raw/master/assets/nano_logo.png") embed.description = response await msg.author.send(embed=embed) await Messages.delete_message(msg)
from util.regex import AmountAmbiguousException, AmountMissingException, RegexUtil from util.validators import Validators from util.util import Utils from util.discord.channel import ChannelUtil from util.discord.messages import Messages from db.models.user import User from db.redis import RedisDB from typing import List from models.constants import Constants from util.number import NumberUtil from db.models.transaction import Transaction from tasks.transaction_queue import TransactionQueue # Commands Documentation RAIN_INFO = CommandInfo( triggers=["brain" if Env.banano() else "rain"], overview="Distribute a tip amount amongst active users", details="Distribute amount amongst active users." + f"\nExample: `{config.Config.instance().command_prefix}rain 100` will distribute 100 {Env.currency_symbol()} between everyone who is active." + f"\n **minimum amount to rain: {config.Config.instance().get_rain_minimum()} {Env.currency_symbol()}**" ) class RainCog(commands.Cog): def __init__(self, bot: Bot): self.bot = bot self.logger = logging.getLogger() @commands.Cog.listener() async def on_message(self, message: discord.Message):
async def winners_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message user = ctx.user as_dm = False # Punish them for trying to do this command in a no spam channel if msg.channel.id in config.Config.instance().get_no_spam_channels(): redis_key = f"ticketspam:{msg.author.id}" if not ctx.god: spam = await RedisDB.instance().get(redis_key) if spam is not None: spam = int(spam) else: spam = 0 await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "You can't view giveaway stats in this channel") await RedisDB.instance().set(f"ticketspam:{msg.author.id}", str(spam + 1), expires=3600) return if not as_dm: # Check spamming of this command if await RedisDB.instance().exists(f'winnersspam:{msg.channel.id}'): as_dm = True else: await RedisDB.instance().set(f'winnersspam:{msg.channel.id}', 'as', expires=60) # Get list winners = await Giveaway.filter(server_id=msg.guild.id, winning_user_id__not_isnull=True, ended_at__not_isnull=True).order_by('-ended_at').prefetch_related('winning_user').limit(10).all() if len(winners) == 0: await msg.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "There haven't been any giveaways on this server yet") return response_msg = "```" # Get biggest amount to adjust the padding biggest_num = 0 for winner in winners: winning_amount = Env.raw_to_amount(int(winner.final_amount)) length = len(f"{NumberUtil.format_float(winning_amount)} {Env.currency_symbol()}") if length > biggest_num: biggest_num = length for rank, winner in enumerate(winners, start=1): adj_rank = str(rank) if rank >= 10 else f" {rank}" user_name = winner.winning_user.name amount_str = f"{NumberUtil.format_float(Env.raw_to_amount(int(winner.final_amount)))} {Env.currency_symbol()}".ljust(biggest_num) response_msg += f"{adj_rank}. {amount_str} - won by {user_name}\n" response_msg += "```" embed = discord.Embed(colour=0xFBDD11 if Env.banano() else discord.Colour.dark_blue()) embed.set_author(name=f"Here are the last {len(winners)} giveaway winners \U0001F44F", icon_url="https://github.com/bbedward/graham_discord_bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://github.com/bbedward/graham_discord_bot/raw/master/assets/nano_logo.png") embed.description = response_msg if not as_dm: await msg.channel.send(f"<@{msg.author.id}>", embed=embed) else: await msg.author.send(embed=embed) await msg.add_reaction('\u2709')
async def tipgiveaway_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message user = ctx.user # Check roles if not await self.role_check(msg): return # Punish them for trying to do this command in a no spam channel if msg.channel.id in config.Config.instance().get_no_spam_channels(): redis_key = f"ticketspam:{msg.author.id}" if not ctx.god: spam = await RedisDB.instance().get(redis_key) if spam is not None: spam = int(spam) else: spam = 0 await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "You can't view donate to the giveaway in this channel") await RedisDB.instance().set(f"ticketspam:{msg.author.id}", str(spam + 1), expires=3600) await Messages.delete_message_if_ok(msg) return # Get their tip amount try: tip_amount = RegexUtil.find_float(msg.content) if tip_amount < Constants.TIP_MINIMUM: await Messages.send_error_dm(msg.author, f"Minimum tip amount if {Constants.TIP_MINIMUM}") await Messages.delete_message_if_ok(msg) return except AmountMissingException: await Messages.send_usage_dm(msg.author, TIPGIVEAWAY_INFO) await Messages.delete_message_if_ok(msg) return # Get active giveaway gw = await Giveaway.get_active_giveaway(server_id=msg.guild.id) if gw is None: # get bot-pending giveaway or create one gw = await Giveaway.get_pending_bot_giveaway(server_id=msg.guild.id) if gw is None: try: # Initiate the bot giveaway with a lock to avoid race condition # Lock this so concurrent giveaways can't be started/avoid race condition async with RedisLock( await RedisDB.instance().get_redis(), key=f"{Env.currency_symbol().lower()}giveawaylock:{msg.guild.id}", timeout=30, wait_timeout=30 ) as lock: # See if giveaway already in progress should_create = False active_giveaway = await Giveaway.get_active_giveaway(server_id=msg.guild.id) if active_giveaway is None: bot_giveaway = await Giveaway.get_pending_bot_giveaway(server_id=msg.guild.id) if bot_giveaway is None: should_create = True if should_create: # Start giveaway async with in_transaction() as conn: gw = await Giveaway.start_giveaway_bot( server_id=msg.guild.id, entry_fee=config.Config.instance().get_giveaway_auto_fee(), started_in_channel=msg.channel.id, conn=conn ) except LockTimeoutError: gw = await Giveaway.get_pending_bot_giveaway(server_id=msg.guild.id) if gw is None: await Messages.send_error_dm(msg.author, "I was unable to process your donation, try again alter!") await Messages.delete_message_if_ok(msg) return # Check balance available_balance = Env.raw_to_amount(await user.get_available_balance()) if tip_amount > available_balance: if not ctx.god: redis_key = f"ticketspam:{msg.author.id}" spam = await RedisDB.instance().get(redis_key) if spam is not None: spam = int(spam) else: spam = 0 await RedisDB.instance().set(f"ticketspam:{msg.author.id}", str(spam + 1), expires=3600) await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "Your balance isn't high enough to complete this tip.") await Messages.delete_message_if_ok(msg) return # See if they already contributed user_tx = await Transaction.filter(giveaway__id=gw.id, sending_user__id=user.id).first() already_entered = False async with in_transaction() as conn: if user_tx is not None: if int(user_tx.amount) >= int(gw.entry_fee): already_entered=True user_tx.amount = str(int(user_tx.amount) + Env.amount_to_raw(tip_amount)) await user_tx.save(update_fields=['amount'], using_db=conn) else: user_tx = await Transaction.create_transaction_giveaway( user, tip_amount, gw, conn=conn ) if gw.end_at is None: if not already_entered and int(user_tx.amount) >= int(gw.entry_fee): await Messages.send_success_dm(msg.author, f"With your generous donation of {Env.raw_to_amount(int(user_tx.amount))} {Env.currency_symbol()} I have reserved your spot for giveaway #{gw.id}!") else: await Messages.send_success_dm(msg.author, f"Your generous donation of {Env.raw_to_amount(int(user_tx.amount))} {Env.currency_symbol()} will help support giveaway #{gw.id}!") # See if we should start this giveaway, and start it if so # TODO - We should use the DB SUM() function but,we store it as a VarChar and tortoise-orm currently doesn't support casting giveaway_sum_raw = 0 for tx in await Transaction.filter(giveaway=gw): giveaway_sum_raw += int(tx.amount) giveaway_sum = Env.raw_to_amount(giveaway_sum_raw) if giveaway_sum >= config.Config.instance().get_giveaway_auto_minimum(): # start giveaway # re-fetch latest version gw = await Giveaway.get_pending_bot_giveaway(server_id=msg.guild.id) if gw is not None: gw.end_at = datetime.datetime.utcnow() + datetime.timedelta(minutes=config.Config.instance().get_giveaway_auto_duration()) gw.started_in_channel = msg.channel.id async with in_transaction() as conn: await gw.save(update_fields=['end_at', 'started_in_channel'], using_db=conn) # Announce giveaway embed = self.format_giveaway_announcement(gw, amount=giveaway_sum_raw) await msg.channel.send(embed=embed) for ch in config.Config.instance().get_giveaway_announce_channels(): if ch != msg.channel.id: channel = msg.guild.get_channel(ch) if channel is not None: try: await channel.send(embed=embed) except Exception: pass # Start the timer asyncio.create_task(self.start_giveaway_timer(gw)) else: if not already_entered and int(user_tx.amount) >= int(gw.entry_fee): await Messages.send_success_dm(msg.author, f"With your generous donation of {tip_amount} {Env.currency_symbol()} I have entered you into giveaway #{gw.id}!") else: await Messages.send_success_dm(msg.author, f"Your generous donation of {tip_amount} {Env.currency_symbol()} will help support giveaway #{gw.id}!") if msg.channel.id in config.Config.instance().get_giveaway_no_delete_channels(): await Messages.add_tip_reaction(msg, tip_amount) await Messages.delete_message_if_ok(msg) # Update stats stats: Stats = await user.get_stats(server_id=msg.guild.id) if msg.channel.id not in config.Config.instance().get_no_stats_channels(): await stats.update_tip_stats(tip_amount)
async def ticket_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message user = ctx.user author = msg.author content = msg.content is_private = ChannelUtil.is_private(msg.channel) id=None if is_private: if 'id=' not in msg.content: await Messages.send_usage_dm(msg.author, TICKET_INFO) await Messages.add_x_reaction(msg) return # Parse message split_content = msg.content.split(' ') cleaned_content = msg.content for split in split_content: if split.startswith('id='): cleaned_content.replace(split, "") split = split.replace('id=','').strip() if not split: continue try: id = int(split) except ValueError as e: await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, TICKET_INFO) return # See if they've been spamming redis_key = f"ticketspam:{msg.author.id}" if not ctx.god: spam = await RedisDB.instance().get(redis_key) if spam is not None: spam = int(spam) if spam >= 3: await Messages.send_error_dm(msg.author, "You're temporarily banned from entering giveaways") await Messages.delete_message_if_ok(msg) return else: spam = 0 else: spam = 0 # Get active giveaway if id is None: gw = await Giveaway.get_active_giveaway(server_id=msg.guild.id) else: gw = await Giveaway.get_active_giveaway_by_id(id=id) if gw is None: await Messages.send_error_dm(msg.author, "There aren't any active giveaways to enter.") await Messages.delete_message_if_ok(msg) # Block ticket spam await RedisDB.instance().set(f"ticketspam:{msg.author.id}", str(spam + 1), expires=3600) return # Check roles if is_private: guild = self.bot.get_guild(gw.server_id) if guild is None: await Messages.send_error_dm(msg.author, "Something went wrong, ask my master for help") return member = guild.get_member(msg.author.id) if member is None: await "You're not a member of that server" return msg.author = member if not await self.role_check(msg): return # There is an active giveaway, enter em if not already entered. active_tx = await Transaction.filter(giveaway__id=gw.id, sending_user__id=user.id).first() if active_tx is not None and int(gw.entry_fee) == 0: await Messages.send_error_dm(msg.author, "You've already entered this giveaway.") await Messages.delete_message_if_ok(msg) return elif active_tx is None: paid_already = 0 else: paid_already = int(active_tx.amount) if paid_already >= int(gw.entry_fee) and int(gw.entry_fee) > 0: await Messages.send_error_dm(msg.author, "You've already entered this giveaway.") await Messages.delete_message_if_ok(msg) return # Enter em fee_raw = int(gw.entry_fee) - paid_already fee = Env.raw_to_amount(fee_raw) # Check balance if fee is > 0 if fee > 0: try: amount = RegexUtil.find_float(msg.content) if amount < fee: await Messages.send_error_dm(msg.author, f"This giveaway has a fee of {fee} {Env.currency_symbol()}. The amount you specified isn't enough to cover the entry fee") await Messages.delete_message_if_ok(msg) return except AmountMissingException: await Messages.send_error_dm(msg.author, f"This giveaway has a fee, you need to specify the amount to enter. `{config.Config.instance().command_prefix}ticket {fee}`") await Messages.delete_message_if_ok(msg) return available_balance = Env.raw_to_amount(await user.get_available_balance()) if fee > available_balance: await Messages.add_x_reaction(ctx.message) await Messages.send_error_dm(msg.author, f"Your balance isn't high enough to complete this tip. You have **{available_balance} {Env.currency_symbol()}**, but this entry would cost you **{fee} {Env.currency_symbol()}**") await Messages.delete_message_if_ok(msg) await RedisDB.instance().set(f"ticketspam:{msg.author.id}", str(spam + 1), expires=3600) return await Transaction.create_transaction_giveaway( user, fee, gw ) await Messages.send_success_dm(msg.author, f"You've successfully been entered into giveaway #{gw.id}") await Messages.delete_message_if_ok(msg) return
async def tiprandom_cmd(self, ctx: Context): if ctx.error: await Messages.add_x_reaction(ctx.message) return msg = ctx.message user = ctx.user send_amount = ctx.send_amount # Check anti-spam if not ctx.god and await RedisDB.instance().exists( f"tiprandomspam{msg.guild.id}{msg.author.id}"): await Messages.add_timer_reaction(msg) await Messages.send_basic_dm( msg.author, "You can only tiprandom once every minute") return active_users = await rain.RainCog.get_active(ctx, excluding=msg.author.id) if len(active_users) < Constants.RAIN_MIN_ACTIVE_COUNT: await Messages.send_error_dm( msg.author, f"There aren't enough active people to do a random tip. Only **{len(active_users)}** are active, but I'd like to see at least **{Constants.RAIN_MIN_ACTIVE_COUNT}**" ) return target_user = secrets.choice(active_users) # See how much they need to make this tip. available_balance = Env.raw_to_amount(await user.get_available_balance()) if send_amount > available_balance: await Messages.add_x_reaction(ctx.message) await Messages.send_error_dm( msg.author, f"Your balance isn't high enough to complete this tip. You have **{available_balance} {Env.currency_symbol()}**, but this tip would cost you **{send_amount} {Env.currency_symbol()}**" ) return # Make the transactions in the database tx = await Transaction.create_transaction_internal_dbuser( sending_user=user, amount=send_amount, receiving_user=target_user) task_list = [] if not await user.is_muted_by(target_user.id): task_list.append( Messages.send_basic_dm( member=msg.guild.get_member(target_user.id), message= f"You were randomly selected and received **{send_amount} {Env.currency_symbol()}** from {msg.author.name.replace('`', '')}.\nUse `{config.Config.instance().command_prefix}mute {msg.author.id}` to disable notifications for this user.", skip_dnd=True)) task_list.append( Messages.send_basic_dm( member=msg.author, message= f'"{target_user.name}" was the recipient of your random tip of {send_amount} {Env.currency_symbol()}' )) asyncio.ensure_future(Utils.run_task_list(task_list)) # Add reactions await Messages.add_tip_reaction(msg, send_amount) # Queue the actual send await TransactionQueue.instance().put(tx) # anti spam await RedisDB.instance().set( f"tiprandomspam{msg.guild.id}{msg.author.id}", "as", expires=60) # Update stats stats: Stats = await user.get_stats(server_id=msg.guild.id) if msg.channel.id not in config.Config.instance( ).get_no_stats_channels(): await stats.update_tip_stats(send_amount)
async def start_giveaway_timer(self, giveaway: Giveaway): # Ensure timer not already started if giveaway.id in self.giveaway_ids: return self.giveaway_ids.append(giveaway.id) # Sleep for <giveaway duration> seconds delta = (giveaway.end_at - datetime.datetime.utcnow()).total_seconds() if delta > 0: await asyncio.sleep(delta) # End the giveaway # Get entries txs = await Transaction.filter(giveaway=giveaway).prefetch_related('sending_user').all() users = [] for tx in txs: if tx.sending_user not in users and int(tx.amount) >= int(giveaway.entry_fee): users.append(tx.sending_user) # Pick winner random.shuffle(users, Utils.random_float) winner = secrets.choice(users) # Calculate total winning amount tx_sum = 0 for tx in txs: tx_sum += int(tx.amount) # Finish this async with in_transaction() as conn: giveaway.ended_at = datetime.datetime.utcnow() giveaway.winning_user = winner giveaway.final_amount = str(tx_sum) await giveaway.save(using_db=conn, update_fields=['ended_at', 'winning_user_id', 'final_amount']) # Update transactions winner_account = await winner.get_address() for tx in txs: if tx.amount == '0': await tx.delete() else: tx.destination = winner_account tx.receiving_user = winner await tx.save(using_db=conn, update_fields=['receiving_user_id', 'destination']) # Queue transactions for tx in txs: await TransactionQueue.instance().put(tx) # Announce winner main_channel = self.bot.get_channel(giveaway.started_in_channel) announce_channels = [] if main_channel is not None: announce_channels.append(main_channel) for ch in config.Config.instance().get_giveaway_announce_channels(): if ch == giveaway.started_in_channel: continue dch = self.bot.get_channel(ch) if dch is not None: announce_channels.append(dch) ann_message = f"Congratulations! <@{winner.id}> was the winner of the giveaway!" ann_message+= f"\nThey have been sent **{Env.raw_to_amount(tx_sum)} {Env.currency_symbol()}**" if isinstance(giveaway.started_by, User): ann_message+= f"\n\nThanks to <@{giveaway.started_by.id}> for sponsoring this giveaway!" embed = discord.Embed(colour=0xFBDD11 if Env.banano() else discord.Colour.dark_blue()) embed.set_author(name="We have a winner!", icon_url="https://github.com/bbedward/graham_discord_bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://github.com/bbedward/graham_discord_bot/raw/master/assets/nano_logo.png") embed.description = ann_message for ann in announce_channels: try: await ann.send(embed=embed) except Exception: pass # DM the winner member = self.bot.get_user(winner.id) if member is not None: await Messages.send_success_dm(member, f"Congratulations! **You've won giveaway #{giveaway.id}**! I've sent you **{Env.raw_to_amount(tx_sum)} {Env.currency_symbol()}**") # Cleanup try: self.giveaway_ids.remove(giveaway.id) except ValueError: pass
async def giveaway_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message user = ctx.user # Check paused if await RedisDB.instance().is_paused(): await Messages.send_error_dm(msg.author, f"Transaction activity is currently suspended. I'll be back online soon!") return # Check roles if not await self.role_check(msg): return elif msg.channel.id in config.Config.instance().get_no_spam_channels(): await Messages.send_error_dm(msg.author, f"You can't start giveaways in this channel") return if 'fee=' not in msg.content or 'duration=' not in msg.content: await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) await Messages.add_x_reaction(msg) return # Parse message split_content = msg.content.split(' ') cleaned_content = msg.content for split in split_content: if split.startswith('fee='): cleaned_content.replace(split, "") split = split.replace('fee=','').strip() if not split: continue try: fee = abs(float(split)) except ValueError as e: await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) return elif split.startswith('duration='): cleaned_content.replace(split, "") split=split.replace('duration=','').strip() if not split: continue try: duration = abs(int(split)) if not ctx.god and (duration < config.Config.instance().get_giveaway_min_duration() or duration > config.Config.instance().get_giveaway_max_duration()): raise ValueError("Bad duration specified") except ValueError as e: await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) return # Find giveaway amount try: giveaway_amount = RegexUtil.find_float(cleaned_content) if Validators.too_many_decimals(giveaway_amount): await Messages.send_error_dm(ctx.message.author, f"You are only allowed to use {Env.precision_digits()} digits after the decimal for giveaway amount.") ctx.error = True return elif fee > giveaway_amount * config.Config.instance().get_giveaway_max_fee_multiplier(): await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) return elif giveaway_amount < config.Config.instance().get_giveaway_minimum(): await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) return except AmountMissingException: await Messages.add_x_reaction(msg) await Messages.send_usage_dm(msg.author, START_GIVEAWAY_INFO) return # See how much they need to make this tip. available_balance = Env.raw_to_amount(await user.get_available_balance()) if giveaway_amount > available_balance: await Messages.add_x_reaction(ctx.message) await Messages.send_error_dm(msg.author, f"Your balance isn't high enough to start this giveaway. You have **{available_balance} {Env.currency_symbol()}**, but this tip would cost you **{giveaway_amount} {Env.currency_symbol()}**") return try: # Lock this so concurrent giveaways can't be started/avoid race condition async with RedisLock( await RedisDB.instance().get_redis(), key=f"{Env.currency_symbol().lower()}giveawaylock:{msg.guild.id}", timeout=30, wait_timeout=30 ) as lock: # See if giveaway already in progress active_giveaway = await Giveaway.get_active_giveaway(server_id=msg.guild.id) if active_giveaway is not None: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "There's already a giveaway in progress on this server") return # Start giveaway async with in_transaction() as conn: gw = await Giveaway.start_giveaway_user( server_id=msg.guild.id, started_by=user, amount=giveaway_amount, entry_fee=fee, duration=duration, started_in_channel=msg.channel.id, conn=conn ) # Create pending TX for this user await Transaction.create_transaction_giveaway( sending_user=user, amount=giveaway_amount, giveaway=gw, conn=conn ) # Announce giveaway embed = self.format_giveaway_announcement(gw) await msg.channel.send(embed=embed) for ch in config.Config.instance().get_giveaway_announce_channels(): if ch != msg.channel.id: channel = msg.guild.get_channel(ch) if channel is not None: try: await channel.send(embed=embed) except Exception: pass # Start the timer asyncio.create_task(self.start_giveaway_timer(gw)) except LockTimeoutError: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "I couldn't start a giveaway, maybe someone else beat you to it as there can only be 1 active at a time.")
async def add_tip_reaction(msg: discord.Message, amount: float, rain: bool = False): if Env.banano(): if amount > 0: try: await msg.add_reaction('\:tip:425878628119871488' ) # TIP mark await msg.add_reaction('\:tick:425880814266351626' ) # check mark except Exception: await msg.add_reaction('\U00002611' ) # fallback for non-banano server if amount > 0 and amount < 50: await msg.add_reaction('\U0001F987') # S elif amount >= 50 and amount < 250: await msg.add_reaction('\U0001F412') # C elif amount >= 250: await msg.add_reaction('\U0001F98D') # W if rain: try: await msg.add_reaction('\:bananorain:430826677543895050' ) # Banano rain except Exception: await msg.add_reaction('\U0001F4A6') # Sweat Drops else: if amount > 0: await msg.add_reaction('\U00002611') # check mark if amount > 0 and amount < 0.01: await msg.add_reaction('\U0001F1F8') # S await msg.add_reaction('\U0001F1ED') # H await msg.add_reaction('\U0001F1F7') # R await msg.add_reaction('\U0001F1EE') # I await msg.add_reaction('\U0001F1F2') # M await msg.add_reaction('\U0001F1F5') # P elif amount >= 0.01 and amount < 0.1: await msg.add_reaction('\U0001F1E8') # C await msg.add_reaction('\U0001F1F7') # R await msg.add_reaction('\U0001F1E6') # A await msg.add_reaction('\U0001F1E7') # B elif amount >= 0.1 and amount < 0.5: await msg.add_reaction('\U0001F1FC') # W await msg.add_reaction('\U0001F1E6') # A await msg.add_reaction('\U0001F1F1') # L await msg.add_reaction('\U0001F1F7') # R await msg.add_reaction('\U0001F1FA') # U await msg.add_reaction('\U0001F1F8') # S elif amount >= 0.5 and amount < 1: await msg.add_reaction('\U0001F1F8') # S await msg.add_reaction('\U0001F1ED') # H await msg.add_reaction('\U0001F1E6') # A await msg.add_reaction('\U0001F1F7') # R await msg.add_reaction('\U0001F1F0') # K elif amount >= 1: await msg.add_reaction('\U0001F1F2') # M await msg.add_reaction('\U0001F1EA') # E await msg.add_reaction('\U0001F1EC') # G await msg.add_reaction('\U0001F1E6') # A await msg.add_reaction('\U0001F1F1') # L await msg.add_reaction('\U0001F1E9') # D await msg.add_reaction('\U0001F1F4') # O await msg.add_reaction('\U0001F1F3') # N if rain: await msg.add_reaction('\U0001F4A6') # Sweat Drops
triggers = ["addfavorite"], overview = "Add a user to your favorites list", details = f"Add a user to your favorites list. You can have up to **25 favorites**. Example: `{config.Config.instance().command_prefix}addfavorite @bbedward`" ) REMOVE_FAVORITE_INFO = CommandInfo( triggers = ["unfavorite", "removefavorite"], overview = "Remove a user from your favorites list", details = f"Remove a user from your favorites list Example: `{config.Config.instance().command_prefix}removefavorite 419483863115366410`" ) FAVORITES_INFO = CommandInfo( triggers = ["favorites"], overview = "View list of users you have favorited", details = f"View the list of every user you have favorited. You can tip all of them using `{config.Config.instance().command_prefix}{'banfavorites' if Env.banano() else 'ntipfavorites'} <amount>`" ) TIPFAVORITES_INFO = CommandInfo( triggers = ["banfavorites" if Env.banano() else "ntipfavorites"], overview = "Tip all the favorites", details = f"Split a tip among all of the users in your favorites list - similar to a tipsplit. (**minimum tip is {Constants.TIP_MINIMUM} {Constants.TIP_UNIT}**)" + f"\nExample: `{config.Config.instance().command_prefix}{'banfavorites' if Env.banano() else 'ntipfavorites'} <amount>`" ) class FavoriteCog(commands.Cog): """Commands for admins only""" def __init__(self, bot: Bot): self.bot = bot async def cog_before_invoke(self, ctx: Context): ctx.error = False msg = ctx.message # See if user exists in DB user = await User.get_user(msg.author)
from util.validators import Validators from db.models.stats import Stats from db.models.transaction import Transaction from db.models.user import User from db.redis import RedisDB from tasks.transaction_queue import TransactionQueue import asyncio import config import cogs.rain as rain import secrets from util.util import Utils ## Command documentation TIP_INFO = CommandInfo( triggers=["ban", "b"] if Env.banano() else ["ntip", "n"], overview="Send a tip to mentioned users", details= f"Tip specified amount to mentioned user(s) (**minimum tip is {Constants.TIP_MINIMUM} {Constants.TIP_UNIT}**)" + "\nThe recipient(s) will be notified of your tip via private message" + "\nSuccessful tips will be deducted from your available balance immediately.\n" + f"Example: `{config.Config.instance().command_prefix}{'ban' if Env.banano() else 'ntip'} 2 @user1 @user2` would send 2 to user1 and 2 to user2" ) TIPSPLIT_INFO = CommandInfo( triggers=["bansplit", "bs"] if Env.banano() else ["ntipsplit", "ns"], overview="Split a tip among mentioned users", details= f"Divide the specified amount between mentioned user(s) (**minimum tip is {Constants.TIP_MINIMUM} {Constants.TIP_UNIT}**)" + "\nThe recipient(s) will be notified of your tip via private message" + "\nSuccessful tips will be deducted from your available balance immediately.\n"
async def rain_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message user = ctx.user send_amount = ctx.send_amount anon = 'anon' in msg.content # Get active users active_users = await self.get_active(ctx, excluding=msg.author.id) if len(active_users) < Constants.RAIN_MIN_ACTIVE_COUNT: await Messages.add_x_reaction(msg) await Messages.send_error_dm( msg.author, f"Not enough users are active to rain - I need at least {Constants.RAIN_MIN_ACTIVE_COUNT} but there's only {len(active_users)} active bros" ) return individual_send_amount = Env.truncate_digits( send_amount / len(active_users), max_digits=Env.precision_digits()) individual_send_amount_str = f"{individual_send_amount:.2f}" if Env.banano( ) else f"{individual_send_amount:.6f}" if individual_send_amount < Constants.TIP_MINIMUM: await Messages.add_x_reaction(msg) await Messages.send_error_dm( msg.author, f"Amount is too small to divide across {len(active_users)} users" ) return # See how much they need to make this tip. amount_needed = Env.truncate_digits(individual_send_amount * len(active_users), max_digits=Env.precision_digits()) available_balance = Env.raw_to_amount(await user.get_available_balance()) if amount_needed > available_balance: await Messages.add_x_reaction(msg) await Messages.send_error_dm( msg.author, f"Your balance isn't high enough to complete this tip. You have **{available_balance} {Env.currency_symbol()}**, but this tip would cost you **{amount_needed} {Env.currency_symbol()}**" ) return # Make the transactions in the database tx_list = [] task_list = [] for u in active_users: tx = await Transaction.create_transaction_internal_dbuser( sending_user=user, amount=individual_send_amount, receiving_user=u) tx_list.append(tx) if not await user.is_muted_by(u.id): if not anon: task_list.append( Messages.send_basic_dm( member=msg.guild.get_member(u.id), message= f"You were tipped **{individual_send_amount_str} {Env.currency_symbol()}** by {msg.author.name.replace('`', '')}.\nUse `{config.Config.instance().command_prefix}mute {msg.author.id}` to disable notifications for this user.", skip_dnd=True)) else: task_list.append( Messages.send_basic_dm( member=msg.guild.get_member(u.id), message= f"You were tipped **{individual_send_amount_str} {Env.currency_symbol()}** anonymously!", skip_dnd=True)) # Send DMs in the background asyncio.ensure_future(Utils.run_task_list(task_list)) # Add reactions await Messages.add_tip_reaction(msg, amount_needed, rain=True) # Queue the actual sends for tx in tx_list: await TransactionQueue.instance().put(tx) # Add anti-spam await RedisDB.instance().set(f"rainspam{msg.author.id}", "as", expires=300) # Update stats stats: Stats = await user.get_stats(server_id=msg.guild.id) if msg.channel.id not in config.Config.instance( ).get_no_stats_channels(): await stats.update_tip_stats(amount_needed) # DM creator await Messages.send_success_dm( msg.author, f"You rained **{amount_needed} {Env.currency_symbol()}** to **{len(tx_list)} users**, they received **{individual_send_amount_str} {Env.currency_symbol()}** each.", header="Make it Rain") # Make the rainer auto-rain eligible await self.auto_rain_eligible(msg)
async def giveawaystats_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message user = ctx.user as_dm = False # Punish them for trying to do this command in a no spam channel if msg.channel.id in config.Config.instance().get_no_spam_channels(): redis_key = f"ticketspam:{msg.author.id}" if not ctx.god: spam = await RedisDB.instance().get(redis_key) if spam is not None: spam = int(spam) else: spam = 0 await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "You can't view giveaway stats in this channel") await RedisDB.instance().set(f"ticketspam:{msg.author.id}", str(spam + 1), expires=3600) return if not as_dm: # Check spamming of this command if len(config.Config.instance().get_giveaway_no_delete_channels()) > 0 and msg.channel.id not in config.Config.instance().get_giveaway_no_delete_channels(): as_dm = True if await RedisDB.instance().exists(f'giveawaystatsspam:{msg.channel.id}'): as_dm = True else: await RedisDB.instance().set(f'giveawaystatsspam:{msg.channel.id}', 'as', expires=60) gw = await Giveaway.get_active_giveaway(server_id=msg.guild.id) pending_gw = None if gw is None: pending_gw = await Giveaway.get_pending_bot_giveaway(server_id=msg.guild.id) if pending_gw is None: if as_dm: await Messages.send_error_dm(msg.author, "There are no active giveaways") else: await Messages.send_error_public(msg.channel, "There are no active giveaways") await Messages.delete_message_if_ok(msg) return else: gw = pending_gw # Get stats transactions = await gw.get_transactions() entries = 0 donors = 0 amount = 0 for tx in transactions: tx: Transaction = tx if int(tx.amount) >= int(gw.entry_fee): entries += 1 donors += 1 amount += Env.raw_to_amount(int(tx.amount)) # Format stats message embed = discord.Embed(colour=0xFBDD11 if Env.banano() else discord.Colour.dark_blue()) embed.set_author(name=f"Giveaway #{gw.id}", icon_url="https://github.com/bbedward/graham_discord_bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://github.com/bbedward/graham_discord_bot/raw/master/assets/nano_logo.png") fee = Env.raw_to_amount(int(gw.entry_fee)) if pending_gw is None: embed.description = f"There are **{entries} entries** to win **{NumberUtil.truncate_digits(amount, max_digits=Env.precision_digits())} {Env.currency_symbol()}**\n" if fee > 0: embed.description+= f"\nThis giveaway has an entry fee of **{fee} {Env.currency_name()}**" embed.description+= f"\n`{config.Config.instance().command_prefix}ticket {fee}` - To enter this giveaway" embed.description+= f"\n`{config.Config.instance().command_prefix}{'donate' if Env.banano() else 'ntipgiveaway'} <amount>` - To increase the pot" else: embed.description+= f"\nThis giveaway is free to enter:" embed.description+= f"\n`{config.Config.instance().command_prefix}ticket` - To enter this giveaway" embed.description+= f"\n`{config.Config.instance().command_prefix}{'donate' if Env.banano() else 'ntipgiveaway'} <amount>` - To increase the pot" duration = (gw.end_at - datetime.datetime.utcnow()).total_seconds() if duration < 60: embed.description += f"\n\nThis giveaway will end in **{int(duration)} seconds**" else: duration = duration // 60 embed.description += f"\n\nThis giveaway will end in **{int(duration)} minutes**" embed.description += "\nGood luck! \U0001F340" else: embed.description = f"This giveaway hasn't started yet\n" embed.description += f"\nSo far **{donors}** people have donated to this giveaway and **{entries}** people are eligible to win" embed.description += f"\n**{NumberUtil.truncate_digits(config.Config.instance().get_giveaway_auto_minimum() - amount, max_digits=Env.precision_digits())} {Env.currency_symbol()}** more needs to be donated to start this giveaway." try: if as_dm: await msg.author.send(embed=embed) if msg.channel.id in config.Config.instance().get_giveaway_no_delete_channels(): await msg.add_reaction('\u2709') else: await msg.channel.send(embed=embed) except Exception: pass await Messages.delete_message_if_ok(msg)
def get_env(variable): return Env.get_env(variable)
async def tipsplit_cmd(self, ctx: Context): if ctx.error: await Messages.add_x_reaction(ctx.message) return msg = ctx.message user = ctx.user send_amount = ctx.send_amount # Get all eligible users to tip in their message users_to_tip = [] for m in msg.mentions: if not m.bot and m.id != msg.author.id: users_to_tip.append(m) if len(users_to_tip) < 1: await Messages.add_x_reaction(msg) await Messages.send_error_dm( msg.author, f"No users you mentioned are eligible to receive tips.") return individual_send_amount = NumberUtil.truncate_digits( send_amount / len(users_to_tip), max_digits=Env.precision_digits()) if individual_send_amount < Constants.TIP_MINIMUM: await Messages.add_x_reaction(msg) await Messages.send_error_dm( msg.author, f"Tip amount too small, each user needs to receive at least {Constants.TIP_MINIMUM}. With your tip they'd only be getting {individual_send_amount}" ) return # See how much they need to make this tip. amount_needed = individual_send_amount * len(users_to_tip) available_balance = Env.raw_to_amount(await user.get_available_balance()) if amount_needed > available_balance: await Messages.add_x_reaction(msg) await Messages.send_error_dm( msg.author, f"Your balance isn't high enough to complete this tip. You have **{available_balance} {Env.currency_symbol()}**, but this tip would cost you **{amount_needed} {Env.currency_symbol()}**" ) return # Make the transactions in the database tx_list = [] task_list = [] for u in users_to_tip: tx = await Transaction.create_transaction_internal( sending_user=user, amount=individual_send_amount, receiving_user=u) if tx is not None: tx_list.append(tx) if not await user.is_muted_by(u.id): task_list.append( Messages.send_basic_dm( member=u, message= f"You were tipped **{individual_send_amount} {Env.currency_symbol()}** by {msg.author.name.replace('`', '')}.\nUse `{config.Config.instance().command_prefix}mute {msg.author.id}` to disable notifications for this user.", skip_dnd=True)) if len(tx_list) < 1: await Messages.add_x_reaction(msg) await Messages.send_error_dm( msg.author, f"No users you mentioned are eligible to receive tips.") return # Send DMs asyncio.ensure_future(Utils.run_task_list(task_list)) # Add reactions await Messages.add_tip_reaction(msg, amount_needed) # Queue the actual sends for tx in tx_list: await TransactionQueue.instance().put(tx) # Update stats stats: Stats = await user.get_stats(server_id=msg.guild.id) if msg.channel.id not in config.Config.instance( ).get_no_stats_channels(): await stats.update_tip_stats(amount_needed)
async def tipfavorites_cmd(self, ctx: Context): if ctx.error: await Messages.add_x_reaction(ctx.message) return msg = ctx.message user = ctx.user send_amount = ctx.send_amount # Check anti-spam if not ctx.god and await RedisDB.instance().exists(f"tipfavoritesspam{msg.author.id}"): await Messages.add_timer_reaction(msg) await Messages.send_basic_dm(msg.author, "You can only tipfavorites once every 5 minutes") return # Get their favorites favorites = await Favorite.filter(user=user).prefetch_related('favorited_user').all() if len(favorites) < 1: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "You don't have any favorites, add some first.") return individual_send_amount = NumberUtil.truncate_digits(send_amount / len(favorites), max_digits=Env.precision_digits()) if individual_send_amount < Constants.TIP_MINIMUM: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, f"Tip amount too small, each user needs to receive at least {Constants.TIP_MINIMUM}. With your tip they'd only be getting {individual_send_amount}") return # See how much they need to make this tip. amount_needed = individual_send_amount * len(favorites) available_balance = Env.raw_to_amount(await user.get_available_balance()) if amount_needed > available_balance: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, f"Your balance isn't high enough to complete this tip. You have **{available_balance} {Env.currency_symbol()}**, but this tip would cost you **{amount_needed} {Env.currency_symbol()}**") return # Make the transactions in the database tx_list = [] task_list = [] for u in favorites: tx = await Transaction.create_transaction_internal_dbuser( sending_user=user, amount=individual_send_amount, receiving_user=u.favorited_user ) if tx is not None: tx_list.append(tx) if not await user.is_muted_by(u.favorited_user.id): task_list.append( Messages.send_basic_dm( member=self.bot.get_user(u.favorited_user.id), message=f"You were tipped **{individual_send_amount} {Env.currency_symbol()}** by {msg.author.name.replace('`', '')}.\nUse `{config.Config.instance().command_prefix}mute {msg.author.id}` to disable notifications for this user." ) ) if len(tx_list) < 1: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, f"No users you mentioned are eligible to receive tips.") return # Send DMs asyncio.ensure_future(Utils.run_task_list(task_list)) # Add reactions await Messages.add_tip_reaction(msg, amount_needed) # Queue the actual sends for tx in tx_list: await TransactionQueue.instance().put(tx) # anti spam await RedisDB.instance().set(f"tipfavoritesspam{msg.author.id}", "as", expires=300) # Update stats stats: Stats = await user.get_stats(server_id=msg.guild.id) if msg.channel.id not in config.Config.instance().get_no_stats_channels(): await stats.update_tip_stats(amount_needed)
triggers = ["ticketstatus", "ts"], overview = "Check entry status", details = "See if you are entered into the current giveaway, if there is one" ) GIVEAWAYSTATS_INFO = CommandInfo( triggers = ["giveawaystats", "gs"], overview = "View stats related to the currently active giveaway", details = "View time left, number of entries, and other information about the currently active giveaway" ) WINNERS_INFO = CommandInfo( triggers = ["winners"], overview = "View recent giveaway winners", details = "View the 10 most recent giveaways winners as well as the amount they've won." ) TIPGIVEAWAY_INFO = CommandInfo( triggers = ["donate", "do"] if Env.banano() else ["ntipgiveaway", "ntg"], overview = "Donate to giveaway", details = "Donate to the currently active giveaway to increase the pot, or donate to towards starting a giveaway automatically." + f"\nExample: `{config.Config.instance().command_prefix}{'donate' if Env.banano() else 'ntipgiveaway'} 1` - Donate 1 {Env.currency_symbol()} to the current or next giveaway" f"\nWhen **{config.Config.instance().get_giveaway_auto_minimum()} {Env.currency_symbol()}** is donated, a giveaway will automatically begin." ) class GiveawayCog(commands.Cog): def __init__(self, bot: Bot): self.bot = bot self.logger = logging.getLogger() self.giveaway_ids = [] @commands.Cog.listener() async def on_ready(self): # Get active giveaways