боты
поиск
фото
Sherlock
SDK под бота
Выбери язык и забирай один готовый файл. Внутри уже есть клиент API, методы для поиска, Sherlock и фото, плюс нормальный вывод под Telegram.
Всё в одном
Не надо собирать три файла. Скопировал один пример, поставил токены, запустил.
Без серверного форматера
Бот сам читает JSON API и сам собирает красивый ответ.
Команды
/search, /sherlock, /photo. Всё коротко и понятно.
Python bot.py
Установка: pip install aiogram aiohttp
import os
import html
import asyncio
import aiohttp
from aiogram import Bot, Dispatcher
from aiogram.filters import Command
from aiogram.types import Message
BOT_TOKEN = os.getenv("BOT_TOKEN")
CRYVEN_API_KEY = os.getenv("CRYVEN_API_KEY")
CRYVEN_BASE_URL = os.getenv("CRYVEN_BASE_URL", "http://cryven.info")
bot = Bot(BOT_TOKEN, parse_mode="HTML")
dp = Dispatcher()
class CryvenAPI:
def __init__(self, api_key: str, base_url: str = CRYVEN_BASE_URL):
self.api_key = api_key
self.base_url = base_url.rstrip("/")
async def get(self, path: str, **params) -> dict:
query = {"key": self.api_key}
query.update({key: value for key, value in params.items() if value})
timeout = aiohttp.ClientTimeout(total=120)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(f"{self.base_url}{path}", params=query) as response:
data = await response.json(content_type=None)
if response.status >= 400:
raise RuntimeError(data.get("error", f"HTTP {response.status}"))
return data
async def post(self, path: str, **payload) -> dict:
body = {"key": self.api_key}
body.update({key: value for key, value in payload.items() if value})
timeout = aiohttp.ClientTimeout(total=120)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(f"{self.base_url}{path}", json=body) as response:
data = await response.json(content_type=None)
if response.status >= 400:
raise RuntimeError(data.get("error", f"HTTP {response.status}"))
return data
async def search(self, query: str) -> dict:
return await self.get("/api/search", search=query)
async def sherlock(self, username: str) -> dict:
return await self.get("/api/telegram/search", search=username)
async def photo_by_url(self, image_url: str) -> dict:
return await self.get("/api/photo/search", image_url=image_url)
async def photo_by_base64(self, image_base64: str) -> dict:
return await self.post("/api/photo/search", image_base64=image_base64)
api = CryvenAPI(CRYVEN_API_KEY)
def safe(value) -> str:
return html.escape(str(value if value is not None else ""))
def short(value, limit: int = 180) -> str:
if value in (None, ""):
return ""
if isinstance(value, list):
value = ", ".join(str(item) for item in value[:5] if item)
elif isinstance(value, dict):
value = ", ".join(str(item) for item in list(value.values())[:5] if item)
else:
value = str(value)
value = " ".join(value.split())
return value[: limit - 3] + "..." if len(value) > limit else value
def search_rows(data: dict, limit: int = 8) -> list:
full = data.get("full-result") or {}
rows = full.get("Базы Данных") or []
return rows[:limit] if isinstance(rows, list) else []
def format_search(query: str, data: dict) -> str:
fast = data.get("fast-result") or {}
rows = search_rows(data)
lines = [
"CRYVEN поиск",
f"Запрос: {safe(query)}",
f"Найдено: {data.get('results_count') or len(rows) or 0}",
f"Источников: {data.get('sources_count') or 0}",
"",
]
quick = []
for key, value in list(fast.items())[:10]:
value = short(value)
if value:
quick.append(f"- {safe(key)}: {safe(value)}")
if quick:
lines.extend(["Быстрые данные", *quick, ""])
if rows:
lines.append("Базы")
for row in rows:
source = row.get("source") or row.get("database") or row.get("name") or "Источник"
preview = []
for key, value in row.items():
if key in ("source", "database", "name"):
continue
value = short(value, 90)
if value:
preview.append(f"{key}: {value}")
if len(preview) == 4:
break
lines.append(f"- {safe(source)} - {safe('; '.join(preview) or 'данные найдены')}")
return "\n".join(lines)[:3900]
def format_sherlock(username: str, data: dict) -> str:
result = data.get("result") or {}
lines = ["Sherlock", f"Запрос: {safe(username)}", ""]
fields = []
for key, value in result.items():
value = short(value, 220)
if value:
fields.append((key, value))
if len(fields) == 14:
break
if not fields:
lines.append("Ничего не найдено.")
else:
for key, value in fields:
lines.append(f"- {safe(key)}: {safe(value)}")
return "\n".join(lines)[:3900]
def format_photo(data: dict) -> str:
result = data.get("result") or data.get("results") or {}
items = result.get("matches") if isinstance(result, dict) else result
items = items if isinstance(items, list) else []
lines = ["Поиск по фото", f"Найдено: {len(items) or data.get('results_count') or 0}", ""]
for item in items[:8]:
name = item.get("name") or item.get("title") or item.get("url") or item.get("source") or "Совпадение"
score = item.get("score") or item.get("similarity") or item.get("percent") or ""
suffix = f" - {safe(score)}" if score else ""
lines.append(f"- {safe(name)}{suffix}")
if len(lines) == 3:
lines.append("Совпадений нет.")
return "\n".join(lines)[:3900]
@dp.message(Command("start"))
async def start(message: Message):
await message.answer(
"CRYVEN bot\n\n"
"/search 79999999999\n"
"/sherlock @username\n"
"/photo https://site.com/image.jpg"
)
@dp.message(Command("search"))
async def search(message: Message):
query = message.text.replace("/search", "", 1).strip()
if not query:
await message.answer("Пример: /search 79999999999")
return
wait = await message.answer("Ищу...")
try:
data = await api.search(query)
await wait.edit_text(format_search(query, data), disable_web_page_preview=True)
except Exception as error:
await wait.edit_text(f"Ошибка: {safe(error)}")
@dp.message(Command("sherlock"))
async def sherlock(message: Message):
username = message.text.replace("/sherlock", "", 1).strip()
if not username:
await message.answer("Пример: /sherlock @username")
return
wait = await message.answer("Sherlock в очереди...")
try:
data = await api.sherlock(username)
await wait.edit_text(format_sherlock(username, data), disable_web_page_preview=True)
except Exception as error:
await wait.edit_text(f"Ошибка: {safe(error)}")
@dp.message(Command("photo"))
async def photo(message: Message):
image_url = message.text.replace("/photo", "", 1).strip()
if not image_url:
await message.answer("Пример: /photo https://site.com/image.jpg")
return
wait = await message.answer("Проверяю фото...")
try:
data = await api.photo_by_url(image_url)
await wait.edit_text(format_photo(data), disable_web_page_preview=True)
except Exception as error:
await wait.edit_text(f"Ошибка: {safe(error)}")
if __name__ == "__main__":
asyncio.run(dp.start_polling(bot))
JavaScript bot.js
Установка: npm i node-telegram-bot-api
const TelegramBot = require("node-telegram-bot-api");
const BOT_TOKEN = process.env.BOT_TOKEN;
const CRYVEN_API_KEY = process.env.CRYVEN_API_KEY;
const CRYVEN_BASE_URL = process.env.CRYVEN_BASE_URL || "http://cryven.info";
const bot = new TelegramBot(BOT_TOKEN, { polling: true });
class CryvenAPI {
constructor({ apiKey, baseUrl = CRYVEN_BASE_URL, timeoutMs = 120000 }) {
this.apiKey = apiKey;
this.baseUrl = baseUrl.replace(/\/$/, "");
this.timeoutMs = timeoutMs;
}
async get(path, params = {}) {
const url = new URL(path, this.baseUrl);
url.searchParams.set("key", this.apiKey);
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null && String(value).trim()) {
url.searchParams.set(key, String(value).trim());
}
}
const response = await fetch(url, { signal: AbortSignal.timeout(this.timeoutMs) });
const data = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`);
return data;
}
async post(path, body = {}) {
const response = await fetch(`${this.baseUrl}${path}`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ key: this.apiKey, ...body }),
signal: AbortSignal.timeout(this.timeoutMs),
});
const data = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`);
return data;
}
search(query) {
return this.get("/api/search", { search: query });
}
sherlock(username) {
return this.get("/api/telegram/search", { search: username });
}
photoByUrl(imageUrl) {
return this.get("/api/photo/search", { image_url: imageUrl });
}
photoByBase64(imageBase64) {
return this.post("/api/photo/search", { image_base64: imageBase64 });
}
}
const api = new CryvenAPI({ apiKey: CRYVEN_API_KEY });
function escapeHtml(value) {
return String(value ?? "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
function short(value, limit = 180) {
if (value === null || value === undefined || value === "") return "";
if (Array.isArray(value)) value = value.filter(Boolean).slice(0, 5).join(", ");
else if (typeof value === "object") value = Object.values(value).filter(Boolean).slice(0, 5).join(", ");
else value = String(value);
value = value.replace(/\s+/g, " ").trim();
return value.length > limit ? `${value.slice(0, limit - 3)}...` : value;
}
function searchRows(data, limit = 8) {
const rows = data?.["full-result"]?.["Базы Данных"];
return Array.isArray(rows) ? rows.slice(0, limit) : [];
}
function formatSearch(query, data) {
const fast = data?.["fast-result"] || {};
const rows = searchRows(data);
const lines = [
"<b>CRYVEN поиск</b>",
`Запрос: <code>${escapeHtml(query)}</code>`,
`Найдено: <b>${data.results_count || rows.length || 0}</b>`,
`Источников: <b>${data.sources_count || 0}</b>`,
"",
];
const quick = Object.entries(fast)
.map(([key, value]) => [key, short(value)])
.filter(([, value]) => value)
.slice(0, 10)
.map(([key, value]) => `- <b>${escapeHtml(key)}:</b> ${escapeHtml(value)}`);
if (quick.length) lines.push("<b>Быстрые данные</b>", ...quick, "");
if (rows.length) {
lines.push("<b>Базы</b>");
for (const row of rows) {
const source = row.source || row.database || row.name || "Источник";
const preview = Object.entries(row)
.filter(([key]) => !["source", "database", "name"].includes(key))
.map(([key, value]) => [key, short(value, 90)])
.filter(([, value]) => value)
.slice(0, 4)
.map(([key, value]) => `${key}: ${value}`)
.join("; ");
lines.push(`- <b>${escapeHtml(source)}</b> - ${escapeHtml(preview || "данные найдены")}`);
}
}
return lines.join("\n").slice(0, 3900);
}
function formatSherlock(username, data) {
const result = data?.result || {};
const lines = ["<b>Sherlock</b>", `Запрос: <code>${escapeHtml(username)}</code>`, ""];
const fields = Object.entries(result)
.map(([key, value]) => [key, short(value, 220)])
.filter(([, value]) => value)
.slice(0, 14);
if (!fields.length) lines.push("Ничего не найдено.");
for (const [key, value] of fields) lines.push(`- <b>${escapeHtml(key)}:</b> ${escapeHtml(value)}`);
return lines.join("\n").slice(0, 3900);
}
function formatPhoto(data) {
const result = data?.result || data?.results || {};
const items = Array.isArray(result.matches) ? result.matches : Array.isArray(result) ? result : [];
const lines = ["<b>Поиск по фото</b>", `Найдено: <b>${items.length || data.results_count || 0}</b>`, ""];
for (const item of items.slice(0, 8)) {
const name = item.name || item.title || item.url || item.source || "Совпадение";
const score = item.score || item.similarity || item.percent || "";
lines.push(`- <b>${escapeHtml(name)}</b>${score ? ` - ${escapeHtml(score)}` : ""}`);
}
if (lines.length === 3) lines.push("Совпадений нет.");
return lines.join("\n").slice(0, 3900);
}
async function editSafe(chatId, messageId, text) {
return bot.editMessageText(text, {
chat_id: chatId,
message_id: messageId,
parse_mode: "HTML",
disable_web_page_preview: true,
});
}
bot.onText(/^\/start$/, (msg) => {
bot.sendMessage(msg.chat.id, [
"<b>CRYVEN bot</b>",
"",
"/search 79999999999",
"/sherlock @username",
"/photo https://site.com/image.jpg",
].join("\n"), { parse_mode: "HTML" });
});
bot.onText(/^\/search\s+(.+)/, async (msg, match) => {
const query = match[1].trim();
const wait = await bot.sendMessage(msg.chat.id, "Ищу...");
try {
const data = await api.search(query);
await editSafe(msg.chat.id, wait.message_id, formatSearch(query, data));
} catch (error) {
await editSafe(msg.chat.id, wait.message_id, `Ошибка: ${escapeHtml(error.message)}`);
}
});
bot.onText(/^\/sherlock\s+(.+)/, async (msg, match) => {
const username = match[1].trim();
const wait = await bot.sendMessage(msg.chat.id, "Sherlock в очереди...");
try {
const data = await api.sherlock(username);
await editSafe(msg.chat.id, wait.message_id, formatSherlock(username, data));
} catch (error) {
await editSafe(msg.chat.id, wait.message_id, `Ошибка: ${escapeHtml(error.message)}`);
}
});
bot.onText(/^\/photo\s+(.+)/, async (msg, match) => {
const imageUrl = match[1].trim();
const wait = await bot.sendMessage(msg.chat.id, "Проверяю фото...");
try {
const data = await api.photoByUrl(imageUrl);
await editSafe(msg.chat.id, wait.message_id, formatPhoto(data));
} catch (error) {
await editSafe(msg.chat.id, wait.message_id, `Ошибка: ${escapeHtml(error.message)}`);
}
});
Переменные
BOT_TOKENТокен Telegram-бота.
CRYVEN_API_KEYКлюч от API.