<commit_before>"""Run the bot.

Channel messages, join/part/quit messages and the like are saved to 
files under irclogs and printed to stdout. Debugging messages are 
printed to stderr and saved in botlog.txt.
"""

import atexit
import collections
import glob
import logging
import os
import time

import curio
from curio import socket

import bot


logger = logging.getLogger(__name__)

LOG_LEN = 1000
logs = {}  # {channel: deque, ...}


def _format_msg(msg):
    return f"[%s] %s\n" % (time.strftime('%d %b %H:%M:%S'), msg)


def _log_filename(channel):
    return os.path.join('irclogs', channel + '.txt')


async def log_msg(channel, msg):
    try:
        log = logs[channel]
    except KeyError:
        log = collections.deque(maxlen=LOG_LEN)
        try:
            async with curio.aopen(_log_filename(channel), 'r') as f:
                async for line in f:
                    log.append(line)
        except FileNotFoundError:
            # We are running for the first time and nothing is logged
            # yet.
            pass
        logs[channel] = log

    print(f"({channel})", msg)
    log.append(_format_msg(msg))


@atexit.register
def save_logs():
    logger.info("saving logs")
    try:
        os.mkdir('irclogs')
    except FileExistsError:
        pass

    for channel, lines in logs.items():
        lines.append(_format_msg("* Shutting down."))
        with open(_log_filename(channel), 'w') as f:
            f.writelines(lines)


async def termbin(iterable):
    """Paste the content of iterable to termbin and return URL.

    The iterable can be asynchronous or synchronous.
    """
    try:
        logger.info("sending %d lines to termbin", len(iterable))
    except TypeError:
        # probably a file object or some other iterator
        logger.info("sending content of %r to termbin", iterable)

    async with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        await sock.connect(('termbin.com', 9999))

        if hasattr(type(iterable), '__aiter__'):
            async for string in iterable:
                # replace is not the best possible way, but at least
                # better than failing to termbin anything
                await sock.sendall(string.encode('utf-8', errors='replace'))
        else:
            for string in iterable:
                await sock.sendall(string.encode('utf-8', errors='replace'))

        byteurl = await sock.recv(1024)
        return byteurl.decode('ascii').strip()


@bot.command("!log")
async def termbin_log(event, channel=None):
    """Termbin the log of the channel."""
    if channel is None:
        channel_given = False
        channel = event.target
    else:
        channel_given = True

    lines = logs.get(channel, [])
    if lines:
        await event.reply(await termbin(lines))
    else:
        # termbin says "Use netcat." if we send it nothing
        msg = f"Nothing is logged from {channel} yet!"
        if not channel_given:
            msg += (" You can use '!log CHANNEL' to get logs from a "
                    "specific channel.")
        await event.reply(msg)


@bot.command("!src")
async def termbin_source(event):
    """Pastebin the source code for this bot."""
    here = os.path.dirname(os.path.abspath(__file__))
    olddir = os.getcwd()
    os.chdir(here)

    await event.reply("I'm termbinning myself, please wait...")
    results = []    # [(filename, url), ...]
    try:
        for filename in glob.glob('*.py'):
            async with curio.aopen(filename, 'r') as f:
                url = await termbin(f)
            results.append((filename, url))
    finally:
        os.chdir(olddir)

    # functional programming ftw
    await event.reply("   ".join(map(": ".join, results)))


bot.add_help_command("!help")


@bot.join
@bot.part
@bot.quit
async def info_handler(event):
    logmsg = "* {} {}s".format(
        event.sender['nick'], event.command.lower())
    await log_msg(event.target, logmsg)


@bot.kick
async def kick_handler(event):
    logmsg = "{} {}s {} (reason: {})".format(
        event.sender['nick'], event.command.lower(),
        event.target, event.reason)
    await log_msg(event.channel, logmsg)


@bot.privmsg
async def privmsg_handler(event):<commit_msg>wtf command<commit_after>"""Run the bot.

Channel messages, join/part/quit messages and the like are saved to 
files under irclogs and printed to stdout. Debugging messages are 
printed to stderr and saved in botlog.txt.
"""

import atexit
import collections
import glob
import logging
import os
import time

import curio
from curio import socket

import bot


logger = logging.getLogger(__name__)

LOG_LEN = 1000
logs = {}  # {channel: deque, ...}


def _format_msg(msg):
    return f"[%s] %s\n" % (time.strftime('%d %b %H:%M:%S'), msg)


def _log_filename(channel):
    return os.path.join('irclogs', channel + '.txt')


async def log_msg(channel, msg):
    try:
        log = logs[channel]
    except KeyError:
        log = collections.deque(maxlen=LOG_LEN)
        try:
            async with curio.aopen(_log_filename(channel), 'r') as f:
                async for line in f:
                    log.append(line)
        except FileNotFoundError:
            # We are running for the first time and nothing is logged
            # yet.
            pass
        logs[channel] = log

    print(f"({channel})", msg)
    log.append(_format_msg(msg))


@atexit.register
def save_logs():
    logger.info("saving logs")
    try:
        os.mkdir('irclogs')
    except FileExistsError:
        pass

    for channel, lines in logs.items():
        lines.append(_format_msg("* Shutting down."))
        with open(_log_filename(channel), 'w') as f:
            f.writelines(lines)


async def termbin(iterable):
    """Paste the content of iterable to termbin and return URL.

    The iterable can be asynchronous or synchronous.
    """
    try:
        logger.info("sending %d lines to termbin", len(iterable))
    except TypeError:
        # probably a file object or some other iterator
        logger.info("sending content of %r to termbin", iterable)

    async with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        await sock.connect(('termbin.com', 9999))

        if hasattr(type(iterable), '__aiter__'):
            async for string in iterable:
                # replace is not the best possible way, but at least
                # better than failing to termbin anything
                await sock.sendall(string.encode('utf-8', errors='replace'))
        else:
            for string in iterable:
                await sock.sendall(string.encode('utf-8', errors='replace'))

        byteurl = await sock.recv(1024)
        return byteurl.decode('ascii').strip()


@bot.command("!log")
async def termbin_log(event, channel=None):
    """Termbin the log of the channel."""
    if channel is None:
        channel_given = False
        channel = event.target
    else:
        channel_given = True

    lines = logs.get(channel, [])
    if lines:
        await event.reply(await termbin(lines))
    else:
        # termbin says "Use netcat." if we send it nothing
        msg = f"Nothing is logged from {channel} yet!"
        if not channel_given:
            msg += (" You can use '!log CHANNEL' to get logs from a "
                    "specific channel.")
        await event.reply(msg)


@bot.command("!src")
async def termbin_source(event):
    """Pastebin the source code for this bot."""
    here = os.path.dirname(os.path.abspath(__file__))
    olddir = os.getcwd()
    os.chdir(here)

    await event.reply("I'm termbinning myself, please wait...")
    results = []    # [(filename, url), ...]
    try:
        for filename in glob.glob('*.py'):
            async with curio.aopen(filename, 'r') as f:
                url = await termbin(f)
            results.append((filename, url))
    finally:
        os.chdir(olddir)

    # functional programming ftw
    await event.reply("   ".join(map(": ".join, results)))


@bot.command("!wtf")
def do_wtf(event, acronym):
    """Translate an acronym to English."""
    async with curio.aopen('wtf-words.txt', 'r') as f:
        async for line in f:
            if line.startswith(acronym + '\t'):
                await event.reply(line.split('\t', 1)[1].strip())
    await event.reply(f"WTF? I have no idea what {acronym} means!")


bot.add_help_command("!help")


@bot.join
@bot.part
@bot.quit
async def info_handler(event):
    logmsg = "* {} {}s".format(
        event.sender['nick'], event.command.lower())
    await log_msg(event.target, logmsg)


@bot.kick
async def kick_handler(event):
    logmsg = "{} {}s {} (reason: {})".format(
        event.sender['nick'], event.command.lower(),
        event.target, event.reason)
    await log_msg(event.channel, logmsg)


@bot.privmsg
async def privmsg_handler(event):
{'fraction_non_alphanumeric': 0.1229062924400181, 'fraction_numerical': 0.0033952014486192846, 'mean_word_length': 2.52633679169992, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2412448', 'n_tokens_mistral': 2767, 'n_tokens_neox': 2670, 'n_words': 797}