예제 #1
0
    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)
예제 #2
0
    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)
        if user is None:
            ctx.error = True
            await Messages.send_error_dm(
                msg.author,
                f"You should create an account with me first, send me `{config.Config.instance().command_prefix}help` to get started."
            )
            return
        elif user.frozen:
            ctx.error = True
            await Messages.send_error_dm(
                msg.author,
                f"Your account is frozen. Contact an admin if you need further assistance."
            )
            return
        ctx.user = user
        # Update name if applicable
        await user.update_name(msg.author.name)

        # Special checks for tipfavorites
        if ctx.command.name == 'tipfavorites_cmd':
            # Check admins
            ctx.god = msg.author.id in config.Config.instance().get_admin_ids()
            ctx.admin = False
            author: discord.Member = msg.author
            for role in author.roles:
                if role.id in config.Config.instance().get_admin_roles():
                    ctx.admin = True
                    break
            # Check paused
            if await RedisDB.instance().is_paused():
                ctx.error = True
                await Messages.send_error_dm(
                    msg.author,
                    f"Transaction activity is currently suspended. I'll be back online soon!"
                )
                return
            # See if amount meets tip_minimum requirement
            try:
                send_amount = RegexUtil.find_float(msg.content)
                if send_amount < Constants.TIP_MINIMUM:
                    raise AmountMissingException(
                        f"Tip amount is too low, minimum is {Constants.TIP_MINIMUM}"
                    )
                elif Validators.too_many_decimals(send_amount):
                    await Messages.send_error_dm(
                        ctx.message.author,
                        f"You are only allowed to use {Env.precision_digits()} digits after the decimal."
                    )
                    ctx.error = True
                    return
            except AmountMissingException:
                ctx.error = True

                await Messages.send_usage_dm(msg.author, TIPFAVORITES_INFO)
            ctx.send_amount = send_amount
예제 #3
0
    async def increasetips_cmd(self, ctx: Context):
        if ctx.error:
            return

        msg = ctx.message

        increasetip_ids = []
        # Get mentioned users
        for m in msg.mentions:
            increasetip_ids.append(m.id)

        # remove duplicates and avoid admins
        increasetip_ids = set(increasetip_ids)
        increasetip_ids = [x for x in increasetip_ids if x not in config.Config.instance().get_admin_ids()]
        if msg.author.id not in config.Config.instance().get_admin_ids():
            for d in increasetip_ids:
                memba = msg.guild.get_member(d)
                if memba is not None:
                    for r in memba.roles:
                        if r.id in [config.Config.instance().get_admin_roles()]:
                            d.remove(r.id)


        if len(increasetip_ids) < 1:
            await Messages.add_x_reaction(msg)
            await msg.author.send("Your message has no users to increasetips for")
            return

        try:
            amount = RegexUtil.find_float(msg.content)
        except AmountMissingException:
            await Messages.send_usage_dm(msg.author, INCREASETIPS_INFO)
            return

        # TODO - tortoise doesnt give us any feedback on update counts atm
        # https://github.com/tortoise/tortoise-orm/issues/126
        # TODO - we also don't have atomic updates :/
        increase_tip_count = 0
        for u in await Stats.filter(user_id__in=increasetip_ids, server_id=msg.guild.id).all():
            async with in_transaction() as conn:
                u.total_tipped_amount = float(u.total_tipped_amount) + amount
                u.legacy_total_tipped_amount = float(u.legacy_total_tipped_amount) + amount
                await u.save(using_db=conn, update_fields=['total_tipped_amount', 'legacy_total_tipped_amount'])
                increase_tip_count += 1

        await msg.author.send(f"Increased stats of {increase_tip_count} by {amount} {Env.currency_name()}")
        await msg.add_reaction("\u2795")
예제 #4
0
    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)
예제 #5
0
    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
예제 #6
0
    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.")
예제 #7
0
 def test_find_float(self):
     self.assertEqual(RegexUtil.find_float('Hello 1.23 World'), 1.23)
     self.assertEqual(RegexUtil.find_float('Hello 1.23  4.56 World'), 1.23)
     with self.assertRaises(AmountMissingException) as exc:
         RegexUtil.find_float('Hello World')
예제 #8
0
 async def cog_before_invoke(self, ctx: Context):
     ctx.error = False
     # Remove duplicate mentions
     ctx.message.mentions = set(ctx.message.mentions)
     # Only allow tip commands in public channels
     msg = ctx.message
     if ChannelUtil.is_private(msg.channel):
         ctx.error = True
         return
     else:
         # Check admins
         ctx.god = msg.author.id in config.Config.instance().get_admin_ids()
         ctx.admin = False
         author: discord.Member = msg.author
         for role in author.roles:
             if role.id in config.Config.instance().get_admin_roles():
                 ctx.admin = True
                 break
     # Check paused
     if await RedisDB.instance().is_paused():
         ctx.error = True
         await Messages.send_error_dm(
             msg.author,
             f"Transaction activity is currently suspended. I'll be back online soon!"
         )
         return
     # See if user exists in DB
     user = await User.get_user(msg.author)
     if user is None:
         ctx.error = True
         await Messages.send_error_dm(
             msg.author,
             f"You should create an account with me first, send me `{config.Config.instance().command_prefix}help` to get started."
         )
         return
     elif user.frozen:
         ctx.error = True
         await Messages.send_error_dm(
             msg.author,
             f"Your account is frozen. Contact an admin if you need further assistance."
         )
         return
     # Update name, if applicable
     await user.update_name(msg.author.name)
     ctx.user = user
     # See if amount meets tip_minimum requirement
     try:
         send_amount = RegexUtil.find_float(msg.content)
         if ctx.command.name == 'tiprandom_cmd' and send_amount < Constants.TIPRANDOM_MINIMUM:
             raise AmountMissingException(
                 f"Tip random amount is too low, minimum is {Constants.TIPRANDOM_MINIMUM}"
             )
         elif ctx.command.name != 'tiprandom_cmd' and ctx.command.name != 'burn' and send_amount < Constants.TIP_MINIMUM:
             raise AmountMissingException(
                 f"Tip amount is too low, minimum is {Constants.TIP_MINIMUM}"
             )
         elif ctx.command.name == 'burn' and send_amount < 1.0:
             raise AmountMissingException(f"Come on, burn at least 1 BAN")
         elif Validators.too_many_decimals(send_amount):
             await Messages.send_error_dm(
                 ctx.message.author,
                 f"You are only allowed to use {Env.precision_digits()} digits after the decimal."
             )
             ctx.error = True
             return
     except AmountMissingException:
         ctx.error = True
         if ctx.command.name == 'tip_cmd':
             await Messages.send_usage_dm(msg.author, TIP_INFO)
         elif ctx.command.name == 'tipsplit_cmd':
             await Messages.send_usage_dm(msg.author, TIPSPLIT_INFO)
         elif ctx.command.name == 'tiprandom_cmd':
             await Messages.send_usage_dm(msg.author, TIPRANDOM_INFO)
         elif ctx.command.name == 'burn':
             await Messages.send_basic_dm(
                 msg.author, 'Come on, burn at least 1 ya cheap skate')
         return
     ctx.send_amount = send_amount