예제 #1
0
async def command_theme(config: Config, match: Match[str]) -> str:
    theme_file = os.path.expanduser('~/.config/babi/theme.json')
    if not os.path.exists(theme_file):
        return format_msg(
            match,
            'awcBabi this is vs dark plus in !babi with one modification to '
            'highlight ini headers: '
            'https://github.com/asottile/babi#setting-up-syntax-highlighting',
        )

    with open(theme_file) as f:
        contents = json.load(f)

    try:
        name = contents.get('name', '(unknown)')
        user = contents['user']
        url = contents['url']
    except KeyError:
        return format_msg(match, "awcBabi I don't know what this theme is!?")
    else:
        return format_msg(
            match,
            f'awcBabi this theme was set by {esc(user)} using channel points! '
            f'it is called {esc(name)!r} and can be download from {esc(url)}',
        )
예제 #2
0
async def cmd_aqi(config: Config, match: Match[str]) -> str:
    _, _, rest = match['msg'].partition(' ')
    if rest:
        zip_code = rest.split()[0]
        if not ZIP_CODE_RE.match(zip_code):
            return format_msg(match, '(invalid zip) usage: !aqi [US_ZIP_CODE]')
    else:
        zip_code = '94401'

    params = {
        'format': 'application/json',
        'zipCode': zip_code,
        'API_KEY': config.airnow_api_key,
    }
    url = 'https://www.airnowapi.org/aq/observation/zipCode/current/'
    async with aiohttp.ClientSession() as session:
        async with session.get(url, params=params) as resp:
            json_resp = await resp.json()
            pm_25 = [d for d in json_resp if d['ParameterName'] == 'PM2.5']
            if not pm_25:
                return format_msg(
                    match,
                    'No PM2.5 info -- is this a US zip code?',
                )
            else:
                data, = pm_25
                return format_msg(
                    match,
                    f'Current AQI ({esc(data["ParameterName"])}) in '
                    f'{esc(data["ReportingArea"])}, '
                    f'{esc(data["StateCode"])}: '
                    f'{esc(str(data["AQI"]))} '
                    f'({esc(data["Category"]["Name"])})',
                )
예제 #3
0
async def change_theme(config: Config, match: Match[str]) -> str:
    url = match['msg'].strip()

    try:
        loaded = await _load_theme(url)
    except ThemeError as e:
        return format_msg(match, str(e))

    loaded['user'] = match['user']
    loaded['url'] = url

    os.makedirs(THEME_DIR, exist_ok=True)
    theme_file = f'{match["user"]}-{uuid.uuid4()}.json'
    theme_file = os.path.join(THEME_DIR, theme_file)
    with open(theme_file, 'w') as f:
        json.dump(loaded, f)

    themedir = os.path.expanduser('~/.config/babi')
    os.makedirs(themedir, exist_ok=True)

    dest = os.path.join(themedir, 'theme.json')
    proc = await asyncio.create_subprocess_exec('ln', '-sf', theme_file, dest)
    await proc.communicate()
    assert proc.returncode == 0

    proc = await asyncio.create_subprocess_exec('pkill', '-USR1', 'babi')
    await proc.communicate()
    # ignore the return code, if there are no editors running it'll be `1`
    # assert proc.returncode == 0

    return format_msg(match, 'theme updated!')
예제 #4
0
async def giveawayend(config: Config, match: Match[str]) -> Optional[str]:
    if not is_moderator(match) and match['user'] != match['channel']:
        return None

    async with aiosqlite.connect('db.db') as db:
        await ensure_giveaway_tables_exist(db)

        async with db.execute('SELECT active FROM giveaway') as cursor:
            row = await cursor.fetchone()
            if row is None or not row[0]:
                return format_msg(match, 'no current giveaway active!')

        query = 'SELECT user FROM giveaway_users'
        async with db.execute(query) as cursor:
            users = [user for user, in await cursor.fetchall()]

        if users:
            await db.execute('INSERT OR REPLACE INTO giveaway VALUES (0)')
            await db.commit()

        await db.execute('DROP TABLE giveaway_users')
        await db.execute('DROP TABLE giveaway')
        await db.commit()

    if not users:
        return format_msg(match, 'no users entered giveaway!')

    winner = random.choice(users)
    return format_msg(match, f'!giveaway winner is {esc(winner)}')
예제 #5
0
async def vim_bits_handler(config: Config, match: Match[str]) -> str:
    info = parse_badge_info(match['info'])

    async with aiosqlite.connect('db.db') as db:
        await ensure_vim_tables_exist(db)
        enabled = await get_enabled(db)

        bits = int(info['bits'])
        if enabled:
            time_left = await add_bits(db, match['user'], bits)
        else:
            time_left = await add_bits_off(db, match['user'], bits)

    if enabled:
        await _set_symlink(should_be_vim=True)

        return format_msg(
            match,
            f'MOAR VIM: {seconds_to_readable(time_left)} remaining',
        )
    else:
        return format_msg(
            match,
            f'vim is currently disabled '
            f'{seconds_to_readable(time_left)} banked',
        )
예제 #6
0
async def cmd_videoidea(config: Config, match: Match[str]) -> str:
    if not is_moderator(match) and match['user'] != match['channel']:
        return format_msg(match, 'https://youtu.be/RfiQYRn7fBg')
    _, _, rest = match['msg'].partition(' ')

    async def _git(*cmd: str) -> None:
        await _check_call('git', '-C', tmpdir, *cmd)

    with tempfile.TemporaryDirectory() as tmpdir:
        await _git(
            'clone',
            '--depth=1',
            '--quiet',
            '[email protected]:asottile/scratch.wiki',
            '.',
        )
        ideas_file = os.path.join(tmpdir, 'anthony-explains-ideas.md')
        with open(ideas_file, 'rb+') as f:
            f.seek(-1, os.SEEK_END)
            c = f.read()
            if c != b'\n':
                f.write(b'\n')
            f.write(f'- {rest}\n'.encode())
        await _git('add', '.')
        await _git('commit', '-q', '-m', 'idea added by !videoidea')
        await _git('push', '-q', 'origin', 'HEAD')

    return format_msg(
        match,
        'added! https://github.com/asottile/scratch/wiki/anthony-explains-ideas',  # noqa: E501
    )
예제 #7
0
async def command_themevalidate(config: Config, match: Match[str]) -> str:
    _, _, url = match['msg'].partition(' ')
    try:
        await _load_theme(url.strip())
    except ThemeError as e:
        return format_msg(match, str(e))
    else:
        return format_msg(match, 'theme is ok!')
예제 #8
0
async def msg_gnu_please(config: Config, match: Match[str]) -> str | None:
    if random.randrange(0, 100) < 90:
        return None
    msg, word = match['msg'], match['word']
    query = re.search(f'gnu[/+]{word}', msg, flags=re.IGNORECASE)
    if query:
        return format_msg(match, f'YES! {query[0]}')
    else:
        return format_msg(match, f"Um please, it's GNU+{esc(word)}!")
예제 #9
0
async def cmd_settoday(config: Config, match: Match[str]) -> str:
    if not is_moderator(match) and match['user'] != match['channel']:
        return format_msg(match, 'https://youtu.be/RfiQYRn7fBg')
    _, _, rest = match['msg'].partition(' ')

    async with aiosqlite.connect('db.db') as db:
        await set_today(db, rest)

    return format_msg(match, 'updated!')
예제 #10
0
async def cmd_bonkrank(config: Config, match: Match[str]) -> str:
    user = optional_user_arg(match)
    ret = _rank(user, BONKER_RE)
    if ret is None:
        return format_msg(match, f'user not found {esc(user)}')
    else:
        rank, n = ret
        return format_msg(
            match,
            f'{esc(user)} is ranked #{rank}, has bonked others {n} times',
        )
예제 #11
0
async def cmd_disablevim(config: Config, match: Match[str]) -> str:
    if not is_moderator(match) and match['user'] != match['channel']:
        return format_msg(match, 'https://youtu.be/RfiQYRn7fBg')

    async with aiosqlite.connect('db.db') as db:
        await ensure_vim_tables_exist(db)

        await db.execute('INSERT INTO vim_enabled VALUES (0)')
        await db.commit()

    return format_msg(match, 'vim has been disabled')
예제 #12
0
async def cmd_bonkedrank(config: Config, match: Match[str]) -> str:
    user = optional_user_arg(match)
    ret = _user_rank_by_line_type(user, BONKED_RE)
    if ret is None:
        return format_msg(match, f'user not found {esc(user)}')
    else:
        rank, n = ret
        return format_msg(
            match,
            f'{esc(user)} is ranked #{rank}, has been bonked {n} times',
        )
예제 #13
0
async def cmd_vimbitsrank(config: Config, match: Match[str]) -> str:
    user = optional_user_arg(match)
    async with aiosqlite.connect('db.db') as db:
        ret = await _user_rank_by_bits(user, db)
        if ret is None:
            return format_msg(match, f'user not found {esc(user)}')
        else:
            rank, n = ret
            return format_msg(
                match,
                f'{esc(user)} is ranked #{rank} with {n} vim bits',
            )
예제 #14
0
async def cmd_followage(config: Config, match: Match[str]) -> str:
    username = optional_user_arg(match)
    token = config.oauth_token.split(':')[1]

    fetched_users = await fetch_twitch_user(
        config.channel,
        oauth_token=token,
        client_id=config.client_id,
    )
    assert fetched_users is not None
    me, = fetched_users

    fetched_users = await fetch_twitch_user(
        username,
        oauth_token=token,
        client_id=config.client_id,
    )
    if not fetched_users:
        return format_msg(match, f'user {esc(username)} not found!')
    target_user, = fetched_users

    # if streamer wants to check the followage to their own channel
    if me['id'] == target_user['id']:
        return format_msg(
            match,
            f"@{esc(target_user['login'])}, you can't check !followage "
            f'to your own channel.  But I appreciate your curiosity!',
        )

    follow_age_results = await fetch_twitch_user_follows(
        from_id=target_user['id'],
        to_id=me['id'],
        oauth_token=token,
        client_id=config.client_id,
    )
    if not follow_age_results:
        return format_msg(
            match,
            f'{esc(target_user["login"])} is not a follower!',
        )
    follow_age, = follow_age_results

    now = datetime.datetime.utcnow()
    date_of_follow = datetime.datetime.fromisoformat(
        # twitch sends ISO date string with "Z" at the end,
        # which python's fromisoformat method does not like
        follow_age['followed_at'].rstrip('Z'), )
    delta = now - date_of_follow
    return format_msg(
        match,
        f'{esc(follow_age["from_name"])} has been following for '
        f'{esc(humanize.naturaldelta(delta))}!',
    )
예제 #15
0
async def cmd_chatrank(config: Config, match: Match[str]) -> str:
    user = optional_user_arg(match)
    ret = _rank(user, CHAT_LOG_RE)
    if ret is None:
        return format_msg(match, f'user not found {esc(user)}')
    else:
        rank, n = ret
        return format_msg(
            match,
            f'{esc(user)} is ranked #{rank} with {n} messages '
            f'(since {_log_start_date()})',
        )
예제 #16
0
async def cmd_vimtimeleft(config: Config, match: Match[str]) -> str:
    async with aiosqlite.connect('db.db') as db:
        await ensure_vim_tables_exist(db)
        if not await get_enabled(db):
            return format_msg(match, 'vim is currently disabled')

        time_left = await get_time_left(db)
        if time_left == 0:
            return format_msg(match, 'not currently using vim')
        else:
            return format_msg(
                match,
                f'vim time remaining: {seconds_to_readable(time_left)}',
            )
예제 #17
0
async def giveaway(config: Config, match: Match[str]) -> str:
    async with aiosqlite.connect('db.db') as db:
        await ensure_giveaway_tables_exist(db)

        async with db.execute('SELECT active FROM giveaway') as cursor:
            row = await cursor.fetchone()
            if row is None or not row[0]:
                return format_msg(match, 'no current giveaway active!')

        await ensure_giveaway_tables_exist(db)
        query = 'INSERT OR REPLACE INTO giveaway_users VALUES (?)'
        await db.execute(query, (match['user'], ))
        await db.commit()

    return format_msg(match, f'{esc(match["user"])} has been entered!')
예제 #18
0
async def cmd_bonk(config: Config, match: Match[str]) -> str:
    _, _, rest = match['msg'].partition(' ')
    rest = rest.strip() or 'marsha_socks'
    return format_msg(
        match,
        f'awcBonk awcBonk awcBonk {esc(rest)} awcBonk awcBonk awcBonk',
    )
예제 #19
0
async def cmd_top_5_bonked(config: Config, match: Match[str]) -> str:
    total = _chat_rank_counts(BONKED_RE)
    user_list = ', '.join(
        f'{rank}. {user}({n})'
        for rank, (user, n) in enumerate(total.most_common(5), start=1)
    )
    return format_msg(match, user_list)
예제 #20
0
async def cmd_top_10_chat(config: Config, match: Match[str]) -> str:
    total = _chat_rank_counts(CHAT_LOG_RE)
    user_list = ', '.join(
        f'{rank}. {user}({n})'
        for rank, (user, n) in enumerate(total.most_common(10), start=1)
    )
    return format_msg(match, f'{user_list} (since {_log_start_date()})')
예제 #21
0
async def cmd_pep_no_arg(config: Config, match: Match[str]) -> str:
    _, _, rest = match['msg'].strip().partition(' ')
    digits_match = DIGITS_RE.match(rest)
    if digits_match is not None:
        return _pep_msg(match, digits_match[0])
    else:
        return format_msg(match, '!pep: expected argument <number>')
예제 #22
0
async def cmd_set_motd(config: Config, match: Match[str]) -> str:
    async with aiosqlite.connect('db.db') as db:
        await set_motd(db, match['user'], match['msg'])
        msg = 'motd updated!  thanks for spending points!'
        if match['msg'] == '!motd':
            motd_count = await msg_count(db, match['msg'])
            msg = f'{msg}  it has been set to !motd {motd_count} times!'
    return format_msg(match, msg)
예제 #23
0
async def cmd_bonk(config: Config, match: Match[str]) -> str:
    _, _, rest = match['msg'].partition(' ')
    rest = rest.strip() or 'Makayla_Fox'
    return format_msg(
        match,
        f'{esc(rest)}: '
        f'https://i.fluffy.cc/DM4QqzjR7wCpkGPwTl6zr907X50XgtBL.png',
    )
예제 #24
0
async def cmd_editor(config: Config, match: Match[str]) -> str:
    async with aiosqlite.connect('db.db') as db:
        await ensure_vim_tables_exist(db)
        if await get_time_left(db):
            return format_msg(
                match,
                'I am currently being forced to use vim by viewers. '
                'awcBabi I normally use my text editor I made, called babi! '
                'https://github.com/asottile/babi more info in this video: '
                'https://www.youtube.com/watch?v=WyR1hAGmR3g',
            )
        else:
            return format_msg(
                match,
                'awcBabi this is my text editor I made, called babi! '
                'https://github.com/asottile/babi more info in this video: '
                'https://www.youtube.com/watch?v=WyR1hAGmR3g',
            )
예제 #25
0
async def vim_normalize_state(config: Config, match: Match[str]) -> str | None:
    async with aiosqlite.connect('db.db') as db:
        await ensure_vim_tables_exist(db)
        time_left = await get_time_left(db)

    cleared_vim = await _set_symlink(should_be_vim=time_left > 0)
    if cleared_vim:
        return format_msg(match, 'vim no more! you are free!')
    else:
        return None
예제 #26
0
async def cmd_bongo(config: Config, match: Match[str]) -> str:
    _, _, rest = match['msg'].partition(' ')
    rest = rest.strip()
    if rest:
        rest = f'{rest} '

    return format_msg(
        match,
        f'awcBongo awcBongo awcBongo {esc(rest)}awcBongo awcBongo awcBongo',
    )
예제 #27
0
async def cmd_uptime(config: Config, match: Match[str]) -> str:
    url = f'https://api.twitch.tv/helix/streams?user_login={config.channel}'
    headers = {
        'Authorization': f'Bearer {config.oauth_token_token}',
        'Client-ID': config.client_id,
    }
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as response:
            json_resp = await response.json()
            if not json_resp['data']:
                return format_msg(match, 'not currently streaming!')
            start_time_s = json_resp['data'][0]['started_at']
            start_time = datetime.datetime.strptime(
                start_time_s,
                '%Y-%m-%dT%H:%M:%SZ',
            )
            elapsed = (datetime.datetime.utcnow() - start_time).seconds

            readable_time = seconds_to_readable(elapsed)
            return format_msg(match, f'streaming for: {readable_time}')
예제 #28
0
async def givewawaystart(config: Config, match: Match[str]) -> str | None:
    if not is_moderator(match) and match['user'] != match['channel']:
        return None

    async with aiosqlite.connect('db.db') as db:
        await ensure_giveaway_tables_exist(db)

        await db.execute('INSERT OR REPLACE INTO giveaway VALUES (1)')
        await db.commit()

    return format_msg(match, 'giveaway started!  use !giveaway to enter')
예제 #29
0
async def cmd_enablevim(config: Config, match: Match[str]) -> str:
    if not is_moderator(match) and match['user'] != match['channel']:
        return format_msg(match, 'https://youtu.be/RfiQYRn7fBg')

    async with aiosqlite.connect('db.db') as db:
        await ensure_vim_tables_exist(db)

        await db.execute('INSERT INTO vim_enabled VALUES (1)')
        move_query = 'INSERT INTO vim_bits SELECT * FROM vim_bits_disabled'
        await db.execute(move_query)
        time_left = await add_time(db, await disabled_seconds(db))
        await db.execute('DELETE FROM vim_bits_disabled')
        await db.commit()

    if time_left == 0:
        return format_msg(match, 'vim has been enabled')
    else:
        await _set_symlink(should_be_vim=True)
        return format_msg(
            match,
            f'vim has been enabled: '
            f'time remaining {seconds_to_readable(time_left)}',
        )
예제 #30
0
async def msg_ping(config: Config, match: Match[str]) -> str:
    _, _, rest = match['msg'].partition(' ')
    return format_msg(match, f'PONG {esc(rest)}')