def format_giveaway_announcement(self, giveaway: Giveaway, amount: int = None) -> discord.Embed: embed = discord.Embed( colour=0xFBDD11 if Env.banano() else discord.Colour.dark_blue()) embed.set_author( name=f"New Giveaway! #{giveaway.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" ) embed.description = f"<@{giveaway.started_by.id if not giveaway.started_by_bot else self.bot.user.id}> has sponsored a giveaway of **{Env.raw_to_amount(int(giveaway.base_amount if amount is None else amount))} {Env.currency_name()}**!\n" fee = Env.raw_to_amount(int(giveaway.entry_fee)) 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 = (giveaway.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" 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." )
class Constants(object): TIP_MINIMUM = 1.0 if Env.banano() else 1 TIP_UNIT = 'BAN' if Env.banano() else 'WATERMELONANO' WITHDRAW_COOLDOWN = 1 # Seconds RAIN_MIN_ACTIVE_COUNT = 4 # Amount of people who have to be active for rain to work RAIN_MSG_REQUIREMENT = 5 # Amount of decent messages required to receive rain REPRESENTATIVE='ban_1tipbotgges3ss8pso6xf76gsyqnb69uwcxcyhouym67z7ofefy1jz7kepoy' if Env.banano() else 'watermelon1aunch1qxkfrjkmhwhewqx9mucmy3f5n5urnf7xh4dhrs4hecf4fx3mit1sk'
async def ufw_cmd(self, ctx: Context): msg = ctx.message try: addresses = RegexUtil.find_address_matches(msg.content) except AddressMissingException: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "No valid addresses in your ufw command") return address_list = await Account.filter(address__in=addresses).prefetch_related('user').all() if len(address_list) < 1: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "No users found with specified addresses.") return for acct in address_list: response = f"Last known name: {acct.user.name}```{acct.user.id}```" response += f"```{acct.address}```" if Env.banano(): response += f"https://creeper.banano.cc/explorer/account/{acct.address}\n" else: response += f"https://nanocrawler.cc/explorer/account/{acct.address}\n" embed = discord.Embed(colour=0xFBDD11 if Env.banano() else discord.Colour.green()) embed.set_author(name="UFW Result", icon_url="https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://i.imgur.com/7QFgoqT.png") embed.description = response await msg.author.send(embed=embed)
async def register_cmd(self, ctx: Context): if ctx.error: return msg = ctx.message try: amount = RegexUtil.find_float(msg.content) except AmountMissingException: amount = 0.0 # Get/create user try: user = await User.create_or_fetch_user(msg.author) user_address = await user.get_address() except Exception: self.logger.exception('Exception creating user') await Messages.send_error_dm(msg.author, "I failed at retrieving your address, try again later and contact my master if the issue persists.") return # Build URI uri_scheme = "ban:" if Env.banano() else "watermelonano:" if amount == 0: uri = user_address else: uri = "{0}{1}?amount={2}".format(uri_scheme, user_address, Env.amount_to_raw(amount)) # Build and send response embed = discord.Embed(colour=0xFBDD11 if Env.banano() else discord.Colour.green()) embed.set_author(name=user_address, icon_url="https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://i.imgur.com/7QFgoqT.png") embed.set_image(url=f"https://chart.googleapis.com/chart?cht=qr&chl={uri}&chs=180x180&choe=UTF-8&chld=L|2") await msg.author.send(embed=embed) await msg.author.send(user_address)
async def legacyboard_cmd(self, ctx: Context): if ctx.error: await Messages.add_x_reaction(ctx.message) return msg = ctx.message if not ctx.god and await RedisDB.instance().exists( f"ballerspam{msg.channel.id}"): await Messages.add_timer_reaction(msg) await Messages.send_error_dm( msg.author, "Why don't you wait awhile before checking the ballers list again" ) return # Get list ballers = await Stats.filter( server_id=msg.guild.id, banned=False).order_by('-legacy_total_tipped_amount' ).prefetch_related('user').limit(15).all() if len(ballers) == 0: await msg.channel.send( f"<@{msg.author.id}> There are no stats for this server yet, send some tips!" ) return response_msg = "```" # Get biggest tip to adjust the padding biggest_num = 0 for stats in ballers: # TODO change to stats.tip_sum length = len( f"{NumberUtil.format_float(stats.legacy_total_tipped_amount)} {Env.currency_symbol()}" ) if length > biggest_num: biggest_num = length for rank, stats in enumerate(ballers, start=1): adj_rank = str(rank) if rank >= 10 else f" {rank}" user_name = stats.user.name amount_str = f"{NumberUtil.format_float(stats.legacy_total_tipped_amount)} {Env.currency_symbol()}" response_msg += f"{adj_rank}. {amount_str.ljust(biggest_num)} - by {user_name}\n" response_msg += "```" embed = discord.Embed( colour=0xFBDD11 if Env.banano() else discord.Colour.green()) embed.set_author( name= f"Here are the top {len(ballers)} tippers of all time\U0001F44F", icon_url= "https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://i.imgur.com/7QFgoqT.png") embed.description = response_msg await RedisDB.instance().set(f"ballerspam{msg.channel.id}", "as", expires=300) await msg.channel.send(f"<@{msg.author.id}>", embed=embed)
class Constants(object): TIP_MINIMUM = 1.0 if Env.banano() else 0.0001 TIP_UNIT = 'BAN' if Env.banano() else 'Nano' WITHDRAW_COOLDOWN = 1 # Seconds RAIN_MIN_ACTIVE_COUNT = 5 # Amount of people who have to be active for rain to work RAIN_MSG_REQUIREMENT = 5 # Amount of decent messages required to receive rain REPRESENTATIVE = 'ban_1tipbotgges3ss8pso6xf76gsyqnb69uwcxcyhouym67z7ofefy1jz7kepoy' if Env.banano( ) else 'nano_3o7uzba8b9e1wqu5ziwpruteyrs3scyqr761x7ke6w1xctohxfh5du75qgaj'
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 'WATERMELONANO'}) 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 **WATERMELONANO** 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 get_giveaway_auto_fee(self) -> float: default = 10 if Env.banano() else 1 if not self.has_yaml(): return default elif 'giveaway' in self.yaml and 'auto_fee' in self.yaml['giveaway']: return self.yaml['giveaway']['auto_fee'] return default
def get_giveaway_minimum(self) -> float: default = 1000 if Env.banano() else 5 if not self.has_yaml(): return default elif 'giveaway' in self.yaml and 'minimum' in self.yaml['giveaway']: return self.yaml['giveaway']['minimum'] return default
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('\:melonT:646094583092084736') await msg.add_reaction('\:melonI:645983222068674561') await msg.add_reaction('\:melonP:646092262077497379') await msg.add_reaction('\:check:646421240600592395') if rain: await msg.add_reaction('\:mlnrain:645968989192978442' ) # Sweat Drops
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)) active_resource = cors.add( self.app.router.add_resource("/active/{server_id}")) cors.add(active_resource.add_route("GET", self.get_active)) self.logger = logging.getLogger() self.host = host self.port = port self.min_amount = 10 if Env.banano() else 0.1
def get_rain_minimum(self) -> int: # 1000 BAN default or 1 WATERMELONANO default = 1000 if Env.banano() else 25 if not self.has_yaml(): return default elif 'restrictions' in self.yaml and 'rain_minimum' in self.yaml[ 'restrictions']: return self.yaml['restrictions']['rain_minimum'] return default
async def wfu_cmd(self, ctx: Context): msg = ctx.message targets = [] # Get mentioned users for m in msg.mentions: targets.append(m.id) # Get users they are spying on by ID alone for sec in msg.content.split(): try: numeric = int(sec.strip()) user = await self.bot.fetch_user(numeric) if user is not None: targets.append(user.id) except Exception: pass targets = set(targets) if len(targets) < 1: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "No valid users in your wfu command") return user_list = await User.filter(id__in=targets).prefetch_related('account').all() if len(user_list) < 1: await Messages.add_x_reaction(msg) await Messages.send_error_dm(msg.author, "None of those users have accounts with me") return for u in user_list: response = f"Last known name: {u.name}```{u.id}```" response += f"```{u.account.address}```" if Env.banano(): response += f"https://creeper.banano.cc/explorer/account/{u.account.address}\n" else: response += f"https://nanocrawler.cc/explorer/account/{u.account.address}\n" embed = discord.Embed(colour=0xFBDD11 if Env.banano() else discord.Colour.green()) embed.set_author(name="WFU Result", icon_url="https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://i.imgur.com/7QFgoqT.png") embed.description = response await msg.author.send(embed=embed)
async def blocks_cmd(self, ctx: Context): if ctx.error: await Messages.add_x_reaction(ctx.message) return msg = ctx.message is_private = ChannelUtil.is_private(msg.channel) if not ctx.god and await RedisDB.instance().exists( f"blocksspam{msg.channel.id if not is_private else msg.author.id}" ): await Messages.add_timer_reaction(msg) await Messages.send_error_dm( msg.author, "Why don't you wait awhile before checking the block count again?" ) return count, unchecked = await RPCClient.instance().block_count() if count is None or unchecked is None: await Messages.send_error_dm( msg.author, "I couldn't retrieve the current block count") return embed = discord.Embed( colour=0xFBDD11 if Env.banano() else discord.Colour.dark_blue()) embed.set_author( name=f"Here's how many blocks I have", 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 = f"```Count: {count:,}\nUnchecked: {unchecked:,}```" await RedisDB.instance().set( f"blocksspam{msg.channel.id if not is_private else msg.author.id}", "as", expires=120) if is_private: await msg.author.send(embed=embed) else: await msg.channel.send(f"<@{msg.author.id}>", embed=embed)
def find_address_matches(input_text: str) -> List[str]: """Find nano/banano addresses in a string""" if Env.banano(): address_regex = '(?:ban)(?:_)(?:1|3)(?:[13456789abcdefghijkmnopqrstuwxyz]{59})' else: address_regex = '(?:watermelon)(?:1|3)(?:[13456789abcdefghijkmnopqrstuwxyz]{59})' matches = re.findall(address_regex, input_text) if len(matches) >= 1: return matches raise AddressMissingException("address_not_found")
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.green()) embed.set_author(name="Balance", icon_url="https://github.com/bbedward/Graham_Nano_Tip_Bot/raw/master/assets/banano_logo.png" if Env.banano() else "https://i.imgur.com/7QFgoqT.png") embed.description = "**Available:**\n" embed.description += f"```{Env.raw_to_amount(balance_raw - pending_send_db):,} {Env.currency_symbol()}\n" pending_receive_str = f" + {Env.raw_to_amount(pending_raw + pending_receive_db):,} {Env.currency_symbol()}" pending_send_str = f" - {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
def find_address_match(input_text: str) -> str: """Find nano/banano address in a string""" if Env.banano(): address_regex = '(?:ban)(?:_)(?:1|3)(?:[13456789abcdefghijkmnopqrstuwxyz]{59})' else: address_regex = '(?:watermelon)(?:1|3)(?:[13456789abcdefghijkmnopqrstuwxyz]{59})' matches = re.findall(address_regex, input_text) if len(matches) == 1: return matches[0] elif len(matches) > 1: raise AddressAmbiguousException("too_many_addresses") raise AddressMissingException("address_not_found")
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 notify_user(self, tx: Transaction, hash: str): if tx.destination == Env.donation_address(): return bot: Bot = self.bot user = bot.get_user(tx.sending_user.id) if user is None: self.logger.warn( f"User with ID {tx.sending_user.id} was not found, so I couldn't notify them of their withdraw" ) return if Env.banano(): await user.send( f"Withdraw processed: https://creeper.banano.cc/explorer/block/{hash}" ) else: await user.send( f"Withdraw processed: https://nanocrawler.cc/explorer/block/{hash}" )
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)
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"
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 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)
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 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)
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):
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
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)
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