Exemple #1
0
async def update_web_user(request: Request, user_data: dict):
    logger.info("updating user data")
    session_id = get_session_id(request)
    user_id = str(user_data["id"])
    database.hset(f"web.session:{session_id}", "user_id", user_id)
    database.expire(f"web.session:{session_id}", DATABASE_SESSION_USER_EXPIRE)
    database.hset(
        f"web.user:{user_id}",
        mapping={
            "avatar_hash": str(user_data["avatar"]),
            "avatar_url": f"https://cdn.discordapp.com/avatars/{user_id}/{user_data['avatar']}.png",
            "username": str(user_data["username"]),
            "discriminator": str(user_data["discriminator"]),
        },
    )
    await user_setup(user_id)
    tempScore = int(database.hget(f"web.session:{session_id}", "tempScore"))
    if tempScore not in (0, -1):
        score_increment(user_id, tempScore)
        database.zincrby(
            f"daily.webscore:{str(datetime.datetime.now(datetime.timezone.utc).date())}",
            1,
            user_id,
        )
        database.hset(f"web.session:{session_id}", "tempScore", -1)
    logger.info("updated user data")
Exemple #2
0
    async def validate(self, ctx, parsed_birdlist):
        validated_birdlist = []
        async with aiohttp.ClientSession() as session:
            logger.info("starting validation")
            await ctx.send(
                "**Validating bird list...**\n*This may take a while.*")
            invalid_output = []
            valid_output = []
            validity = []
            for x in range(0, len(parsed_birdlist), 10):
                validity += await asyncio.gather(
                    *(valid_bird(bird, session)
                      for bird in parsed_birdlist[x:x + 10]))
                logger.info("sleeping during validation...")
                await asyncio.sleep(5)
            logger.info("checking validation")
            for item in validity:
                if item[1]:
                    validated_birdlist.append(
                        string.capwords(
                            item[3].split(" - ")[0].strip().replace("-", " ")))
                    valid_output.append(
                        f"Item `{item[0]}`: Detected as **{item[3]}**\n")
                else:
                    invalid_output.append(
                        f"Item `{item[0]}`: **{item[2]}** {f'(Detected as *{item[3]}*)' if item[3] else ''}\n"
                    )
            logger.info("done validating")

        if valid_output:
            logger.info("sending validation success")
            valid_output = (
                "**Succeeded Items:** Please verify items were detected correctly.\n"
                + "".join(valid_output))
            await self.broken_send(ctx, valid_output)
        if invalid_output:
            logger.info("sending validation failure")
            invalid_output = "**FAILED ITEMS:** Please fix and resubmit.\n" + "".join(
                invalid_output)
            await self.broken_send(ctx, invalid_output)
            return False

        await ctx.send("**Saving bird list...**")
        database.sadd(f"custom.list:{ctx.author.id}", *validated_birdlist)
        database.expire(f"custom.list:{ctx.author.id}", 86400)
        database.set(f"custom.confirm:{ctx.author.id}", "valid", ex=86400)
        await ctx.send(
            "**Ok!** Your bird list has been temporarily saved. " +
            "Please use `b!custom validate` to view and confirm your bird list. "
            +
            "To start over, upload a new list with the message `b!custom replace`. "
            +
            "You have 24 hours to confirm before your bird list will automatically be deleted."
        )
        return True
Exemple #3
0
def web_session_setup(session_id):
    logger.info("setting up session")
    session_id = str(session_id)
    if database.exists(f"web.session:{session_id}"):
        logger.info("session data ok")
    else:
        database.hset(
            f"web.session:{session_id}",
            mapping={
                "bird": "",
                "answered": 1,  # true = 1, false = 0
                "prevB": "",
                "prevJ": 20,
                "tempScore": 0,  # not used = -1
                "user_id": 0,  # not set = 0
            },
        )
        database.expire(f"web.session:{session_id}", DATABASE_SESSION_EXPIRE)
        logger.info("session set up")
Exemple #4
0
def update_web_user(user_data):
    logger.info("updating user data")
    session_id = get_session_id()
    user_id = str(user_data["id"])
    database.hset(f"web.session:{session_id}", "user_id", user_id)
    database.expire(f"web.session:{session_id}", DATABASE_SESSION_EXPIRE)
    database.hset(
        f"web.user:{user_id}",
        mapping={
            "avatar_hash": str(user_data["avatar"]),
            "avatar_url":
            f"https://cdn.discordapp.com/avatars/{user_id}/{user_data['avatar']}.png",
            "username": str(user_data["username"]),
            "discriminator": str(user_data["discriminator"]),
        },
    )
    asyncio.run(user_setup(user_id))
    tempScore = int(database.hget(f"web.session:{session_id}", "tempScore"))
    if tempScore not in (0, -1):
        database.zincrby("users:global", tempScore, int(user_id))
        database.hset(f"web.session:{session_id}", "tempScore", -1)
    logger.info("updated user data")
Exemple #5
0
    async def custom(self, ctx, *, args=""):
        logger.info("command: custom list set")

        args = args.lower().strip().split(" ")
        logger.info(f"parsed args: {args}")

        if ("replace" not in args and ctx.message.attachments
                and database.exists(f"custom.list:{ctx.author.id}")):
            await ctx.send(
                "Woah there. You already have a custom list. " +
                "To view its contents, use `b!custom view`. " +
                "If you want to replace your list, upload the file with `b!custom replace`."
            )
            return

        if "delete" in args and database.exists(
                f"custom.list:{ctx.author.id}"):
            if (database.exists(f"custom.confirm:{ctx.author.id}")
                    and database.get(f"custom.confirm:{ctx.author.id}").decode(
                        "utf-8") == "delete"):
                database.delete(f"custom.list:{ctx.author.id}",
                                f"custom.confirm:{ctx.author.id}")
                await ctx.send("Ok, your list was deleted.")
                return

            database.set(f"custom.confirm:{ctx.author.id}", "delete", ex=86400)
            await ctx.send(
                "Are you sure you want to permanently delete your list? " +
                "Use `b!custom delete` again within 24 hours to clear your custom list."
            )
            return

        if ("confirm" in args
                and database.exists(f"custom.confirm:{ctx.author.id}") and
                database.get(f"custom.confirm:{ctx.author.id}").decode("utf-8")
                == "confirm"):
            # list was validated by server and user, making permanent
            logger.info("user confirmed")
            database.persist(f"custom.list:{ctx.author.id}")
            database.delete(f"custom.confirm:{ctx.author.id}")
            database.set(f"custom.cooldown:{ctx.author.id}", 0, ex=86400)
            await ctx.send(
                "Ok, your custom bird list is now available. Use `b!custom view` "
                +
                "to view your list. You can change your list again in 24 hours."
            )
            return

        if ("validate" in args
                and database.exists(f"custom.confirm:{ctx.author.id}") and
                database.get(f"custom.confirm:{ctx.author.id}").decode("utf-8")
                == "valid"):
            # list was validated, now for user confirm
            logger.info("valid list, user needs to confirm")
            database.expire(f"custom.list:{ctx.author.id}", 86400)
            database.set(f"custom.confirm:{ctx.author.id}",
                         "confirm",
                         ex=86400)
            birdlist = "\n".join(
                bird.decode("utf-8")
                for bird in database.smembers(f"custom.list:{ctx.author.id}"))
            await ctx.send(
                f"**Please confirm the following list.** ({int(database.scard(f'custom.list:{ctx.author.id}'))} items)"
            )
            await self.broken_send(ctx, birdlist, between="```\n")
            await ctx.send(
                "Once you have looked over the list and are sure you want to add it, "
                +
                "please use `b!custom confirm` to have this list added as a custom list. "
                + "You have another 24 hours to confirm. " +
                "To start over, upload a new list with the message `b!custom replace`."
            )
            return

        if "view" in args:
            if not database.exists(f"custom.list:{ctx.author.id}"):
                await ctx.send(
                    "You don't have a custom list. To add a custom list, " +
                    "upload a txt file with a bird's name on each line to this DM "
                    + "and put `b!custom` in the **Add a Comment** section.")
                return
            birdlist = "\n".join(
                bird.decode("utf-8")
                for bird in database.smembers(f"custom.list:{ctx.author.id}"))
            birdlist = f"{birdlist}"
            await ctx.send(
                f"**Your Custom Bird List** ({int(database.scard(f'custom.list:{ctx.author.id}'))} items)"
            )
            await self.broken_send(ctx, birdlist, between="```\n")
            return

        if not database.exists(
                f"custom.list:{ctx.author.id}") or "replace" in args:
            # user inputted bird list, now validating
            start = time.perf_counter()
            if database.exists(f"custom.cooldown:{ctx.author.id}"):
                await ctx.send(
                    "Sorry, you'll have to wait 24 hours between changing lists."
                )
                return
            logger.info("reading received bird list")
            if not ctx.message.attachments:
                logger.info("no file detected")
                await ctx.send(
                    "Sorry, no file was detected. Upload your txt file and put `b!custom` in the **Add a Comment** section."
                )
                return
            decoded = await auto_decode(await
                                        ctx.message.attachments[0].read())
            if not decoded:
                logger.info("invalid character encoding")
                await ctx.send(
                    "Sorry, something went wrong. Are you sure this is a text file?"
                )
                return
            parsed_birdlist = set(
                map(lambda x: x.strip(),
                    decoded.strip().split("\n")))
            parsed_birdlist.discard("")
            parsed_birdlist = list(parsed_birdlist)
            if len(parsed_birdlist) > 200:
                logger.info("parsed birdlist too long")
                await ctx.send(
                    "Sorry, we're not supporting custom lists larger than 200 birds. Make sure there are no empty lines."
                )
                return
            logger.info("checking for invalid characters")
            char = re.compile(r"[^A-Za-z '\-\xC0-\xD6\xD8-\xF6\xF8-\xFF]")
            for item in parsed_birdlist:
                if len(item) > 1000:
                    logger.info("item too long")
                    await ctx.send(
                        f"Line starting with `{item[:100]}` exceeds 1000 characters."
                    )
                    return
                search = char.search(item)
                if search:
                    logger.info("invalid character detected")
                    await ctx.send(
                        f"An invalid character `{search.group()}` was detected. Only letters, spaces, hyphens, and apostrophes are allowed."
                    )
                    await ctx.send(
                        f"Error on line starting with `{item[:100]}`, position {search.span()[0]}"
                    )
                    return
            database.delete(f"custom.list:{ctx.author.id}",
                            f"custom.confirm:{ctx.author.id}")
            await self.validate(ctx, parsed_birdlist)
            elapsed = time.perf_counter() - start
            await ctx.send(
                f"**Finished validation in {round(elapsed//60)} minutes {round(elapsed%60, 4)} seconds.** {ctx.author.mention}"
            )
            logger.info(
                f"Finished validation in {round(elapsed//60)} minutes {round(elapsed%60, 4)} seconds."
            )
            return

        if database.exists(f"custom.confirm:{ctx.author.id}"):
            next_step = database.get(f"custom.confirm:{ctx.author.id}").decode(
                "utf-8")
            if next_step == "valid":
                await ctx.send(
                    "You need to validate your list. Use `b!custom validate` to do so. "
                    +
                    "You can also delete or replace your list with `b!custom [delete|replace]`"
                )
                return
            if next_step == "confirm":
                await ctx.send(
                    "You need to confirm your list. Use `b!custom confirm` to do so. "
                    +
                    "You can also delete or replace your list with `b!custom [delete|replace]`"
                )
                return
            if next_step == "delete":
                await ctx.send(
                    "You're in the process of deleting your list. Use `b!custom delete` to do so. "
                    + "You can also replace your list with `b!custom replace`")
                return
            capture_message(
                f"custom.confirm database invalid with {next_step}")
            await ctx.send(
                "Whoops, something went wrong. Please report this incident " +
                "in the support server below.\nhttps://discord.gg/2HbshwGjnm")
            return

        await ctx.send(
            "Use `b!custom view` to view your bird list or `b!custom replace` to replace your bird list."
        )
Exemple #6
0
async def send_bird(ctx,
                    bird: str,
                    media_type: str,
                    filters: Filter,
                    on_error=None,
                    message=None):
    """Gets bird media and sends it to the user.

    `ctx` - Discord context object\n
    `bird` (str) - bird to send\n
    `media_type` (str) - type of media (images/songs)\n
    `filters` (bot.filters Filter)\n
    `on_error` (function) - async function to run when an error occurs, passes error as argument\n
    `message` (str) - text message to send before bird\n
    """
    if bird == "":
        logger.error("error - bird is blank")
        await ctx.send("**There was an error fetching birds.**")
        if on_error is not None:
            await on_error(GenericError("bird is blank", code=100))
        else:
            await ctx.send("*Please try again.*")
        return

    # add special condition for screech owls
    # since screech owl is a genus and SciOly
    # doesn't specify a species
    if bird == "Screech Owl":
        logger.info("choosing specific Screech Owl")
        bird = random.choice(screech_owls)

    delete = await ctx.send("**Fetching.** This may take a while.")
    # trigger "typing" discord message
    await ctx.trigger_typing()

    try:
        filename, extension = await get_media(ctx, bird, media_type, filters)
    except GenericError as e:
        await delete.delete()
        if e.code == 100:
            await ctx.send(
                f"**This combination of filters has no valid {media_type} for the current bird.**"
            )
        elif e.code == 201:
            capture_exception(e)
            logger.exception(e)
            await ctx.send(
                "**A network error has occurred.**\n*Please try again later.*")
            database.incrby("cooldown:global", amount=1)
            database.expire("cooldown:global", 300)
        else:
            capture_exception(e)
            logger.exception(e)
            await ctx.send(
                f"**An error has occurred while fetching {media_type}.**\n**Reason:** {e}"
            )
        if on_error is not None:
            await on_error(e)
        else:
            await ctx.send("*Please try again.*")
        return

    if os.stat(
            filename).st_size > MAX_FILESIZE:  # another filesize check (4mb)
        await delete.delete()
        await ctx.send("**Oops! File too large :(**\n*Please try again.*")
        return

    if media_type == "images":
        if filters.bw:
            # prevent the black and white conversion from blocking
            loop = asyncio.get_running_loop()
            fn = functools.partial(_black_and_white, filename)
            filename = await loop.run_in_executor(None, fn)

    elif media_type == "songs" and not filters.vc:
        # remove spoilers in tag metadata
        audioFile = eyed3.load(filename)
        if audioFile is not None and audioFile.tag is not None:
            audioFile.tag.remove(filename)

    if message is not None:
        await ctx.send(message)

    if media_type == "songs" and filters.vc:
        await voice_functions.play(ctx, filename)
    else:
        # change filename to avoid spoilers
        file_obj = discord.File(filename, filename=f"bird.{extension}")
        await ctx.send(file=file_obj)
    await delete.delete()