async def spell(self, ctx, *, name: str): """Looks up a spell.""" choices = await get_spell_choices(ctx, filter_by_license=False) spell = await self._lookup_search3(ctx, {'spell': choices}, name) embed = EmbedWithAuthor(ctx) embed.url = spell.url color = embed.colour embed.title = spell.name school_level = f"{spell.get_level()} {spell.get_school().lower()}" if spell.level > 0 \ else f"{spell.get_school().lower()} cantrip" embed.description = f"*{school_level}. " \ f"({', '.join(itertools.chain(spell.classes, spell.subclasses))})*" if spell.ritual: time = f"{spell.time} (ritual)" else: time = spell.time meta = f"**Casting Time**: {time}\n" \ f"**Range**: {spell.range}\n" \ f"**Components**: {spell.components}\n" \ f"**Duration**: {spell.duration}" embed.add_field(name="Meta", value=meta) text = spell.description higher_levels = spell.higherlevels if len(text) > 1020: pieces = [text[:1020]] + [ text[i:i + 2040] for i in range(1020, len(text), 2040) ] else: pieces = [text] embed.add_field(name="Description", value=pieces[0], inline=False) embed_queue = [embed] if len(pieces) > 1: for piece in pieces[1:]: temp_embed = discord.Embed() temp_embed.colour = color temp_embed.description = piece embed_queue.append(temp_embed) if higher_levels: add_fields_from_long_text(embed_queue[-1], "At Higher Levels", higher_levels) embed_queue[-1].set_footer(text=f"Spell | {spell.source_str()}") if spell.homebrew: add_homebrew_footer(embed_queue[-1]) if spell.image: embed_queue[0].set_thumbnail(url=spell.image) await Stats.increase_stat(ctx, "spells_looked_up_life") destination = await self._get_destination(ctx) for embed in embed_queue: await destination.send(embed=embed)
async def subscribe(self, ctx, url): coll_match = re.match(WORKSHOP_ADDRESS_RE, url) if coll_match is None: return await ctx.send("This is not an Alias Workshop link.") if self.before_edit_check: await self.before_edit_check(ctx) collection_id = coll_match.group(1) the_collection = await workshop.WorkshopCollection.from_id( ctx, collection_id) # private and duplicate logic handled here, also loads aliases/snippets if self.is_server: await the_collection.set_server_active(ctx) else: await the_collection.subscribe(ctx) embed = EmbedWithAuthor(ctx) embed.title = f"Subscribed to {the_collection.name}" embed.url = the_collection.url embed.description = the_collection.description if the_collection.aliases: embed.add_field( name="Server Aliases" if self.is_server else "Aliases", value=", ".join(sorted(a.name for a in the_collection.aliases))) if the_collection.snippets: embed.add_field( name="Server Snippets" if self.is_server else "Snippets", value=", ".join(sorted(a.name for a in the_collection.snippets))) await ctx.send(embed=embed)
async def classfeat(self, ctx, *, name: str): """Looks up a class feature.""" choices = compendium.cfeats + compendium.ncfeat_names result = await self._lookup_search(ctx, choices, name, lambda e: e['name'], search_type='classfeat') if not result: return embed = EmbedWithAuthor(ctx) embed.title = result['name'] set_maybe_long_desc(embed, result['text']) await (await self._get_destination(ctx)).send(embed=embed)
async def classfeat(self, ctx, *, name: str): """Looks up a class feature.""" result: SourcedTrait = await self._lookup_search3(ctx, {'class': compendium.cfeats}, name, query_type='classfeat') embed = EmbedWithAuthor(ctx) embed.title = result.name embed.url = result.url set_maybe_long_desc(embed, result.text) handle_source_footer(embed, result, "Class Feature") await (await self._get_destination(ctx)).send(embed=embed)
async def racefeat(self, ctx, *, name: str): """Looks up a racial feature.""" result: SourcedTrait = await self._lookup_search3(ctx, {'race': compendium.rfeats, 'subrace': compendium.subrfeats}, name, 'racefeat') embed = EmbedWithAuthor(ctx) embed.title = result.name embed.url = result.url set_maybe_long_desc(embed, result.text) handle_source_footer(embed, result, "Race Feature") await (await self._get_destination(ctx)).send(embed=embed)
async def subclass(self, ctx, name: str): """Looks up a subclass.""" guild_settings = await self.get_settings(ctx.guild) pm = guild_settings.get("pm_result", False) srd = guild_settings.get("srd", False) destination = ctx.author if pm else ctx.channel result, metadata = await search_and_select(ctx, c.subclasses, name, lambda e: e['name'], srd=srd, return_metadata=True) metadata['srd'] = srd await self.add_training_data("subclass", name, result['name'], metadata=metadata) if not result.get('srd') and srd: return await self.send_srd_error(ctx, result) embed = EmbedWithAuthor(ctx) embed.title = result['name'] embed.description = f"*Source: {result['source']}*" for level_features in result['subclassFeatures']: for feature in level_features: for entry in feature['entries']: if not isinstance(entry, dict): continue if not entry.get('type') == 'entries': continue text = parse_data_entry(entry['entries']) embed.add_field(name=entry['name'], value=(text[:1019] + "...") if len(text) > 1023 else text) embed.set_footer(text=f"Use {ctx.prefix}classfeat to look up a feature if it is cut off.") await destination.send(embed=embed)
async def randname(self, ctx, race=None, option=None): """Generates a random name, optionally from a given race.""" if race is None: return await ctx.send(f"Your random name: {self.old_name_gen()}") embed = EmbedWithAuthor(ctx) race_names = await search_and_select(ctx, compendium.names, race, lambda e: e['race']) if option is None: table = await get_selection(ctx, [(t['name'], t) for t in race_names['tables']]) else: table = await search_and_select(ctx, race_names['tables'], option, lambda e: e['name']) embed.title = f"{table['name']} {race_names['race']} Name" embed.description = random.choice(table['choices']) await ctx.send(embed=embed)
async def _non_srd(self, ctx, result, search_type=None): if search_type is not None: await self.bot.mdb.analytics_nsrd_lookup.update_one({"type": search_type, "name": result.name}, {"$inc": {"num_lookups": 1}}, upsert=True) embed = EmbedWithAuthor(ctx) embed.title = f"{result.name} is not available in the SRD!" embed.description = f"Unfortunately, {result.name} is not available in the SRD (what Wizards of the Coast " \ f"offers for free). You can see everything that is available in the SRD [here](" \ f"http://dnd.wizards.com/articles/features/systems-reference-document-srd).\n\n" \ f"In the near future, you will be able to connect your D&D Beyond account to Avrae to " \ f"view the non-SRD content you own on D&D Beyond; stay tuned!" await ctx.send(embed=embed)
async def _view(self, ctx, name): collectable = await helpers.get_collectable_named( ctx, name, self.personal_cls, self.workshop_cls, self.workshop_sub_meth, self.is_alias, self.obj_name, self.obj_name_pl, self.name) if collectable is None: return await ctx.send(f"No {self.obj_name} named {name} found.") elif isinstance(collectable, self.personal_cls): # personal out = f'**{name}**: ```py\n{ctx.prefix}{self.obj_copy_command} {collectable.name} {collectable.code}\n```' out = out if len( out ) <= 2000 else f'**{collectable.name}**:\nCommand output too long to display.' return await ctx.send(out) else: # collection embed = EmbedWithAuthor(ctx) the_collection = await collectable.load_collection(ctx) owner = await user_from_id(ctx, the_collection.owner) embed.title = f"{ctx.prefix}{name}" if self.is_alias else name embed.description = f"From {the_collection.name} by {owner}.\n" \ f"[View on Workshop]({the_collection.url})" embed.add_field(name="Help", value=collectable.docs or "No documentation.", inline=False) if isinstance(collectable, workshop.WorkshopAlias): await collectable.load_subcommands(ctx) if collectable.subcommands: subcommands = "\n".join(f"**{sc.name}** - {sc.short_docs}" for sc in collectable.subcommands) embed.add_field(name="Subcommands", value=subcommands, inline=False) return await ctx.send(embed=embed)
async def condition(self, ctx, *, name: str): """Looks up a condition.""" guild_settings = await self.get_settings(ctx.message.server) pm = guild_settings.get("pm_result", False) destination = ctx.message.author if pm else ctx.message.channel result = await search_and_select(ctx, c.conditions, name, lambda e: e['name']) embed = EmbedWithAuthor(ctx) embed.title = result['name'] embed.description = result['desc'] await self.bot.send_message(destination, embed=embed)
async def condition(self, ctx, *, name: str): """Looks up a condition.""" guild_settings = await self.get_settings(ctx.guild) pm = guild_settings.get("pm_result", False) destination = ctx.author if pm else ctx.channel result, metadata = await search_and_select(ctx, c.conditions, name, lambda e: e['name'], return_metadata=True) await self.add_training_data("condition", name, result['name'], metadata=metadata) embed = EmbedWithAuthor(ctx) embed.title = result['name'] embed.description = result['desc'] await destination.send(embed=embed)
async def list(self, ctx): embed = first_embed = EmbedWithAuthor(ctx) fields = 0 out = [embed] has_at_least_1 = False user_objs = await self.personal_cls.get_ctx_map(ctx) user_obj_names = list(user_objs.keys()) if user_obj_names: has_at_least_1 = True fields += embeds.add_fields_from_long_text( embed, f"Your {self.obj_name_pl.title()}", ', '.join(sorted(user_obj_names))) async for subscription_doc in self.workshop_sub_meth(ctx): try: the_collection = await workshop.WorkshopCollection.from_id( ctx, subscription_doc['object_id']) except workshop.CollectionNotFound: continue if bindings := subscription_doc[self.binding_key]: has_at_least_1 = True embed.add_field(name=the_collection.name, value=', '.join( sorted(ab['name'] for ab in bindings)), inline=False) fields += 1 if fields > embeds.MAX_NUM_FIELDS: embed = discord.Embed(colour=embed.colour) fields = 0 out.append(embed)
async def race(self, ctx, *, name: str): """Looks up a race.""" result: gamedata.Race = await self._lookup_search3( ctx, { 'race': compendium.races, 'subrace': compendium.subraces }, name, 'race') embed = EmbedWithAuthor(ctx) embed.title = result.name embed.url = result.url embed.add_field(name="Speed", value=result.speed) embed.add_field(name="Size", value=result.size) for t in result.traits: add_fields_from_long_text(embed, t.name, t.text) embed.set_footer(text=f"Race | {result.source_str()}") await (await self._get_destination(ctx)).send(embed=embed)
async def background(self, ctx, *, name: str): """Looks up a background.""" guild_settings = await self.get_settings(ctx.guild) pm = guild_settings.get("pm_result", False) srd = guild_settings.get("srd", False) result, metadata = await search_and_select(ctx, c.backgrounds, name, lambda e: e.name, srd=srd and (lambda e: e.srd), return_metadata=True) metadata['srd'] = srd await self.add_training_data("background", name, result.name, metadata=metadata) if not result.srd and srd: return await self.send_srd_error(ctx, result) embed = EmbedWithAuthor(ctx) embed.title = result.name embed.set_footer(text=f"Background | {result.source} {result.page}") ignored_fields = ['suggested characteristics', 'personality trait', 'ideal', 'bond', 'flaw', 'specialty', 'harrowing event'] for trait in result.traits: if trait['name'].lower() in ignored_fields: continue text = trait['text'] text = textwrap.shorten(text, width=1020, placeholder="...") embed.add_field(name=trait['name'], value=text) # do stuff here if pm: await ctx.author.send(embed=embed) else: await ctx.send(embed=embed)
async def subclass(self, ctx, name: str): """Looks up a subclass.""" guild_settings = await self.get_settings(ctx.message.server) pm = guild_settings.get("pm_result", False) srd = guild_settings.get("srd", False) destination = ctx.message.author if pm else ctx.message.channel result = await search_and_select(ctx, c.subclasses, name, lambda e: e['name'], srd=srd) if not result.get('srd') and srd: return await self.send_srd_error(ctx, result) embed = EmbedWithAuthor(ctx) embed.title = result['name'] for level_features in result['subclassFeatures']: for feature in level_features: for entry in feature['entries']: if not isinstance(entry, dict): continue if not entry.get('type') == 'entries': continue text = parse_data_entry(entry['entries']) embed.add_field( name=entry['name'], value=(text[:1019] + "...") if len(text) > 1023 else text) embed.set_footer( text="Use !classfeat to look up a feature if it is cut off.") await self.bot.send_message(destination, embed=embed)
async def race(self, ctx, *, name: str): """Looks up a race.""" choices = compendium.fancyraces + compendium.nrace_names result = await self._lookup_search(ctx, choices, name, lambda e: e.name, search_type='race', is_obj=True) if not result: return embed = EmbedWithAuthor(ctx) embed.title = result.name embed.description = f"Source: {result.source}" embed.add_field(name="Speed", value=result.get_speed_str()) embed.add_field(name="Size", value=result.size) if result.ability: embed.add_field(name="Ability Bonuses", value=result.get_asi_str()) for t in result.get_traits(): add_fields_from_long_text(embed, t['name'], t['text']) await (await self._get_destination(ctx)).send(embed=embed)
async def token(self, ctx, *, name=None): """Shows a token for a monster or player. May not support all monsters.""" if name is None: token_cmd = self.bot.get_command('playertoken') if token_cmd is None: return await ctx.send("Error: SheetManager cog not loaded.") return await ctx.invoke(token_cmd) monster, metadata = await select_monster_full(ctx, name, return_metadata=True) metadata['homebrew'] = monster.source == 'homebrew' await self.add_training_data("monster", name, monster.name, metadata=metadata) url = monster.get_image_url() embed = EmbedWithAuthor(ctx) embed.title = monster.name embed.description = f"{monster.size} monster." if not monster.source == 'homebrew': embed.set_image(url=url) embed.set_footer(text="This command may not support all monsters.") await ctx.send(embed=embed) else: if not url: return await ctx.channel.send("This monster has no image.") try: processed = await generate_token(url) except Exception as e: return await ctx.channel.send(f"Error generating token: {e}") file = discord.File(processed, filename="image.png") embed.set_image(url="attachment://image.png") await ctx.send(file=file, embed=embed)
async def classfeat(self, ctx, *, name: str): """Looks up a class feature.""" guild_settings = await self.get_settings(ctx.guild) pm = guild_settings.get("pm_result", False) srd = guild_settings.get("srd", False) destination = ctx.author if pm else ctx.channel result, metadata = await search_and_select(ctx, c.cfeats, name, lambda e: e['name'], srd=srd, return_metadata=True) metadata['srd'] = srd await self.add_training_data("classfeat", name, result['name'], metadata=metadata) if not result['srd'] and srd: return await self.send_srd_error(ctx, result) embed = EmbedWithAuthor(ctx) embed.title = result['name'] desc = result['text'] desc = [desc[i:i + 1024] for i in range(0, len(desc), 1024)] embed.description = ''.join(desc[:2]) for piece in desc[2:]: embed.add_field(name="** **", value=piece) await destination.send(embed=embed)
async def rule(self, ctx, *, name: str = None): """Looks up a rule.""" destination = await self._get_destination(ctx) if name is None: return await self._show_reference_options(ctx, destination) options = [] for actiontype in compendium.rule_references: if name == actiontype['type']: return await self._show_action_options(ctx, actiontype, destination) else: options.extend(actiontype['items']) result, metadata = await search_and_select(ctx, options, name, lambda e: e['fullName'], return_metadata=True) await self._add_training_data("reference", name, result['fullName'], metadata=metadata) embed = EmbedWithAuthor(ctx) embed.title = result['fullName'] embed.description = f"*{result['short']}*" add_fields_from_long_text(embed, "Description", result['desc']) embed.set_footer(text=f"Rule | {result['source']}") await destination.send(embed=embed)
async def _view(self, ctx, name): collectable = await helpers.get_collectable_named( ctx, name, self.personal_cls, self.workshop_cls, self.workshop_sub_meth, self.is_alias, self.obj_name, self.obj_name_pl, self.name) if collectable is None: return await ctx.send(f"No {self.obj_name} named {name} found.") elif isinstance(collectable, self.personal_cls): # personal await send_long_code_text( ctx, outside_codeblock=f'**{name}**:', inside_codeblock= f"{ctx.prefix}{self.obj_copy_command} {collectable.name} {collectable.code}", codeblock_language='py') return else: # collection embed = EmbedWithAuthor(ctx) the_collection = await collectable.load_collection(ctx) owner = await user_from_id(ctx, the_collection.owner) embed.title = f"{ctx.prefix}{name}" if self.is_alias else name embed.description = f"From {the_collection.name} by {owner}.\n" \ f"[View on Workshop]({the_collection.url})" embeds.add_fields_from_long_text( embed, "Help", collectable.docs or "No documentation.") if isinstance(collectable, workshop.WorkshopAlias): await collectable.load_subcommands(ctx) if collectable.subcommands: subcommands = "\n".join(f"**{sc.name}** - {sc.short_docs}" for sc in collectable.subcommands) embed.add_field(name="Subcommands", value=subcommands, inline=False) return await ctx.send(embed=embed)
async def classfeat(self, ctx, *, name: str): """Looks up a class feature.""" try: guild_id = ctx.message.server.id pm = self.settings.get(guild_id, {}).get("pm_result", False) srd = self.settings.get(guild_id, {}).get("srd", False) except: pm = False srd = False destination = ctx.message.author if pm else ctx.message.channel result = await search_and_select(ctx, c.cfeats, name, lambda e: e['name'], srd=srd) if not result['srd'] and srd: return await self.send_srd_error(ctx, result) embed = EmbedWithAuthor(ctx) embed.title = result['name'] desc = result['text'] desc = [desc[i:i + 1024] for i in range(0, len(desc), 1024)] embed.description = ''.join(desc[:2]) for piece in desc[2:]: embed.add_field(name="** **", value=piece) await self.bot.send_message(destination, embed=embed)
async def list(self, ctx): embed = EmbedWithAuthor(ctx) has_at_least_1 = False user_objs = await self.personal_cls.get_ctx_map(ctx) user_obj_names = list(user_objs.keys()) if user_obj_names: has_at_least_1 = True embeds.add_fields_from_long_text( embed, f"Your {self.obj_name_pl.title()}", ', '.join(sorted(user_obj_names))) async for subscription_doc in self.workshop_sub_meth(ctx): try: the_collection = await workshop.WorkshopCollection.from_id( ctx, subscription_doc['object_id']) except workshop.CollectionNotFound: continue if bindings := subscription_doc[self.binding_key]: has_at_least_1 = True embed.add_field(name=the_collection.name, value=', '.join( sorted(ab['name'] for ab in bindings)), inline=False) else: embed.add_field( name=the_collection.name, value=f"This collection has no {self.obj_name_pl}.", inline=False)
async def background(self, ctx, *, name: str): """Looks up a background.""" guild_settings = await self.get_settings(ctx.message.server) pm = guild_settings.get("pm_result", False) srd = guild_settings.get("srd", False) result = await search_and_select(ctx, c.backgrounds, name, lambda e: e['name'], srd=srd) if not result['srd'] and srd: return await self.send_srd_error(ctx, result) embed = EmbedWithAuthor(ctx) embed.title = result['name'] embed.description = f"*Source: {result.get('source', 'Unknown')}*" ignored_fields = [ 'suggested characteristics', 'personality trait', 'ideal', 'bond', 'flaw', 'specialty', 'harrowing event' ] for trait in result['trait']: if trait['name'].lower() in ignored_fields: continue text = '\n'.join(t for t in trait['text'] if t) text = textwrap.shorten(text, width=1020, placeholder="...") embed.add_field(name=trait['name'], value=text) # do stuff here if pm: await self.bot.send_message(ctx.message.author, embed=embed) else: await self.bot.say(embed=embed)
async def rule(self, ctx, *, name: str): """Looks up a rule.""" guild_settings = await self.get_settings(ctx.guild) pm = guild_settings.get("pm_result", False) destination = ctx.author if pm else ctx.channel result, metadata = await search_and_select(ctx, c.rules, name, lambda e: e['name'], return_metadata=True) await self.add_training_data("rule", name, result['name'], metadata=metadata) embed = EmbedWithAuthor(ctx) embed.title = result['name'] desc = result['desc'] desc = [desc[i:i + 1024] for i in range(0, len(desc), 1024)] embed.description = ''.join(desc[:2]) for piece in desc[2:]: embed.add_field(name="** **", value=piece) await destination.send(embed=embed)
async def feat(self, ctx, *, name: str): """Looks up a feat.""" choices = compendium.feats + compendium.nfeat_names result = await self._lookup_search(ctx, choices, name, lambda e: e['name'], search_type='feat') if not result: return embed = EmbedWithAuthor(ctx) embed.title = result['name'] if result['prerequisite']: embed.add_field(name="Prerequisite", value=result['prerequisite'], inline=False) if result['ability']: embed.add_field(name="Ability Improvement", value=f"Increase your {result['ability']} score by 1, up to a maximum of 20.", inline=False) add_fields_from_long_text(embed, "Description", result['desc']) embed.set_footer(text=f"Feat | {result['source']} {result['page']}") await (await self._get_destination(ctx)).send(embed=embed)
async def feat(self, ctx, *, name: str): """Looks up a feat.""" result: gamedata.Feat = await self._lookup_search3( ctx, {'feat': compendium.feats}, name) embed = EmbedWithAuthor(ctx) embed.title = result.name embed.url = result.url if result.prerequisite: embed.add_field(name="Prerequisite", value=result.prerequisite, inline=False) add_fields_from_long_text(embed, "Description", result.desc) embed.set_footer(text=f"Feat | {result.source_str()}") await (await self._get_destination(ctx)).send(embed=embed)
async def background(self, ctx, *, name: str): """Looks up a background.""" result: gamedata.Background = await self._lookup_search3( ctx, {'background': compendium.backgrounds}, name) embed = EmbedWithAuthor(ctx) embed.url = result.url embed.title = result.name embed.set_footer(text=f"Background | {result.source_str()}") for trait in result.traits: text = trim_str(trait.text, 1024) embed.add_field(name=trait.name, value=text, inline=False) await (await self._get_destination(ctx)).send(embed=embed)
async def spell(self, ctx, *, name: str): """Looks up a spell.""" choices = await get_spell_choices(ctx, filter_by_license=False) spell = await self._lookup_search3(ctx, {'spell': choices}, name) embed = EmbedWithAuthor(ctx) embed.url = spell.url color = embed.colour embed.title = spell.name school_level = f"{spell.get_level()} {spell.get_school().lower()}" if spell.level > 0 \ else f"{spell.get_school().lower()} cantrip" embed.description = f"*{school_level}. " \ f"({', '.join(itertools.chain(spell.classes, spell.subclasses))})*" if spell.ritual: time = f"{spell.time} (ritual)" else: time = spell.time meta = f"**Casting Time**: {time}\n" \ f"**Range**: {spell.range}\n" \ f"**Components**: {spell.components}\n" \ f"**Duration**: {spell.duration}" embed.add_field(name="Meta", value=meta) higher_levels = spell.higherlevels pieces = chunk_text(spell.description) embed.add_field(name="Description", value=pieces[0], inline=False) embed_queue = [embed] if len(pieces) > 1: for i, piece in enumerate(pieces[1::2]): temp_embed = discord.Embed() temp_embed.colour = color if (next_idx := (i + 1) * 2) < len( pieces ): # this is chunked into 1024 pieces, and descs can handle 2 temp_embed.description = piece + pieces[next_idx] else: temp_embed.description = piece embed_queue.append(temp_embed)
async def subclass(self, ctx, *, name: str): """Looks up a subclass.""" result: gamedata.Subclass = await self._lookup_search3( ctx, {'class': compendium.subclasses}, name, query_type='subclass') embed = EmbedWithAuthor(ctx) embed.url = result.url embed.title = result.name embed.description = f"*Source: {result.source_str()}*" for level in result.levels: for feature in level: text = trim_str(feature.text, 1024) embed.add_field(name=feature.name, value=text, inline=False) embed.set_footer( text= f"Use {ctx.prefix}classfeat to look up a feature if it is cut off." ) await (await self._get_destination(ctx)).send(embed=embed)
async def tutorial_list(self, ctx): """Lists the available tutorials.""" embed = EmbedWithAuthor(ctx) embed.title = "Available Tutorials" embed.description = f"Use `{ctx.prefix}tutorial <name>` to select a tutorial from the ones available below!\n" \ f"First time here? Try `{ctx.prefix}tutorial quickstart`!" for tutorial in self.tutorials.values(): embed.add_field(name=tutorial.name, value=tutorial.description, inline=False) await ctx.send(embed=embed)