async def cpp(self, ctx, *, query): """ This is still very experimental, as there is nothing of use in the implementation of the en.cppreference.com MediaWiki API, so this relies on crawling HTML. This may be relatively slow and buggy. """ async with ctx.typing(): book = neko.Book(ctx) results = await self.search_for(query) curr_page = None for i, result in enumerate(results): if curr_page is None: curr_page = neko.Page( title=f'Search results for `{query}`', url=f'http://en.cppreference.com', color=neko.random_colour()) curr_page.add_field(name=result.name, value='\n'.join((result.desc, result.url))) if len(curr_page.fields) > 2 or i + 1 >= len(results): book += curr_page curr_page = None if not len(book): await ctx.send('No results', delete_after=10) else: await book.send()
async def help_command(self, ctx: neko.Context, *, query=None): """ Shows a set of help pages outlining the available commands, and details on how to operate each of them. If a command name is passed as a parameter (`help command`) then the parameter is searched for as a command name and that page is opened. """ # TODO: maybe try to cache this! It's a fair amount of work each time. # Generates the book bk = neko.Book(ctx) # Maps commands to pages, so we can just jump to the page command_to_page = {} bk += await self.gen_front_page(ctx) command_to_page[None] = 0 # Walk commands all_cmds = sorted(set(self.bot.walk_commands()), key=lambda c: c.qualified_name) # We offset each index in the enumeration by this to get the # correct page number. offset = len(bk) # Strip out any commands we don't want to show. cmds = [cmd for cmd in all_cmds if await should_show(cmd, ctx)] page_index = None for i, cmd in enumerate(cmds): bk += await self.gen_spec_page(ctx, cmd) if page_index is None and query in cmd.qualified_names: # I assume checking equality of commands is slower # than checking for is None each iteration. page_index = i + offset # Set the page if page_index is None and query: await ctx.send(f'I could not find a command called {query}!', delete_after=10) else: if page_index is None: page_index = 0 bk.index = page_index await bk.send()
async def tag_inspect(self, ctx, tag_name): """ This is only runnable by the bot owner. """ async with ctx.bot.postgres_pool.acquire() as conn: book = neko.Book(ctx) with ctx.typing(): tag_name = tag_name.lower() results = await conn.fetch( ''' SELECT * FROM nekozilla.tags LEFT JOIN nekozilla.tags_attach ON pk = tag_pk WHERE LOWER(name) = ($1); ''', tag_name) if not results: raise neko.NekoCommandError('No results.') for result in results: data = dict(result) content = data.pop('content') author = data.pop('author') file = data.pop('file_name') # Don't want to send this!!! data.pop('b64data') user: discord.User = await ctx.bot.get_user_info(author) data['author'] = ' '.join([ 'BOT' if user.bot else '', user.display_name, str(user.id) ]) page = neko.Page(title=f'`{data.pop("name")}`', description=content) page.set_thumbnail(url=user.avatar_url) if file is not None: page.set_footer(text=f'Attached file: {file}') page.add_field(name='Attributes', value='\n'.join(f'**{k}**: `{v}`' for k, v in data.items())) book += page await book.send()
async def discord(self, ctx: neko.Context): """ Gets a list of all Discord systems, and their service status. Lists of upcoming scheduled maintenances and unresolved incidents will be implemented eventually. """ book = neko.Book(ctx) async with ctx.message.channel.typing(): bot = ctx.bot """ status = await self.get_status(bot) components = await self.get_components(bot) incidents = await self.get_incidents(bot) sms = await self.get_scheduled_maintenances(bot) """ stat_res, comp_res, inc_res, sms_res = await asyncio.gather( bot.http_pool.get(get_endpoint('summary.json')), bot.http_pool.get(get_endpoint('components.json')), bot.http_pool.get(get_endpoint('incidents.json')), bot.http_pool.get(get_endpoint('scheduled-maintenances.json'))) status, components, incidents, sms = await asyncio.gather( self.get_status(stat_res), self.get_components(comp_res), self.get_incidents(inc_res), self.get_scheduled_maintenances(sms_res)) # Make the front page! if status['indicator'] == 'None': desc = '' else: desc = f'**{status["indicator"]}**\n\n' desc += (f'{status["description"]}\n\n' f'Last updated: {friendly_date(status["updated_at"])}.') if not incidents['unresolved']: color = status['color'] else: color = get_impact_color( find_highest_impact(incidents['unresolved'])) """ PAGE 1 ------ Overall status """ page = neko.Page(title='Discord API Status', description=desc, color=color, url=status['url']) if incidents['unresolved']: first = incidents['unresolved'][0] name = first['Name'] body = make_incident_body(first) page.add_field(name=name, value=body, inline=False) book += page """ PAGE 2 ------ Overall status again, but with more information on showcase components. """ page = neko.Page(title='Discord API Status', description=desc, color=color, url=status['url']) for component in components['showcase']: title = component.pop('Name') desc = [] for k, v in component.items(): line = f'**{k}**: ' if isinstance(v, datetime.datetime): line += friendly_date(v) else: line += str(v) desc.append(line) page.add_field(name=title, value='\n'.join(desc), inline=False) book += page """ PAGE 3 ====== Non showcase components """ page = None fields = 0 for component in components['rest']: if fields >= max_fields: book += page page = None fields = 0 if page is None: page = neko.Page( title='Other components', description='Other minor components for Discord.', color=color, url=status['url']) title = component.pop('Name') desc = [] for k, v in component.items(): line = f'**{k}**: ' if isinstance(v, datetime.datetime): line += friendly_date(v) else: line += str(v) desc.append(line) page.add_field(name=title, value='\n'.join(desc), inline=False) fields += 1 if fields > 0: book += page """ PAGE 3 ====== Incidents. """ page = neko.Page(title='Unresolved incidents', color=color) if incidents['unresolved']: incident = incidents['unresolved'][0] name = f'**{incident["Name"]}**' desc = make_incident_body(incident) page.description = name + '\n\n' + desc.strip() for incident in incidents['unresolved'][1:3]: body = make_incident_body(incident) name = incident['Name'] body = name + '\n\n' + body page.add_field(name='\u200b', value=body.strip()) book += page """ PAGE 4 ====== Resolved incidents. """ page = neko.Page( title='Resolved incidents', color=color, ) if incidents['resolved']: incident = incidents['resolved'][0] name = f'**{incident["Name"]}**' desc = make_incident_body(incident) page.description = name + '\n\n' + desc.strip() # Add the next three most recent. for incident in incidents['resolved'][1:3]: body = make_incident_body(incident) name = f'**{incident["Name"]}**' body = name + '\n\n' + body.strip() page.add_field(name='\u200b', value=body) book += page await book.send()
async def get_word(self, ctx, *, word: str): """ Gets a definition of a given word or phrase from WordNik """ def _define(): # Much complex. Very definition. Such API! Wow! api = wordapi.WordApi(self.client) # *prays to god this isn't lazy iterative. return api.getDefinitions(word, sourceDictionaries=wordnik_dictionaries, includeRelated=True) with ctx.typing(): words: typing.List[ wordnik_definition.Definition] = await ctx.bot.do_job_in_pool( _define) # Attempt to favour gcide and wordnet, as they have better definitions # imho. # Fixes #9 if not words: await ctx.send('I couldn\'t find a definition for that.', delete_after=10) else: front = [] back = [] for word in words: if word.sourceDictionary in ('gcide', 'wordnet'): front.append(word) else: back.append(word) # Re-join. words = [*front, *back] words: typing.List[wordnik_definition.Definition] = [ word for word in words if not word.sourceDictionary.startswith('ahd') ] # Max results to get is 100. max_count = min(100, len(words)) book = neko.Book(ctx) for i in range(0, max_count): word = words[i] text = '' if word.partOfSpeech: text += f'**{word.partOfSpeech}** ' if word.text: text += word.text if word.extendedText: text += '\n\n' text += word.extendedText page = neko.Page(title=neko.capitalize(word.word), description=text, color=neko.random_colour()) if word.exampleUses: example = word.exampleUses[0] ex_text = neko.ellipses(example.text, 800) page.add_field(name='Example', value=ex_text, inline=False) if word.relatedWords: related = ', '.join( [', '.join(rw.words) for rw in word.relatedWords]) page.add_field(name='Synonyms', value=related) if word.textProns: pron = '\n'.join([tp.raw for tp in word.textProns]) pron = neko.ellipses(pron, 400) page.add_field( name='Pronunciations', value=pron, ) if word.score: page.add_field(name='Scrabble score', value=word.score.value) if word.labels: labels = ', '.join(label.text for label in word.labels) labels = neko.ellipses(labels, 300) page.add_field(name='Labels', value=labels) if word.notes: notes = [] for j, note in enumerate(word.notes): notes.append(f'[{j+1}] {note.value}') notes = neko.ellipses('\n\n'.join(notes), 300) page.add_field(name='Notes', value=notes) if word.attributionText: attr = word.attributionText else: attr = ('Extracted from ' f'{neko.capitalise(word.sourceDictionary)}') page.set_footer(text=attr) book += page await book.send()
async def urban(self, ctx, *, phrase: str = None): """ Searches urban dictionary for the given phrase or word. If no word is specified, we pick a few random entries. """ with ctx.typing(): if phrase: api, user = ud_define_def resp = await ctx.bot.request('GET', api, params={'term': phrase}) user = user + '?' + urllib.parse.urlencode({'term': phrase}) else: api, user = ud_random_def resp = await ctx.bot.request('GET', api) # Discard the rest, only be concerned with upto the first 10 results. resp = await resp.json() results = resp['list'][0:10] book = neko.Book(ctx) for definition in results: page = neko.Page(title=definition['word'].title(), description=definition['definition'], color=0xFFFF00, url=user) page.add_field(name='Example of usage', value=definition['example'], inline=False) page.add_field(name='Author', value=definition['author']) ups = definition['thumbs_up'] downs = definition['thumbs_down'] page.add_field( name=f'\N{THUMBS UP SIGN} {ups}', # No content (little discord trick) value=f'\N{THUMBS DOWN SIGN} {downs}ᅠ') page.set_thumbnail(url=ud_thumb_url) if 'tags' in resp: # Seems the tags can contain duplicates. Quick messy solution # is to pass it into a set first. page.set_footer(text=' '.join({*resp['tags']}), icon_url=ud_icon_url) else: page.set_footer(text=definition['permalink'], icon_url=ud_icon_url) book += page if book.pages: await book.send() else: await ctx.send('I couldn\'t find a definition for that.', delete_after=10)