inatcog package

Subpackages

Submodules

inatcog.api module

Module to access iNaturalist API.

class inatcog.api.INatAPI[source]

Bases: object

Access the iNat API and assets via (api|static).inaturalist.org.

await get_controlled_terms(*args, **kwargs)[source]

Query API for controlled terms.

await get_observation_bounds(taxon_ids)[source]

Get the bounds for the specified observations.

await get_observations(*args, **kwargs)[source]

Query API for observations.

Parameters:
  • *args

    • If first positional argument is given, it is passed through as-is, appended to the /v1/observations endpoint.

  • **kwargs

    • All kwargs are passed as params on the API call.

await get_observers_from_projects(project_ids: Optional[List] = None, user_ids: Optional[List] = None)[source]

Get observers for a list of project ids.

Since the cache is filled as a side effect, this method can be used to prime the cache prior to fetching multiple users at once by id.

Users may also be specified, and in that case, project ids may be omitted. The cache will then be primed from a list of user ids.

await get_observers_stats(**kwargs)[source]

Query API for user counts & rankings.

await get_places(query: Union[int, str, list], refresh_cache=False, **kwargs)[source]

Get places for the specified ids or text query.

await get_projects(query: Union[str, int, list], refresh_cache=False, **kwargs)[source]

Get projects for the specified ids or text query.

await get_search_results(**kwargs)[source]

Get site search results.

await get_users(query: Union[int, str], refresh_cache=False, by_login_id=False, **kwargs)[source]

Get the users for the specified login, user_id, or query.

inatcog.checks module

Checks for iNatcog.

inatcog.checks.can_manage_places()[source]

Check if guild member can manage places.

inatcog.checks.can_manage_projects()[source]

Check if guild member can manage projects.

inatcog.checks.can_manage_users()[source]

Check if guild member can manage users.

inatcog.checks.known_inat_user()[source]

Allow command to be used by iNat user known in any guild.

inatcog.checks.known_inat_user_here()[source]

Allow command to be used by iNat user known in this guild.

inatcog.client module

inatcog.client.asyncify(self, method)[source]
class inatcog.client.iNatClient(*args, **kwargs)[source]

Bases: iNatClient

async with set_ctx_from_user(red_ctx: Context, author: Optional[Union[Member, User]] = None, dronefly_ctx: Optional[Context] = None)[source]

A client with both Red and Dronefly command contexts.

inatcog.common module

Module for common code.

inatcog.common.grouper(iterable, n, fillvalue=None)[source]

Collect data into fixed-length chunks or blocks

inatcog.common.make_decorator(function)[source]

Make a decorator that has arguments.

inatcog.constants module

Module for constants.

inatcog.converters module

inatcog.embeds module

inatcog.help module

Custom iNat help module.

class inatcog.help.INatHelp[source]

Bases: RedHelpFormatter

Custom help, with support for additional (non-command) help topics.

await format_command_help(ctx, obj, help_settings: HelpSettings)[source]
await get_bot_help_mapping(ctx, help_settings: HelpSettings)[source]
staticmethod get_command_signature(ctx, command)[source]
await make_and_send_embeds(ctx, emb, help_settings: HelpSettings)[source]
await send_help(*args, **kwargs)[source]

This delegates to other functions.

For most cases, you should use this and only this directly.

inatcog.inatcog module

A cog for using the iNaturalist platform.

class inatcog.inatcog.CompositeMetaClass(*args: Any, **kwargs: Any)[source]

Bases: CogMeta, ABCMeta

See https://github.com/mikeshardmind/SinbadCogs/blob/v3/rolemanagement/core.py

class inatcog.inatcog.INatCog(*args: Any, **kwargs: Any)[source]

Bases: Listeners, Cog, CommandsEvent, CommandsInat, CommandsLast, CommandsMap, CommandsObs, CommandsPlace, CommandsProject, CommandsSearch, CommandsTaxon, CommandsUser

Commands provided by inatcog.

await cog_before_invoke(ctx: Context)[source]

Initialization before cog is invoked.

await cog_unload()[source]

Cleanup when the cog unloads.

await initialize() None[source]

Initialization after bot is ready.

spam_intervals = [(datetime.timedelta(seconds=3), 5), (datetime.timedelta(seconds=20), 10), (datetime.timedelta(seconds=180), 45)]

inatcog.interfaces module

Module for abc interfaces.

class inatcog.interfaces.MixinMeta(*_args)[source]

Bases: ABC

Metaclass for well behaved type hint detection with composite class.

inatcog.last module

Module for handling recent history.

class inatcog.last.INatLinkMsg(cog)[source]

Bases: object

Get INat link message from channel history supplemented with info from iNat.

await get_last_obs_msg(ctx, msgs)[source]

Find recent observation link.

await get_last_taxon_msg(ctx, msgs)[source]

Find recent taxon link.

class inatcog.last.ObsLinkMsg(url: str, obs: Observation, ago: str, name: str)[source]

Bases: NamedTuple

Discord & iNat fields from a recent observation link.

ago: str

Alias for field number 2

name: str

Alias for field number 3

obs: Observation

Alias for field number 1

url: str

Alias for field number 0

class inatcog.last.TaxonLinkMsg(url: str, taxon: dict)[source]

Bases: NamedTuple

Discord & iNat fields from a recent taxon link.

taxon: dict

Alias for field number 1

url: str

Alias for field number 0

inatcog.listeners module

Listeners module for inatcog.

class inatcog.listeners.Listeners(*_args)[source]

Bases: INatEmbeds, MixinMeta

Listeners mixin for inatcog.

await handle_member_reaction(emoji: PartialEmoji, member: Member, message: Message, action: str)[source]

Central handler for member reactions.

await maybe_get_reaction(payload: RawReactionActionEvent) Tuple[Member, Message][source]

Return reaction member & message if valid.

await on_message_without_command(message: Message) None[source]

Handle links to iNat.

await on_raw_reaction_add(payload: RawReactionActionEvent) None[source]

Central handler for reactions added to bot messages.

await on_raw_reaction_remove(payload: RawReactionActionEvent) None[source]

Central handler for reactions removed from bot messages.

class inatcog.listeners.PartialContext(bot: Red, guild: Guild, channel: ChannelType, author: User, message: Optional[Union[Message, PartialMessage]], command: Optional[str] = '', assume_yes: bool = True, interaction: Optional[Interaction] = None, inat_client: iNatClient = None)[source]

Bases: object

Partial Context synthesized from objects passed into listeners.

assume_yes: bool
author: User
bot: Red
channel: ChannelType
command: Optional[str]
guild: Guild
inat_client: iNatClient
interaction: Optional[Interaction]
message: Optional[Union[Message, PartialMessage]]
class inatcog.listeners.PartialMessage(author: User, guild: Guild)[source]

Bases: object

Partial Message to satisfy bot & guild checks.

author: User
guild: Guild

inatcog.maps module

Module to make maps for iNat.

class inatcog.maps.INatMapURL(api)[source]

Bases: object

Make URL for iNat range map via /taxa/map interface on website.

await get_map_coords_for_taxon_ids(taxon_ids)[source]

Get map coordinates encompassing taxa ranges/observations.

await get_map_url_for_taxa(taxa)[source]

Get a map url for taxa from the provided coords.

class inatcog.maps.MapCoords(zoom_level, center_lat, center_lon)

Bases: tuple

center_lat

Alias for field number 1

center_lon

Alias for field number 2

zoom_level

Alias for field number 0

Bases: tuple

title

Alias for field number 0

url

Alias for field number 1

inatcog.maps.get_zoom_level(swlat, swlng, nelat, nelng)[source]

Get zoom level from coordinate pairs.

inatcog.maps.normalize_longitude(deg)[source]

Account for wrap around the globe.

inatcog.menus module

inatcog.obs_query module

Module to query iNat observations.

class inatcog.obs_query.INatObsQuery(cog)[source]

Bases: object

Query iNat for one or more observation.

await query_observations(ctx, query: Query, page=1)[source]

Query observations and return iterator for any found.

await query_single_obs(ctx, query: Query)[source]

Query observations and return first if found.

inatcog.obs module

Module to work with iNat observations.

inatcog.obs.get_formatted_user_counts(user_counts: dict, base_url: str, species_only: bool = False, view: str = 'obs')[source]

Format per user observation & species counts.

await inatcog.obs.maybe_match_obs(cog, ctx, content, id_permitted=False)[source]

Maybe retrieve an observation from content.

inatcog.obs.obs_count_community_id(obs)[source]

inatcog.places module

Module to handle users.

class inatcog.places.INatPlaceTable(cog)[source]

Bases: object

Lookup helper for places.

await get_place(guild, query: Union[int, str], user: QuotedContextMemberConverter = None)[source]

Get place by guild abbr or via id#/keyword lookup in API.

inatcog.projects module

Module to handle projects.

class inatcog.projects.INatProjectTable(cog)[source]

Bases: object

Lookup helper for projects.

await get_project(guild, query: Union[int, str])[source]

Get project by guild abbr or via id#/keyword lookup in API.

class inatcog.projects.UserProject(id: Any = None, uuid: str = None, banner_color: str = None, created_at: Any = _Nothing.NOTHING, description: str = None, header_image_url: str = None, hide_title: bool = None, icon: str = None, is_umbrella: bool = None, last_post_at: Any = None, location: Union[Dict, List, None, str] = None, observation_requirements_updated_at: Any = None, place_id: int = None, prefers_user_trust: bool = None, project_observation_rules: List[Dict] = _Nothing.NOTHING, project_type: str = None, rule_preferences: List[Dict] = _Nothing.NOTHING, search_parameters: List[Dict] = _Nothing.NOTHING, site_features: List[Dict] = _Nothing.NOTHING, slug: str = None, terms: str = None, title: str = None, updated_at: Any = None, user_ids: List[int] = _Nothing.NOTHING, admins=_Nothing.NOTHING, project_observation_fields=_Nothing.NOTHING, user=None)[source]

Bases: Project

A collection project for observations by specific users.

This class handles projects that include observations by members, with membership being managed either solely by the user, soley by the admins, or a combination of both:

  1. Open membership, users join on the web:

    • members_only: True

    • do not set any “observed_by_user?” rules

  2. Closed membership, admins edit the project to join users:

    • members_only: False

    • set at least one “observed_by_user?” rule

  3. Closed membership, users join on web and admins edit project to “approve” join:

    • members_only: True

    • set at least one “observed_by_user?” rule

    • a user’s observations are included when both the user joins and the admin “approves” the join by adding a rule for them

banner_color: str
created_at: datetime
description: str
header_image_url: str
hide_title: bool
icon: str
is_umbrella: bool
last_post_at: Optional[datetime]
location: Coordinates
members_only()[source]
observation_requirements_updated_at: Optional[datetime]
observed_by_ids()[source]

Valid observer user ids for the project.

TODO: clarify what the iNat code actually does for these cases and fix as needed. we implement, based on some reasonable assumptions:

  • for closed membership projects, exclude any user that has both an included rule (observed_by_user?) and an excluded rule (not_observed_by_user?)

place_id: int
prefers_user_trust: bool
project_observation_rules: List[Dict]
project_type: str
rule_preferences: List[Dict]
search_parameters: List[Dict]
site_features: List[Dict]
slug: str
terms: str
title: str
updated_at: DateTime
user_ids: List[int]

inatcog.query module

Module to query iNat.

class inatcog.query.INatQuery(cog)[source]

Bases: object

Query iNat for all requested entities.

await get(ctx: Context, query: Query, scientific_name=False, locale=None, **kwargs)[source]

Get all requested iNat entities.

await get_inat_user(ctx: Context, user: str)[source]

Get iNat user from iNat user_id, known member, or iNat login, in that order.

inatcog.search module

Module to search iNat site.

class inatcog.search.INatSiteSearch(cog)[source]

Bases: object

Lookup helper for site search.

await search(ctx, query, **kwargs)[source]

Search iNat site.

inatcog.search.get_place(result)[source]

Get place result.

inatcog.search.get_project(result)[source]

Get project result.

inatcog.search.get_result(result, result_type: str = None)[source]

Get fields for any result type.

inatcog.search.get_taxon(result)[source]

Get taxon result (v1/search).

inatcog.search.get_taxon2(result)[source]

Get taxon result (/v1/taxa).

inatcog.search.get_user(result)[source]

Get user result.

inatcog.taxa module

Module to work with iNat taxa.

class inatcog.taxa.NameMatch(term: Optional[match], name: Optional[match], common: Optional[match])[source]

Bases: NamedTuple

Match for each name field in Taxon matching a pattern.

common: Optional[match]

Alias for field number 2

name: Optional[match]

Alias for field number 1

term: Optional[match]

Alias for field number 0

await inatcog.taxa.format_place_taxon_counts(cog, place: Union[Place, str], taxon: Taxon = None, **kwargs)[source]

Format user observation & species counts for taxon.

await inatcog.taxa.format_user_taxon_counts(cog, user: Union[User, str], taxon: Taxon = None, **kwargs)[source]

Format user observation & species counts for taxon.

await inatcog.taxa.get_taxon(ctx: Context, taxon_id, **kwargs)[source]

Get taxon by id.

await inatcog.taxa.get_taxon_preferred_establishment_means(ctx, taxon)[source]

Get the preferred establishment means for the taxon.

inatcog.taxa.match_pat(record, pat, scientific_name=False, locale=None)[source]

Match specified pattern.

Parameters:
  • record (Taxon) – A candidate taxon to match.

  • pat (re.Pattern or str) – A pattern to match against each name field in the record.

  • scientific_name (bool) – Only search scientific name

  • locale (str) – Only search common names matching locale

Returns:

A tuple of search results for the pat for each name in the record.

Return type:

NameMatch

inatcog.taxa.match_pat_list(record, pat_list, scientific_name=False, locale=None)[source]

Match all of a list of patterns.

Parameters:
  • record (Taxon) – A candidate taxon to match.

  • exact (list) – A list of patterns to match.

Returns:

A tuple of ORed search results for every pat for each name in the record, i.e. each name in the tuple is the match result from the first matching pattern.

Return type:

NameMatch

inatcog.taxa.match_taxon(taxon_query: TaxonQuery, records, scientific_name=False, locale=None)[source]

Match a single taxon for the given query among records returned by API.

inatcog.taxa.score_match(taxon_query: TaxonQuery, record, all_terms, pat_list=None, scientific_name=False, locale=None)[source]

Score a matched record. A higher score is a better match. :param taxon_query: The query for the matched record being scored. :type taxon_query: TaxonQuery :param record: A candidate taxon to match. :type record: Taxon :param all_terms: A pattern matching all terms. :type all_terms: re.Pattern :param pat_list: A list of patterns to match. :type pat_list: list

Returns:

score < 0 indicates the match is not a valid candidate. score >= 0 and score < 200 indicates a non-exact match score >= 200 indicates an exact match either on a phrase or the whole query

Return type:

int

inatcog.taxon_query module

Module to query iNat taxa.

class inatcog.taxon_query.INatTaxonQuery(cog)[source]

Bases: object

Query iNat for one or more taxa.

await get_taxon_ancestor(ctx: Context, taxon, rank)[source]

Get Taxon ancestor for specified rank from a Taxon object.

Parameters:
  • taxon (Taxon) – The taxon for which the ancestor at the specified rank is requested.

  • rank (str) – The rank of the ancestor to return.

Returns:

A Taxon object for the matching ancestor, if any, else None.

Return type:

Taxon

await maybe_match_taxon(ctx: Context, taxon_query: TaxonQuery, ancestor_id: int = None, preferred_place_id: int = None, scientific_name: bool = False, locale: str = None)[source]

Get taxon and return a match, if any.

await maybe_match_taxon_compound(ctx: Context, query: Query, preferred_place_id=None, scientific_name=False, locale=None)[source]

Get one or more taxa and return a match, if any.

Currently the grammar supports only one ancestor taxon and one child taxon.

await query_paginated_taxa(ctx, query)[source]

Query for one or more taxa and return paginator for matching taxa, if any.

Notes:

  • In its original conception, this was used only for comma-delimited lists of taxon queries for map & related, or a list of taxon ancestor IDs. These had a small, definite number of elements (whatever the user typed, or all the ancestors of a taxon), were de-duplicated, and didn’t need to be paginated.

  • We want to go one step further here and return multiple taxa, whether or not multiple were given as input:

    • The return is then a paginator for all matching taxa.

    • Which may be filtered in some fashion, e.g.
      • All taxa matching the supplied name(s).

      • For a given rank keyword.

    • And if there are no filters, then just all results matching the query.

  • The “rank” filter is baked into maybe_match_taxon_compound (I think) and needs to be pulled out of that.

    • In fact, most of that relates to selecting “one best” match, so really isn’t needed here.

await query_taxa(ctx, query)[source]

Query for one or more taxa and return list of matching taxa, if any.

inatcog.users module

Module to handle users.

class inatcog.users.INatUserTable(cog)[source]

Bases: object

Lookup helper for registered iNat users.

async for ... in get_member_pairs(guild: Guild, users, anywhere: True) AsyncIterator[Tuple[Member, User]][source]
Yields:

discord.Member, User

Parameters:

users (dict) – discord_id -> inat_id mapping

await get_user(member: Union[Member, User], refresh_cache=False, anywhere=True)[source]

Get user for Discord member.

inatcog.utils module

Utilities module.

inatcog.utils.get_cog(cog_or_ctx=typing.Union[redbot.core.commands.commands.Cog, redbot.core.commands.context.Context]) Cog[source]
await inatcog.utils.get_dronefly_ctx(red_ctx: Context, user: Optional[Union[Member, User]] = None, anywhere=True)[source]
await inatcog.utils.get_dronefly_user(ctx: Context, user: Optional[Union[Member, User]] = None, anywhere: bool = True) User[source]

Get a Dronefly user and their configuration in the current context.

await inatcog.utils.get_dronefly_user_config(ctx: Context, user: Optional[Union[Member, User]] = None, anywhere: bool = True) dict[source]

Return the config parameters for a Dronefly user.

Supplies defaults from the guild and global configs if:

  • the Dronefly user is not known either globally or in the guild scope (i.e. anywhere=False vs. True)

await inatcog.utils.get_home(ctx: Context, user: Optional[Union[Member, User]] = None, anywhere: bool = True) dict[source]

Get configured home place for user.

await inatcog.utils.get_lang(ctx: Context, user: Optional[Union[Member, User]] = None, anywhere: bool = True) dict[source]

Get configured preferred language for user.

await inatcog.utils.get_valid_user_config(cog_or_ctx: Union[Cog, Context], user: Union[Member, User], anywhere: bool = True)[source]

Return iNat user config if known in this server.

Note 1: Even if the user is known in another guild, they are not considered known anywhere until they permit it with ,user set known True. This setting is ignored if anywhere=False (e.g. permission checks).

Note 2: A user may be registered to a certain user id#, but the id# is invalid (e.g. account deleted). If that is the case, they’ll still have access to functions that only require that their user ID be known to the bot!

await inatcog.utils.has_valid_user_config(cog_or_ctx: Union[Cog, Context], user: Union[Member, User], anywhere: bool = True)[source]

Check if user is known in the specified scope.

See Note 2 on get_valid_user_config() for how validity is determined (i.e. the config is considered valid even if the iNat account isn’t).

inatcog.utils.use_client(coro_or_command)[source]
async with inatcog.utils.valid_user_config(cog_or_ctx: Union[Cog, Context], user: Union[Member, User], anywhere: bool = True)[source]

Module contents

INatCog init.

await inatcog.setup(bot)[source]

Setup bot with our custom formatter, then add our cog.

Note: incompatible with cogs providing their own help formatter, but inatcog is such a special-purpose cog, it’s not really intended to use it on a general-purpose bot, so this is OK(-ish).

inatcog.teardown(bot)[source]