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

# --------------------------------------------------------------------------- #
#                                                                             #
#    Plugin for iSida Jabber Bot                                              #
#    Copyright (C) Vit@liy <vitaliy@root.ua>                                  #
#                                                                             #
#    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/>.    #
#                                                                             #
# --------------------------------------------------------------------------- #

import re
import base64
import urllib.request
import urllib.parse
from html import unescape

bmn_last_res = {}

def _bmn_debug(text):
    try: printlog('[bugmenot] %s' % text)
    except Exception: pass

def _bmn_key(html):
    """Extract the per-page XOR key from the inline data-z decode script."""
    for scr in re.findall(r'<script[^>]*>(.*?)</script>', html, re.S):
        if 'atob' in scr and 'data-z' in scr:
            key = []
            arrays = re.findall(r'=\s*\[([0-9]+(?:,\s*[0-9]+)*)\]', scr)
            for a in arrays:
                for n in re.split(r'[,\s]+', a.strip()):
                    if n: key.append(int(n))
            return key
    return []

def _bmn_decode(z, key):
    """Decode a data-z value: base64 then XOR with the page key."""
    if not z or not key: return ''
    try:
        raw = base64.b64decode(z + '=' * (-len(z) % 4))
    except Exception:
        try: raw = base64.urlsafe_b64decode(z + '=' * (-len(z) % 4))
        except Exception: return ''
    try:
        return ''.join(chr(b ^ key[i % len(key)]) for i, b in enumerate(raw))
    except Exception:
        return ''

def _bmn_text_flatten(html):
    """Strip tags (and scripts/styles) to a plain label:value layout."""
    html = re.sub(r'<(script|style)[^>]*>.*?</\1>', '', html, flags=re.S | re.I)
    html = re.sub(r'><', '>\n<', html)
    html = re.sub(r'<[^>]+>', '', html)
    return unescape(html)

def _bmn_parse_html(html):
    """Parse account <article> blocks from the BugMeNot HTML page."""
    key = _bmn_key(html)
    entries = []
    for art in re.findall(r'<article[^>]*class="account".*?</article>', html, re.S):
        fields = {}
        for label, tag in re.findall(r'<dt>(.*?)</dt><dd>(.*?)</dd>', art, re.S):
            name = unescape(re.sub(r'<[^>]+>', '', label)).strip().rstrip(':').lower()
            if name not in ('username', 'password', 'other'): continue
            mz = re.search(r'data-z="([^"]+)"', tag)
            if mz: fields[name] = _bmn_decode(mz.group(1), key)
            else: fields[name] = unescape(re.sub(r'<[^>]+>', '', tag)).strip()
        rate = votes = ''
        for li in re.findall(r'<li[^>]*>(.*?)</li>', art, re.S):
            s = unescape(re.sub(r'<[^>]+>', '', li)).strip()
            mr = re.search(r'(\d+)\s*%', s)
            if 'success rate' in s and mr: rate = mr.group(1)
            mv = re.search(r'^(\d+)\s*votes?$', s)
            if mv: votes = mv.group(1)
        username = fields.get('username', '')
        if username:
            entries.append((username, fields.get('password', ''), fields.get('other', ''), rate, votes))
    return entries

def _bmn_parse_text(data):
    """Parse BugMeNot plain-text output (fallback) into
    (username, password, other, success_rate, votes) tuples."""
    entries = []
    cur = None
    pending = None
    for raw in (data or '').splitlines():
        line = raw.strip()
        m = re.match(r'^(Username|Password|Other)\s*:\s*(.*)$', line, re.I)
        if m:
            label, inline = m.group(1).lower(), m.group(2).strip()
            label = {'username': 'user', 'password': 'pass'}.get(label, label)
            if label == 'user':
                if cur: entries.append(cur)
                cur = {'user': '', 'pass': '', 'other': '', 'rate': '', 'votes': ''}
            pending = label if not inline else None
            if inline and cur is not None:
                cur[label] = inline.strip().lstrip('- ').strip()
            continue
        if cur is None: continue
        low = line.lower()
        rm = re.search(r'(\d+)\s*%\s*success rate', low)
        vm = re.search(r'(\d+)\s*votes', low)
        if rm:
            cur['rate'] = rm.group(1)
            continue
        if vm:
            cur['votes'] = vm.group(1)
            continue
        if pending and line:
            cur[pending] = line.lstrip('- ').strip()
            pending = None
    if cur: entries.append(cur)
    return [(e['user'], e['pass'], e['other'], e['rate'], e['votes']) for e in entries if e['user']]

def _bmn_parse(data):
    """Parse a BugMeNot response (HTML or plain text)."""
    if not data: return []
    entries = _bmn_parse_html(data)
    if entries: return entries
    return _bmn_parse_text(_bmn_text_flatten(data))

def _bmn_fetch(domain):
    """Fetch the BugMeNot view page over HTTPS (follows redirects)."""
    path = 'https://www.bugmenot.com/view/' + urllib.parse.quote(domain)
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'en-US,en;q=0.9',
        'Referer': 'https://www.bugmenot.com/',
    }
    try:
        req = urllib.request.Request(path, headers=headers)
        with urllib.request.urlopen(req, timeout=10) as resp:
            return resp.status, resp.read()
    except Exception:
        pass
    return 0, b''

def bugmenot(type, jid, nick, text):
    global bmn_last_res
    result = ''
    text = (text or '').strip()
    if text:
        status, body = _bmn_fetch(text)
        entries = _bmn_parse(body.decode('utf-8', 'ignore'))
        if entries:
            bmn_last_res.setdefault(jid, {})[nick] = entries[1:]
            lines = []
            for i, (u, p, o, r, v) in enumerate(entries, 1):
                lines.append('%d. %s' % (i, rss_replace(L('Login: %s, Pass: %s | %s | %s%% (%s votes)', '%s/%s' % (jid, nick)) % (u, p, o, r, v))))
            result = '\n'.join(lines)
        else:
            _bmn_debug('no entries parsed: http=%s len=%s sample=%r' % (status, len(body), body[:200]))
            result = L('No data found', '%s/%s' % (jid, nick))
    else:
        if jid in bmn_last_res and nick in bmn_last_res[jid] and bmn_last_res[jid][nick]:
            first = bmn_last_res[jid][nick][0]
            bmn_last_res[jid][nick] = bmn_last_res[jid][nick][1:]
        else:
            result = L('No data found', '%s/%s' % (jid, nick))
    if not result:
        result = rss_replace(L('Login: %s, Pass: %s | %s | %s%% (%s votes)', '%s/%s' % (jid, nick)) % first)
    send_msg(type, jid, nick, result)

def bmn_clear(room,jid,nick,type,arr):
    if type == 'unavailable' and room in bmn_last_res and nick in bmn_last_res[room]: del bmn_last_res[room][nick]

global execute, presence_control

presence_control = [bmn_clear]

execute = [(3, 'bugmenot', bugmenot, 2, 'Search in bugmenot.com')]