From e9748e19491c23a309a86273543fde30c30854fa Mon Sep 17 00:00:00 2001 From: protokey Date: Sun, 11 Oct 2026 00:53:09 +0400 Subject: [PATCH] feat: Robotex landing on robotex.sokolab.xyz and announcement post - projects section: standalone landing template and styles, served on a subdomain by the ingress Caddy (project) snippet - Robotex landing in RU/EN, links to Redmine forum/issues - blog post: Robotex is now a free service Co-Authored-By: Claude Opus 5.5 --- assets/css/landing.css | 120 +++++++++++++++++ content/blog/robotex-free-saas/index.en.md | 35 +++++ content/blog/robotex-free-saas/index.md | 35 +++++ content/projects/_index.en.md | 6 + content/projects/_index.md | 6 + content/projects/robotex/index.en.md | 104 +++++++++++++++ content/projects/robotex/index.md | 104 +++++++++++++++ layouts/projects/single.html | 142 +++++++++++++++++++++ 8 files changed, 552 insertions(+) create mode 100644 assets/css/landing.css create mode 100644 content/blog/robotex-free-saas/index.en.md create mode 100644 content/blog/robotex-free-saas/index.md create mode 100644 content/projects/_index.en.md create mode 100644 content/projects/_index.md create mode 100644 content/projects/robotex/index.en.md create mode 100644 content/projects/robotex/index.md create mode 100644 layouts/projects/single.html diff --git a/assets/css/landing.css b/assets/css/landing.css new file mode 100644 index 0000000..10b8823 --- /dev/null +++ b/assets/css/landing.css @@ -0,0 +1,120 @@ +/* Project landing pages (layouts/projects/single.html). Standalone, no theme CSS. */ + +:root { + --bg: #0b1020; + --bg-alt: #10172b; + --surface: rgba(255, 255, 255, .04); + --border: rgba(255, 255, 255, .09); + --text: #e6e9f2; + --muted: #9aa3b8; + --accent: #7c8cff; + --accent-strong: #5b6cf0; + --code-bg: #070b16; + --radius: .9rem; +} + +* { box-sizing: border-box; } +html { scroll-behavior: smooth; } +body { + margin: 0; + background: var(--bg); + color: var(--text); + font: 400 1rem/1.6 'Inter', system-ui, sans-serif; + -webkit-font-smoothing: antialiased; +} +a { color: var(--accent); } +code, pre { font-family: 'JetBrains Mono', monospace; } +h1, h2, h3 { letter-spacing: -.02em; line-height: 1.15; margin: 0; } +h2 { font-size: clamp(1.6rem, 3vw, 2.2rem); font-weight: 800; } +h3 { font-size: 1.05rem; font-weight: 600; } + +.wrap { width: min(100% - 2rem, 1040px); margin-inline: auto; } + +/* nav */ +.nav { + position: sticky; top: 0; z-index: 10; + background: rgba(11, 16, 32, .8); + backdrop-filter: blur(10px); + border-bottom: 1px solid var(--border); +} +.nav__row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; height: 3.5rem; } +.nav__logo { font-weight: 800; font-size: 1.15rem; color: var(--text); text-decoration: none; } +.nav__links { display: flex; align-items: center; gap: 1.25rem; } +.nav__links a { color: var(--muted); text-decoration: none; font-size: .9rem; } +.nav__links a:hover { color: var(--text); } +.nav__lang { padding: .15rem .5rem; border: 1px solid var(--border); border-radius: .4rem; } +@media (max-width: 640px) { + .nav__links a:not(.nav__lang) { display: none; } +} + +/* hero */ +.hero { + padding: clamp(4rem, 12vw, 7.5rem) 0 clamp(3rem, 8vw, 5rem); + background: + radial-gradient(60rem 30rem at 50% -10%, rgba(124, 140, 255, .22), transparent 70%), + var(--bg); + text-align: center; +} +.badge { + display: inline-block; margin: 0 0 1.25rem; padding: .3rem .8rem; + border: 1px solid rgba(124, 140, 255, .4); border-radius: 999px; + color: var(--accent); font-size: .85rem; font-weight: 600; +} +.hero__title { font-size: clamp(2.2rem, 6vw, 3.6rem); font-weight: 800; max-width: 18ch; margin-inline: auto; } +.hero__lead { max-width: 40rem; margin: 1.25rem auto 0; color: var(--muted); font-size: 1.15rem; } +.hero__cta { display: flex; flex-wrap: wrap; justify-content: center; gap: .75rem; margin-top: 2rem; } + +.btn { + display: inline-block; padding: .75rem 1.4rem; border-radius: .7rem; + border: 1px solid var(--border); background: var(--surface); + color: var(--text); font-weight: 600; text-decoration: none; + transition: background .15s, transform .15s; +} +.btn:hover { background: rgba(255, 255, 255, .08); transform: translateY(-1px); } +.btn--primary { background: var(--accent-strong); border-color: var(--accent-strong); color: #fff; } +.btn--primary:hover { background: var(--accent); } + +/* sections */ +.section { padding: clamp(3rem, 8vw, 5rem) 0; scroll-margin-top: 3.5rem; } +.section--alt { background: var(--bg-alt); border-block: 1px solid var(--border); } +.section__lead { color: var(--muted); max-width: 44rem; margin: .75rem 0 0; font-size: 1.05rem; } + +.problems { margin: 1.5rem 0; padding: 0; list-style: none; display: grid; gap: .6rem; counter-reset: p; } +.problems li { + counter-increment: p; display: flex; gap: .9rem; align-items: center; + padding: .8rem 1rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); +} +.problems li::before { + content: counter(p); flex: none; display: grid; place-items: center; + width: 1.8rem; height: 1.8rem; border-radius: 50%; + background: rgba(124, 140, 255, .15); color: var(--accent); font-weight: 600; font-size: .9rem; +} + +.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1rem; margin-top: 2rem; } +.grid--4 { grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); } +.card { padding: 1.25rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); } +.card p { margin: .5rem 0 0; color: var(--muted); font-size: .95rem; } +.card code { font-size: .85em; color: var(--text); } +.card--link { text-decoration: none; color: var(--text); transition: border-color .15s, transform .15s; } +.card--link:hover { border-color: rgba(124, 140, 255, .5); transform: translateY(-2px); } +.card--link h3 { color: var(--accent); } + +.steps { margin: 1.5rem 0 0; padding-left: 1.25rem; display: grid; gap: .4rem; } +.steps code { font-size: .9em; } + +.code { display: grid; gap: 1rem; margin-top: 2rem; } +.code figure { margin: 0; min-width: 0; } +.code figcaption { color: var(--muted); font-size: .85rem; margin-bottom: .4rem; } +.code pre { + margin: 0; padding: 1rem; overflow-x: auto; + background: var(--code-bg); border: 1px solid var(--border); border-radius: var(--radius); + font-size: .82rem; line-height: 1.5; color: #c9d1e8; tab-size: 2; +} + +.limits { margin: 1.5rem 0 0; padding-left: 1.25rem; display: grid; gap: .5rem; color: var(--muted); } +.limits strong { color: var(--text); } + +/* footer */ +.footer { padding: 2rem 0; border-top: 1px solid var(--border); color: var(--muted); font-size: .9rem; } +.footer__row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem 2rem; } +.footer p { margin: 0; } diff --git a/content/blog/robotex-free-saas/index.en.md b/content/blog/robotex-free-saas/index.en.md new file mode 100644 index 0000000..163d2fe --- /dev/null +++ b/content/blog/robotex-free-saas/index.en.md @@ -0,0 +1,35 @@ +--- +title: "Robotex is now a free service" +date: 2026-10-11T00:52:58+04:00 +draft: false +tags: + - api + - saas + - anti-bot + - parsing +summary: "Robotex has its own page now: robotex.sokolab.xyz. It's free to use, access on request" +--- + +I recently [wrote](/blog/robotex-browser-automation-api/) about how Robotex works. It's my microservice that takes over all the browser work: it gets past anti-bot protection, runs a scenario on the page and hands your bot the cookies. Now it has a home of its own: **[robotex.sokolab.xyz](https://robotex.sokolab.xyz/en/)**. + +## Why a separate page + +A blog post is good for explaining how something works. But a project needs a place where you can quickly see what it is and why, get access and ask a question. So each of my projects will get its own subdomain with a landing page, and the blog will cover what's new. + +## Free + +Robotex will be a free service. You only pay for your own proxies and, if needed, captcha recognition in 2captcha. + +It's an early MVP for now, so I grant access manually. Register in my [Redmine](https://redmine.sokolab.xyz/account/register), I'll approve the account and send you the API address. + +## Where to discuss + +The project has its own section in Redmine: + +- a forum for questions, ideas and scenario discussions +- issues for bugs and feature requests +- news and plans + +If it's more convenient, message me on [Telegram](https://t.me/xs0k0lx). + +The source code is still open: [git.sokolab.xyz/s0k0l/robotex](https://git.sokolab.xyz/s0k0l/robotex). diff --git a/content/blog/robotex-free-saas/index.md b/content/blog/robotex-free-saas/index.md new file mode 100644 index 0000000..0298c85 --- /dev/null +++ b/content/blog/robotex-free-saas/index.md @@ -0,0 +1,35 @@ +--- +title: "Robotex теперь бесплатный сервис" +date: 2026-10-11T00:52:58+04:00 +draft: false +tags: + - api + - saas + - anti-bot + - parsing +summary: "У Robotex появилась своя страница: robotex.sokolab.xyz. Пользоваться можно бесплатно, доступ по заявке" +--- + +Недавно я [рассказывал](/blog/robotex-browser-automation-api/), как устроен Robotex, мой микросервис, который берет на себя всю работу с браузером: проходит анти-бот защиту, выполняет сценарий на странице и отдает боту куки. Теперь у него появился свой дом: **[robotex.sokolab.xyz](https://robotex.sokolab.xyz/)**. + +## Зачем отдельная страница + +Пост в блоге хорош, чтобы рассказать, как что-то устроено. Но проекту нужно место, где можно быстро понять, что это и зачем, получить доступ и задать вопрос. Поэтому у каждого моего проекта будет свой поддомен с лендингом, а в блоге я буду рассказывать, что нового. + +## Бесплатно + +Robotex будет бесплатным сервисом. Платить нужно только за свои прокси и, если понадобится, за распознавание капчи в 2captcha. + +Пока это ранний MVP, поэтому доступ выдаю вручную. Зарегистрируйтесь в моем [Redmine](https://redmine.sokolab.xyz/account/register), я подтвержу аккаунт и пришлю адрес API. + +## Где обсуждать + +Для проекта заведен раздел в Redmine: + +- форум для вопросов, идей и обсуждения сценариев +- задачи для багов и пожеланий +- новости и планы + +Если удобнее, пишите мне в [Telegram](https://t.me/xs0k0lx). + +Исходники по-прежнему открыты: [git.sokolab.xyz/s0k0l/robotex](https://git.sokolab.xyz/s0k0l/robotex). diff --git a/content/projects/_index.en.md b/content/projects/_index.en.md new file mode 100644 index 0000000..09fc0bf --- /dev/null +++ b/content/projects/_index.en.md @@ -0,0 +1,6 @@ +--- +title: "Projects" +build: + render: never + list: never +--- diff --git a/content/projects/_index.md b/content/projects/_index.md new file mode 100644 index 0000000..98783a4 --- /dev/null +++ b/content/projects/_index.md @@ -0,0 +1,6 @@ +--- +title: "Проекты" +build: + render: never + list: never +--- diff --git a/content/projects/robotex/index.en.md b/content/projects/robotex/index.en.md new file mode 100644 index 0000000..d07197c --- /dev/null +++ b/content/projects/robotex/index.en.md @@ -0,0 +1,104 @@ +--- +title: "Robotex" +tagline: "A browser for your bots, over an API" +description: "Robotex gets past anti-bot protection, runs a scenario on the page and hands your bot the cookies. From there the bot works with plain HTTP requests." +domain: "https://robotex.sokolab.xyz/" +badge: "Free · early access" +post: "/blog/robotex-browser-automation-api" +postLabel: "How Robotex works" +build: + list: never +sitemap: + disable: true + +nav: + - { label: "Features", href: "#features" } + - { label: "How it works", href: "#how" } + - { label: "Community", href: "#community" } + +cta: + - { label: "Request access", href: "https://redmine.sokolab.xyz/account/register", primary: true } + - { label: "How it works", href: "#how" } + +problem: + title: "Why you need it" + lead: "Every website bot sooner or later runs into one of three problems:" + items: + - "a captcha" + - "anti-bot protection" + - "heavy JS that has to be rendered to submit a form" + text: "A spider built on raw HTTP requests is helpless here, you need a real browser. But a browser is heavy, it has to be hidden from detection and kept next to the bot. Robotex takes that job over: the browser is used only where there's no way around it." + +features: + title: "Features" + items: + - title: "Scenarios" + text: "More than opening a page. Fill a form, click, move the mouse, wait for an element, grab a piece of HTML." + - title: "Anti-bot pages" + text: "Detects a Cloudflare check before and during a scenario and gets through it. If it escalates to an image captcha, sends it to a recognition service." + - title: "Captcha" + text: "The `captcha` step solves hCaptcha and other captchas via 2captcha before the form is submitted." + - title: "Profiles" + text: "Cookies, local storage and the browser fingerprint live in one profile. Locale and timezone match the proxy's geo. To the site it's still the same user." + - title: "Real Chrome" + text: "Chrome driven by Patchright, without the CDP leaks anti-bots use to detect automation." + - title: "Any language" + text: "It's a service, not a library. Your bot only needs an HTTP client, whatever it's written in." + +how: + title: "How it works" + steps: + - "The bot sends a URL, an action scenario and its proxy to `POST /v1/solve`." + - "Robotex opens the page, passes the checks and runs the scenario. The browser profile is saved and returned in the `session_id` cookie." + - "The bot gets the cookies and User-Agent and keeps browsing on its own, **through the same proxy**. When it needs a browser again, it sends `session_id` and continues in the same profile." + code: + - caption: "Request" + body: | + POST /v1/solve + X-API-Key: + + { + "url": "https://example.com/login", + "actions": [ + {"type": "fill", "selector": "#email", "value": "user@example.com"}, + {"type": "fill", "selector": "#password", "value": "..."}, + {"type": "click", "selector": "#submit"}, + {"type": "wait_for", "selector": ".dashboard", "timeout": 30} + ], + "proxy": "socks5://user:pass@host:port" + } + - caption: "Response" + body: | + { + "status": "ok", + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...", + "cookies": [ + {"name": "sessionid", "value": "...", "domain": ".example.com"} + ], + "actions": [ + {"type": "fill", "ok": true}, + {"type": "fill", "ok": true}, + {"type": "click", "ok": true}, + {"type": "wait_for", "ok": true} + ] + } + +limits: + title: "Where it stands, honestly" + items: + - "**It's an early MVP.** I grant access manually, and the API may change." + - "**It's free.** You only pay for your own proxies and, if needed, 2captcha." + - "Proxy quality matters a lot: no fingerprint will help on a dirty IP." + - "Turnstile doesn't pass on every site yet, I'm working on stability." + +community: + title: "Community" + lead: "Questions, ideas and bugs go to Redmine. Sign-up uses manual activation: register and I'll approve your account." + links: + - { title: "Forum", text: "Questions, ideas and scenario discussions.", href: "https://redmine.sokolab.xyz/projects/robotex/boards" } + - { title: "Bugs and requests", text: "Something broke or a feature is missing.", href: "https://redmine.sokolab.xyz/projects/robotex/issues" } + - { title: "Telegram", text: "Message me directly.", href: "https://t.me/xs0k0lx" } + - { title: "Source code", text: "The service code in my Gitea.", href: "https://git.sokolab.xyz/s0k0l/robotex" } + +footer: "Use Robotex only on sites where you have the right to do so." +--- diff --git a/content/projects/robotex/index.md b/content/projects/robotex/index.md new file mode 100644 index 0000000..6d660fd --- /dev/null +++ b/content/projects/robotex/index.md @@ -0,0 +1,104 @@ +--- +title: "Robotex" +tagline: "Браузер для ваших ботов по API" +description: "Robotex проходит анти-бот защиту, выполняет сценарий на странице и отдает боту куки. Дальше бот работает обычными HTTP запросами." +domain: "https://robotex.sokolab.xyz/" +badge: "Бесплатно · ранний доступ" +post: "/blog/robotex-browser-automation-api" +postLabel: "Как устроен Robotex" +build: + list: never +sitemap: + disable: true + +nav: + - { label: "Возможности", href: "#features" } + - { label: "Как работает", href: "#how" } + - { label: "Сообщество", href: "#community" } + +cta: + - { label: "Запросить доступ", href: "https://redmine.sokolab.xyz/account/register", primary: true } + - { label: "Как это работает", href: "#how" } + +problem: + title: "Зачем это нужно" + lead: "Каждый бот под сайт рано или поздно упирается в одну из трех проблем:" + items: + - "капча" + - "анти-бот защита" + - "сложный JS, который нужно отрендерить, чтобы отправить форму" + text: "Паук на сырых HTTP запросах тут бессилен, нужен настоящий браузер. Но браузер тяжелый, его нужно прятать от детекта и держать рядом с ботом. Robotex берет эту работу на себя: браузер включается только там, где без него никак." + +features: + title: "Возможности" + items: + - title: "Сценарии" + text: "Не просто открыть страницу. Заполнить форму, кликнуть, навести мышь, дождаться элемента, забрать кусок HTML." + - title: "Анти-бот страницы" + text: "Сам находит проверку Cloudflare до и во время сценария и проходит ее. Если она переросла в капчу с картинками, отправляет в сервис распознавания." + - title: "Капча" + text: "Шаг `captcha` в сценарии решает hCaptcha и другие капчи через 2captcha до отправки формы." + - title: "Профили" + text: "Куки, local storage и отпечаток браузера живут в одном профиле. Локаль и часовой пояс подбираются под гео прокси. Для сайта это все тот же пользователь." + - title: "Настоящий Chrome" + text: "Chrome под управлением Patchright, без утечек CDP, по которым анти-боты узнают автоматизацию." + - title: "Любой язык" + text: "Это сервис, а не библиотека. Боту нужен только HTTP клиент, на чем он написан, неважно." + +how: + title: "Как это работает" + steps: + - "Бот отправляет в `POST /v1/solve` адрес, сценарий действий и свой прокси." + - "Robotex открывает страницу, проходит проверки и выполняет сценарий. Профиль браузера сохраняется и возвращается в куке `session_id`." + - "Бот получает куки и User-Agent и дальше ходит по сайту сам, **через тот же прокси**. Понадобится браузер снова, передаст `session_id` и продолжит в том же профиле." + code: + - caption: "Запрос" + body: | + POST /v1/solve + X-API-Key: + + { + "url": "https://example.com/login", + "actions": [ + {"type": "fill", "selector": "#email", "value": "user@example.com"}, + {"type": "fill", "selector": "#password", "value": "..."}, + {"type": "click", "selector": "#submit"}, + {"type": "wait_for", "selector": ".dashboard", "timeout": 30} + ], + "proxy": "socks5://user:pass@host:port" + } + - caption: "Ответ" + body: | + { + "status": "ok", + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...", + "cookies": [ + {"name": "sessionid", "value": "...", "domain": ".example.com"} + ], + "actions": [ + {"type": "fill", "ok": true}, + {"type": "fill", "ok": true}, + {"type": "click", "ok": true}, + {"type": "wait_for", "ok": true} + ] + } + +limits: + title: "Честно о текущем состоянии" + items: + - "**Это ранний MVP.** Доступ выдаю вручную, API может меняться." + - "**Бесплатно.** Платить нужно только за свои прокси и, если нужна, капчу в 2captcha." + - "Прокси решает многое: на грязном IP не поможет никакой отпечаток." + - "Turnstile проходит не на каждом сайте, над стабильностью работаю." + +community: + title: "Сообщество" + lead: "Вопросы, идеи и баги принимаются в Redmine. Регистрация с ручной активацией: оставьте заявку, и я ее подтвержу." + links: + - { title: "Форум", text: "Вопросы, идеи и обсуждение сценариев.", href: "https://redmine.sokolab.xyz/projects/robotex/boards" } + - { title: "Баги и пожелания", text: "Что-то не работает или не хватает функции.", href: "https://redmine.sokolab.xyz/projects/robotex/issues" } + - { title: "Telegram", text: "Написать мне напрямую.", href: "https://t.me/xs0k0lx" } + - { title: "Исходники", text: "Код сервиса в моей Gitea.", href: "https://git.sokolab.xyz/s0k0l/robotex" } + +footer: "Используйте Robotex только на сайтах, где у вас есть на это право." +--- diff --git a/layouts/projects/single.html b/layouts/projects/single.html new file mode 100644 index 0000000..ecb478c --- /dev/null +++ b/layouts/projects/single.html @@ -0,0 +1,142 @@ +{{- /* + Project landing. Served on its own subdomain (params.domain) by the ingress Caddy, + which rewrites "/" to this page and "/en/" to its English translation. + So every link here is host-relative ("/", "/en/", "#id") and assets come from + global /css/ and /img/, never from the page bundle. +*/ -}} +{{- $p := .Params -}} +{{- $home := cond (eq .Language.Lang hugo.Sites.Default.Language.Lang) "/" (printf "/%s/" .Language.Lang) -}} +{{- $css := resources.Get "css/landing.css" | minify | fingerprint -}} + + + + + + {{ .Title }} · {{ $p.tagline }} + + + {{- range .AllTranslations }} + {{- $h := cond (eq .Language.Lang hugo.Sites.Default.Language.Lang) "/" (printf "/%s/" .Language.Lang) }} + + {{- end }} + + + + + + + + + + + + + +
+
+
+ {{ with $p.badge }}

{{ . }}

{{ end }} +

{{ $p.tagline }}

+

{{ .Description }}

+
+ {{ range $p.cta }}{{ .label }}{{ end }} +
+
+
+ + {{ with $p.problem }} +
+
+

{{ .title }}

+

{{ .lead }}

+
    + {{ range .items }}
  1. {{ . }}
  2. {{ end }} +
+

{{ .text }}

+
+
+ {{ end }} + + {{ with $p.features }} +
+
+

{{ .title }}

+
+ {{ range .items }} +
+

{{ .title }}

+

{{ .text | markdownify }}

+
+ {{ end }} +
+
+
+ {{ end }} + + {{ with $p.how }} +
+
+

{{ .title }}

+
    + {{ range .steps }}
  1. {{ . | markdownify }}
  2. {{ end }} +
+
+ {{ range .code }} +
+
{{ .caption }}
+
{{ .body }}
+
+ {{ end }} +
+
+
+ {{ end }} + + {{ with $p.limits }} +
+
+

{{ .title }}

+
    + {{ range .items }}
  • {{ . | markdownify }}
  • {{ end }} +
+
+
+ {{ end }} + + {{ with $p.community }} +
+
+

{{ .title }}

+

{{ .lead }}

+
+ {{ range .links }} + +

{{ .title }} →

+

{{ .text }}

+
+ {{ end }} +
+
+
+ {{ end }} +
+ + + +