#!/usr/bin/python
# -*- coding: utf-8 -*-

# --------------------------------------------------------------------------- #
#    Isida-NG Jabber Bot (fork of iSida)                                     #
#    Original iSida Copyright (C) 2009-2015 diSabler <dsy@dsy.name>          #
#    Isida-NG Copyright (C) 2026 Luciferus <luciferus@ubunix.pro>            #
#                                                                             #
#    This program is free software: you can redistribute it and/or modify     #
#    it under the terms of the GNU General Public License as published by     #
#    the Free Software Foundation, either version 3 of the License, or        #
#    (at your option) any later version.                                      #
#                                                                             #
#    This program is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#    GNU General Public License for more details.                             #
#                                                                             #
#    You should have received a copy of the GNU General Public License        #
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.    #
#                                                                             #
#    Project home: https://ubunix.pro/isida-ng                               #
# --------------------------------------------------------------------------- #

import time
import re

last_cleanup_sayto_base = 0

left_users = {}
AUTO_SAYTO_TIMEOUT = 60

# --------------------------------------------------------------------------- #
# ОРИГИНАЛЬНЫЙ КОД                                                           #
# --------------------------------------------------------------------------- #

def sayto(type, jid, nick, text):
    while len(text) and text[0] == '\n': text=text[1:]
    while len(text) and (text[-1] == '\n' or text[-1] == ' '): text=text[:-1]

    if text.split(' ')[0] == 'show':
        try: text = text.split(' ',1)[1]
        except: text = ''
        ga = get_level(jid, nick)
        if ga[0] != 9: msg = L('You access level is to low!','%s/%s'%(jid,nick))
        else:
            cm = cur_execute_fetchall('select * from sayto')
            if len(cm):
                msg = ''
                for cc in cm:
                    zz = cc[0].split('\n')
                    tmsg = '\n' + cc[1] +'/'+ zz[0] +' ('+un_unix(time.time()-int(zz[1]),'%s/%s'%(jid,nick))+'|'+un_unix(GT('sayto_timeout')-(time.time()-int(zz[1])),'%s/%s'%(jid,nick))+') '+L('for','%s/%s'%(jid,nick))+' '+cc[2]+' - '+cc[3]
                    if len(text) and text.lower() in tmsg.lower(): msg += tmsg
                    elif not len(text): msg += tmsg
                if len(msg): msg = L('Not transfered messages: %s','%s/%s'%(jid,nick)) % msg
                else: msg = L('Not found!','%s/%s'%(jid,nick))
                if type == 'groupchat':
                    send_msg('chat', jid, nick, msg)
                    msg = L('Sent in private message','%s/%s'%(jid,nick))
            else: msg = L('List is empty.','%s/%s'%(jid,nick))
    elif ' ' in text or '\n' in text:
        if '\n' in text: splitter = '\n'
        else: splitter = ' '
        to,what = text.split(splitter,1)[0],text.split(splitter,1)[1]
        frm = '%s\n%s' % (nick,int(time.time()))
        fnd = cur_execute_fetchall('select jid, status from age where room=%s and nick=%s group by jid, status',(jid,to))
        if len(fnd) == 1:
            if fnd[0][1]:
                msg = L('I will convey your message.','%s/%s'%(jid,nick))
                cur_execute('insert into sayto values (%s,%s,%s,%s)', (frm, jid, fnd[0][0], what))
            else: msg = L('Or am I a fool or %s is here.','%s/%s'%(jid,nick)) % to
        elif len(fnd) > 1:
            off_count = 0
            for tmp in fnd:
                if tmp[1]:
                    cur_execute('insert into sayto values (%s,%s,%s,%s)', (frm, jid, tmp[0], what))
                    off_count += 1
            if off_count: msg = L('I seen some people with this nick, and I can convey is incorrect. Coincidence: %s, and count convey messages: %s','%s/%s'%(jid,nick)) % (str(len(fnd)), str(off_count))
            else: msg = L('All people with this nickname are here!','%s/%s'%(jid,nick))
        else:
            if '@' in to:
                fnd = cur_execute_fetchall('select jid, status from age where room=%s and jid=%s group by jid,status',(jid,to))
                if fnd:
                    off_count = 0
                    for tmp in fnd:
                        if tmp[1]:
                            cur_execute('insert into sayto values (%s,%s,%s,%s)', (frm, jid, tmp[0], what))
                            off_count += 1
                    if off_count: msg = L('I will convey your message.','%s/%s'%(jid,nick))
                    else: msg = L('This jid is here!','%s/%s'%(jid,nick))
                else:
                    msg = L('I didn\'t seen user with jid %s, but if he join here I convey your message.','%s/%s'%(jid,nick)) % to
                    cur_execute('insert into sayto values (%s,%s,%s,%s)', (frm, jid, to, what))
            else: msg = L('I didn\'t see user with nick %s. You can use jid.','%s/%s'%(jid,nick)) % to
    else: msg = L('What convey to?','%s/%s'%(jid,nick))
    send_msg(type, jid, nick, msg)

def say_memo(type, jid, nick, text):
    while len(text) and text[0] == '\n': text=text[1:]
    while len(text) and (text[-1] == '\n' or text[-1] == ' '): text=text[:-1]
    gj = getRoom(get_level(jid, nick)[1])
    if text.split(' ')[0] == 'show':
        try: text = text.split(' ',1)[1]
        except: text = ''
        t = cur_execute_fetchall('select message,jid from sayto where room=%s and jid ilike %s', (jid,gj))
        if t: msg = '\n%s' % '\n'.join([u'• %s' % tmp[0] for tmp in t])
        else: msg = L('There is no memo for you!','%s/%s'%(jid,nick))
    elif text:
        cur_execute('insert into sayto values (%s,%s,%s,%s)', ('\n%s' % int(time.time()), jid, gj, text))
        msg = L('I\'ll remember it to you.','%s/%s'%(jid,nick))
    else: msg = L('What remember to you?','%s/%s'%(jid,nick))
    send_msg(type, jid, nick, msg)

def sayjid(type, jid, nick, text):
    try:
        text = text.split(' ',1)
        if len(text) != 2: msg = L('Error!','%s/%s'%(jid,nick))
        elif '@' not in text[0] and '@' not in text[0]: msg = L('Error!','%s/%s'%(jid,nick))
        elif not len(text[1]): msg = L('Error!','%s/%s'%(jid,nick))
        else:
            send_msg(type, jid, nick, L('Sent','%s/%s'%(jid,nick)))
            msg = L('%s from conference %s convey message for you: %s','%s/%s'%(jid,nick)) % (nick, jid, text[1])
            type, nick, jid = 'chat', '', text[0]
    except: msg = L('Error!','%s/%s'%(jid,nick))
    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# ОЧИСТКА И PRESENCE                                                          #
# --------------------------------------------------------------------------- #

def cleanup_sayto_base():
    global last_cleanup_sayto_base
    ctime = int(time.time())
    if ctime-last_cleanup_sayto_base > GT('sayto_cleanup_time'):
        last_cleanup_sayto_base = ctime
        cm = cur_execute_fetchall('select who, room, jid from sayto')
        if len(cm):
            for cc in cm:
                if '\n' in cc[0]:
                    tim = int(cc[0].split('\n')[1])
                    if ctime-tim > GT('sayto_timeout'): cur_execute('delete from sayto where room=%s and jid=%s',(cc[1], cc[2]))
                else: cur_execute('delete from sayto where room=%s and jid=%s',(cc[1], cc[2]))

def sayto_presence(room,jid,nick,type,text):
    global conn, left_users

    if nick != '' and type != 'unavailable':
        cm = cur_execute_fetchall('select * from sayto where room=%s and (jid=%s or jid=%s)',(room, getRoom(jid), nick))
        if cm:
            cur_execute('delete from sayto where room=%s and (jid=%s or jid=%s)',(room, getRoom(jid), nick))
            for cc in cm:
                if '\n' in cc[0]:
                    zz = cc[0].split('\n')
                    if zz[0]: msg = L('%s (%s ago) convey for you: %s','%s/%s'%(room,nick)) % (zz[0], un_unix(time.time()-int(zz[1]),'%s/%s'%(room,nick)), cc[3])
                    else: msg = L('You ask remember: %s','%s/%s'%(room,nick)) % cc[3]
                else: msg = L('%s convey for you: %s','%s/%s'%(room,nick)) % (cc[3], cc[0])
                send_msg('chat', room, nick, msg)

    if type == 'unavailable' and nick and nick != '':
        if room not in left_users:
            left_users[room] = {}
        left_users[room][nick] = {
            'leave_time': time.time(),
            'jid': getRoom(jid),
            'full_jid': jid,
            'warned': False,
            'pending': []
        }
        pprint('*** AUTO-SAYTO: %s left %s' % (nick, room), 'yellow')

    if nick != '' and type != 'unavailable':
        if room in left_users and nick in left_users[room]:
            data = left_users[room][nick]
            pending = data.get('pending', [])
            if pending:
                msg = L('You have %s pending message(s) from while you were away:', '%s/%s' % (room, nick)) % len(pending)
                for from_nick, text_msg, ts in pending:
                    msg += '\n%s: %s' % (from_nick, text_msg)
                send_msg('chat', room, nick, msg)
                pprint('*** AUTO-SAYTO: delivered %s messages to %s in %s' % (len(pending), nick, room), 'green')
            del left_users[room][nick]

# --------------------------------------------------------------------------- #
# АНАЛИЗ СООБЩЕНИЙ                                                            #
# --------------------------------------------------------------------------- #

def auto_sayto_message_control(room, jid, nick, type, text):
    global left_users

    if not get_config(room, 'auto_sayto_enabled'):
        return

    if type != 'groupchat' or not text or text == 'None' or not nick:
        return

    if nick == get_xnick(room):
        return

    if room not in left_users or not left_users[room]:
        return

    max_msgs = int(get_config(room, 'auto_sayto_max_messages') or 5)
    max_len = int(get_config(room, 'auto_sayto_max_length') or 768)
    timeout = int(get_config(room, 'auto_sayto_timeout') or AUTO_SAYTO_TIMEOUT)

    for left_nick, data in list(left_users[room].items()):
        leave_time = data['leave_time']
        pattern = r'(?:^|[\s,])@?%s[\s:,!?]' % re.escape(left_nick)
        if re.search(pattern, text, re.I):
            elapsed = time.time() - leave_time

            # Сохраняем сообщение в любом случае
            if 'pending' not in data:
                data['pending'] = []
            if len(data['pending']) < max_msgs:
                msg_text = text[:max_len]
                data['pending'].append((nick, msg_text, time.time()))
                pprint('*** AUTO-SAYTO: queued message from %s to %s (elapsed: %s)' % (nick, left_nick, elapsed), 'cyan')

            # Если таймаут прошёл и предупреждение ещё не отправляли
            if elapsed >= timeout and not data.get('warned', False):
                warn_msg = L('User %s left %s ago. Your message(s) will be delivered when they return. Use "sayto %s <message>" if you want to finish your thought.', '%s/%s' % (room, nick)) % (left_nick, un_unix(int(elapsed), '%s/%s' % (room, nick)), left_nick)
                send_msg(type, room, nick, warn_msg)
                data['warned'] = True
                pprint('*** AUTO-SAYTO: warned %s about %s in %s' % (nick, left_nick, room), 'cyan')
            break

# --------------------------------------------------------------------------- #
# ОЧИСТКА СТАРЫХ ДАННЫХ                                                       #
# --------------------------------------------------------------------------- #

def auto_sayto_cleanup():
    global left_users
    now = time.time()

    for room in list(left_users.keys()):
        for nick in list(left_users[room].keys()):
            data = left_users[room][nick]
            if now - data['leave_time'] > GT('sayto_timeout'):
                del left_users[room][nick]
                pprint('*** AUTO-SAYTO: cleaned old left user %s in %s' % (nick, room), 'yellow')
        if not left_users[room]:
            del left_users[room]

# --------------------------------------------------------------------------- #
# КОМАНДЫ НАСТРОЙКИ                                                           #
# --------------------------------------------------------------------------- #

def auto_sayto_toggle(type, jid, nick, text):
    text = text.strip().lower()
    if text not in ['on', 'off']:
        send_msg(type, jid, nick, L('Usage: auto_sayto on|off', '%s/%s' % (jid, nick)))
        return

    if get_level(jid, nick)[0] < 7:
        send_msg(type, jid, nick, L('You need access level 7 or higher.', '%s/%s' % (jid, nick)))
        return

    room = getRoom(jid)
    current = get_config(room, 'auto_sayto_enabled')
    if current is None:
        current = False

    if text == 'on':
        if current:
            msg = L('Auto-sayto is already enabled for this room.', '%s/%s' % (jid, nick))
        else:
            put_config(room, 'auto_sayto_enabled', True)
            msg = L('Auto-sayto enabled for this room.', '%s/%s' % (jid, nick))
    else:
        if not current:
            msg = L('Auto-sayto is already disabled for this room.', '%s/%s' % (jid, nick))
        else:
            put_config(room, 'auto_sayto_enabled', False)
            if room in left_users:
                del left_users[room]
            msg = L('Auto-sayto disabled for this room. Pending messages cleared.', '%s/%s' % (jid, nick))

    send_msg(type, jid, nick, msg)

def auto_sayto_set_timeout(type, jid, nick, text):
    if get_level(jid, nick)[0] < 7:
        send_msg(type, jid, nick, L('You need access level 7 or higher.', '%s/%s' % (jid, nick)))
        return
    try:
        val = int(text.strip())
        if 30 <= val <= 180:
            put_config(getRoom(jid), 'auto_sayto_timeout', val)
            msg = L('Auto-sayto timeout set to %s seconds for this room.', '%s/%s' % (jid, nick)) % val
        else:
            msg = L('Timeout must be between 30 and 180 seconds.', '%s/%s' % (jid, nick))
    except:
        current = get_config(getRoom(jid), 'auto_sayto_timeout')
        if current is None:
            current = AUTO_SAYTO_TIMEOUT
        msg = L('Current auto-sayto timeout: %s seconds. Usage: auto_sayto_timeout <30-180>', '%s/%s' % (jid, nick)) % current
    send_msg(type, jid, nick, msg)

def auto_sayto_set_max_messages(type, jid, nick, text):
    if get_level(jid, nick)[0] < 7:
        send_msg(type, jid, nick, L('You need access level 7 or higher.', '%s/%s' % (jid, nick)))
        return
    try:
        val = int(text.strip())
        if 1 <= val <= 20:
            put_config(getRoom(jid), 'auto_sayto_max_messages', val)
            msg = L('Auto-sayto max messages set to %s.', '%s/%s' % (jid, nick)) % val
        else:
            msg = L('Value must be between 1 and 20.', '%s/%s' % (jid, nick))
    except:
        msg = L('Usage: auto_sayto_max_messages <1-20>', '%s/%s' % (jid, nick))
    send_msg(type, jid, nick, msg)

def auto_sayto_set_max_length(type, jid, nick, text):
    if get_level(jid, nick)[0] < 7:
        send_msg(type, jid, nick, L('You need access level 7 or higher.', '%s/%s' % (jid, nick)))
        return
    try:
        val = int(text.strip())
        if 256 <= val <= 2048:
            put_config(getRoom(jid), 'auto_sayto_max_length', val)
            msg = L('Auto-sayto max message length set to %s.', '%s/%s' % (jid, nick)) % val
        else:
            msg = L('Value must be between 256 and 2048.', '%s/%s' % (jid, nick))
    except:
        msg = L('Usage: auto_sayto_max_length <256-2048>', '%s/%s' % (jid, nick))
    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# РЕГИСТРАЦИЯ                                                                #
# --------------------------------------------------------------------------- #

global execute, timer, presence_control, message_act_control

timer = [cleanup_sayto_base, auto_sayto_cleanup]
presence_control = [sayto_presence]
message_act_control = [auto_sayto_message_control]

execute = [
    (3, 'sayto', sayto, 2, '"Say to" command.\nsayto jid|nick message - if jid or nick join in conference, bot send "message". Messages saves 14 days, after if message didn\'t be send this message remove.'),
    (7, 'sayjid', sayjid, 2, 'Send message to jid\n sayjid jid message.'),
    (3, 'memo', say_memo, 2, 'Send a message to yourself at next join'),
    (7, 'auto_sayto', auto_sayto_toggle, 2, 'Enable/disable auto-sayto in this room.\nauto_sayto on|off'),
    (7, 'auto_sayto_timeout', auto_sayto_set_timeout, 2, 'Set timeout for auto-sayto (30-180 seconds). Usage: auto_sayto_timeout <seconds>'),
    (7, 'auto_sayto_max_messages', auto_sayto_set_max_messages, 2, 'Set max messages per user (1-20). Usage: auto_sayto_max_messages <number>'),
    (7, 'auto_sayto_max_length', auto_sayto_set_max_length, 2, 'Set max message length (256-2048). Usage: auto_sayto_max_length <number>'),
]
