import random import hashlib import telebot from t...
Creato il: 8 agosto 2026
Risposto usando GPT-5.6 Thinking da Chat01
Creato il: 8 agosto 2026
Risposto usando GPT-5.6 Thinking da Chat01
import random
import hashlib
import telebot
from telebot import apihelper
TOKEN = "8954396367:AAHdXT5-_l9pTKUkKdCk34wJgku5TZ2SGwg"
if not TOKEN:
raise RuntimeError(
"Не найден TELEGRAM_BOT_TOKEN."
)
bot = telebot.TeleBot(TOKEN, parse_mode="HTML")
apihelper.proxy = None
ALPHABET = (
"абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"
"1234567890"
" .,!?-"
)
BASE_EMOJIS = [
"😀", "😅", "😇", "🥰", "🤪", "🧐", "😎", "🤩",
"☹️", "🥶", "😴", "🤠", "☠️", "🤡", "👋", "👍",
"🤟", "💋", "👁", "🍁", "🍄", "🌙", "🌎", "💫",
"⭐️", "⚡️", "💥", "🔥", "❄️", "🍋", "🎂", "☕️",
"🍷", "🥃", "🍾", "⚽️", "🎱", "🎼", "🚘", "⚓️",
"🏛", "☎️", "⏰", "💵", "💰", "💎", "⚖️", "⚙️",
"🧨", "💊", "🎁", "🎀", "🎉", "✉️", "📎", "🔐",
"❤️", "❌", "⛔️", "🔞", "⚠️", "🔔", "📣", "♠️",
"♣️", "♥️", "♦️", "🏴☠️", "🏁", "🚩", "🔲", "☑️",
"🔺", "🔻", "🔸", "🔹", "🔱", "♻️", "🗂", "🔌",
"📺", "📸", "🕹", "🎲", "♟", "🏓",
# НОВЫЕ ЭМОДЗИ
"🦷", "👀", "👣", "👤", "🧵", "🦺", "👙", "🎩", "⛑", "👜",
"🌂", "👛", "🐶", "🐼", "🐸", "🐯", "🙉", "🙈", "🐵", "🙊",
"🐴", "🦋", "🪰", "🐝", "🪱", "🕷", "🦂", "🐢", "🐙", "🦞",
"🦀", "🦈", "🐳", "🐬", "🐊", "🐓", "🐇", "🌻", "🥚", "🍔",
"🍩", "🥛", "🤿", "🏏", "🎗", "♈️", "♉️", "♊️", "♋️", "♌️",
"♍️", "♎️", "♏️", "♐️", "♑️", "♒️", "♓️"
]
RUSSIAN_SYLLABLES = [
"ПРИ", "ВЕ", "ЗА", "ПО", "НА", "ОТ", "ДА", "НЕ", "РА", "СЯ",
"ТО", "СТА", "ВО", "ИЗ", "КО", "МО", "ТЕ", "ЧЕ", "ПЕ", "ЛИ",
"РУ", "СА", "ТА", "УТ", "ХО", "ЦА", "ЧА", "ШЕ", "ЩА", "ЬЕ",
"ЮС", "ЯС", "ДО", "МИ", "НО", "РО", "СО", "ША", "ЭТ", "БЫ",
"ГДЕ", "ЖЕ", "ЗДЕ", "КА", "ЛЕ", "МЕ", "ОБ", "РЕ",
"СЕ", "ТИ", "УЖ", "ФО", "ХА", "ЦЕ", "ЧИ", "ШИ", "ЩЕ", "ЭК",
"ЮМ", "ЯЗ", "АР", "БЕ", "ВИ", "ГЛ", "ДЕ", "ЕС", "ЁЛ", "ЖИ",
"ЗЕ", "ИК", "ЙО", "КЛ", "ЛЮ", "МН", "НИ", "ОВ", "ПЛ", "РЯ",
"СВ", "ТР", "УД", "ФР", "ХР", "ЦВ", "ЧУ", "ШЛ", "ЩУ", "ЭН",
"ЮР", "ЯТ", "АК", "БЛ", "ВН", "ГР", "ДР", "ЕВ", "ЁР", "ЖУ"
]
if len(set(BASE_EMOJIS)) != len(BASE_EMOJIS):
raise RuntimeError(
"В BASE_EMOJIS есть повторяющиеся emoji."
)
if len(BASE_EMOJIS) < len(ALPHABET):
raise RuntimeError(
f"Недостаточно emoji для алфавита. "
f"Нужно минимум {len(ALPHABET)}, "
f"есть {len(BASE_EMOJIS)}."
)
def build_cipher_table(key_word):
"""
Создаёт таблицу:
textбуква -> emoji слог -> emoji Для каждого ключа таблица будет одинаковой. Один и тот же ключ у отправителя и получателя даст одинаковое соответствие. """ digest = hashlib.sha256( key_word.encode("utf-8") ).digest() seed = int.from_bytes(digest, "big") rng = random.Random(seed) shuffled_emojis = BASE_EMOJIS.copy() rng.shuffle(shuffled_emojis) # -------------------------------------------------------- # Таблица букв # -------------------------------------------------------- char_to_emoji = {} emoji_to_char = {} for char, emoji in zip(ALPHABET, shuffled_emojis): char_to_emoji[char] = emoji emoji_to_char[emoji] = char # -------------------------------------------------------- # Таблица слогов # # Для слогов используем отдельную группу emoji. # # ВАЖНО: # один слог = один emoji # одна буква = один emoji # -------------------------------------------------------- syllable_to_emoji = {} emoji_to_syllable = {} # Используем emoji, которые НЕ используются # для одиночных букв. remaining_emojis = [ emoji for emoji in shuffled_emojis if emoji not in char_to_emoji.values() ] # Если emoji не хватает, создаём дополнительное # детерминированное перемешивание. # # В текущем наборе это может потребоваться, # потому что слогов больше, чем свободных emoji. if len(remaining_emojis) < len(RUSSIAN_SYLLABLES): # Для каждого слога создаём отдельный # составной emoji-код из двух emoji. # # Но чтобы расшифровка была однозначной, # ниже используется специальный формат. pass return ( char_to_emoji, emoji_to_char, shuffled_emojis )
def build_syllable_table(key_word):
"""
Создаёт таблицу слогов.
textКаждый слог получает один emoji. Чтобы получить достаточно уникальных кодов, используем все BASE_EMOJIS и детерминированно выбираем соответствия. """ digest = hashlib.sha256( ("SYLLABLES:" + key_word).encode("utf-8") ).digest() seed = int.from_bytes(digest, "big") rng = random.Random(seed) emojis = BASE_EMOJIS.copy() rng.shuffle(emojis) syllables = sorted( set(RUSSIAN_SYLLABLES), key=lambda x: (-len(x), x) ) # -------------------------------------------------------- # Здесь принципиальный момент: # # В BASE_EMOJIS меньше уникальных emoji, чем слогов. # # Поэтому один emoji не может быть назначен двум # разным слогам, иначе расшифровка будет неоднозначной. # # Поэтому создаём расширенный набор emoji-последовательностей. # -------------------------------------------------------- codes = [] # Одно emoji for emoji in emojis: codes.append(emoji) # Два emoji for first in emojis: for second in emojis: code = first + second if code not in codes: codes.append(code) if len(codes) >= len(syllables): break if len(codes) >= len(syllables): break if len(codes) < len(syllables): raise RuntimeError( "Невозможно создать уникальную таблицу слогов." ) syllable_to_emoji = {} emoji_to_syllable = {} for syllable, code in zip(syllables, codes): syllable_to_emoji[syllable] = code emoji_to_syllable[code] = syllable return syllable_to_emoji, emoji_to_syllable
def split_word(word):
"""
Разбирает слово слева направо.
textПриоритет: 3 буквы 2 буквы 1 буква То есть всегда выбирается самый длинный существующий слог. Если подходящего слога нет, используется одна буква. """ word_upper = word.upper() result = [] i = 0 while i < len(word_upper): matched = False # Сначала ищем 3-буквенный слог # затем 2-буквенный. for length in (3, 2): if i + length <= len(word_upper): candidate = word_upper[ i:i + length ] if candidate in RUSSIAN_SYLLABLES: result.append(candidate) i += length matched = True break # ---------------------------------------------------- # Если слог не найден: # одна буква # ---------------------------------------------------- if not matched: result.append(word_upper[i]) i += 1 return result
def encrypt_word(word, syllable_to_emoji, char_to_emoji):
"""
Например:
textПРИВЕТ может превратиться в: ПРИ | ВЕ | Т а затем: 😀 | 🔥 | 🎲 """ parts = split_word(word) result = [] for part in parts: # ---------------------------------------------------- # Если это найденный слог # ---------------------------------------------------- if part in syllable_to_emoji: result.append( syllable_to_emoji[part] ) # ---------------------------------------------------- # Если это одиночная буква # ---------------------------------------------------- elif part in char_to_emoji: result.append( char_to_emoji[part] ) else: # На всякий случай оставляем символ. result.append(part) return "".join(result)
def encrypt(text, key_word):
textchar_to_emoji, emoji_to_char, _ = \ build_cipher_table(key_word) syllable_to_emoji, emoji_to_syllable = \ build_syllable_table(key_word) result = [] # -------------------------------------------------------- # Разбираем текст на слова и отдельные символы. # # Пробелы и знаки препинания сохраняются. # -------------------------------------------------------- current_word = [] def flush_word(): if not current_word: return word = "".join(current_word) result.append( encrypt_word( word, syllable_to_emoji, char_to_emoji ) ) current_word.clear() for char in text: if char.isalpha(): current_word.append(char) else: flush_word() # Пробелы, запятые, точки и т.д. # остаются без изменений. if char in char_to_emoji: result.append( char_to_emoji[char] ) else: result.append(char) flush_word() return "".join(result)
def decrypt(emoji_text, key_word):
textchar_to_emoji, emoji_to_char, _ = \ build_cipher_table(key_word) syllable_to_emoji, emoji_to_syllable = \ build_syllable_table(key_word) # -------------------------------------------------------- # Создаём общий словарь: # # emoji-код -> слог # emoji-код -> буква # # Сначала проверяем длинные коды. # -------------------------------------------------------- decode_map = {} for emoji, char in emoji_to_char.items(): decode_map[emoji] = char for emoji, syllable in emoji_to_syllable.items(): decode_map[emoji] = syllable # -------------------------------------------------------- # Сортируем по длине. # # Это критически важно, потому что слог может иметь # код из двух emoji. # -------------------------------------------------------- codes_sorted = sorted( decode_map.keys(), key=len, reverse=True ) result = [] i = 0 while i < len(emoji_text): found = False # ---------------------------------------------------- # Ищем самый длинный подходящий код. # ---------------------------------------------------- for code in codes_sorted: if emoji_text.startswith(code, i): result.append( decode_map[code] ) i += len(code) found = True break # ---------------------------------------------------- # Если это обычный символ: # пробел, запятая и т.д. # ---------------------------------------------------- if not found: result.append( emoji_text[i] ) i += 1 # -------------------------------------------------------- # Важно: # # В исходном тексте слоги были записаны заглавными, # поэтому приводим расшифрованные слоги к обычному # регистру по исходной структуре здесь нельзя. # # Поэтому после расшифровки восстанавливаем слова # с заглавной первой буквой. # -------------------------------------------------------- decoded = "".join(result) # Слоги сейчас получаются заглавными. # Приводим их к нижнему регистру, сохраняя первую # букву предложения/слова автоматически отдельно. decoded = decoded.lower() # -------------------------------------------------------- # Восстанавливаем заглавные буквы по позиции: # первая буква текста и первая буква после . ! ? # -------------------------------------------------------- chars = list(decoded) capitalize_next = True for index, char in enumerate(chars): if char.isalpha() and capitalize_next: chars[index] = char.upper() capitalize_next = False if char in ".!?": capitalize_next = True return "".join(chars)
def clean_key(key_word):
textcleaned = "".join( char for char in key_word if char.isalpha() ) if not cleaned: return "default" return cleaned
def generate_unique_key():
textkey_words = [ "ромашка", "луна", "звезда", "ветер", "река", "гора", "лес", "море", "солнце", "облако", "дождь", "снег", "радуга", "молния", "камень", "цветок", "дерево", "птица", "волна", "скала", "туча", "заря", "туман", "роса", "иней", "град", "буря", "шторм", "штиль", "ветвь" ] return random.choice(key_words)
def looks_like_encrypted_text(text, key_word):
textchar_to_emoji, _, _ = \ build_cipher_table(key_word) _, emoji_to_syllable = \ build_syllable_table(key_word) known_codes = set(char_to_emoji.values()) known_codes.update( emoji_to_syllable.keys() ) for code in known_codes: if text.startswith(code): return True return False
@bot.message_handler(commands=["start"])
def send_welcome(message):
texttext = ( "🔐 <b>EnigmaBot</b>\n\n" "📌 <b>Как использовать:</b>\n\n" "1️⃣ Получите ключ:\n" "<code>/newkey</code>\n\n" "2️⃣ Чтобы зашифровать сообщение:\n" "<code>Привет, мир! ромашка</code>\n\n" "3️⃣ Для расшифровки отправьте emoji " "и тот же ключ.\n\n" "🔤 Сначала используются слоги.\n" "🔹 Если подходящего слога нет — " "используется одна буква.\n\n" "⚠️ Ключ должны знать только " "отправитель и получатель." ) bot.reply_to(message, text)
@bot.message_handler(commands=["newkey"])
def generate_new_key(message):
textnew_key = generate_unique_key() text = ( "🆕 <b>Новый ключ:</b>\n\n" f"<code>{new_key}</code>\n\n" "💡 Можно использовать и любой другой ключ." ) bot.reply_to(message, text)
@bot.message_handler(func=lambda message: True)
def handle_all_messages(message):
texttext = message.text if not text: return # -------------------------------------------------------- # Последнее слово = ключ # -------------------------------------------------------- parts = text.rsplit(" ", 1) if len(parts) != 2: bot.reply_to( message, "❌ <b>Ошибка формата</b>\n\n" "Нужно написать текст и через пробел ключ.\n\n" "Пример:\n" "<code>Привет, мир! ромашка</code>" ) return content = parts[0].strip() raw_key = parts[1].strip() if not content: bot.reply_to( message, "❌ Текст для обработки пустой." ) return if not raw_key: bot.reply_to( message, "❌ Ключ не указан." ) return key_word = clean_key(raw_key) # -------------------------------------------------------- # Определяем направление # -------------------------------------------------------- is_encrypted = looks_like_encrypted_text( content, key_word ) # ======================================================== # РАСШИФРОВКА # ======================================================== if is_encrypted: try: result = decrypt( content, key_word ) bot.reply_to( message, "🔓 <b>Расшифровано:</b>\n\n" f"<code>{result}</code>" ) except Exception as error: bot.reply_to( message, "❌ <b>Ошибка расшифровки:</b>\n\n" f"<code>{str(error)}</code>" ) return # ======================================================== # ШИФРОВАНИЕ # ======================================================== try: result = encrypt( content, key_word ) bot.reply_to( message, "🔒 <b>Зашифровано:</b>\n\n" f"{result}\n\n" f"🗝 <b>Ключ:</b> " f"<code>{raw_key}</code>" ) except Exception as error: bot.reply_to( message, "❌ <b>Ошибка шифрования:</b>\n\n" f"<code>{str(error)}</code>" )
if name == "main":
textprint("==========================================") print("🔐 EnigmaBot") print("==========================================") print("✅ Бот запущен!") print("✅ Русский язык") print("✅ Слоговое emoji-шифрование") print("✅ Одиночные буквы как fallback") print("✅ Шифрование обратимое") print(f"✅ Всего emoji: {len(BASE_EMOJIS)}") print("==========================================") bot.infinity_polling( skip_pending=True, timeout=30, long_polling_timeout=30
найти ошибки и исправить
)
pythonbase = [ "😀", "😅", "😇", "🥰", "🤪", "🧐", "😎", "🤩", "☹️", "🥶", "😴", "🤠", "☠️", "🤡", "👋", "👍", "🤟", "💋", "👁", "🍁", "🍄", "🌙", "🌎", "💫", "⭐️", "⚡️", "💥", "🔥", "❄️", "🍋", "🎂", "☕️", "🍷", "🥃", "🍾", "⚽️", "🎱", "🎼", "🚘", "⚓️", "🏛", "☎️", "⏰", "💵", "💰", "💎", "⚖️", "⚙️", "🧨", "💊", "🎁", "🎀", "🎉", "✉️", "📎", "🔐", "❤️", "❌", "⛔️", "🔞", "⚠️", "🔔", "📣", "♠️", "♣️", "♥️", "♦️", "🏴☠️", "🏁", "🚩", "🔲", "☑️", "🔺", "🔻", "🔸", "🔹", "🔱", "♻️", "🗂", "🔌", "📺", "📸", "🕹", "🎲", "♟", "🏓", "🦷", "👀", "👣", "👤", "🧵", "🦺", "👙", "🎩", "⛑", "👜", "🌂", "👛", "🐶", "🐼", "🐸", "🐯", "🙉", "🙈", "🐵", "🙊", "🐴", "🦋", "🪰", "🐝", "🪱", "🕷", "🦂", "🐢", "🐙", "🦞", "🦀 (143, 143, 98, 98, 82)
pythonimport random, hashlib, unicodedata BASE_EMOJIS = base RUSSIAN_SYLLABLES = syll ALPHABET = alphabet CIPHER_PREFIX = "🧿" LITERAL_PREFIX = "🪪" SYLLABLE_MARKERS = { "lower": "🧩", "upper": "🧱", "title": "🪄", } SPECIAL_TOKENS = [CIPHER_PREFIX, LITERAL_PREFIX, *SYLLABLE_MARKERS.values()] assert not (set(SPECIAL_TOKENS) & set(BASE_EMOJIS)) assert len(BASE_EMOJIS) >= len(ALPHABET) assert len(BASE_EMOJIS) >= len(set(RUSSIAN_SYLLABLES)) def _rng(key, namespace): digest = hashlib.sha256(f"{namespace}\0{key}".encode("utf-8")).digest() return random.Random(int.from_bytes(digest, "big")) def build_cipher_table(key): emojis = BASE_EMOJIS.copy() _rng(key, "CHARS").shuffle(emojis) c2e = dict(zip(ALPHABET, emojis)) return
Да. Основные ошибки здесь не в telebot, а в самой схеме шифрования.
Самые критичные:
@BotFather и выпустить новый; старый больше не использовать. Telegram прямо рекомендует хранить токен как пароль. (Telegram)decode_map[emoji] = syllable часть буквенных соответствий перезаписывается.pass в build_cipher_table() ничего не исправляет, а вычисленная там таблица слогов вообще не возвращается и не используется.word.upper() и последующего decoded.lower().looks_like_encrypted_text() проверяет только начало строки, поэтому зашифрованный текст может ошибочно распознаться как обычный.<code>...</code>, нужно экранировать через html.escape().generate_unique_key() на самом деле не генерирует уникальный ключ.clean_key() превращает, например, ключ123 и ключ456 в один ключ ключ.найти ошибки и исправить действительно находится внутри файла между аргументами infinity_polling(), это ещё и обычная SyntaxError.infinity_polling(skip_pending=True, timeout=30, long_polling_timeout=30) корректен для актуальной версии pyTelegramBotAPI. (pyTelegramBotAPI)Ниже исправленная версия. Я также поменял формат шифра: каждый зашифрованный текст начинается с 🧿, а слоги получают специальный маркер регистра. Благодаря этому буква и слог больше не могут конфликтовать.
pythonimport os import html import random import hashlib import secrets import unicodedata import telebot from telebot import apihelper # ============================================================ # НАСТРОЙКИ # ============================================================ # Никогда не храните реальный токен прямо в исходнике. TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") if not TOKEN: raise RuntimeError( "Не найден TELEGRAM_BOT_TOKEN. " "Добавьте токен в переменную окружения." ) # ============================================================ # СОЗДАНИЕ БОТА # ============================================================ bot = telebot.TeleBot( TOKEN, parse_mode="HTML" ) apihelper.proxy = None # ============================================================ # АЛФАВИТ # ============================================================ ALPHABET = ( "абвгдеёжзийклмнопрстуфхцчшщъыьэюя" "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ" "1234567890" " .,!?-" ) # ============================================================ # EMOJI # ============================================================ BASE_EMOJIS = [ "😀", "😅", "😇", "🥰", "🤪", "🧐", "😎", "🤩", "☹️", "🥶", "😴", "🤠", "☠️", "🤡", "👋", "👍", "🤟", "💋", "👁", "🍁", "🍄", "🌙", "🌎", "💫", "⭐️", "⚡️", "💥", "🔥", "❄️", "🍋", "🎂", "☕️", "🍷", "🥃", "🍾", "⚽️", "🎱", "🎼", "🚘", "⚓️", "🏛", "☎️", "⏰", "💵", "💰", "💎", "⚖️", "⚙️", "🧨", "💊", "🎁", "🎀", "🎉", "✉️", "📎", "🔐", "❤️", "❌", "⛔️", "🔞", "⚠️", "🔔", "📣", "♠️", "♣️", "♥️", "♦️", "🏴☠️", "🏁", "🚩", "🔲", "☑️", "🔺", "🔻", "🔸", "🔹", "🔱", "♻️", "🗂", "🔌", "📺", "📸", "🕹", "🎲", "♟", "🏓", "🦷", "👀", "👣", "👤", "🧵", "🦺", "👙", "🎩", "⛑", "👜", "🌂", "👛", "🐶", "🐼", "🐸", "🐯", "🙉", "🙈", "🐵", "🙊", "🐴", "🦋", "🪰", "🐝", "🪱", "🕷", "🦂", "🐢", "🐙", "🦞", "🦀", "🦈", "🐳", "🐬", "🐊", "🐓", "🐇", "🌻", "🥚", "🍔", "🍩", "🥛", "🤿", "🏏", "🎗", "♈️", "♉️", "♊️", "♋️", "♌️", "♍️", "♎️", "♏️", "♐️", "♑️", "♒️", "♓️" ] # ============================================================ # СЛОГИ # ============================================================ RUSSIAN_SYLLABLES = [ "ПРИ", "ВЕ", "ЗА", "ПО", "НА", "ОТ", "ДА", "НЕ", "РА", "СЯ", "ТО", "СТА", "ВО", "ИЗ", "КО", "МО", "ТЕ", "ЧЕ", "ПЕ", "ЛИ", "РУ", "СА", "ТА", "УТ", "ХО", "ЦА", "ЧА", "ШЕ", "ЩА", "ЬЕ", "ЮС", "ЯС", "ДО", "МИ", "НО", "РО", "СО", "ША", "ЭТ", "БЫ", "ГДЕ", "ЖЕ", "ЗДЕ", "КА", "ЛЕ", "МЕ", "ОБ", "РЕ", "СЕ", "ТИ", "УЖ", "ФО", "ХА", "ЦЕ", "ЧИ", "ШИ", "ЩЕ", "ЭК", "ЮМ", "ЯЗ", "АР", "БЕ", "ВИ", "ГЛ", "ДЕ", "ЕС", "ЁЛ", "ЖИ", "ЗЕ", "ИК", "ЙО", "КЛ", "ЛЮ", "МН", "НИ", "ОВ", "ПЛ", "РЯ", "СВ", "ТР", "УД", "ФР", "ХР", "ЦВ", "ЧУ", "ШЛ", "ЩУ", "ЭН", "ЮР", "ЯТ", "АК", "БЛ", "ВН", "ГР", "ДР", "ЕВ", "ЁР", "ЖУ" ] SYLLABLE_SET = set(RUSSIAN_SYLLABLES) # ============================================================ # СПЕЦИАЛЬНЫЕ МАРКЕРЫ # ============================================================ # Признак того, что сообщение зашифровано этим алгоритмом. CIPHER_PREFIX = "🧿" # Если исходный текст уже содержит emoji из таблицы, # экранируем его этим символом. LITERAL_PREFIX = "🪪" # Маркеры регистра слога. SYLLABLE_MARKERS = { "lower": "🧩", "upper": "🧱", "title": "🪄", } SPECIAL_TOKENS = [ CIPHER_PREFIX, LITERAL_PREFIX, *SYLLABLE_MARKERS.values() ] # ============================================================ # ПРОВЕРКИ # ============================================================ if len(BASE_EMOJIS) != len(set(BASE_EMOJIS)): raise RuntimeError( "В BASE_EMOJIS есть повторяющиеся emoji." ) if len(BASE_EMOJIS) < len(ALPHABET): raise RuntimeError( f"Недостаточно emoji для алфавита: " f"нужно {len(ALPHABET)}, " f"есть {len(BASE_EMOJIS)}." ) if len(BASE_EMOJIS) < len(SYLLABLE_SET): raise RuntimeError( f"Недостаточно emoji для слогов: " f"нужно {len(SYLLABLE_SET)}, " f"есть {len(BASE_EMOJIS)}." ) for token in SPECIAL_TOKENS: if token in BASE_EMOJIS: raise RuntimeError( f"Служебный emoji {token} " f"не должен находиться в BASE_EMOJIS." ) # ============================================================ # ДЕТЕРМИНИРОВАННЫЙ RANDOM # ============================================================ def create_rng(key_word, namespace): """ Для одинакового ключа всегда создаётся одинаковая последовательность. """ data = ( namespace + "\0" + key_word ).encode("utf-8") digest = hashlib.sha256(data).digest() seed = int.from_bytes( digest, "big" ) return random.Random(seed) # ============================================================ # ТАБЛИЦА БУКВ # ============================================================ def build_cipher_table(key_word): emojis = BASE_EMOJIS.copy() rng = create_rng( key_word, "CHARS" ) rng.shuffle(emojis) char_to_emoji = dict( zip( ALPHABET, emojis ) ) emoji_to_char = { emoji: char for char, emoji in char_to_emoji.items() } return ( char_to_emoji, emoji_to_char ) # ============================================================ # ТАБЛИЦА СЛОГОВ # ============================================================ def build_syllable_table(key_word): emojis = BASE_EMOJIS.copy() rng = create_rng( key_word, "SYLLABLES" ) rng.shuffle(emojis) syllables = sorted( SYLLABLE_SET, key=lambda value: ( -len(value), value ) ) syllable_to_emoji = dict( zip( syllables, emojis ) ) emoji_to_syllable = { emoji: syllable for syllable, emoji in syllable_to_emoji.items() } return ( syllable_to_emoji, emoji_to_syllable ) # ============================================================ # ОПРЕДЕЛЕНИЕ РЕГИСТРА # ============================================================ def get_case_mode(text): if text.islower(): return "lower" if text.isupper(): return "upper" if ( text[:1].isupper() and text[1:].islower() ): return "title" return None # ============================================================ # РАЗБОР СЛОВА # ============================================================ def split_word(word): """ Возвращает элементы вида: ("syllable", "ПРИ", "lower") ("char", "т", None) Регистр исходного текста не теряется. """ result = [] index = 0 while index < len(word): matched = False for length in (3, 2): if index + length > len(word): continue original = word[ index:index + length ] canonical = original.upper() case_mode = get_case_mode( original ) if ( canonical in SYLLABLE_SET and case_mode is not None ): result.append( ( "syllable", canonical, case_mode ) ) index += length matched = True break if not matched: result.append( ( "char", word[index], None ) ) index += 1 return result # ============================================================ # ШИФРОВАНИЕ СЛОВА # ============================================================ def encrypt_word( word, syllable_to_emoji, char_to_emoji ): result = [] for ( item_type, value, case_mode ) in split_word(word): if item_type == "syllable": marker = ( SYLLABLE_MARKERS[ case_mode ] ) payload = ( syllable_to_emoji[ value ] ) # Например: # # 🧩😀 = нижний регистр # 🧱😀 = верхний регистр # 🪄😀 = Первая заглавная result.append( marker + payload ) else: result.append( char_to_emoji.get( value, value ) ) return "".join(result) # ============================================================ # ПОИСК EMOJI В ТЕКСТЕ # ============================================================ COLLISION_TOKENS = sorted( set( BASE_EMOJIS + SPECIAL_TOKENS ), key=len, reverse=True ) def match_token( text, position, tokens ): for token in tokens: if text.startswith( token, position ): return token return None # ============================================================ # ШИФРОВАНИЕ # ============================================================ def encrypt(text, key_word): ( char_to_emoji, _ ) = build_cipher_table( key_word ) ( syllable_to_emoji, _ ) = build_syllable_table( key_word ) # Префикс позволяет однозначно понять, # что перед нами ciphertext. result = [ CIPHER_PREFIX ] current_word = [] def flush_word(): if not current_word: return word = "".join( current_word ) result.append( encrypt_word( word, syllable_to_emoji, char_to_emoji ) ) current_word.clear() index = 0 while index < len(text): # -------------------------------------------- # Если пользователь сам написал emoji, # который используется нашим шифром, # его необходимо экранировать. # -------------------------------------------- collision = match_token( text, index, COLLISION_TOKENS ) if collision is not None: flush_word() result.append( LITERAL_PREFIX + collision ) index += len( collision ) continue char = text[index] # -------------------------------------------- # Буквы собираем в слово, # чтобы использовать слоги. # -------------------------------------------- if char.isalpha(): current_word.append( char ) else: flush_word() result.append( char_to_emoji.get( char, char ) ) index += 1 flush_word() return "".join(result) # ============================================================ # РАСШИФРОВКА # ============================================================ def decrypt( emoji_text, key_word ): if not emoji_text.startswith( CIPHER_PREFIX ): raise ValueError( "Текст не имеет префикса EnigmaBot." ) ( _, emoji_to_char ) = build_cipher_table( key_word ) ( _, emoji_to_syllable ) = build_syllable_table( key_word ) char_codes = sorted( emoji_to_char.keys(), key=len, reverse=True ) syllable_codes = sorted( emoji_to_syllable.keys(), key=len, reverse=True ) marker_to_mode = { marker: mode for mode, marker in SYLLABLE_MARKERS.items() } markers = sorted( marker_to_mode.keys(), key=len, reverse=True ) result = [] index = len( CIPHER_PREFIX ) while index < len( emoji_text ): # ==================================================== # ЭКРАНИРОВАННЫЙ ИСХОДНЫЙ EMOJI # ==================================================== if emoji_text.startswith( LITERAL_PREFIX, index ): index += len( LITERAL_PREFIX ) literal = match_token( emoji_text, index, COLLISION_TOKENS ) if literal is None: raise ValueError( "Повреждён escape-код." ) result.append( literal ) index += len( literal ) continue # ==================================================== # СЛОГ # ==================================================== marker = match_token( emoji_text, index, markers ) if marker is not None: case_mode = ( marker_to_mode[ marker ] ) index += len( marker ) syllable_code = ( match_token( emoji_text, index, syllable_codes ) ) if syllable_code is None: raise ValueError( "Повреждён код слога." ) syllable = ( emoji_to_syllable[ syllable_code ] ) if case_mode == "lower": syllable = ( syllable.lower() ) elif case_mode == "title": syllable = ( syllable.capitalize() ) # upper уже хранится # в верхнем регистре. result.append( syllable ) index += len( syllable_code ) continue # ==================================================== # ОДИНОЧНАЯ БУКВА / ЦИФРА / ЗНАК # ==================================================== char_code = match_token( emoji_text, index, char_codes ) if char_code is not None: result.append( emoji_to_char[ char_code ] ) index += len( char_code ) continue # ==================================================== # НЕИЗВЕСТНЫЙ СИМВОЛ # ==================================================== result.append( emoji_text[index] ) index += 1 return "".join(result) # ============================================================ # КЛЮЧ # ============================================================ def clean_key(key_word): key_word = unicodedata.normalize( "NFKC", key_word ) key_word = key_word.strip() if not key_word: raise ValueError( "Ключ не может быть пустым." ) # В отличие от старого варианта # цифры и символы не удаляем. return key_word # ============================================================ # ГЕНЕРАЦИЯ КЛЮЧА # ============================================================ def generate_unique_key(): key_words = [ "ромашка", "луна", "звезда", "ветер", "река", "гора", "лес", "море", "солнце", "облако", "дождь", "снег", "радуга", "молния", "камень", "цветок", "дерево", "птица", "волна", "скала", "туча", "заря", "туман", "роса", "иней", "град", "буря", "шторм", "штиль", "ветвь" ] word = secrets.choice( key_words ) # Добавляем случайную часть, # чтобы ключи не повторялись постоянно. suffix = secrets.token_hex(6) return ( f"{word}-{suffix}" ) # ============================================================ # ОПРЕДЕЛЕНИЕ НАПРАВЛЕНИЯ # ============================================================ def looks_like_encrypted_text(text): return text.startswith( CIPHER_PREFIX ) # ============================================================ # /START # ============================================================ @bot.message_handler( commands=["start"] ) def send_welcome(message): text = ( "🔐 <b>EnigmaBot</b>\n\n" "📌 <b>Как использовать:</b>\n\n" "1️⃣ Получите ключ:\n" "<code>/newkey</code>\n\n" "2️⃣ Чтобы зашифровать сообщение:\n" "<code>Привет, мир! ромашка</code>\n\n" "3️⃣ Для расшифровки отправьте " "зашифрованный текст и тот же ключ.\n\n" "🔤 Сначала используются слоги.\n" "🔹 Если слог не найден — " "используется одна буква.\n\n" "✅ Регистр букв сохраняется.\n" "✅ Исходные emoji сохраняются.\n\n" "⚠️ Отправитель и получатель " "должны использовать один ключ." ) bot.reply_to( message, text ) # ============================================================ # /NEWKEY # ============================================================ @bot.message_handler( commands=["newkey"] ) def generate_new_key(message): new_key = ( generate_unique_key() ) text = ( "🆕 <b>Новый ключ:</b>\n\n" f"<code>{html.escape(new_key)}</code>\n\n" "💡 Можно использовать " "и собственный ключ без пробелов." ) bot.reply_to( message, text ) # ============================================================ # ОБРАБОТКА СООБЩЕНИЙ # ============================================================ @bot.message_handler( content_types=["text"], func=lambda message: True ) def handle_all_messages(message): text = message.text if not text: return # Последняя группа без пробелов = ключ. parts = text.rsplit( maxsplit=1 ) if len(parts) != 2: bot.reply_to( message, "❌ <b>Ошибка формата</b>\n\n" "Напишите текст и через пробел ключ.\n\n" "Пример:\n" "<code>Привет, мир! ромашка</code>" ) return content = ( parts[0].strip() ) raw_key = ( parts[1].strip() ) if not content: bot.reply_to( message, "❌ Текст для обработки пустой." ) return if not raw_key: bot.reply_to( message, "❌ Ключ не указан." ) return try: key_word = clean_key( raw_key ) except ValueError as error: bot.reply_to( message, "❌ <b>Ошибка ключа:</b>\n\n" f"<code>{html.escape(str(error))}</code>" ) return # ======================================================== # РАСШИФРОВКА # ======================================================== if looks_like_encrypted_text( content ): try: result = decrypt( content, key_word ) bot.reply_to( message, "🔓 <b>Расшифровано:</b>\n\n" f"<code>{html.escape(result)}</code>" ) except Exception as error: bot.reply_to( message, "❌ <b>Ошибка расшифровки:</b>\n\n" f"<code>{html.escape(str(error))}</code>" ) return # ======================================================== # ШИФРОВАНИЕ # ======================================================== try: result = encrypt( content, key_word ) bot.reply_to( message, "🔒 <b>Зашифровано:</b>\n\n" f"{result}\n\n" "🗝 <b>Ключ:</b> " f"<code>{html.escape(raw_key)}</code>" ) except Exception as error: bot.reply_to( message, "❌ <b>Ошибка шифрования:</b>\n\n" f"<code>{html.escape(str(error))}</code>" ) # ============================================================ # ЗАПУСК # ============================================================ if __name__ == "__main__": print("==========================================") print("🔐 EnigmaBot") print("==========================================") print("✅ Бот запущен!") print("✅ Русский язык") print("✅ Слоговое emoji-шифрование") print("✅ Регистр сохраняется") print("✅ Исходные emoji экранируются") print("✅ Однозначная расшифровка") print(f"✅ Всего emoji: {len(BASE_EMOJIS)}") print(f"✅ Всего слогов: {len(SYLLABLE_SET)}") print("==========================================") bot.infinity_polling( skip_pending=True, timeout=30, long_polling_timeout=30 )
Я проверил ключевое свойство новой схемы на строках вроде Привет, мир!, ПРИВЕТ, привет, смешанном регистре, цифрах и исходных emoji: decrypt(encrypt(text, key), key) возвращает исходную строку.
Чтобы запустить безопасно, после замены скомпрометированного токена задайте новый токен через переменную окружения, например в Linux/macOS:
bashexport TELEGRAM_BOT_TOKEN="НОВЫЙ_ТОКЕН" python bot.py
В Windows PowerShell:
powershell$env:TELEGRAM_BOT_TOKEN="НОВЫЙ_ТОКЕН" python bot.py
Важно: это всё ещё пользовательский emoji-шифр, а не криптографически стойкое шифрование для секретной информации. Если задача именно в реальной конфиденциальности переписки, архитектуру лучше сделать как нормальное шифрование + представление результата через emoji, а не как подстановочный шифр.