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

# --------------------------------------------------------------------------- #
#                                                                             #
#    Plugin for iSida Jabber Bot                                              #
#    Hash tools: md5, sha1, sha256 + reverse lookup                         #
#                                                                             #
#    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 hashlib
import os
import json

# ============== НАСТРОЙКИ ==============
HASH_DB_FILE = data_folder % 'hash_rainbow.json'
HASH_DB = {}

# ============== ЗАГРУЗКА/СОХРАНЕНИЕ БАЗЫ ==============

def _load_hash_db():
    global HASH_DB
    try:
        if os.path.exists(HASH_DB_FILE):
            with open(HASH_DB_FILE, 'r', encoding='utf-8') as f:
                HASH_DB = json.load(f)
        else:
            HASH_DB = {}
    except:
        HASH_DB = {}

def _save_hash_db():
    try:
        with open(HASH_DB_FILE, 'w', encoding='utf-8') as f:
            json.dump(HASH_DB, f, ensure_ascii=False, indent=2)
        return True
    except:
        return False

# ============== ОСНОВНЫЕ ФУНКЦИИ ==============

def _get_hash(text, algo='md5'):
    text = text.encode('utf-8')
    if algo == 'md5':
        return hashlib.md5(text).hexdigest()
    elif algo == 'sha1':
        return hashlib.sha1(text).hexdigest()
    elif algo == 'sha256':
        return hashlib.sha256(text).hexdigest()
    else:
        return None

def _lookup_hash(hash_value, algo='md5'):
    if not HASH_DB:
        _load_hash_db()
    key = f"{algo}:{hash_value}"
    return HASH_DB.get(key)

def _store_hash(word, hash_value, algo='md5'):
    if not HASH_DB:
        _load_hash_db()
    key = f"{algo}:{hash_value}"
    if key not in HASH_DB:
        HASH_DB[key] = word
        _save_hash_db()
        return True
    return False

# ============== СПРАВКА ==============

def help_hash(type, jid, nick, text):
    """Краткая справка"""
    send_msg(type, jid, nick, L('md5/sha1/sha256 <text>\n'
                                 'md5_save/sha1_save/sha256_save <text>\n'
                                 'md5_reverse/sha1_reverse/sha256_reverse <hash>\n'
                                 'hash <algo> <text>\n'
                                 'hash_reverse <algo> <hash>\n'
                                 'algo: md5|sha1|sha256', '%s/%s' % (jid, nick)))

# ============== КОМАНДЫ ==============

def hash_cmd(type, jid, nick, text, algo='md5'):
    if not text:
        send_msg(type, jid, nick, L('What?', '%s/%s' % (jid, nick)))
        return
    result = _get_hash(text, algo)
    if result:
        send_msg(type, jid, nick, f"{algo.upper()}: {result}")
    else:
        send_msg(type, jid, nick, L('Unknown algorithm', '%s/%s' % (jid, nick)))

def hash_store_cmd(type, jid, nick, text, algo='md5'):
    if not text:
        send_msg(type, jid, nick, L('What?', '%s/%s' % (jid, nick)))
        return
    result = _get_hash(text, algo)
    if result:
        _store_hash(text, result, algo)
        send_msg(type, jid, nick, f"{algo.upper()}: {result} (saved)")
    else:
        send_msg(type, jid, nick, L('Unknown algorithm', '%s/%s' % (jid, nick)))

def hash_reverse_cmd(type, jid, nick, text, algo='md5'):
    if not text:
        send_msg(type, jid, nick, L('What?', '%s/%s' % (jid, nick)))
        return
    result = _lookup_hash(text.strip(), algo)
    if result:
        send_msg(type, jid, nick, f"{text} -> {result}")
    else:
        send_msg(type, jid, nick, L('Not found in rainbow table', '%s/%s' % (jid, nick)))

# ============== MD5 ==============

def md5(type, jid, nick, text):
    hash_cmd(type, jid, nick, text, 'md5')

def md5_save(type, jid, nick, text):
    hash_store_cmd(type, jid, nick, text, 'md5')

def md5_reverse(type, jid, nick, text):
    hash_reverse_cmd(type, jid, nick, text, 'md5')

# ============== SHA1 ==============

def sha1(type, jid, nick, text):
    hash_cmd(type, jid, nick, text, 'sha1')

def sha1_save(type, jid, nick, text):
    hash_store_cmd(type, jid, nick, text, 'sha1')

def sha1_reverse(type, jid, nick, text):
    hash_reverse_cmd(type, jid, nick, text, 'sha1')

# ============== SHA256 ==============

def sha256(type, jid, nick, text):
    hash_cmd(type, jid, nick, text, 'sha256')

def sha256_save(type, jid, nick, text):
    hash_store_cmd(type, jid, nick, text, 'sha256')

def sha256_reverse(type, jid, nick, text):
    hash_reverse_cmd(type, jid, nick, text, 'sha256')

# ============== УНИВЕРСАЛЬНЫЕ ==============

def hash_tool(type, jid, nick, text):
    if not text:
        send_msg(type, jid, nick, L('Usage: hash <md5|sha1|sha256> <text>', '%s/%s' % (jid, nick)))
        return
    parts = text.strip().split(' ', 1)
    if len(parts) < 2:
        send_msg(type, jid, nick, L('Usage: hash <md5|sha1|sha256> <text>', '%s/%s' % (jid, nick)))
        return
    algo, data = parts[0].lower(), parts[1]
    if algo == 'md5':
        hash_cmd(type, jid, nick, data, 'md5')
    elif algo == 'sha1':
        hash_cmd(type, jid, nick, data, 'sha1')
    elif algo == 'sha256':
        hash_cmd(type, jid, nick, data, 'sha256')
    else:
        send_msg(type, jid, nick, L('Unknown algorithm: %s', '%s/%s' % (jid, nick)) % algo)

def hash_reverse(type, jid, nick, text):
    if not text:
        send_msg(type, jid, nick, L('Usage: hash_reverse <md5|sha1|sha256> <hash>', '%s/%s' % (jid, nick)))
        return
    parts = text.strip().split(' ', 1)
    if len(parts) < 2:
        send_msg(type, jid, nick, L('Usage: hash_reverse <md5|sha1|sha256> <hash>', '%s/%s' % (jid, nick)))
        return
    algo, data = parts[0].lower(), parts[1]
    if algo == 'md5':
        hash_reverse_cmd(type, jid, nick, data, 'md5')
    elif algo == 'sha1':
        hash_reverse_cmd(type, jid, nick, data, 'sha1')
    elif algo == 'sha256':
        hash_reverse_cmd(type, jid, nick, data, 'sha256')
    else:
        send_msg(type, jid, nick, L('Unknown algorithm: %s', '%s/%s' % (jid, nick)) % algo)

# ============== ПОДРОБНАЯ СПРАВКА (ЗАКОММЕНТИРОВАНА) ==============

"""
=== ХЭШ-ИНСТРУМЕНТЫ ===

Команды:
  md5 <текст>                — MD5 хэш
  sha1 <текст>               — SHA1 хэш
  sha256 <текст>             — SHA256 хэш
  hash <algo> <текст>        — универсально (algo = md5|sha1|sha256)

Сохранение в радужную таблицу:
  md5_save <текст>           — сохранить MD5
  sha1_save <текст>          — сохранить SHA1
  sha256_save <текст>        — сохранить SHA256

Обратный поиск:
  md5_reverse <хэш>          — найти по MD5
  sha1_reverse <хэш>         — найти по SHA1
  sha256_reverse <хэш>       — найти по SHA256
  hash_reverse <algo> <хэш>  — универсально

Примеры:
  .md5 Admin
  .md5_save Admin
  .md5_reverse e3afed0047...
  .hash sha256 привет
  .hash_reverse md5 e3afed0047...

База хэшей: data/hash_rainbow.json
"""

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

_load_hash_db()

global execute

execute = [
    # Справка
    (3, 'hash_help', help_hash, 2, 'hash_help — short help'),

    # MD5
    (3, 'md5', md5, 2, 'md5 <text> — MD5 hash'),
    (4, 'md5_save', md5_save, 2, 'md5_save <text> — save MD5 to rainbow table'),
    (4, 'md5_reverse', md5_reverse, 2, 'md5_reverse <hash> — reverse MD5 lookup'),

    # SHA1
    (3, 'sha1', sha1, 2, 'sha1 <text> — SHA1 hash'),
    (4, 'sha1_save', sha1_save, 2, 'sha1_save <text> — save SHA1 to rainbow table'),
    (4, 'sha1_reverse', sha1_reverse, 2, 'sha1_reverse <hash> — reverse SHA1 lookup'),

    # SHA256
    (3, 'sha256', sha256, 2, 'sha256 <text> — SHA256 hash'),
    (4, 'sha256_save', sha256_save, 2, 'sha256_save <text> — save SHA256 to rainbow table'),
    (4, 'sha256_reverse', sha256_reverse, 2, 'sha256_reverse <hash> — reverse SHA256 lookup'),

    # Универсальные
    (3, 'hash', hash_tool, 2, 'hash <md5|sha1|sha256> <text> — universal hash'),
    (4, 'hash_reverse', hash_reverse, 2, 'hash_reverse <md5|sha1|sha256> <hash> — universal reverse'),
]
