async def _get_now_playing(lastfm, author=None): try: response = (await asyncio.get_event_loop().run_in_executor( None, lambda: requests.get( API_URL, headers={'User-Agent': config['user_agent']}, params={ 'method': 'user.getrecenttracks', 'api_key': config['lastfm']['api_key'], 'user': lastfm, 'limit': 1, 'format': 'json', }, timeout=5, ), )).json() except JSONDecodeError as e: logger.error(f'Failed to query Last.FM API: {e}.') raise BotError(f'Failed to query Last.FM API.') if 'error' in response: message = (f'Failed to query Last.FM API: {response["error"]} ' f'{response["message"]}.') logger.error(message) raise BotError(message) if not response['recenttracks']['track']: raise BotError(f'{author} has never scrobbled before.') return response['recenttracks']['track'][0]
def _parse_quote_ids(message): try: quote_ids = [int(qid) for qid in message.split(' ')] except ValueError: raise BotError('Quote IDs must be integers.') if len(quote_ids) > 3: raise BotError('A maximum of three quotes may be queried at once.') return quote_ids
async def wrapper(*, listener, author, **kwargs): username = await listener.is_authed(author) if not username: raise BotError('Identify with NickServ to use this command.') return await func( listener=listener, author=author, username=username, **kwargs )
def wrapper(*, message, **kwargs): for regex in regexes: match = regex.match(message) if match: return func(args=match.groups(), **kwargs) else: raise BotError('Invalid arguments.')
def _compile_regex(regex, flags): try: if flags['nocase']: return re.compile(regex, flags=re.IGNORECASE) return re.compile(regex) except Exception as e: # noqa E722 raise BotError(f'{regex} is not a valid regex.') from e
async def call(*, bot, listener, target, author, message, private): """Chooses one of many provided options.""" match = re.match(r'(\d+) *- *(\d+)', message) if match: return randint(int(match[1]), int(match[2])) elif ',' in message: return choice([c.strip() for c in message.split(',')]) elif ' ' in message: return choice([c.strip() for c in message.split(' ')]) raise BotError('No options found.')
async def call(*, bot, listener, target, author, args, private): """Part a channel.""" if args: channel = args[0] asyncio.ensure_future(listener.part(channel)) return f'Attempted to part {channel}.' elif target != author: channel = target asyncio.ensure_future(listener.part(channel)) else: raise BotError('This command must be ran in a channel.')
def _get_lastfm_nick(username, listener): with database() as (conn, cursor): cursor.execute( """ SELECT lastfm FROM lastfm WHERE user = ? AND listener = ? """, (username, str(listener)), ) row = cursor.fetchone() if not row: raise BotError('No Last.FM name set.') return row['lastfm']
async def call(*, bot, listener, target, author, args, private): """Queries the UrbanDictionary API and relays the response.""" entry = int(args[0]) if len(args) == 2 else 1 search = args[-1] future = asyncio.get_event_loop().run_in_executor( None, lambda: requests.get( 'https://api.urbandictionary.com/v0/define', headers={'User-Agent': config['user_agent']}, params={'term': search}, timeout=15, ), ) try: response = (await future).json() except (requests.RequestException, JSONDecodeError) as e: logger.error(f'Failed to query UrbanDictionary: {e}') raise BotError('Failed to query UrbanDictionary.') if not response['list']: raise BotError( f'Could not find a definition for {search.rstrip(".")}.') defi = (re.sub( r'\[(.*?)\]', r'\1', sorted( response['list'], key=lambda x: int(x['thumbs_up']) - int(x['thumbs_down']), reverse=True, )[entry]['definition'], ).strip().replace('\n', ' ')) return f'{defi[:497]}...' if len(defi) >= 500 else defi
def _calculate_time_since_played(track, author=None): if '@attr' in track: return scrobble_time = datetime.strptime(track['date']['#text'], '%d %b %Y, %H:%M') diff = datetime.utcnow() - scrobble_time hours, seconds = divmod(diff.seconds, 3600) minutes = divmod(seconds, 60)[0] time_since = [] if diff.days > 0: time_since.append(f'{diff.days} days') if hours > 0: time_since.append(f'{hours} hours') if minutes > 0: time_since.append(f'{minutes} minutes') if diff.days > 0 or hours > 0: raise BotError(f'{author} is not playing anything (last seen: ' f'{" ".join(time_since)} ago)') return f'{minutes} minutes'
def _parse_quote_ids(message): try: quote_ids = [int(qid) for qid in message.split(' ')] except ValueError: raise BotError('Quote IDs must be integers.') return quote_ids
async def wrapper(*, listener, author, **kwargs): if not await listener.is_admin(author): raise BotError('Unauthorized.') return await func(listener=listener, author=author, **kwargs)
def wrapper(*, listener, **kwargs): if any(isinstance(listener, l) for l in listeners): return func(listener=listener, **kwargs) raise BotError('This command cannot be run on this listener.')
def wrapper(*, private, **kwargs): if private: return func(private=private, **kwargs) raise BotError('This command can only be run in a private message.')
def wrapper(*, private, **kwargs): if not private: return func(private=private, **kwargs) raise BotError('This command can only be run in a channel.')
def confirm_database_is_updated(): if calculate_migrations_needed(): if not len(sys.argv) == 2 or sys.argv[1] != 'migrate': raise BotError( 'The database needs to be migrated. Run `chitanda migrate`.')