# ______ ___ ___ _ _
# ____ | ___ \ | \/ | | | | |
# / __ \| |_/ / _| . . | ___ __| |_ _| | ___
# / / _` | __/ | | | |\/| |/ _ \ / _` | | | | |/ _ \
# | | (_| | | | |_| | | | | (_) | (_| | |_| | | __/
# \ \__,_\_| \__, \_| |_/\___/ \__,_|\__,_|_|\___|
# \____/ __/ |
# |___/
# На модуль распространяется лицензия "GNU General Public License v3.0"
# https://github.com/all-licenses/GNU-General-Public-License-v3.0
# meta developer: @pymodule
# meta fhsdesc: tool, tools, ai, username
# requires: aiohttp
import asyncio
import aiohttp
import logging
import re
from telethon import functions
from .. import loader, utils
@loader.tds
class AiUsernameGen(loader.Module):
"""AI-powered username generation and automatic creation of public channels with available usernames. (Before you begin, set up the config: .config AiUsernameGen)"""
strings = {
"name": "AiUsernameGen",
"no_prompt": "🚫 Specify a query to generate a username",
"checking": "🤖 Generating and checking username availability...",
"created_many": "✅ Public channels have been created:\n{}",
"available_many": "✅ Available usernames found (no auto-creation):\n{}",
"no_free": "😔 No available usernames found. Try a different search!",
"error_ai": "❌ Error requesting AI. Check your configuration.",
"config_api_key": "Key API for AI (https://openrouter.ai/settings/keys)",
"config_model": "Model AI for generation",
"config_channel_title_prefix": "Prefix for channel title (use {username} to insert username)",
"config_channel_about": "Channel description",
"config_autocreate_channels": "Automatically create channels for available usernames (True/False)",
}
strings_ru = {
"_cls_doc": "Генерация username с помощью AI и автоматическое создание публичных каналов с доступными юзернеймами. (Перед началом настройте модуль: .config AiUsernameGen)",
"no_prompt": "🚫 Укажите запрос для генерации username",
"checking": "🤖 Генерация и проверка доступности username...",
"created_many": "✅ Созданы публичные каналы:\n{}",
"available_many": "✅ Доступные username найдены (автосоздание выключено):\n{}",
"no_free": "😔 Свободных username не найдено. Попробуйте другой запрос!",
"error_ai": "❌ Ошибка при запросе к AI. Проверьте конфигурацию.",
"config_api_key": "Ключ API для AI (https://openrouter.ai/settings/keys)",
"config_model": "Модель AI для генерации",
"config_channel_title_prefix": "Префикс для заголовка канала (используйте {username} для вставки username)",
"config_channel_about": "Описание канала",
"config_autocreate_channels": "Автоматически создавать каналы с доступными username (True/False)",
}
USERNAME_REGEX = re.compile(r'^[a-zA-Z][\w\d]{3,30}[a-zA-Z\d]$')
SYSTEM_PROMPT = (
"Ты без дополнительных слов будешь придумывать ровно 10 уникальных username. "
"Не меньше, не больше! После каждого username делай перенос строки. "
"Не используй символ @. Условие: username должен состоять из 5 или более символов "
"и соответствовать паттерну [a-zA-Z][\\w\\d]{3,30}[a-zA-Z\\d]. "
"Сделай их креативными и релевантными запросу."
)
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"AI_KEY",
"your_token_here",
lambda: self.strings["config_api_key"],
validator=loader.validators.String()
),
loader.ConfigValue(
"MODEL",
"deepseek/deepseek-r1-0528:free",
lambda: self.strings["config_model"],
validator=loader.validators.String()
),
loader.ConfigValue(
"CHANNEL_TITLE_PREFIX",
"Юзернейм: {username}",
lambda: self.strings["config_channel_title_prefix"],
validator=loader.validators.String()
),
loader.ConfigValue(
"CHANNEL_ABOUT",
"@pymodule",
lambda: self.strings["config_channel_about"],
validator=loader.validators.String()
),
loader.ConfigValue(
"AUTOCREATE_CHANNELS",
True,
lambda: self.strings["config_autocreate_channels"],
validator=loader.validators.Boolean()
)
)
async def client_ready(self, client, db):
self.client = client
self.db = db
self.logger = logging.getLogger(__name__)
self.api_url = "https://openrouter.ai/api/v1/chat/completions"
async def _query_ai(self, prompt: str) -> str | None:
try:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) as session:
headers = {
"Authorization": f"Bearer {self.config['AI_KEY']}",
"Content-Type": "application/json"
}
payload = {
"model": self.config["MODEL"],
"messages": [
{"role": "system", "content": self.SYSTEM_PROMPT},
{"role": "user", "content": prompt}
],
"temperature": 0.7,
"max_tokens": 150
}
async with session.post(self.api_url, json=payload, headers=headers) as resp:
if resp.status != 200:
self.logger.error(f"AI response status: {resp.status} - {await resp.text()}")
return None
res = await resp.json()
if "choices" not in res or not res["choices"]:
self.logger.error("Invalid AI response structure")
return None
return res["choices"][0]["message"]["content"].strip()
except Exception as e:
self.logger.error(f"AI request error: {e}")
return None
async def _is_username_available(self, username: str) -> bool:
try:
return await self.client(functions.account.CheckUsernameRequest(username=username))
except Exception as e:
self.logger.error(f"Error checking username '{username}': {e}")
return False
async def get_available(self, usernames: list[str]) -> list[str]:
semaphore = asyncio.Semaphore(10)
async def check_one(u: str):
async with semaphore:
return u if await self._is_username_available(u) else None
tasks = [check_one(u) for u in usernames]
results = await asyncio.gather(*tasks)
return [r for r in results if r is not None]
async def create_channels(self, usernames: list[str]) -> list[str]:
semaphore = asyncio.Semaphore(3)
async def create_one(u: str):
async with semaphore:
return await self._create_channel_with_username(u)
tasks = [create_one(u) for u in usernames]
results = await asyncio.gather(*tasks)
return [r for r in results if r is not None]
async def _create_channel_with_username(self, username: str) -> str | None:
title = self.config["CHANNEL_TITLE_PREFIX"].format(username=username)
about = self.config["CHANNEL_ABOUT"]
try:
if not await self._is_username_available(username):
return None
result = await self.client(functions.channels.CreateChannelRequest(
title=title,
about=about,
broadcast=True
))
channel = result.chats[0]
await self.client(functions.channels.UpdateUsernameRequest(
channel=channel,
username=username
))
self.logger.info(f"Successfully created channel with username: {username}")
return username
except Exception as e:
self.logger.error(f"Error creating channel for '{username}': {e}")
try:
if 'channel' in locals():
await self.client(functions.channels.DeleteChannelRequest(channel=channel))
except:
pass
return None
def _filter_and_validate_usernames(self, ai_text: str) -> list[str]:
lines = [u.strip() for u in ai_text.splitlines() if u.strip()]
valid = []
for u in lines[:10]:
if len(u) >= 5 and self.USERNAME_REGEX.match(u):
valid.append(u)
return valid
@loader.command(ru_doc="— <запрос> Генерирует username по запросу и (опционально) создаёт каналы")
async def genusercmd(self, message):
"""— Generates usernames and optionally creates channels"""
user_query = utils.get_args_raw(message)
if not user_query:
return await utils.answer(message, self.strings["no_prompt"])
msg = await utils.answer(message, self.strings["checking"])
ai_text = await self._query_ai(user_query)
if not ai_text:
return await msg.edit(self.strings["error_ai"])
usernames = self._filter_and_validate_usernames(ai_text)
available = await self.get_available(usernames)
autocreate = self.config["AUTOCREATE_CHANNELS"]
created = []
if autocreate and available:
created = await self.create_channels(available)
if (autocreate and not created) or (not autocreate and not available):
retry_prompt = f"{user_query}. Придумай ещё 10 других уникальных username, строго по тем же правилам."
ai_text_retry = await self._query_ai(retry_prompt)
if ai_text_retry:
usernames_retry = self._filter_and_validate_usernames(ai_text_retry)
available_retry = await self.get_available(usernames_retry)
if autocreate:
created = await self.create_channels(available_retry)
else:
available = available_retry or available
if autocreate:
if created:
channels_list = "\n".join(f"• t.me/{u}" for u in created)
await msg.edit(self.strings["created_many"].format(channels_list))
else:
await msg.edit(self.strings["no_free"])
else:
if available:
avail_list = "\n".join(f"• t.me/{u}" for u in available)
await msg.edit(self.strings["available_many"].format(avail_list))
else:
await msg.edit(self.strings["no_free"])