class BotResponse:
    def __init__(self, message, type, data=None, onsend_actions=()):
        self.type = type
        self.user_id = message.user_id
        self.peer_id = message.peer_id
        self.message_body = message.body
        self.message_id = message.id
        self.message_has_action = bool(message.action)
        self.data = data
        self.onsend_actions = list(onsend_actions)

    def fake_message(self):
        if self.peer_id == self.user_id:
            return {'user_id': self.user_id}
        else:
            return {'user_id': self.user_id, 'chat_id': self.peer_id - CONF_START}

    @property
    def is_chat(self):
        return self.peer_id > CONF_START

    @property
    def text(self):
        return self.data if self.type == ResponseType.TEXT else ''

    def get_peer_info(self):
        if self.peer_id == self.user_id:
            return PeerInfo(self.user_id)
        return PeerInfo(self.user_id, self.peer_id - CONF_START) class ResponseType(enum.Enum):
    NO_READ = 0
    IGNORE = 1
    NO_RESPONSE = 2
    TEXT = 3
    STICKER = 4 class LimiterCache:

    def __init__(self, config_file):
        self.data = defaultdict(dict)
        self.intervals = {}
        self.config_file = config_file
        self.reload()

    def reload(self):
        with open(self.config_file) as f:
            lines = filter(str.strip, f.readlines())
            self.intervals = {limit: int(interval) for limit, interval in map(str.split, lines)}

    def add(self, pid, limiters):
        t = time.time()
        for l in limiters.split():
            self.data[pid][l[1:]] = t

    def get(self, pid):
        t = time.time()
        res = []
        for l in self.data[pid]:
            if l not in self.intervals:
                logger.error('Unknown limiter: ' + l)
            elif self.data[pid][l] + self.intervals[l] > t:
                res.append(l)
        return ' '.join('@' + i for i in res) def evalExpression(s):
    s = s.replace('\u00d7', '*').replace('\u2022', '*').replace('\u00f7', '/')
    if seq_minus.match(s):
        return None
    s = not_a_minus.sub(' ', s)
    s = s.replace('(', ' ( ').replace(')', ' ) ').replace('+', ' + ').replace('-', ' - ').replace('*', ' * ').replace('/', ' // ')
    if '[' in s:
        return None
    s = ''.join(i if i in allowed else ' ' for i in s.lower()).split()
    ans = []
    for i in s:
        if set(i) <= set('0123456789+-*/() '):
            ans.append(i)
        elif i in op:
            ans.append(op[i])
        elif i in rep:
            ans.append(rep[i])
    for i in range(1, len(ans)):
        if ans[i].isdigit() and ans[i - 1].isdigit():
            a = int(ans[i])
            b = int(ans[i - 1])
            if 1 <= a <= 9 and 20 <= b <= 90 and b % 10 == 0:
                ans[i] = str(int(ans[i]) + int(ans[i - 1]))
                ans[i - 1] = ''
            else:
                return None
    s = ''.join(ans).strip()
    if not s:
        return None
    if s[0] == '+' or isnum(s) or isnum(s.replace('(', '').replace(')', '').strip('+').strip('-')):
        return None
    if '**' in s or '--' in s or '++' in s or '-0' in s:
        return None
    if set(s) <= set('0123456789()-') and s[0] == '8':
        return None
    try:
        res = str(eval(s, {'__builtins__': {}}))
    except ZeroDivisionError:
        return None
    except Exception:
        s = s.replace('(', ' ').replace(')', ' ').strip()
        if not s or s[0] == '+' or isnum(s) or isnum(s.strip('+')):
            return None
        if '**' in s or '--' in s or '++' in s or '-0' in s or ''.join(s.split()) == '1*1':
            return None
        try:
            res = str(eval(s, {'__builtins__': {}}))
        except Exception:
            return None
    s = s.lstrip('-')
    if set(s) <= set('0123456789-') and s.count('-') == 1 and int(res) <= 0:
        return None
    if s == '24//7':
        return None
    ss = s.split('//')
    if len(ss) == 2 and ss[0] == ss[1]:
        return None
    if isnum(res) and res != '0' and len(res) < 80:
        return res
    else:
        return None class CppBot:
    source_files = ['Analyzer.cpp', 'ChatBot.cpp', 'main.cpp', 'ChatBot.h', 'build.sh']
    exe_name = 'chat.exe'
    path = 'chat/'
    data_path = 'data/'
    data_files = ['bot.txt', 'blacklist.txt', 'fixedstem.txt', 'names.txt', 'noans.txt', 'smiles.txt']

    def __init__(self, name, max_smiles, dump_filename):
        try:
            exe_time = os.path.getmtime(self.path + self.exe_name)
            src_time = max(os.path.getmtime(self.path + i) for i in self.source_files)
            if src_time > exe_time:
                self.buildExe()
        except FileNotFoundError:
            self.buildExe()
        self.name = name.upper()
        self.max_smiles = max_smiles
        self.start_time = time.time()
        self.bot = None
        self.runExe()
        self.bot_lock = threading.Lock()
        self.dump_filename = dump_filename
        self.load()

    def runExe(self):
        self.bot = Popen([self.path + self.exe_name, str(self.max_smiles), str(self.getNameIndex())], stdout=PIPE, stdin=PIPE, stderr=PIPE)

    def interact(self, msg, do_log=True):
        try:
            with self.bot_lock:
                self.bot.stdin.write(msg.replace('\n', '\a').strip().encode() + b'\n')
                self.bot.stdin.flush()
                answer = self.bot.stdout.readline().rstrip().replace(b'\a', b'\n')
                while True:
                    info = nonBlockRead(self.bot.stderr)
                    if not info:
                        break
                    info = info.decode().rstrip().replace('\x07', ' ')
                    if do_log:
                        bot_logger.info(info, extra={'db': html.escape(info)})
        except BrokenPipeError:
            logger.warning('Broken pipe, restarting ' + self.exe_name)
            self.runExe()
            return self.interact(msg, do_log)
        return answer.decode().strip()

    def buildExe(self):
        logger.info('Rebuilding ' + self.exe_name)
        if os.system(self.path + 'build.sh'):
            logger.critical('Unable to build')
        logger.info('Build successful')

    def reload(self):
        self.start_time = time.time()
        self.interact('reld ' + str(self.getNameIndex()))
        logger.info('Reloaded!')

    def dataTime(self):
        return max(os.path.getmtime(self.data_path + i) for i in self.data_files)

    def reloadIfChanged(self):
        data_time = self.dataTime()
        if data_time > self.start_time and time.time() > data_time + 5:
            self.reload()

    def dump(self):
        if not self.dump_filename:
            return
        data = self.interact('dump')
        data = str(int(self.start_time)) + '\n' + data
        with open(self.dump_filename, 'w') as f:
            f.write(data)

    def load(self):
        if not self.dump_filename:
            return
        if not os.path.isfile(self.dump_filename):
            logging.info('Chat dump does not exist')
            return
        data = open(self.dump_filename).read().splitlines()
        if len(data) < 2:
            return
        modtime = int(data[0])
        if modtime < self.dataTime():
            logging.info('Chat database has been modified')
        else:
            self.interact('load ' + data[1])
        os.remove(self.dump_filename)

    @staticmethod
    def escape(message):
        message = message.replace('\u0401', '\u0415').replace('\u0451', '\u0435')  # yo
        message = message.replace('\u0490', '\u0413').replace('\u0491', '\u0433')  # g
        message = message.replace('\u0404', '\u042d').replace('\u0454', '\u044d')  # e
        message = message.replace('\u0406', '\u0418').replace('\u0456', '\u0438')  # i
        message = message.replace('\u0407', '\u0418').replace('\u0457', '\u0438')  # i
        message = message.replace("`", "'")
        message = message.replace('{', '\u200b{\u200b').replace('}', '\u200b}\u200b')  # zero width spaces
        return message

    def getNameIndex(self):
        names = open('data/names.txt', encoding='utf-8').readlines()
        for i, n in enumerate(names):
            if self.name in n.upper().split():
                return i
        return -1 class MyHandler(logging.Handler):
    def emit(self, record):
    def handle(self, record):
def availableScripts(): class MessageServer:
    def __init__(self, port):
        self.handlers = {}
        self.port = port

    def addHandler(self, name, proc):
        self.handlers[name] = proc

    def _listen(self):
        sock = socket.socket()
        sock.bind(('127.0.0.1', self.port))
        sock.listen(16)
        while True:
            conn, addr = sock.accept()
            data = conn.recv(65536).decode('utf-8')  # message format: type|text
            if not data:
                continue
            if '|' in data:
                data = data.split('|', maxsplit=1)
            else:
                data = [data, None]
            if data[0] not in self.handlers:
                conn.send(b'error')
                continue
            try:
                res = self.handlers[data[0]](data[1])
                conn.send(res.encode('utf-8') or b' ')
            except Exception:
                logger.exception('MessageServer error')
                conn.send(b'error')

    def listen(self):
        t = threading.Thread(target=self._listen)
        t.daemon = True
        t.start() CONF_START = 2000000000 class VkbotMessage(IncomingMessage):

    VOICE_FALLBACK = 'voice'

    def __init__(self, *args, self_id=None, voice_recognizer=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.self_id = self_id
        self.voice_recognizer = voice_recognizer

    def _construct_forwarded_message(self, data):
        return self.__class__(data, self_id=self.self_id)

    def _get_voice_content(self, voice):
        if self.voice_recognizer is None or 'link_mp3' not in voice:
            return self.VOICE_FALLBACK
        text = self.voice_recognizer.get_text(voice['link_mp3'])
        return text if text is not None else self.VOICE_FALLBACK

    def _process_body(self, toplevel=False):
        if self.is_my_message:
            return ''
        if self.action:
            return None

        result = self.body
        att = []
        for a in self.attachments:
            if a['type'] == 'audio':
                if not config.get('vkbot.ignore_audio', 'b'):
                    att.append(a['audio']['title'])
            elif a['type'] == 'video':
                att.append(a['video']['title'])
            elif a['type'] == 'wall':
                att.append(a['wall']['text'])
                if not a['wall']['text'] and 'copy_history' in a['wall']:
                    att[-1] = a['wall']['copy_history'][0]['text']
            elif a['type'] == 'doc':
                att.append(a['doc']['title'])
            elif a['type'] == 'audio_message':
                if toplevel:
                    att.append(self._get_voice_content(a['audio_message']))
                else:
                    att.append(self.VOICE_FALLBACK)
            elif a['type'] == 'gift':
                att.append('vkgift')
            elif a['type'] == 'link':
                att.append(a['link']['title'] + ': ' + a['link']['description'])
            elif a['type'] == 'market':
                att.append(a['market']['description'])
            elif a['type'] == 'sticker':
                att.append('sticker')
            elif a['type'] == 'photo' or a['type'] == 'graffiti':
                result += ' ..'
            elif a['type'] == 'call':
                return None
        for a in att:
            result += ' [' + a + ']'

        if self.fwd_messages:
            fwd_users = {fwd.user_id for fwd in self.fwd_messages}
            if fwd_users in ({self.self_id}, {self.user_id, self.self_id}):
                return result.strip() + ' ' + '{}' * len(self.fwd_messages)
            elif fwd_users == {self.user_id}:
                for fwd in self.fwd_messages:
                    r = fwd._process_body()
                    if r is None:
                        return None
                    result += ' {' + r.strip() + '}'
            else:
                return None

        return result.strip()

    @property
    def is_my_message(self):
        return self.user_id == self.self_id

    @cached_property
    def processed_body(self):
        return self._process_body(True)

    def get_answer_case(self):
        text = self.body
        if text != text.lower():
            return 'normal'
        if not text or not text[0].isalpha() or STARTS_WITH_URL_RE.match(text):
            return None
        return 'lower'

    def get_peer_info(self):
        return PeerInfo(self.user_id, self.chat_id) class PeerInfo:

    def __init__(self, user_id, chat_id=None):
        self.user_id = user_id
        self.chat_id = chat_id

    @property
    def peer_id(self):
        if self.chat_id is not None:
            return CONF_START + self.chat_id
        return self.user_id

    @property
    def is_chat(self):
        return self.chat_id is not None
import html
import logging
import random
import re
import signal
import sys
import threading
import time
import json
import accounts
import config
import log
import stats
import vkbot
import storage
from bot_response import BotResponse, ResponseType
from cache import LimiterCache
from calc import evalExpression
from cppbot import CppBot
from prepare import login, password
from server import MessageServer
from vkapi import CONF_START
from vkbot_message import VkbotMessage, PeerInfo
    from scripts import runScript
    if not (set(ref_re.findall(message.body)) <= {str(vk.self_id)}):
        vk.logSender('(%sender%) {} - ignored (mention)'.format(message.processed_body), message.get_peer_info())
        return BotResponse(message, ResponseType.NO_RESPONSE)
    if club_ref_re.findall(message.body):
        return BotResponse(message, ResponseType.NO_RESPONSE)
    user_msg = vk.last_message.byUser(uid)
    if message.processed_body == user_msg.get('text') and message.processed_body != '..':
        user_msg['count'] = user_msg.get('count', 0) + 1  # this modifies the cache entry too
        if message.is_voice and user_msg == vk.last_message.bySender(message.peer_id) and not user_msg.get('resent'):
            vk.logSender('(%sender%) - voice again', message.get_peer_info())
            return BotResponse(message, ResponseType.TEXT, user_msg.get('reply'))
        if message.is_sticker:
            return BotResponse(message, ResponseType.NO_RESPONSE)
        if user_msg['count'] == 5:
            noaddUsers([uid], reason='flood')
        elif user_msg['count'] < 5:
            vk.logSender('(%sender%) {} - ignored (repeated)'.format(message.body), message.get_peer_info())
        return BotResponse(message, ResponseType.NO_RESPONSE)

    if 'reply' in user_msg and message.body.upper() == user_msg['reply'].upper() and len(message.body.split()) > 1:
        vk.logSender('(%sender%) {} - ignored (my reply)'.format(message.processed_body), message.get_peer_info())
        user_msg['text'] = user_msg['reply']  # this modifies the cache entry too
        return BotResponse(message, ResponseType.NO_RESPONSE)

    t = evalExpression(message.body)
    if t:
        if getBotReplyFlat(message.processed_body):
            return BotResponse(message, ResponseType.NO_RESPONSE)
        vk.logSender('(%sender%) {} = {} (calculated)'.format(message.processed_body, t), message.get_peer_info())
        log.write('calc', '{}: "{}" = {}'.format(vk.loggableName(uid), message.body, t))
        return BotResponse(message, ResponseType.NO_RESPONSE)
    tbody = message.body.replace('<br>', '')
    if tbody.upper() == tbody and sum(i.isalpha() for i in tbody) > 1 and config.get('vkbot.ignore_caps', 'b'):
        vk.logSender('(%sender%) {} - ignored (caps)'.format(message.processed_body), message.get_peer_info())
        return BotResponse(message, ResponseType.NO_RESPONSE)

    return getBotReply(message)


def preprocessReply(s, params, uid, onsend_actions):
    if s == 'myname':
        return vk.users[uid]['first_name']
    if s == 'mylastname':
        return vk.users[uid]['last_name']
    if s == 'curtime':
        return time.strftime("%k:%M", time.localtime()).lstrip()
    if s == 'gosp':
        onsend_actions.append(lambda: vk.setRelation(uid))
        return ''
    if s == 'rmsp':
        onsend_actions.append(lambda: vk.setRelation(None, uid))
        return ''
    if s == 'phone':
        return vk.vars['phone']
    if s == 'age':
        return str(vk.vars['age'])
    if s == 'name':
        return vk.vars['name'][0]
    if s == 'lastname':
        return vk.vars['name'][1]
    if s == 'bf':
        if vk.vars['bf']:
            return 'https://vk.com/id' + str(vk.vars['bf']['id'])
        else:
            return ''
    if s == 'bfname':
        if vk.vars['bf']:
            return vk.vars['bf']['first_name']
        else:
            return ''
    if s == 'bflastname':
        if vk.vars['bf']:
            return vk.vars['bf']['last_name']
        else:
            return ''
    if s == 'ifbf':
        if len(params) != 2:
            logging.error('ifbf: 2 arguments required')
            return ''
        if uid == vk.vars['bf']['id']:
            return params[0]
        else:
            return params[1]
    logging.error('Unknown variable: ' + s)

# 1: female, 2: male
def applyGender(msg, uid, male_re=re.compile(r'\{m([^{}]*)\}'), female_re=re.compile(r'\{f([^{}]*)\}')):
    gender = ['male', 'female', 'male'][vk.users[uid]['sex']]
    if gender == 'female':
        msg = male_re.sub('', msg)
        msg = female_re.sub('\\1', msg)
    else:
        msg = female_re.sub('', msg)
        msg = male_re.sub('\\1', msg)
    return msg, gender

def testFriend(uid, need_reason=False):
    fr = vk.users[uid]
    if fr is None:
        return False
    return friend_controller.isGood(fr, need_reason)

def noaddUsers(users, remove=False, reason=None, sure=False):
    users = set(users)
    if not users:
        return 0
    vk.users.load(users)
    if not remove and config.get('vkbot.no_ignore', 'b') and not sure:
        for i in users:
            vk.logSender('Wanted to ignore %sender% ({})'.format(reason), PeerInfo(i))
        return 0
    if remove:
        return storage.deletemany('ignored', users)
    else:
        deleted = []
        for user in users:
            if storage.add('ignored', user):
                deleted.append(user)
        if not deleted:
            return 0

        text_msg = 'Deleting ' + ', '.join([vk.printableSender(PeerInfo(i), False) for i in deleted]) + (