Source code for ebirdcog.ebirdcog

"""Module to access eBird API."""
from datetime import datetime
import logging
from urllib.error import URLError
from redbot.core import commands, checks, Config
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
from ebird.api import get_observations
import discord
from .api import EBirdAPI

LOG = logging.getLogger("red.dronefly.ebirdcog")


# TODO: switch to dataclasses-json as per inatcog
[docs]class ObsRecord(dict): """A human-readable observation record.""" def __init__( self, date_format="%d %b, %Y", datetime_format="%H:%M, %d %b, %Y", **kwargs ): self.date_format = date_format self.datetime_format = datetime_format super().__init__(**kwargs) def __getitem__(self, key): """Reformat datetime into human-readable format.""" try: val = super().__getitem__(key) except KeyError: val = None if key == "obsDt": try: parsed_time = datetime.strptime(val, "%Y-%m-%d %H:%M") return parsed_time.strftime(self.datetime_format) except ValueError: parsed_time = datetime.strptime(val, "%Y-%m-%d") return parsed_time.strftime(self.date_format) if key == "howMany" and val is None: return "uncounted" return val
[docs]class EBirdCog(commands.Cog, name="eBird"): """An eBird commands cog.""" def __init__(self, bot): self.bot = bot self.config = Config.get_conf(self, identifier=8008) self.config.register_global( region="CA-NS", days=30, date_format="%d %b", datetime_format="%H:%M, %d %b" ) self.api = EBirdAPI(self) @commands.group() async def ebird(self, ctx): """Access the eBird platform.""" pass # pylint: disable=unnecessary-pass @ebird.command() @checks.is_owner() async def checkdays(self, ctx): """Checks days setting.""" days = await self.config.days() await ctx.send("eBird days is {}.".format(days)) @ebird.command() @checks.is_owner() async def checkregion(self, ctx): """Checks region setting.""" region_code = await self.config.region() region = {} try: region = await self.api.get_region(ctx.channel, region_code) except ValueError as err: msg = ( "eBird region not valid: {code}; error: {err}.\n" "Please set to a valid code with:\n" " [p]ebird setregion code" ).format(code=region_code, err=err) await ctx.send(msg) return await ctx.send( "eBird region is {region} ({code}).".format( region=region["result"], code=region_code ) ) @ebird.command() async def hybrids(self, ctx, region_code=None, days=None): """Reports recent hybrid observations.""" days_back = int(days) if days else await self.config.days() if days_back not in range(1, 31): await ctx.send( "Value for days, %s, must be a number from 1 through 30." % days_back ) return if region_code: try: region = await self.api.get_region(ctx.channel, region_code) except ValueError as err: await ctx.send(str(err) % region_code) return if not region: await ctx.send("Region not found: {}".format(region_code)) return else: region_code = await self.config.region() try: records = ( await self.get_hybrid_observations(ctx, region_code, days_back) or [] ) except URLError as err: LOG.error("eBird request failed: %s", err) await ctx.send("eBird could not be contacted. Please try again later.") return if not records: await ctx.send("No hybrids observed in the past %d days." % days_back) return date_fmt = await self.config.date_format() datetime_fmt = await self.config.datetime_format() embeds = [] title = f"Hybrids in {region_code} from past {days_back} days" color = 0x90EE90 embed = discord.Embed(color=color) for record in records: if len(embed.fields) == 5: embeds.append(embed) embed = discord.Embed(color=color) rec = ObsRecord(date_fmt, datetime_fmt, **record) name = rec["comName"].replace(" (hybrid)", "") embed.add_field( name=name, value=("ยท {obsDt}: {howMany} at {locName}").format_map(rec), inline=False, ) if embeds: embeds.append(embed) pages = len(embeds) for page, embed in enumerate(embeds, start=1): embed.title = "%s (Page %d of %d)" % (title, page, pages) await menu(ctx, embeds, DEFAULT_CONTROLS) else: embed.title = title await ctx.send(embed=embed) @ebird.command() @checks.is_owner() async def setregion(self, ctx, region_code: str): """Sets region.""" region = None if region_code.lower() == "world": await ctx.send("eBird region cannot be world") return try: region = await self.api.get_region(ctx.channel, region_code) except ValueError as err: await ctx.send(str(err) % region_code) return if not region: await ctx.send("eBird region not found: {}".format(region_code)) return await self.config.region.set(region_code) await ctx.send("eBird region has been changed.") @ebird.command() @checks.is_owner() async def setdays(self, ctx, value: int): """Sets days considered recent (1 through 30; default: 30).""" days = int(value) if days in range(1, 31): await self.config.days.set(days) await ctx.send("eBird days has been changed.") else: await ctx.send("eBird days must be a number from 1 through 30.")
[docs] async def get_hybrid_observations(self, ctx, region_code, days): """Gets recent hybrid observations.""" ebird_key = await self.api.get_api_key(ctx.channel) if ebird_key is None: return False try: # Docs at: https://github.com/ProjectBabbler/ebird-api observations = get_observations( ebird_key["api_key"], region_code, back=days, category="hybrid", detail="simple", provisional=True, ) except ConnectionResetError: raise LookupError("Could not contact eBird") return observations