async def gsheet(self, ctx, url: str): """Loads a character sheet from [GSheet v2.0](http://gsheet2.avrae.io) (auto) or [GSheet v1.3](http://gsheet.avrae.io) (manual), resetting all settings. The sheet must be shared with Avrae for this to work. Avrae's google account is `[email protected]`.""" loading = await ctx.send('Loading character data from Google... (This usually takes ~30 sec)') try: url = extract_gsheet_id_from_url(url) except NoValidUrlKeyFound: return await loading.edit(content="This is not a Google Sheets link.") override = await self._confirm_overwrite(ctx, f"google-{url}") if not override: return await ctx.send("Character overwrite unconfirmed. Aborting.") try: parser = GoogleSheet(url, self.gsheet_client) except AssertionError: await self.init_gsheet_client() # hmm. return await loading.edit(content="I am still connecting to Google. Try again in 15-30 seconds.") try: await parser.get_character() except (KeyError, SpreadsheetNotFound): return await loading.edit(content= "Invalid character sheet. Make sure you've shared it with me at " "`[email protected]`!") except HttpError: return await loading.edit(content= "Error: Google returned an error. Please ensure your sheet is shared with " "`[email protected]` and try again in a few minutes.") except Exception as e: return await loading.edit(content='Error: Could not load character sheet.\n' + str(e)) try: sheet = await parser.get_sheet() except Exception as e: traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr) return await loading.edit(content='Error: Invalid character sheet.\n' + str(e)) try: await loading.edit(content= 'Loaded and saved data for {}!'.format(sheet['sheet']['stats']['name'])) except TypeError as e: traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr) return await loading.edit(content= 'Invalid character sheet. Make sure you have shared the sheet so that anyone with the link can view.') char = Character(sheet['sheet'], f"google-{url}").initialize_consumables() await char.commit(ctx) await char.set_active(ctx) try: await ctx.send(embed=char.get_sheet_embed()) except: await ctx.send( "...something went wrong generating your character sheet. Don't worry, your character has been saved. " "This is usually due to an invalid image.")
async def dicecloud(self, ctx, url: str, *, args=""): """Loads a character sheet from [Dicecloud](https://dicecloud.com/), resetting all settings. Share your character with `avrae` on Dicecloud (edit perms) for live updates. __Valid Arguments__ `-cc` - Will automatically create custom counters for class resources and features.""" if 'dicecloud.com' in url: url = url.split('/character/')[-1].split('/')[0] override = await self._confirm_overwrite(ctx, f"dicecloud-{url}") if not override: return await ctx.send("Character overwrite unconfirmed. Aborting.") loading = await ctx.send('Loading character data from Dicecloud...') parser = DicecloudParser(url) try: await parser.get_character() except Exception as eep: return await loading.edit(content=f"Dicecloud returned an error: {eep}") try: sheet = parser.get_sheet() except Exception as e: traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr) return await loading.edit(content= 'Error: Invalid character sheet. Capitalization matters!\n' + str(e)) c = Character(sheet['sheet'], f"dicecloud-{url}").initialize_consumables() await loading.edit(content=f'Loaded and saved data for {c.get_name()}!') if '-cc' in args: for counter in parser.get_custom_counters(): displayType = 'bubble' if c.evaluate_cvar(counter['max']) < 6 else None try: c.create_consumable(counter['name'], maxValue=str(counter['max']), minValue=str(counter['min']), reset=counter['reset'], displayType=displayType, live=counter['live']) except InvalidArgument: pass del parser # uh. maybe some weird instance things going on here. await c.commit(ctx) await c.set_active(ctx) try: await ctx.send(embed=c.get_sheet_embed()) except: await ctx.send( "...something went wrong generating your character sheet. Don't worry, your character has been saved. " "This is usually due to an invalid image.")
async def beyond(self, ctx, url: str): """Loads a character sheet from D&D Beyond, resetting all settings.""" loading = await ctx.send('Loading character data from Beyond...') url = re.search(r"/characters/(\d+)", url) if url is None: return await loading.edit(content="This is not a D&D Beyond link.") url = url.group(1) override = await self._confirm_overwrite(ctx, f"beyond-{url}") if not override: return await ctx.send("Character overwrite unconfirmed. Aborting.") parser = BeyondSheetParser(url) try: character = await parser.get_character() except Exception as e: return await loading.edit(content='Error: Could not load character sheet.\n' + str(e)) try: sheet = parser.get_sheet() except Exception as e: traceback.print_exception(type(e), e, e.__traceback__, file=sys.stderr) return await loading.edit(content='Error: Invalid character sheet.\n' + str(e)) await loading.edit(content='Loaded and saved data for {}!'.format(character['name'])) char = Character(sheet['sheet'], f"beyond-{url}").initialize_consumables() await char.commit(ctx) await char.set_active(ctx) try: await ctx.send(embed=char.get_sheet_embed()) except: await ctx.send( "...something went wrong generating your character sheet. Don't worry, your character has been saved. " "This is usually due to an invalid image.")
async def update(self, ctx, *, args=''): """Updates the current character sheet, preserving all settings. Valid Arguments: `-v` - Shows character sheet after update is complete. `-cc` - Updates custom counters from Dicecloud.""" char = await Character.from_ctx(ctx) url = char.id old_character = char.character prefixes = 'dicecloud-', 'pdf-', 'google-', 'beyond-' _id = copy.copy(url) for p in prefixes: if url.startswith(p): _id = url[len(p):] break sheet_type = old_character.get('type', 'dicecloud') if sheet_type == 'dicecloud': parser = DicecloudParser(_id) loading = await ctx.send('Updating character data from Dicecloud...') elif sheet_type == 'google': try: parser = GoogleSheet(_id, self.gsheet_client) except AssertionError: await self.init_gsheet_client() # attempt reconnection return await ctx.send("I am still connecting to Google. Try again in 15-30 seconds.") loading = await ctx.send('Updating character data from Google...') elif sheet_type == 'beyond': loading = await ctx.send('Updating character data from Beyond...') parser = BeyondSheetParser(_id) else: return await ctx.send("Error: Unknown sheet type.") try: await parser.get_character() except (timeout, aiohttp.ClientResponseError) as e: log.warning( f"Response error importing char:\n{''.join(traceback.format_exception(type(e), e, e.__traceback__))}") return await loading.edit(content= "I'm having some issues connecting to Dicecloud or Google right now. " "Please try again in a few minutes.") except HttpError: return await loading.edit(content= "Google returned an error trying to access your sheet. " "Please ensure your sheet is shared and try again in a few minutes.") except Exception as e: log.warning( f"Failed to import character\n{''.join(traceback.format_exception(type(e), e, e.__traceback__))}") return await loading.edit(content='Error: Invalid character sheet.\n' + str(e)) try: if sheet_type == 'dicecloud': sheet = parser.get_sheet() elif sheet_type == 'pdf': sheet = parser.get_sheet() elif sheet_type == 'google': sheet = await parser.get_sheet() elif sheet_type == 'beyond': sheet = parser.get_sheet() else: return await ctx.send("Error: Unknown sheet type.") await loading.edit(content= 'Updated and saved data for {}!'.format(sheet['sheet']['stats']['name'])) except TypeError as e: del parser log.info(f"Exception in parser.get_sheet: {e}") log.debug('\n'.join(traceback.format_exception(type(e), e, e.__traceback__))) return await loading.edit(content= 'Invalid character sheet. ' 'If you are using a dicecloud sheet, ' 'make sure you have shared the sheet so that anyone with the ' 'link can view.') except Exception as e: del parser return await loading.edit(content='Error: Invalid character sheet.\n' + str(e)) sheet = sheet['sheet'] sheet['settings'] = old_character.get('settings', {}) sheet['overrides'] = old_character.get('overrides', {}) sheet['cvars'] = old_character.get('cvars', {}) sheet['consumables'] = old_character.get('consumables', {}) overrides = old_character.get('overrides', {}) sheet['stats']['description'] = overrides.get('desc') or sheet.get('stats', {}).get("description", "No description available.") sheet['stats']['image'] = overrides.get('image') or sheet.get('stats', {}).get('image', '') override_spells = [] for s in overrides.get('spells', []): if isinstance(s, str): override_spells.append({'name': s, 'strict': True}) else: override_spells.append(s) sheet['spellbook']['spells'].extend(override_spells) c = Character(sheet, url).initialize_consumables() if '-cc' in args and sheet_type == 'dicecloud': counters = parser.get_custom_counters() for counter in counters: displayType = 'bubble' if c.evaluate_cvar(counter['max']) < 6 else None try: c.create_consumable(counter['name'], maxValue=str(counter['max']), minValue=str(counter['min']), reset=counter['reset'], displayType=displayType, live=counter['live']) except InvalidArgument: pass # if c.get_combat_id() and not self.bot.rdb.exists(c.get_combat_id()): # c.leave_combat() # reimplement this later await c.commit(ctx) await c.set_active(ctx) del parser, old_character # pls don't freak out avrae if '-v' in args: await ctx.send(embed=c.get_sheet_embed())