def build_id_list(user_id=None, taxon=None, state=None, media="images") -> list: """Generates an ID list based on given arguments - `user_id`: User ID of custom list - `taxon`: taxon string/list - `state`: state string/list - `media`: images/songs """ logger.info("building id list") if isinstance(taxon, str): taxon = taxon.split(" ") if isinstance(state, str): state = state.split(" ") state_roles = state if state else [] if media in ("songs", "song", "s", "a"): state_list = "songBirds" default = songBirds elif media in ("images", "image", "i", "p"): state_list = "birdList" default = birdList else: raise GenericError("Invalid media type", code=990) custom_list = [] if (user_id and "CUSTOM" in state_roles and database.exists(f"custom.list:{user_id}") and not database.exists(f"custom.confirm:{user_id}")): custom_list = [ bird.decode("utf-8") for bird in database.smembers(f"custom.list:{user_id}") ] birds = [] if taxon: birds_in_taxon = set( itertools.chain.from_iterable(taxons.get(o, []) for o in taxon)) if state_roles: birds_in_state = set( itertools.chain( *(states[state][state_list] for state in state_roles), custom_list)) birds = list(birds_in_taxon.intersection(birds_in_state)) else: birds = list(birds_in_taxon.intersection(set(default))) elif state_roles: birds = list( set( itertools.chain( *(states[state][state_list] for state in state_roles), custom_list))) else: birds = default logger.info(f"number of birds: {len(birds)}") return birds
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 incorrect_increment(ctx, bird: str, amount: int): """Increments the value of an incorrect bird by `amount`. `ctx` - Discord context object or user id\n `bird` - bird that was incorrect\n `amount` (int) - amount to increment by, usually 1 """ if isinstance(ctx, (str, int)): user_id = ctx guild = None else: user_id = ctx.author.id guild = ctx.guild logger.info(f"incrementing incorrect {bird} by {amount}") date = str(datetime.datetime.now(datetime.timezone.utc).date()) database.zincrby("incorrect:global", amount, string.capwords(str(bird))) database.zincrby(f"incorrect.user:{user_id}", amount, string.capwords(str(bird))) database.zincrby(f"daily.incorrect:{date}", amount, string.capwords(str(bird))) if guild is not None: logger.info("no dm") database.zincrby( f"incorrect.server:{ctx.guild.id}", amount, string.capwords(str(bird)) ) else: logger.info("dm context") if database.exists(f"session.data:{user_id}"): logger.info("session in session") database.zincrby( f"session.incorrect:{user_id}", amount, string.capwords(str(bird)) ) else: logger.info("no session")
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")
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.hmset( f"channel:{ctx.channel.id}", { "bird": "", "answered": 1, "sBird": "", "sAnswered": 1, "goatsucker": "", "gsAnswered": 1, "prevJ": 20, "prevB": "", "prevS": "", "prevK": 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")
def score_increment(ctx, amount: int): """Increments the score of a user by `amount`. `ctx` - Discord context object\n `amount` (int) - amount to increment by, usually 1 """ if isinstance(ctx, (str, int)): user_id = str(ctx) guild = None channel_id = "" else: user_id = str(ctx.author.id) guild = ctx.guild channel_id = str(ctx.channel.id) logger.info(f"incrementing score by {amount}") date = str(datetime.datetime.now(datetime.timezone.utc).date()) database.zincrby("score:global", amount, channel_id) database.zincrby("users:global", amount, user_id) database.zincrby(f"daily.score:{date}", amount, user_id) if guild is not None: logger.info("no dm") database.zincrby(f"users.server:{ctx.guild.id}", amount, user_id) if database.exists(f"race.data:{ctx.channel.id}"): logger.info("race in session") database.zincrby(f"race.scores:{ctx.channel.id}", amount, user_id) else: logger.info("dm context")
def __call__(self, ctx: commands.Context): if (ctx.command.name in ( "bird", "song", "goatsucker", "check", "skip", ) and database.exists("cooldown:global") and int(database.get("cooldown:global")) > 1): bucket = self.rate_limit_mapping.get_bucket(ctx.message) elif not self.disable and ctx.guild is None: bucket = self.dm_mapping.get_bucket(ctx.message) elif ctx.channel.name.startswith( "racing") and ctx.command.name.startswith("check"): bucket = self.race_mapping.get_bucket(ctx.message) else: bucket = self.default_mapping.get_bucket(ctx.message) retry_after = bucket.update_rate_limit() if retry_after: raise commands.CommandOnCooldown(bucket, retry_after) return True
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 stop(self, ctx): logger.info("command: stop race") if database.exists(f"race.data:{ctx.channel.id}"): await self.stop_race_(ctx) else: await ctx.send( "**There is no race in session.** *You can start one with `b!race start`*" )
async def view(self, ctx): logger.info("command: view race") if database.exists(f"race.data:{ctx.channel.id}"): await self._send_stats(ctx, "**Race In Progress**") else: await ctx.send( "**There is no race in session.** *You can start one with `b!race start`*" )
def start_session() -> int: logger.info("creating session id") session_id = 0 session_id = random.randint(420000000, 420999999) while database.exists(f"web.session:{session_id}") and session_id == 0: session_id = random.randint(420000000, 420999999) logger.info(f"session_id: {session_id}") web_session_setup(session_id) logger.info(f"created session id: {session_id}") return session_id
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!")
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 start(self, ctx, *, args_str: str = ""): logger.info("command: start session") await channel_setup(ctx) await user_setup(ctx) if database.exists(f"session.data:{ctx.author.id}"): logger.info("already session") await ctx.send("**There is already a session running.** *Change settings/view stats with `b!session edit`*") return else: args = args_str.split(" ") logger.info(f"args: {args}") if "bw" in args: bw = "bw" else: bw = "" states_args = set(states.keys()).intersection({arg.upper() for arg in args}) if states_args: state = " ".join(states_args).strip() else: state = " ".join(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 = "" 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" elif juvenile: addon = "juvenile" else: addon = "" logger.info(f"adding bw: {bw}; addon: {addon}; state: {state}") database.hmset( f"session.data:{ctx.author.id}", { "start": round(time.time()), "stop": 0, "correct": 0, "incorrect": 0, "total": 0, "bw": bw, "state": state, "addon": addon, "taxon": taxon } ) await ctx.send(f"**Session started with options:**\n{await self._get_options(ctx)}")
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`*")
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 user_setup(ctx): """Sets up a new discord user for score tracking. `ctx` - Discord context object or user id """ if isinstance(ctx, (str, int)): user_id = str(ctx) guild = None ctx = None else: user_id = str(ctx.author.id) guild = ctx.guild logger.info("checking user data") if database.zscore("users:global", user_id) is None: database.zadd("users:global", {user_id: 0}) logger.info("user global added") if ctx is not None: await ctx.send("Welcome <@" + user_id + ">!") date = str(datetime.datetime.now(datetime.timezone.utc).date()) if database.zscore(f"daily.score:{date}", user_id) is None: database.zadd(f"daily.score:{date}", {user_id: 0}) logger.info("user daily added") # Add streak if (database.zscore("streak:global", user_id) is None) or (database.zscore( "streak.max:global", user_id) is None): database.zadd("streak:global", {user_id: 0}) database.zadd("streak.max:global", {user_id: 0}) logger.info("added streak") if guild is not None: global_score = database.zscore("users:global", str(ctx.author.id)) database.zadd(f"users.server:{ctx.guild.id}", {str(ctx.author.id): global_score}) logger.info("synced scores") if not database.exists(f"custom.list:{ctx.author.id}"): role_ids = [role.id for role in ctx.author.roles] role_names = [role.name.lower() for role in ctx.author.roles] if set(role_names).intersection(set(states["CUSTOM"]["aliases"])): index = role_names.index( states["CUSTOM"]["aliases"][0].lower()) role = ctx.guild.get_role(role_ids[index]) await ctx.author.remove_roles( role, reason="Remove state role for bird list") logger.info("synced roles")
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 bird_setup(ctx, bird: str): """Sets up a new bird for incorrect tracking. `ctx` - Discord context object `bird` - bird to setup """ logger.info("checking bird data") if database.zscore("incorrect:global", string.capwords(bird)) is not None: logger.info("bird global ok") else: database.zadd("incorrect:global", {string.capwords(bird): 0}) logger.info("bird global added") if database.zscore(f"incorrect.user:{ctx.author.id}", string.capwords(bird)) is not None: logger.info("bird user ok") else: database.zadd(f"incorrect.user:{ctx.author.id}", {string.capwords(bird): 0}) logger.info("bird user added") if ctx.guild is not None: logger.info("no dm") if database.zscore(f"incorrect.server:{ctx.guild.id}", string.capwords(bird)) is not None: logger.info("bird server ok") else: database.zadd(f"incorrect.server:{ctx.guild.id}", {string.capwords(bird): 0}) logger.info("bird server added") else: logger.info("dm context") if database.exists(f"session.data:{ctx.author.id}"): logger.info("session in session") if database.zscore(f"session.incorrect:{ctx.author.id}", string.capwords(bird)) is not None: logger.info("bird session ok") else: database.zadd(f"session.incorrect:{ctx.author.id}", {string.capwords(bird): 0}) logger.info("bird session added") else: logger.info("no session")
def score_increment(ctx, amount: int): """Increments the score of a user by `amount`. `ctx` - Discord context object\n `amount` (int) - amount to increment by, usually 1 """ logger.info(f"incrementing score by {amount}") database.zincrby("score:global", amount, str(ctx.channel.id)) database.zincrby("users:global", amount, str(ctx.author.id)) if ctx.guild is not None: logger.info("no dm") database.zincrby(f"users.server:{ctx.guild.id}", amount, str(ctx.author.id)) else: logger.info("dm context") if database.exists(f"race.data:{ctx.channel.id}"): logger.info("race in session") database.zincrby(f"race.scores:{ctx.channel.id}", amount, str(ctx.author.id))
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")
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 _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 incorrect_increment(ctx, bird: str, amount: int): """Increments the value of an incorrect bird by `amount`. `ctx` - Discord context object\n `bird` - bird that was incorrect\n `amount` (int) - amount to increment by, usually 1 """ logger.info(f"incrementing incorrect {bird} by {amount}") database.zincrby("incorrect:global", amount, string.capwords(str(bird))) database.zincrby(f"incorrect.user:{ctx.author.id}", amount, string.capwords(str(bird))) if ctx.guild is not None: logger.info("no dm") database.zincrby(f"incorrect.server:{ctx.guild.id}", amount, string.capwords(str(bird))) else: logger.info("dm context") if database.exists(f"session.data:{ctx.author.id}"): logger.info("session in session") database.zincrby(f"session.incorrect:{ctx.author.id}", amount, string.capwords(str(bird))) else: logger.info("no session")
async def leave(self, ctx, confirm: typing.Optional[bool] = False): logger.info("command: leave") if database.exists(f"leave:{ctx.guild.id}"): logger.info("confirming") if confirm: logger.info(f"confirmed. Leaving {ctx.guild}") database.delete(f"leave:{ctx.guild.id}") await ctx.send("**Ok, bye!**") await ctx.guild.leave() return logger.info("confirm failed. leave canceled") database.delete(f"leave:{ctx.guild.id}") await ctx.send("**Leave canceled.**") return logger.info("not confirmed") database.set(f"leave:{ctx.guild.id}", 0, ex=60) await ctx.send( "**Are you sure you want to remove me from the guild?**\n" + "Use `b!leave yes` to confirm, `b!leave no` to cancel. " + "You have 60 seconds to confirm before it will automatically cancel." )
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 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 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 on_command_error(ctx, error): """Handles errors for all commands without local error handlers.""" logger.info("Error: " + str(error)) # don't handle errors with local handlers if hasattr(ctx.command, 'on_error'): return if isinstance(error, commands.CommandOnCooldown): # send cooldown await ctx.send("**Cooldown.** Try again after " + str(round(error.retry_after)) + " s.", delete_after=5.0) elif isinstance(error, commands.CommandNotFound): capture_exception(error) await ctx.send("Sorry, the command was not found.") elif isinstance(error, commands.MissingRequiredArgument): await ctx.send("This command requires an argument!") elif isinstance(error, commands.BadArgument): await ctx.send("The argument passed was invalid. Please try again." ) elif isinstance(error, commands.ArgumentParsingError): await ctx.send( "An invalid character was detected. Please try again.") elif isinstance(error, commands.BotMissingPermissions): await ctx.send( "**The bot does not have enough permissions to fully function.**\n" + f"**Permissions Missing:** `{', '.join(map(str, error.missing_perms))}`\n" + "*Please try again once the correct permissions are set.*") elif isinstance(error, commands.NoPrivateMessage): capture_exception(error) await ctx.send("**This command is unavaliable in DMs!**") elif isinstance(error, GenericError): if error.code == 842: await ctx.send("**Sorry, you cannot use this command.**") elif error.code == 666: logger.info("GenericError 666") elif error.code == 201: logger.info("HTTP Error") capture_exception(error) await ctx.send( "**An unexpected HTTP Error has occurred.**\n *Please try again.*" ) else: logger.info("uncaught generic error") capture_exception(error) await ctx.send( "**An uncaught generic error has occurred.**\n" + "*Please log this message in #support in the support server below, or try again.*\n" + "**Error:** " + str(error)) await ctx.send("https://discord.gg/fXxYyDJ") raise error elif isinstance(error, commands.CommandInvokeError): if isinstance(error.original, redis.exceptions.ResponseError): capture_exception(error.original) if database.exists(f"channel:{ctx.channel.id}"): await ctx.send( "**An unexpected ResponseError has occurred.**\n" "*Please log this message in #support in the support server below, or try again.*\n" "**Error:** " + str(error)) await ctx.send("https://discord.gg/fXxYyDJ") else: await channel_setup(ctx) await ctx.send("Please run that command again.") elif isinstance(error.original, wikipedia.exceptions.DisambiguationError): await ctx.send( "Wikipedia page not found. (Disambiguation Error)") elif isinstance(error.original, wikipedia.exceptions.PageError): await ctx.send("Wikipedia page not found. (Page Error)") elif isinstance(error.original, wikipedia.exceptions.WikipediaException): capture_exception(error.original) await ctx.send("Wikipedia page unavaliable. Try again later.") elif isinstance(error.original, discord.Forbidden): if error.original.code == 50007: await ctx.send( "I was unable to DM you. Check if I was blocked and try again." ) else: capture_exception(error) await ctx.send( "**An unexpected Forbidden error has occurred.**\n" "*Please log this message in #support in the support server below, or try again.*\n" "**Error:** " + str(error)) elif isinstance(error.original, discord.HTTPException): if error.original.status == 502: await ctx.send( "**An error has occured with discord. :(**\n*Please try again.*" ) else: capture_exception(error.original) await ctx.send( "**An unexpected HTTPException has occurred.**\n" + "*Please log this message in #support in the support server below, or try again*\n" + "**Error:** " + str(error.original)) await ctx.send("https://discord.gg/fXxYyDJ") elif isinstance(error.original, aiohttp.ClientOSError): if error.original.errno == errno.ECONNRESET: await ctx.send( "**An error has occured with discord. :(**\n*Please try again.*" ) else: capture_exception(error.original) await ctx.send( "**An unexpected ClientOSError has occurred.**\n" + "*Please log this message in #support in the support server below, or try again.*\n" + "**Error:** " + str(error.original)) await ctx.send("https://discord.gg/fXxYyDJ") else: logger.info("uncaught command error") capture_exception(error.original) await ctx.send( "**An uncaught command error has occurred.**\n" + "*Please log this message in #support in the support server below, or try again.*\n" + "**Error:** " + str(error)) await ctx.send("https://discord.gg/fXxYyDJ") raise error else: logger.info("uncaught non-command") capture_exception(error) await ctx.send( "**An uncaught non-command error has occurred.**\n" + "*Please log this message in #support in the support server below, or try again.*\n" + "**Error:** " + str(error)) await ctx.send("https://discord.gg/fXxYyDJ") raise error
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)