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)
def verify_session(session_id) -> Union[int, bool]: session_id = str(session_id) logger.info(f"verifying session id: {session_id}") if not database.exists(f"web.session:{session_id}"): logger.info("doesn't exist") return False if int(database.hget(f"web.session:{session_id}", "user_id")) == 0: logger.info("exists, no user id") return True logger.info("exists with user id") return int(database.hget(f"web.session:{session_id}", "user_id"))
async def song(self, ctx): logger.info("command: song") await channel_setup(ctx) await user_setup(ctx) logger.info("bird: " + database.hget(f"channel:{ctx.channel.id}", "sBird").decode("utf-8")) logger.info( "answered: " + str(int(database.hget(f"channel:{ctx.channel.id}", "sAnswered")))) await self.send_song_(ctx)
async def send_song_(self, ctx): songAnswered = int( database.hget(f"channel:{ctx.channel.id}", "sAnswered")) # check to see if previous bird was answered if songAnswered: # if yes, give a new bird roles = check_state_role(ctx) if database.exists(f"session.data:{ctx.author.id}"): logger.info("session active") session_increment(ctx, "total", 1) roles = database.hget(f"session.data:{ctx.author.id}", "state").decode("utf-8").split(" ") if roles[0] == "": roles = [] if not roles: logger.info("no session lists") roles = check_state_role(ctx) logger.info(f"roles: {roles}") if roles: birds = list( itertools.chain.from_iterable(states[state]["songBirds"] for state in roles)) else: birds = songBirds logger.info(f"number of birds: {len(birds)}") currentSongBird = random.choice(birds) prevS = database.hget(f"channel:{ctx.channel.id}", "prevS").decode("utf-8") while currentSongBird == prevS and len(birds) > 1: currentSongBird = random.choice(birds) database.hset(f"channel:{ctx.channel.id}", "prevS", str(currentSongBird)) database.hset(f"channel:{ctx.channel.id}", "sBird", str(currentSongBird)) logger.info("currentSongBird: " + str(currentSongBird)) database.hset(f"channel:{ctx.channel.id}", "sAnswered", "0") await send_birdsong(ctx, currentSongBird, on_error=error_skip_song, message=SONG_MESSAGE) else: await send_birdsong(ctx, database.hget(f"channel:{ctx.channel.id}", "sBird").decode("utf-8"), on_error=error_skip_song, message=SONG_MESSAGE)
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")
async def _send_stats(self, ctx, preamble): placings = 5 database_key = f"race.scores:{ctx.channel.id}" if database.zcard(database_key) == 0: logger.info(f"no users in {database_key}") await ctx.send("There are no users in the database.") return if placings > database.zcard(database_key): placings = database.zcard(database_key) leaderboard_list = database.zrevrangebyscore( database_key, "+inf", "-inf", 0, placings, True ) embed = discord.Embed( type="rich", colour=discord.Color.blurple(), title=preamble ) embed.set_author(name="Bird ID - An Ornithology Bot") leaderboard = "" for i, stats in enumerate(leaderboard_list): if ctx.guild is not None: user = await fetch_get_user(int(stats[0]), ctx=ctx, member=True) else: user = None if user is None: user = await fetch_get_user(int(stats[0]), ctx=ctx, member=False) if user is None: user_info = "**Deleted**" else: user_info = f"**{esc(user.name)}#{user.discriminator}**" else: user_info = f"**{esc(user.name)}#{user.discriminator}** ({user.mention})" leaderboard += f"{i+1}. {user_info} - {int(stats[1])}\n" start = int(database.hget(f"race.data:{ctx.channel.id}", "start")) elapsed = str(datetime.timedelta(seconds=round(time.time()) - start)) embed.add_field( name="Options", value=await self._get_options(ctx), inline=False ) embed.add_field( name="Stats", value=f"**Race Duration:** `{elapsed}`", inline=False ) embed.add_field(name="Leaderboard", value=leaderboard, inline=False) if ctx.author: if database.zscore(database_key, str(ctx.author.id)) is not None: placement = int(database.zrevrank(database_key, str(ctx.author.id))) + 1 embed.add_field( name="You:", value=f"You are #{placement}.", inline=False ) else: embed.add_field( name="You:", value="You haven't answered any correctly." ) await ctx.send(embed=embed)
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!")
async def hint(self, ctx): logger.info("command: hint") currentBird = database.hget(f"channel:{ctx.channel.id}", "bird").decode("utf-8") if currentBird != "": # check if there is bird await ctx.send(f"The first letter is {currentBird[0]}") else: await ctx.send("You need to ask for a bird first!")
async def song(self, ctx, *, args_str: str = ""): logger.info("command: song") filters, taxon, state = await self.parse(ctx, args_str) media = "songs" if database.exists(f"race.data:{ctx.channel.id}"): media = database.hget(f"race.data:{ctx.channel.id}", "media").decode("utf-8") await self.send_bird_(ctx, media, filters, taxon, state)
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!")
async def _send_next_race_media(self, ctx): 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") logger.info(f"auto sending next bird {media}") filter_int, taxon, state = database.hmget( f"race.data:{ctx.channel.id}", ["filter", "taxon", "state"]) await self.send_bird_( ctx, media, Filter.from_int(int(filter_int)), taxon.decode("utf-8"), state.decode("utf-8"), )
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))
async def goatsucker(self, ctx): logger.info("command: goatsucker") if database.exists(f"race.data:{ctx.channel.id}"): await ctx.send("This command is disabled during races.") return answered = int(database.hget(f"channel:{ctx.channel.id}", "answered")) # check to see if previous bird was answered if answered: # if yes, give a new bird session_increment(ctx, "total", 1) database.hset(f"channel:{ctx.channel.id}", "answered", "0") currentBird = random.choice(goatsuckers) self.increment_bird_frequency(ctx, currentBird) database.hset(f"channel:{ctx.channel.id}", "bird", str(currentBird)) logger.info("currentBird: " + str(currentBird)) await send_bird( ctx, currentBird, "images", Filter(), on_error=self.error_skip(ctx), message=GS_MESSAGE, ) else: # if no, give the same bird await send_bird( ctx, database.hget(f"channel:{ctx.channel.id}", "bird").decode("utf-8"), "images", Filter(), on_error=self.error_skip(ctx), message=GS_MESSAGE, )
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]
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]
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!")
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]
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]
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")
async def get_media(bird, media_type, filters): # images or songs if bird not in birdList + screech_owls: raise GenericError("Invalid Bird", code=990) # fetch scientific names of birds try: sciBird = await get_sciname(bird) except GenericError: sciBird = bird session_id = get_session_id() 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 (media_type == "images" and extension.lower() in valid_types["images"].values()) or \ (media_type == "songs" and extension.lower() in valid_types["songs"].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
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")
async def stop_race_(self, ctx): if Filter.from_int( int(database.hget(f"race.data:{ctx.channel.id}", "filter"))).vc: await voice_functions.disconnect(ctx, silent=True) database.delete(f"voice.server:{ctx.guild.id}") first = database.zrevrange(f"race.scores:{ctx.channel.id}", 0, 0, True)[0] if ctx.guild is not None: user = await fetch_get_user(int(first[0]), ctx=ctx, member=True) else: user = None if user is None: user = await fetch_get_user(int(first[0]), ctx=ctx, member=False) if user is None: user_info = "Deleted" else: user_info = f"{esc(user.name)}#{user.discriminator}" else: user_info = f"{esc(user.name)}#{user.discriminator} ({user.mention})" await ctx.send( f"**Congratulations, {user_info}!**\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}") logger.info("race end: skipping last bird") database.hset(f"channel:{ctx.channel.id}", "bird", "") database.hset(f"channel:{ctx.channel.id}", "answered", "1")
async def start(self, ctx, *, args_str: str = ""): logger.info("command: start race") if not str(ctx.channel.name).startswith("racing"): logger.info("not race channel") await ctx.send( "**Sorry, racing is not available in this channel.**\n" + "*Set the channel name to start with `racing` to enable it.*" ) return if database.exists(f"race.data:{ctx.channel.id}"): logger.info("already race") await ctx.send( "**There is already a race in session.** *Change settings/view stats with `b!race view`*" ) return filters = Filter.parse(args_str, use_numbers=False) if filters.vc: if database.get(f"voice.server:{ctx.guild.id}") is not None: logger.info("already vc race") await ctx.send( "**There is already a VC race in session in this server!**" ) return client = await voice_functions.get_voice_client(ctx, connect=True) if client is None: return database.set(f"voice.server:{ctx.guild.id}", str(ctx.channel.id)) args = args_str.split(" ") logger.info(f"args: {args}") taxon_args = set(taxons.keys()).intersection({arg.lower() for arg in args}) if taxon_args: taxon = " ".join(taxon_args).strip() else: taxon = "" if "strict" in args: strict = "strict" else: strict = "" if "alpha" in args: alpha = "alpha" else: alpha = "" states_args = set(states.keys()).intersection({arg.upper() for arg in args}) if states_args: if {"CUSTOM"}.issubset(states_args): if database.exists( f"custom.list:{ctx.author.id}" ) and not database.exists(f"custom.confirm:{ctx.author.id}"): states_args.discard("CUSTOM") states_args.add(f"CUSTOM:{ctx.author.id}") else: states_args.discard("CUSTOM") await ctx.send( "**You don't have a custom list set.**\n*Ignoring the argument.*" ) state = " ".join(states_args).strip() else: state = "" song = "song" in args or "songs" in args or "s" in args or filters.vc image = ( "image" in args or "images" in args or "i" in args or "picture" in args or "pictures" in args or "p" in args ) if song and image: await ctx.send( "**Songs and images are not yet supported.**\n*Please try again*" ) return if song: media = "song" elif image: media = "image" else: media = "image" ints = [] for n in args: try: ints.append(int(n)) except ValueError: continue if ints: limit = int(ints[0]) else: limit = 10 if limit > 1000000: await ctx.send("**Sorry, the maximum amount to win is 1 million.**") limit = 1000000 logger.info( f"adding filters: {filters}; state: {state}; media: {media}; limit: {limit}" ) database.hset( f"race.data:{ctx.channel.id}", mapping={ "start": round(time.time()), "stop": 0, "limit": limit, "filter": str(filters.to_int()), "state": state, "media": media, "taxon": taxon, "strict": strict, "alpha": alpha, }, ) database.zadd(f"race.scores:{ctx.channel.id}", {str(ctx.author.id): 0}) await ctx.send( f"**Race started with options:**\n{await self._get_options(ctx)}" ) media = database.hget(f"race.data:{ctx.channel.id}", "media").decode("utf-8") logger.info("clearing previous bird") database.hset(f"channel:{ctx.channel.id}", "bird", "") database.hset(f"channel:{ctx.channel.id}", "answered", "1") 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)), # type: ignore taxon.decode("utf-8"), # type: ignore state.decode("utf-8"), # type: ignore )
async def send_bird_( self, ctx, media_type: Optional[str], filters: Filter, taxon_str: str = "", role_str: str = "", retries=0, ): media_type = ("images" if media_type in ("images", "image", "i", "p") else ("songs" if media_type in ("songs", "song", "s", "a") else None)) if not media_type: raise GenericError("Invalid media type", code=990) if media_type == "songs" and filters.vc: current_voice = database.get(f"voice.server:{ctx.guild.id}") if current_voice is not None and current_voice.decode( "utf-8") != str(ctx.channel.id): logger.info("already vc race") await ctx.send("**The voice channel is currently in use!**") return if taxon_str: taxon = taxon_str.split(" ") else: taxon = [] if role_str: roles = role_str.split(" ") else: roles = [] logger.info( "bird: " + database.hget(f"channel:{ctx.channel.id}", "bird").decode("utf-8")) currently_in_race = bool( database.exists(f"race.data:{ctx.channel.id}")) answered = int(database.hget(f"channel:{ctx.channel.id}", "answered")) logger.info(f"answered: {answered}") # check to see if previous bird was answered if answered: # if yes, give a new bird session_increment(ctx, "total", 1) logger.info(f"filters: {filters}; taxon: {taxon}; roles: {roles}") if not currently_in_race and retries == 0: await ctx.send( "**Recognized arguments:** " + f"*Active Filters*: `{'`, `'.join(filters.display())}`, " + f"*Taxons*: `{'None' if taxon_str == '' else taxon_str}`, " + f"*Detected State*: `{'None' if role_str == '' else role_str}`" ) find_custom_role = { i if i.startswith("CUSTOM:") else "" for i in roles } find_custom_role.discard("") if (database.exists(f"race.data:{ctx.channel.id}") and len(find_custom_role) == 1): custom_role = find_custom_role.pop() roles.remove(custom_role) roles.append("CUSTOM") user_id = custom_role.split(":")[1] birds = build_id_list(user_id=user_id, taxon=taxon, state=roles, media=media_type) else: birds = build_id_list(user_id=ctx.author.id, taxon=taxon, state=roles, media=media_type) if not birds: logger.info("no birds for taxon/state") await ctx.send( "**Sorry, no birds could be found for the taxon/state combo.**\n*Please try again*" ) return currentBird = random.choice(birds) self.increment_bird_frequency(ctx, currentBird) prevB = database.hget(f"channel:{ctx.channel.id}", "prevB").decode("utf-8") while currentBird == prevB and len(birds) > 1: currentBird = random.choice(birds) database.hset(f"channel:{ctx.channel.id}", "prevB", str(currentBird)) database.hset(f"channel:{ctx.channel.id}", "bird", str(currentBird)) logger.info("currentBird: " + str(currentBird)) database.hset(f"channel:{ctx.channel.id}", "answered", "0") await send_bird( ctx, currentBird, media_type, filters, on_error=self.error_handle(ctx, media_type, filters, taxon_str, role_str, retries), message=( SONG_MESSAGE if media_type == "songs" else BIRD_MESSAGE) if not currently_in_race else "*Here you go!*", ) else: # if no, give the same bird await ctx.send( f"**Active Filters**: `{'`, `'.join(filters.display())}`") await send_bird( ctx, database.hget(f"channel:{ctx.channel.id}", "bird").decode("utf-8"), media_type, filters, on_error=self.error_handle(ctx, media_type, filters, taxon_str, role_str, retries), message=( SONG_MESSAGE if media_type == "songs" else BIRD_MESSAGE) if not currently_in_race else "*Here you go!*", )
async def checkgoat(self, ctx, *, arg): logger.info("command: checkgoat") await channel_setup(ctx) await user_setup(ctx) currentBird = database.hget(f"channel:{ctx.channel.id}", "goatsucker").decode("utf-8") if currentBird == "": # no bird await ctx.send("You must ask for a bird first!") else: # if there is a bird, it checks answer await bird_setup(ctx, currentBird) index = goatsuckers.index(currentBird) sciBird = sciGoat[index] database.hset(f"channel:{ctx.channel.id}", "gsAnswered", "1") database.hset(f"channel:{ctx.channel.id}", "goatsucker", "") if spellcheck(arg, currentBird) or spellcheck(arg, sciBird): logger.info("correct") if database.exists(f"session.data:{ctx.author.id}"): logger.info("session active") session_increment(ctx, "correct", 1) # increment streak and update max database.zincrby("streak:global", 1, str(ctx.author.id)) if database.zscore("streak:global", str( ctx.author.id)) > database.zscore( "streak.max:global", str(ctx.author.id)): database.zadd( "streak.max:global", { str(ctx.author.id): database.zscore("streak:global", str( ctx.author.id)) }) await ctx.send("Correct! Good job!") url = get_wiki_url(currentBird) await ctx.send(url) score_increment(ctx, 1) if int(database.zscore("users:global", str(ctx.author.id))) in achievement: number = str( int(database.zscore("users:global", str(ctx.author.id)))) await ctx.send( f"Wow! You have answered {number} birds correctly!") filename = f"bot/media/achievements/{number}.PNG" with open(filename, 'rb') as img: await ctx.send( file=discord.File(img, filename="award.png")) else: logger.info("incorrect") database.zadd("streak:global", {str(ctx.author.id): 0}) if database.exists(f"session.data:{ctx.author.id}"): logger.info("session active") session_increment(ctx, "incorrect", 1) incorrect_increment(ctx, str(currentBird), 1) await ctx.send("Sorry, the bird was actually " + currentBird.lower() + ".") url = get_wiki_url(currentBird) await ctx.send(url) logger.info("currentBird: " + str(currentBird.lower().replace("-", " "))) logger.info("args: " + str(arg.lower().replace("-", " ")))
async def parse(ctx, args_str: str): """Parse arguments for options.""" args = args_str.split(" ") logger.info(f"args: {args}") if not database.exists(f"race.data:{ctx.channel.id}"): roles = check_state_role(ctx) taxon_args = set(taxons.keys()).intersection( {arg.lower() for arg in args}) if taxon_args: taxon = " ".join(taxon_args).strip() else: taxon = "" state_args = set(states.keys()).intersection( {arg.upper() for arg in args}) if state_args: state = " ".join(state_args).strip() else: state = "" if database.exists(f"session.data:{ctx.author.id}"): logger.info("session parameters") if taxon_args: current_taxons = set( database.hget(f"session.data:{ctx.author.id}", "taxon").decode("utf-8").split(" ")) logger.info(f"toggle taxons: {taxon_args}") logger.info(f"current taxons: {current_taxons}") taxon_args.symmetric_difference_update(current_taxons) taxon_args.discard("") logger.info(f"new taxons: {taxon_args}") taxon = " ".join(taxon_args).strip() else: taxon = database.hget(f"session.data:{ctx.author.id}", "taxon").decode("utf-8") roles = (database.hget(f"session.data:{ctx.author.id}", "state").decode("utf-8").split(" ")) if roles[0] == "": roles = [] if not roles: logger.info("no session lists") roles = check_state_role(ctx) session_filter = int( database.hget(f"session.data:{ctx.author.id}", "filter")) filters = Filter.parse(args_str, defaults=False) if filters.vc: filters.vc = False await ctx.send("**The VC filter is not allowed inline!**") default_quality = Filter().quality if (Filter.from_int(session_filter).quality == default_quality and filters.quality and filters.quality != default_quality): filters ^= Filter() # clear defaults filters ^= session_filter else: filters = Filter.parse(args_str) if filters.vc: filters.vc = False await ctx.send("**The VC filter is not allowed inline!**") if state_args: logger.info(f"toggle states: {state_args}") logger.info(f"current states: {roles}") state_args.symmetric_difference_update(set(roles)) state_args.discard("") logger.info(f"new states: {state_args}") state = " ".join(state_args).strip() else: state = " ".join(roles).strip() if "CUSTOM" in state.upper().split(" "): if not database.exists(f"custom.list:{ctx.author.id}"): await ctx.send("**You don't have a custom list set!**") state_list = state.split(" ") state_list.remove("CUSTOM") state = " ".join(state_list) elif database.exists(f"custom.confirm:{ctx.author.id}"): await ctx.send( "**Please verify or confirm your custom list before using!**" ) state_list = state.split(" ") state_list.remove("CUSTOM") state = " ".join(state_list) else: logger.info("race parameters") race_filter = int( database.hget(f"race.data:{ctx.channel.id}", "filter")) filters = Filter.parse(args_str, defaults=False) if filters.vc: filters.vc = False await ctx.send("**The VC filter is not allowed inline!**") default_quality = Filter().quality if (Filter.from_int(race_filter).quality == default_quality and filters.quality and filters.quality != default_quality): filters ^= Filter() # clear defaults filters ^= race_filter taxon = database.hget(f"race.data:{ctx.channel.id}", "taxon").decode("utf-8") state = database.hget(f"race.data:{ctx.channel.id}", "state").decode("utf-8") logger.info( f"args: filters: {filters}; taxon: {taxon}; state: {state}") return (filters, taxon, state)
async def check(self, ctx, *, arg): logger.info("command: check") currentBird = database.hget(f"channel:{ctx.channel.id}", "bird").decode("utf-8") if currentBird == "": # no bird await ctx.send("You must ask for a bird first!") return # if there is a bird, it checks answer sciBird = (await get_sciname(currentBird)).lower().replace("-", " ") arg = arg.lower().replace("-", " ") currentBird = currentBird.lower().replace("-", " ") alpha_code = alpha_codes.get(string.capwords(currentBird)) logger.info("currentBird: " + currentBird) logger.info("arg: " + arg) bird_setup(ctx, currentBird) race_in_session = bool(database.exists(f"race.data:{ctx.channel.id}")) if race_in_session: logger.info("race in session") if database.hget(f"race.data:{ctx.channel.id}", "strict"): logger.info("strict spelling") correct = arg in (currentBird, sciBird) else: logger.info("spelling leniency") correct = spellcheck(arg, currentBird) or spellcheck( arg, sciBird) if not correct and database.hget(f"race.data:{ctx.channel.id}", "alpha"): logger.info("checking alpha codes") correct = arg.upper() == alpha_code else: logger.info("no race") if database.hget(f"session.data:{ctx.author.id}", "strict"): logger.info("strict spelling") correct = arg in (currentBird, sciBird) else: logger.info("spelling leniency") correct = (spellcheck(arg, currentBird) or spellcheck(arg, sciBird) or arg.upper() == alpha_code) if correct: logger.info("correct") database.hset(f"channel:{ctx.channel.id}", "bird", "") database.hset(f"channel:{ctx.channel.id}", "answered", "1") session_increment(ctx, "correct", 1) streak_increment(ctx, 1) database.zincrby(f"correct.user:{ctx.author.id}", 1, string.capwords(str(currentBird))) if (race_in_session and Filter.from_int( int(database.hget(f"race.data:{ctx.channel.id}", "filter"))).vc): await voice_functions.stop(ctx, silent=True) await ctx.send( f"Correct! Good job! The bird was **{currentBird}**." if not race_in_session else f"**{ctx.author.mention}**, you are correct! The bird was **{currentBird}**." ) url = get_wiki_url(ctx, currentBird) await ctx.send(url) score_increment(ctx, 1) if int(database.zscore("users:global", str(ctx.author.id))) in achievement: number = str( int(database.zscore("users:global", str(ctx.author.id)))) await ctx.send( f"Wow! You have answered {number} birds correctly!") filename = f"bot/media/achievements/{number}.PNG" with open(filename, "rb") as img: await ctx.send(file=discord.File(img, filename="award.png") ) if race_in_session: 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: logger.info("incorrect") streak_increment(ctx, None) # reset streak session_increment(ctx, "incorrect", 1) incorrect_increment(ctx, str(currentBird), 1) if race_in_session: await ctx.send("Sorry, that wasn't the right answer.") else: database.hset(f"channel:{ctx.channel.id}", "bird", "") database.hset(f"channel:{ctx.channel.id}", "answered", "1") await ctx.send("Sorry, the bird was actually **" + currentBird + "**.") url = get_wiki_url(ctx, currentBird) await ctx.send(url)
async def edit(self, ctx, *, args_str: str = ""): logger.info("command: view session") if database.exists(f"session.data:{ctx.author.id}"): new_filter = Filter.parse(args_str, defaults=False) args = args_str.lower().split(" ") logger.info(f"args: {args}") new_filter ^= int( database.hget(f"session.data:{ctx.author.id}", "filter")) database.hset(f"session.data:{ctx.author.id}", "filter", str(new_filter.to_int())) if "wiki" in args: if database.hget(f"session.data:{ctx.author.id}", "wiki"): logger.info("enabling wiki embeds") database.hset(f"session.data:{ctx.author.id}", "wiki", "") else: logger.info("disabling wiki embeds") database.hset(f"session.data:{ctx.author.id}", "wiki", "wiki") if "strict" in args: if database.hget(f"session.data:{ctx.author.id}", "strict"): logger.info("disabling strict spelling") database.hset(f"session.data:{ctx.author.id}", "strict", "") else: logger.info("enabling strict spelling") database.hset(f"session.data:{ctx.author.id}", "strict", "strict") states_args = set(states.keys()).intersection( {arg.upper() for arg in args}) if states_args: current_states = set( database.hget(f"session.data:{ctx.author.id}", "state").decode("utf-8").split(" ")) logger.info(f"toggle states: {states_args}") logger.info(f"current states: {current_states}") states_args.symmetric_difference_update(current_states) logger.info(f"new states: {states_args}") database.hset( f"session.data:{ctx.author.id}", "state", " ".join(states_args).strip(), ) taxon_args = set(taxons.keys()).intersection( {arg.lower() for arg in args}) if taxon_args: current_taxons = set( database.hget(f"session.data:{ctx.author.id}", "taxon").decode("utf-8").split(" ")) logger.info(f"toggle taxons: {taxon_args}") logger.info(f"current taxons: {current_taxons}") taxon_args.symmetric_difference_update(current_taxons) logger.info(f"new taxons: {taxon_args}") database.hset( f"session.data:{ctx.author.id}", "taxon", " ".join(taxon_args).strip(), ) await self._send_stats(ctx, "**Session started previously.**\n") else: await ctx.send( "**There is no session running.** *You can start one with `b!session start`*" )
async def check(self, ctx, *, arg): logger.info("command: check") await channel_setup(ctx) await user_setup(ctx) currentBird = database.hget(f"channel:{ctx.channel.id}", "bird").decode("utf-8") if currentBird == "": # no bird await ctx.send("You must ask for a bird first!") else: # if there is a bird, it checks answer logger.info("currentBird: " + str(currentBird.lower().replace("-", " "))) logger.info("args: " + str(arg.lower().replace("-", " "))) await bird_setup(ctx, currentBird) sciBird = await get_sciname(currentBird) if spellcheck(arg, currentBird) or spellcheck(arg, sciBird): logger.info("correct") database.hset(f"channel:{ctx.channel.id}", "bird", "") database.hset(f"channel:{ctx.channel.id}", "answered", "1") if database.exists(f"session.data:{ctx.author.id}"): logger.info("session active") session_increment(ctx, "correct", 1) database.zincrby("streak:global", 1, str(ctx.author.id)) # check if streak is greater than max, if so, increases max if database.zscore("streak:global", str( ctx.author.id)) > database.zscore( "streak.max:global", str(ctx.author.id)): database.zadd( "streak.max:global", { str(ctx.author.id): database.zscore("streak:global", str( ctx.author.id)) }) await ctx.send("Correct! Good job!" if not database. exists(f"race.data:{ctx.channel.id}") else f"**{ctx.author.mention}**, you are correct!") url = get_wiki_url(currentBird) await ctx.send(url if not database.exists( f"race.data:{ctx.channel.id}") else f"<{url}>") score_increment(ctx, 1) if int(database.zscore("users:global", str(ctx.author.id))) in achievement: number = str( int(database.zscore("users:global", str(ctx.author.id)))) await ctx.send( f"Wow! You have answered {number} birds correctly!") filename = f"bot/media/achievements/{number}.PNG" with open(filename, 'rb') as img: await ctx.send( file=discord.File(img, filename="award.png")) if database.exists(f"race.data:{ctx.channel.id}") and str( database.hget(f"race.data:{ctx.channel.id}", "media"))[2:-1] == "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: logger.info("incorrect") database.zadd("streak:global", {str(ctx.author.id): 0}) if database.exists(f"session.data:{ctx.author.id}"): logger.info("session active") session_increment(ctx, "incorrect", 1) incorrect_increment(ctx, str(currentBird), 1) if database.exists(f"race.data:{ctx.channel.id}"): await ctx.send("Sorry, that wasn't the right answer.") else: database.hset(f"channel:{ctx.channel.id}", "bird", "") database.hset(f"channel:{ctx.channel.id}", "answered", "1") await ctx.send("Sorry, the bird was actually " + currentBird.lower() + ".") url = get_wiki_url(currentBird) await ctx.send(url)
async def edit(self, ctx, *, args_str: str = ""): logger.info("command: view session") await channel_setup(ctx) await user_setup(ctx) if database.exists(f"session.data:{ctx.author.id}"): args = args_str.split(" ") logger.info(f"args: {args}") if "bw" in args: if not database.hget(f"session.data:{ctx.author.id}", "bw"): logger.info("adding bw") database.hset(f"session.data:{ctx.author.id}", "bw", "bw") else: logger.info("removing bw") database.hset(f"session.data:{ctx.author.id}", "bw", "") states_args = set(states.keys()).intersection({arg.upper() for arg in args}) if states_args: toggle_states = list(states_args) current_states = database.hget(f"session.data:{ctx.author.id}", "state").decode("utf-8").split(" ") add_states = [] logger.info(f"toggle states: {toggle_states}") logger.info(f"current states: {current_states}") for state in set(toggle_states).symmetric_difference(set(current_states)): add_states.append(state) logger.info(f"adding states: {add_states}") database.hset(f"session.data:{ctx.author.id}", "state", " ".join(add_states).strip()) taxon_args = set(taxons.keys()).intersection({arg.lower() for arg in args}) if taxon_args: toggle_taxon = list(taxon_args) current_taxons = database.hget(f"session.data:{ctx.author.id}", "taxon").decode("utf-8").split(" ") add_taxons = [] logger.info(f"toggle taxons: {toggle_taxon}") logger.info(f"current taxons: {current_taxons}") for o in set(toggle_taxon).symmetric_difference(set(current_taxons)): add_taxons.append(o) logger.info(f"adding taxons: {add_taxons}") database.hset(f"session.data:{ctx.author.id}", "taxon", " ".join(add_taxons).strip()) female = "female" in args or "f" in args juvenile = "juvenile" in args or "j" in args if female and juvenile: await ctx.send("**Juvenile females are not yet supported.**\n*Please try again*") return elif female: addon = "female" if not database.hget(f"session.data:{ctx.author.id}", "addon"): logger.info("adding female") database.hset(f"session.data:{ctx.author.id}", "addon", addon) else: logger.info("removing female") database.hset(f"session.data:{ctx.author.id}", "addon", "") elif juvenile: addon = "juvenile" if not database.hget(f"session.data:{ctx.author.id}", "addon"): logger.info("adding juvenile") database.hset(f"session.data:{ctx.author.id}", "addon", addon) else: logger.info("removing juvenile") database.hset(f"session.data:{ctx.author.id}", "addon", "") await self._send_stats(ctx, f"**Session started previously.**\n") else: await ctx.send("**There is no session running.** *You can start one with `b!session start`*")