Пример #1
0
    def test_bird_other_options(self):
        self.setup(guild=True)
        database.hset(f"channel:{self.ctx.channel.id}", "bird", "Canada Goose")
        database.hset(f"channel:{self.ctx.channel.id}", "answered", "0")

        coroutine = self.cog.bird.callback(  # pylint: disable=no-member
            self.cog, self.ctx, args_str="large egg nest"
        )
        assert asyncio.run(coroutine) is None
        for i in (
            "Active Filters",
            "tags: eggs",
            "tags: nest",
            "large: yes",
            "quality: good",
        ):
            assert i in self.ctx.messages[2].content
        for i in ("Taxons", "Detected State"):
            assert i not in self.ctx.messages[2].content
        for i in (
            "Here you go!",
            "Use `b!bird` again",
            "b!skip",
            "Use `b!check guess` to check your answer.",
        ):
            assert i in self.ctx.messages[4].content
Пример #2
0
        async def inner(error):
            # pylint: disable=unused-argument

            # skip current bird
            database.hset(f"channel:{ctx.channel.id}", "bird", "")
            database.hset(f"channel:{ctx.channel.id}", "answered", "1")
            await ctx.send("*Please try again.*")
Пример #3
0
    async def goatsucker(self, ctx):
        logger.info("command: goatsucker")

        await channel_setup(ctx)
        await user_setup(ctx)

        answered = int(database.hget(f"channel:{ctx.channel.id}",
                                     "gsAnswered"))
        # check to see if previous bird was answered
        if answered:  # if yes, give a new bird
            if database.exists(f"session.data:{ctx.author.id}"):
                logger.info("session active")
                session_increment(ctx, "total", 1)

            database.hset(f"channel:{ctx.channel.id}", "gsAnswered", "0")
            currentBird = random.choice(goatsuckers)
            database.hset(f"channel:{ctx.channel.id}", "goatsucker",
                          str(currentBird))
            logger.info("currentBird: " + str(currentBird))
            await send_bird(ctx,
                            currentBird,
                            on_error=error_skip_goat,
                            message=GS_MESSAGE)
        else:  # if no, give the same bird
            await send_bird(ctx,
                            database.hget(f"channel:{ctx.channel.id}",
                                          "goatsucker").decode("utf-8"),
                            on_error=error_skip_goat,
                            message=GS_MESSAGE)
Пример #4
0
    async def stop_race_(self, ctx):
        first = database.zrevrange(f"race.scores:{ctx.channel.id}", 0, 0,
                                   True)[0]
        if ctx.guild is not None:
            user = ctx.guild.get_member(int(first[0]))
        else:
            user = None

        if user is None:
            user = self.bot.get_user(int(first[0]))
            if user is None:
                user = "******"
            else:
                user = f"{user.name}#{user.discriminator}"
        else:
            user = f"{user.name}#{user.discriminator} ({user.mention})"

        await ctx.send(
            f"**Congratulations, {user}!**\n" +
            f"You have won the race by correctly identifying `{int(first[1])}` birds. "
            + "*Way to go!*")

        database.hset(f"race.data:{ctx.channel.id}", "stop",
                      round(time.time()))

        await self._send_stats(ctx, "**Race stopped.**")
        database.delete(f"race.data:{ctx.channel.id}")
        database.delete(f"race.scores:{ctx.channel.id}")
Пример #5
0
async def channel_setup(ctx):
    """Sets up a new discord channel.

    `ctx` - Discord context object
    """
    logger.info("checking channel setup")
    if database.exists(f"channel:{ctx.channel.id}"):
        logger.info("channel data ok")
    else:
        database.hset(
            f"channel:{ctx.channel.id}",
            mapping={"bird": "", "answered": 1, "prevB": "", "prevJ": 20},
        )
        # true = 1, false = 0, index 0 is last arg, prevJ is 20 to define as integer
        logger.info("channel data added")
        await ctx.send("Ok, setup! I'm all ready to use!")

    if database.zscore("score:global", str(ctx.channel.id)) is not None:
        logger.info("channel score ok")
    else:
        database.zadd("score:global", {str(ctx.channel.id): 0})
        logger.info("channel score added")

    if ctx.guild is not None:
        if (
            database.zadd("channels:global", {f"{ctx.guild.id}:{ctx.channel.id}": 0})
            != 0
        ):
            logger.info("server lookup ok")
        else:
            logger.info("server lookup added")
Пример #6
0
def error_skip_goat(ctx):
    """Skips the current goatsucker.
    
    Passed to send_bird() as on_error to skip the bird when an error occurs to prevent error loops.
    """
    logger.info("ok")
    database.hset(f"channel:{ctx.channel.id}", "goatsucker", "")
    database.hset(f"channel:{ctx.channel.id}", "gsAnswered", "1")
Пример #7
0
def error_skip_song(ctx):
    """Skips the current song.
    
    Passed to send_birdsong() as on_error to skip the bird when an error occurs to prevent error loops.
    """
    logger.info("ok")
    database.hset(f"channel:{ctx.channel.id}", "sBird", "")
    database.hset(f"channel:{ctx.channel.id}", "sAnswered", "1")
Пример #8
0
    def test_check_song_dm_2(self):
        test_word = "Northern Cardinal"

        self.setup(guild=True)
        database.hset(f"channel:{self.ctx.channel.id}", "sBird", test_word)
        coroutine = self.cog.checksong.callback(self.cog, self.ctx, arg=test_word*2) # pylint: disable=no-member
        assert asyncio.run(coroutine) is None
        assert self.ctx.messages[1].content == f"Sorry, the bird was actually {test_word.lower()}."
Пример #9
0
    def test_check_song_dm_1(self):
        test_word = "Northern Cardinal"

        self.setup(guild=True)
        database.hset(f"channel:{self.ctx.channel.id}", "sBird", test_word)
        coroutine = self.cog.checksong.callback(self.cog, self.ctx, arg=test_word) # pylint: disable=no-member
        assert asyncio.run(coroutine) is None
        assert self.ctx.messages[1].content == "Correct! Good job!"
Пример #10
0
    def test_check_goat_dm_1(self):
        test_word = "Common Pauraque"

        self.setup(guild=True)
        database.hset(f"channel:{self.ctx.channel.id}", "goatsucker", test_word)
        coroutine = self.cog.checkgoat.callback(self.cog, self.ctx, arg=test_word) # pylint: disable=no-member
        assert asyncio.run(coroutine) is None
        assert self.ctx.messages[1].content == "Correct! Good job!"
Пример #11
0
    def test_check_goat_dm_2(self):
        test_word = "Common Pauraque"

        self.setup(guild=True)
        database.hset(f"channel:{self.ctx.channel.id}", "goatsucker", test_word)
        coroutine = self.cog.checkgoat.callback(self.cog, self.ctx, arg=test_word*2) # pylint: disable=no-member
        assert asyncio.run(coroutine) is None
        assert self.ctx.messages[1].content == f"Sorry, the bird was actually {test_word.lower()}."
Пример #12
0
    def test_skipsong_bird_dm(self):
        test_word = "Northern Cardinal"

        self.setup(guild=True)
        database.hset(f"channel:{self.ctx.channel.id}", "sBird", test_word)
        coroutine = self.cog.skipsong.callback(self.cog, self.ctx)  # pylint: disable=no-member
        assert asyncio.run(coroutine) is None
        assert self.ctx.messages[
            1].content == f"Ok, skipping {test_word.lower()}"
Пример #13
0
    def test_check_bird_dm_1(self):
        self.setup(guild=True)
        test_word = "Canada Goose"
        database.hset(f"channel:{self.ctx.channel.id}", "bird", test_word)

        coroutine = self.cog.check.callback(  # pylint: disable=no-member
            self.cog, self.ctx, arg=test_word)
        assert asyncio.run(coroutine) is None
        assert self.ctx.messages[2].content == "Correct! Good job!"
Пример #14
0
    def test_skipgoat_bird_dm(self):
        test_word = "Common Pauraque"

        self.setup(guild=True)
        database.hset(f"channel:{self.ctx.channel.id}", "goatsucker",
                      test_word)
        coroutine = self.cog.skipgoat.callback(self.cog, self.ctx)  # pylint: disable=no-member
        assert asyncio.run(coroutine) is None
        assert self.ctx.messages[
            1].content == f"Ok, skipping {test_word.lower()}"
Пример #15
0
    def test_hint_bird_dm(self):
        self.setup(guild=True)
        test_word = "banana_test"
        database.hset(f"channel:{self.ctx.channel.id}", "bird", test_word)

        coroutine = self.cog.hint.callback(  # pylint: disable=no-member
            self.cog, self.ctx)
        assert asyncio.run(coroutine) is None
        assert self.ctx.messages[
            2].content == f"The first letter is {test_word[0]}"
Пример #16
0
    def test_skip_bird_dm(self):
        self.setup(guild=True)
        test_word = "Canada Goose"
        database.hset(f"channel:{self.ctx.channel.id}", "bird", test_word)

        coroutine = self.cog.skip.callback(  # pylint: disable=no-member
            self.cog, self.ctx)
        assert asyncio.run(coroutine) is None
        assert self.ctx.messages[
            2].content == f"Ok, skipping {test_word.lower()}"
Пример #17
0
def session_increment(ctx, item: str, amount: int):
    """Increments the value of a database hash field by `amount`.

    `ctx` - Discord context object\n
    `item` - hash field to increment (see data.py for details,
    possible values include correct, incorrect, total)\n
    `amount` (int) - amount to increment by, usually 1
    """
    logger.info(f"incrementing {item} by {amount}")
    value = int(database.hget(f"session.data:{ctx.author.id}", item))
    value += int(amount)
    database.hset(f"session.data:{ctx.author.id}", item, str(value))
Пример #18
0
    def test_check_bird_dm_2(self):
        self.setup(guild=True)
        test_word = "Canada Goose"
        database.hset(f"channel:{self.ctx.channel.id}", "bird", test_word)

        coroutine = self.cog.check.callback(  # pylint: disable=no-member
            self.cog,
            self.ctx,
            arg=test_word * 2)
        assert asyncio.run(coroutine) is None
        assert (self.ctx.messages[2].content ==
                f"Sorry, the bird was actually {test_word.lower()}.")
Пример #19
0
    async def stop(self, ctx):
        logger.info("command: stop session")

        await channel_setup(ctx)
        await user_setup(ctx)

        if database.exists(f"session.data:{ctx.author.id}"):
            database.hset(f"session.data:{ctx.author.id}", "stop", round(time.time()))

            await self._send_stats(ctx, "**Session stopped.**\n")
            database.delete(f"session.data:{ctx.author.id}")
            database.delete(f"session.incorrect:{ctx.author.id}")
        else:
            await ctx.send("**There is no session running.** *You can start one with `b!session start`*")
Пример #20
0
async def get_media(ctx, bird: str, media_type: str, filters: Filter):
    """Chooses media from a list of filenames.

    This function chooses a valid image to pass to send_bird().
    Valid images are based on file extension and size. (8mb discord limit)

    Returns a list containing the file path and extension type.

    `ctx` - Discord context object\n
    `bird` (str) - bird to get media of\n
    `media_type` (str) - type of media (images/songs)\n
    `filters` (bot.filters Filter)\n
    """

    # fetch scientific names of birds
    try:
        sciBird = await get_sciname(bird)
    except GenericError:
        sciBird = bird
    media = await get_files(sciBird, media_type, filters)
    logger.info("media: " + str(media))
    prevJ = int(database.hget(f"channel:{ctx.channel.id}", "prevJ"))
    # Randomize start (choose beginning 4/5ths in case it fails checks)
    if media:
        j = (prevJ + 1) % len(media)
        logger.info("prevJ: " + str(prevJ))
        logger.info("j: " + str(j))

        for x in range(0, len(media)):  # check file type and size
            y = (x + j) % len(media)
            path = media[y]
            extension = path.split(".")[-1]
            logger.info("extension: " + str(extension))
            statInfo = os.stat(path)
            logger.info("size: " + str(statInfo.st_size))
            if (extension.lower() in valid_types[media_type].values()
                    and statInfo.st_size <
                    MAX_FILESIZE):  # keep files less than 4mb
                logger.info("found one!")
                break
            raise GenericError(f"No Valid {media_type.title()} Found",
                               code=999)

        database.hset(f"channel:{ctx.channel.id}", "prevJ", str(j))
    else:
        raise GenericError(f"No {media_type.title()} Found", code=100)

    return [path, extension]
Пример #21
0
    async def skip(self, ctx):
        logger.info("command: skip")

        currentBird = database.hget(f"channel:{ctx.channel.id}",
                                    "bird").decode("utf-8")
        database.hset(f"channel:{ctx.channel.id}", "bird", "")
        database.hset(f"channel:{ctx.channel.id}", "answered", "1")
        if currentBird != "":  # check if there is bird
            url = get_wiki_url(ctx, currentBird)
            await ctx.send(f"Ok, skipping {currentBird.lower()}")
            await ctx.send(url)  # sends wiki page

            streak_increment(ctx, None)  # reset streak

            if database.exists(f"race.data:{ctx.channel.id}"):
                if Filter.from_int(
                        int(
                            database.hget(f"race.data:{ctx.channel.id}",
                                          "filter"))).vc:
                    await voice_functions.stop(ctx, silent=True)

                media = database.hget(f"race.data:{ctx.channel.id}",
                                      "media").decode("utf-8")

                limit = int(
                    database.hget(f"race.data:{ctx.channel.id}", "limit"))
                first = database.zrevrange(f"race.scores:{ctx.channel.id}", 0,
                                           0, True)[0]
                if int(first[1]) >= limit:
                    logger.info("race ending")
                    race = self.bot.get_cog("Race")
                    await race.stop_race_(ctx)
                else:
                    logger.info(f"auto sending next bird {media}")
                    filter_int, taxon, state = database.hmget(
                        f"race.data:{ctx.channel.id}",
                        ["filter", "taxon", "state"])
                    birds = self.bot.get_cog("Birds")
                    await birds.send_bird_(
                        ctx,
                        media,
                        Filter.from_int(int(filter_int)),
                        taxon.decode("utf-8"),
                        state.decode("utf-8"),
                    )
        else:
            await ctx.send("You need to ask for a bird first!")
Пример #22
0
    async def skipgoat(self, ctx):
        logger.info("command: skipgoat")

        await channel_setup(ctx)
        await user_setup(ctx)

        currentBird = str(
            database.hget(f"channel:{ctx.channel.id}", "goatsucker"))[2:-1]
        database.hset(f"channel:{ctx.channel.id}", "goatsucker", "")
        database.hset(f"channel:{ctx.channel.id}", "gsAnswered", "1")
        if currentBird != "":  # check if there is bird
            url = get_wiki_url(currentBird)
            await ctx.send(f"Ok, skipping {currentBird.lower()}")
            await ctx.send(url)  # sends wiki page
            database.zadd("streak:global", {str(ctx.author.id): 0})
        else:
            await ctx.send("You need to ask for a bird first!")
Пример #23
0
async def get_image(ctx, bird, addOn=None):
    """Chooses an image from a list of images.

    This function chooses a valid image to pass to send_bird().
    Valid images are based on file extension and size. (8mb discord limit)

    Returns a list containing the file path and extension type.

    `ctx` - Discord context object\n
    `bird` (str) - bird to get image of\n
    `addOn` (str) - string to append to search for female/juvenile birds\n
    """

    # fetch scientific names of birds
    try:
        sciBird = await get_sciname(bird)
    except GenericError:
        sciBird = bird
    images = await get_files(sciBird, "images", addOn)
    logger.info("images: " + str(images))
    prevJ = int(str(database.hget(f"channel:{ctx.channel.id}", "prevJ"))[2:-1])
    # Randomize start (choose beginning 4/5ths in case it fails checks)
    if images:
        j = (prevJ + 1) % len(images)
        logger.info("prevJ: " + str(prevJ))
        logger.info("j: " + str(j))

        for x in range(0, len(images)):  # check file type and size
            y = (x + j) % len(images)
            image_link = images[y]
            extension = image_link.split('.')[-1]
            logger.info("extension: " + str(extension))
            statInfo = os.stat(image_link)
            logger.info("size: " + str(statInfo.st_size))
            if extension.lower(
            ) in valid_image_extensions and statInfo.st_size < 4000000:  # keep files less than 4mb
                logger.info("found one!")
                break
            elif y == prevJ:
                raise GenericError("No Valid Images Found", code=999)

        database.hset(f"channel:{ctx.channel.id}", "prevJ", str(j))
    else:
        raise GenericError("No Images Found", code=100)

    return [image_link, extension]
Пример #24
0
        async def inner(error):
            nonlocal retries

            # skip current bird
            database.hset(f"channel:{ctx.channel.id}", "bird", "")
            database.hset(f"channel:{ctx.channel.id}", "answered", "1")

            if retries >= 2:  # only retry twice
                await ctx.send("**Too many retries.**\n*Please try again.*")
                return

            if isinstance(error, GenericError) and error.code == 100:
                retries += 1
                await ctx.send("**Retrying...**")
                await self.send_bird_(ctx, media_type, filters, taxon_str,
                                      role_str, retries)
            else:
                await ctx.send("*Please try again.*")
Пример #25
0
async def get_song(ctx, bird):
    """Chooses a song from a list of songs.

    This function chooses a valid song to pass to send_birdsong().
    Valid songs are based on file extension and size. (8mb discord limit)

    Returns a list containing the file path and extension type.

    `ctx` - Discord context object\n
    `bird` (str) - bird to get song of
    """

    # fetch scientific names of birds
    try:
        sciBird = await get_sciname(bird)
    except GenericError:
        sciBird = bird
    songs = await get_files(sciBird, "songs")
    logger.info("songs: " + str(songs))
    prevK = int(str(database.hget(f"channel:{ctx.channel.id}", "prevK"))[2:-1])
    if songs:
        k = (prevK + 1) % len(songs)
        logger.info("prevK: " + str(prevK))
        logger.info("k: " + str(k))

        for x in range(0, len(songs)):  # check file type and size
            y = (x + k) % len(songs)
            song_link = songs[y]
            extension = song_link.split('.')[-1]
            logger.info("extension: " + str(extension))
            statInfo = os.stat(song_link)
            logger.info("size: " + str(statInfo.st_size))
            if extension.lower(
            ) in valid_audio_extensions and statInfo.st_size < 4000000:  # keep files less than 4mb
                logger.info("found one!")
                break
            elif y == prevK:
                raise GenericError("No Valid Songs Found", code=999)

        database.hset(f"channel:{ctx.channel.id}", "prevK", str(k))
    else:
        raise GenericError("No Songs Found", code=100)

    return [song_link, extension]
Пример #26
0
async def get_media(
    request: Request, bird: str, media_type: str, filters: Filter
):  # images or songs
    if bird not in birdList + screech_owls:
        raise GenericError("Invalid Bird", code=990)

    if media_type not in ("images", "songs"):
        logger.error(f"invalid media type {media_type}")
        raise HTTPException(status_code=422, detail="Invalid media type")

    # fetch scientific names of birds
    try:
        sciBird = await get_sciname(bird)
    except GenericError:
        sciBird = bird

    session_id = get_session_id(request)
    database_key = f"web.session:{session_id}"

    media = await get_files(sciBird, media_type, filters)
    logger.info(f"fetched {media_type}: {media}")
    prevJ = int(database.hget(database_key, "prevJ").decode("utf-8"))
    if media:
        j = (prevJ + 1) % len(media)
        logger.info("prevJ: " + str(prevJ))
        logger.info("j: " + str(j))

        for x in range(0, len(media)):  # check file type and size
            y = (x + j) % len(media)
            media_path = media[y]
            extension = media_path.split(".")[-1]
            logger.info("extension: " + str(extension))
            if extension.lower() in valid_types[media_type].values():
                logger.info("found one!")
                break
            if y == prevJ:
                raise GenericError(f"No Valid {media_type.title()} Found", code=999)

        database.hset(database_key, "prevJ", str(j))
    else:
        raise GenericError(f"No {media_type.title()} Found", code=100)

    return media_path, extension, content_type_lookup[extension]
Пример #27
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")
Пример #28
0
    async def skip(self, ctx):
        logger.info("command: skip")

        await channel_setup(ctx)
        await user_setup(ctx)

        currentBird = str(database.hget(f"channel:{ctx.channel.id}",
                                        "bird"))[2:-1]
        database.hset(f"channel:{ctx.channel.id}", "bird", "")
        database.hset(f"channel:{ctx.channel.id}", "answered", "1")
        if currentBird != "":  # check if there is bird
            url = get_wiki_url(currentBird)
            await ctx.send(f"Ok, skipping {currentBird.lower()}")
            await ctx.send(
                url if not database.exists(f"race.data:{ctx.channel.id}") else
                f"<{url}>")  # sends wiki page
            database.zadd("streak:global",
                          {str(ctx.author.id): 0})  # end streak
            if database.exists(
                    f"race.data:{ctx.channel.id}") and database.hget(
                        f"race.data:{ctx.channel.id}",
                        "media").decode("utf-8") == "image":

                limit = int(
                    database.hget(f"race.data:{ctx.channel.id}", "limit"))
                first = database.zrevrange(f"race.scores:{ctx.channel.id}", 0,
                                           0, True)[0]
                if int(first[1]) >= limit:
                    logger.info("race ending")
                    race = self.bot.get_cog("Race")
                    await race.stop_race_(ctx)
                else:
                    logger.info("auto sending next bird image")
                    addon, bw, taxon = database.hmget(
                        f"race.data:{ctx.channel.id}",
                        ["addon", "bw", "taxon"])
                    birds = self.bot.get_cog("Birds")
                    await birds.send_bird_(ctx, addon.decode("utf-8"),
                                           bw.decode("utf-8"),
                                           taxon.decode("utf-8"))
        else:
            await ctx.send("You need to ask for a bird first!")
Пример #29
0
def session_increment(ctx, item: str, amount: int):
    """Increments the value of a database hash field by `amount`.

    `ctx` - Discord context object or user id\n
    `item` - hash field to increment (see data.py for details,
    possible values include correct, incorrect, total)\n
    `amount` (int) - amount to increment by, usually 1
    """
    if isinstance(ctx, (str, int)):
        user_id = ctx
    else:
        user_id = ctx.author.id

    if database.exists(f"session.data:{user_id}"):
        logger.info("session active")
        logger.info(f"incrementing {item} by {amount}")
        value = int(database.hget(f"session.data:{user_id}", item))
        value += int(amount)
        database.hset(f"session.data:{user_id}", item, str(value))
    else:
        logger.info("session not active")
Пример #30
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")