боты поиск фото Sherlock

SDK под бота

Выбери язык и забирай один готовый файл. Внутри уже есть клиент API, методы для поиска, Sherlock и фото, плюс нормальный вывод под Telegram.

Всё в одном

Не надо собирать три файла. Скопировал один пример, поставил токены, запустил.

Без серверного форматера

Бот сам читает JSON API и сам собирает красивый ответ.

Команды

/search, /sherlock, /photo. Всё коротко и понятно.

Python bot.py

Установка: pip install aiogram aiohttp

перейти к JS
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

перейти к Python
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, "&lt;")
    .replace(/>/g, "&gt;");
}

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.