Exemple #1
0
async def _get_now_playing(lastfm, formatted_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("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"{formatted_author} has never scrobbled before.")
    return response["recenttracks"]["track"][0]
Exemple #2
0
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]
Exemple #3
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
Exemple #4
0
async def call(message):
    """Hot reload the bot's config and modules."""
    try:
        config.reload()
    except Exception as e:  # noqa: E203
        logger.error(f'Error reloading config: {e}')
        raise BotError('Couldn\'t reload config.')

    try:
        load_commands(message.bot, run_setup=False)
    except Exception as e:  # noqa: E203
        logger.error(f'Error reloading modules: {e}')
        raise BotError('Couldn\'t reload modules.')

    return 'Commands reloaded.'
Exemple #5
0
async def call(message):
    """Queries the UrbanDictionary API and relays the response."""
    entry, search = _parse_args(message.args)
    response = await _make_request(search)
    if response["list"]:
        return trim_message(_get_definition(response, entry), length=400)
    raise BotError(f'Could not find a definition for {search.rstrip(".")}.')
Exemple #6
0
def _compile_regex(regex, flags):
    try:
        if flags["nocase"]:
            return re.compile(regex, flags=re.IGNORECASE)
        return re.compile(regex)
    except re.error:  # noqa E722
        raise BotError(f"{regex} is not a valid regex.")
Exemple #7
0
def _calculate_time_since_played(track, formatted_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} day(s)")
    if hours > 0:
        time_since.append(f"{hours} hour(s)")
    if minutes > 0:
        time_since.append(f"{minutes} minute(s)")

    if diff.days > 0 or hours > 0:
        raise BotError(
            f"{formatted_author} is not playing anything (last seen: "
            f'{" ".join(time_since)} ago)')

    return f"{minutes} minutes"
Exemple #8
0
 def wrapper(message):
     for regex in regexes:
         match = regex.match(message.contents)
         if match:
             message.args = match.groups()
             return func(message)
     else:
         raise BotError("Invalid arguments.")
Exemple #9
0
async def call(message):
    """Part a channel."""
    if message.args:
        await message.listener.part(message.args[0])
        return f"Attempted to part {message.args[0]}."
    elif message.target != message.author:
        await message.listener.part(message.target)
    else:
        raise BotError("This command must be ran in a channel.")
Exemple #10
0
async def call(message):
    """Chooses one of many provided options."""
    match = re.match(r"(\d+) *- *(\d+)", message.contents)
    if match:
        return randint(int(match[1]), int(match[2]))
    elif "," in message.contents:
        return choice([c.strip() for c in message.contents.split(",") if c])
    elif " " in message.contents:
        return choice([c.strip() for c in message.contents.split(" ") if c])
    raise BotError("No options found.")
Exemple #11
0
async def call(message):
    """Chooses one of many provided options."""
    match = re.match(r'(\d+) *- *(\d+)', message.contents)
    if match:
        return randint(int(match[1]), int(match[2]))
    elif ',' in message.contents:
        return choice([c.strip() for c in message.contents.split(',') if c])
    elif ' ' in message.contents:
        return choice([c.strip() for c in message.contents.split(' ') if c])
    raise BotError('No options found.')
Exemple #12
0
def _get_event_handler(headers):
    events = {
        'push': handle_push,
        'issues': handle_issue,
        'pull_request': handle_pull_request,
    }

    try:
        return events[headers['X-Github-Event']]
    except KeyError:
        logger.info('GitHub request\'s event is unsupported, ignoring.')
        raise BotError('Unsupported event.')
Exemple #13
0
def _get_event_handler(headers):
    events = {
        "push": handle_push,
        "issues": handle_issue,
        "pull_request": handle_pull_request,
    }

    try:
        return events[headers["X-Github-Event"]]
    except KeyError:
        logger.info("GitHub request's event is unsupported, ignoring.")
        raise BotError("Unsupported event.")
Exemple #14
0
def _check_signature(payload, headers):
    secret = config["github_relay"]["secret"]
    if not secret:
        return True

    expected_sig = hmac.new(
        key=secret.encode(), msg=payload.encode(), digestmod=hashlib.sha1
    ).hexdigest()
    try:
        return hmac.compare_digest(f"sha1={expected_sig}", headers["X-Hub-Signature"])
    except KeyError:
        raise BotError("Expected signature.")
Exemple #15
0
async def test_handle_message_bot_error(monkeypatch):
    monkeypatch.setattr("chitanda.bot.config", {"webserver": {"enable": True}})
    listener = Mock()
    listener.message.return_value = Future()
    listener.message.return_value.set_result(123)

    chitanda = Chitanda()
    with patch.object(chitanda, "message_handlers") as msg_handlers:
        msg_handlers.__iter__.side_effect = BotError("test error")
        message = Message(chitanda, listener, 2, "azul", 4, 5)
        await chitanda.handle_message(message)
        listener.message.assert_called_with(2, "Error: test error")
Exemple #16
0
async def test_handle_message_bot_error(monkeypatch):
    monkeypatch.setattr('chitanda.bot.config', {'webserver': {'enable': True}})
    listener = Mock()
    listener.message.return_value = Future()
    listener.message.return_value.set_result(123)

    chitanda = Chitanda()
    with patch.object(chitanda, 'message_handlers') as msg_handlers:
        msg_handlers.__iter__.side_effect = BotError('test error')
        message = Message(chitanda, listener, 2, 'azul', 4, 5)
        await chitanda.handle_message(message)
        listener.message.assert_called_with(2, f'Error: test error')
Exemple #17
0
def _check_signature(payload, headers):
    secret = config['github_relay']['secret']
    if not secret:
        return True

    expected_sig = hmac.new(key=secret.encode(),
                            msg=payload.encode(),
                            digestmod=hashlib.sha1).hexdigest()
    try:
        return hmac.compare_digest(f'sha1={expected_sig}',
                                   headers['X-Hub-Signature'])
    except KeyError:
        raise BotError('Expected signature.')
Exemple #18
0
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"]
Exemple #19
0
async def _make_request(search):
    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:
        return (await future).json()
    except (requests.RequestException, JSONDecodeError) as e:
        logger.error(f"Failed to query UrbanDictionary: {e}")
        raise BotError("Failed to query UrbanDictionary.")
Exemple #20
0
async def _find_and_replace(message_log, regex, repl, count):
    parent_connection, child_connection = Pipe()
    process = Process(
        target=_sed_and_send_value_over_conn,
        args=(child_connection, message_log, regex, repl, count),
    )
    process.start()
    logging.info(f"Started Process {process.pid} for sed command.")

    for _ in range(int(TIMEOUT / 0.02)):
        if process.exitcode is not None:
            logging.info(f"Process {process.pid} has completed its sed.")
            result = parent_connection.recv()
            parent_connection.close()
            return result
        await asyncio.sleep(0.02)

    logging.info(f"Process {process.pid}'s sed has timed out, killing...")
    process.kill()
    raise BotError("Regex substitution timed out.")
Exemple #21
0
async def _handle_request(bot, request, **kwargs):
    try:
        logger.info("Received request from GitHub webhook.")
        if not _check_signature(await request.text(), request.headers):
            logger.info("GitHub request contained invalid signature, ignoring.")
            raise BotError("Invalid signature.")

        payload = await request.json()
        repo_cfgs = _get_repo_cfgs(payload["repository"]["id"])
        logger.info('Event for repository {payload["repository"]["name"]}.')
        event_handler = _get_event_handler(request.headers)

        for cfg in repo_cfgs:
            asyncio.ensure_future(
                event_handler(get_listener(bot, cfg["listener"]), payload, cfg)
            )

        return web.Response(body="Received.")
    except InvalidListener:
        return web.Response(body="Invalid listener configured.", status=500)
    except BotError as e:
        return web.Response(body=e.args[0], status=500)
Exemple #22
0
async def call(message):
    """Queries the Wolfram|Alpha API and relays the response."""
    future = asyncio.get_event_loop().run_in_executor(
        None,
        lambda: requests.get(
            "https://api.wolframalpha.com/v1/result",
            headers={"User-Agent": config["user_agent"]},
            params={
                "appid": config["wolframalpha"]["appid"],
                "i": message.args[0],
            },
            timeout=15,
        ),
    )

    try:
        response = (await future).text
    except requests.RequestException as e:
        logger.error(f"Failed to query Wolfram|Alpha: {e}")
        raise BotError("Failed to query Wolfram|Alpha.")

    return trim_message(response, length=400)
Exemple #23
0
async def call(message):
    """Queries the Wolfram|Alpha API and relays the response."""
    future = asyncio.get_event_loop().run_in_executor(
        None,
        lambda: requests.get(
            'https://api.wolframalpha.com/v1/result',
            headers={'User-Agent': config['user_agent']},
            params={
                'appid': config['wolframalpha']['appid'],
                'i': message.args[0],
            },
            timeout=15,
        ),
    )

    try:
        response = (await future).text
    except requests.RequestException as e:
        logger.error(f'Failed to query Wolfram|Alpha: {e}')
        raise BotError('Failed to query Wolfram|Alpha.')

    return trim_message(response, length=400)
Exemple #24
0
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} day(s)')
    if hours > 0:
        time_since.append(f'{hours} hour(s)')
    if minutes > 0:
        time_since.append(f'{minutes} minute(s)')

    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'
Exemple #25
0
 async def wrapper(message):
     message.username = await message.listener.is_authed(message.author)
     if not message.username:
         raise BotError("Identify with NickServ to use this command.")
     return await func(message)
Exemple #26
0
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`.')
Exemple #27
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.')
    return quote_ids
Exemple #28
0
 async def wrapper(message):
     if not await message.listener.is_admin(message.author):
         raise BotError("Unauthorized.")
     async for r in func(message):
         yield r
Exemple #29
0
 async def wrapper(message):
     if not await message.listener.is_admin(message.author):
         raise BotError("Unauthorized.")
     return await func(message)
Exemple #30
0
 def wrapper(message):
     if not message.private:
         return func(message)
     raise BotError("This command can only be run in a channel.")