chore: init

This commit is contained in:
protokey committed 2026-09-08 16:10:23 +04:00
commit 85b28835d1
8 files changed
+294

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = tab
insert_final_newline = true
max_line_length = 120
tab_width = 4
[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,jest.config}]
indent_size = 2
[{*.yaml,*.yml,environment.yaml,environment.yml}]
indent_size = 2
+4
View File
@@ -0,0 +1,4 @@
extra/
.venv/
.idea/
wheels/
+51
View File
@@ -0,0 +1,51 @@
VENV := .venv
PYTHON := $(VENV)/bin/python
PIP := $(VENV)/bin/pip
WHEELS_DIR := wheels
REQUIREMENTS := requirements.txt
MAKECMDGOALS_TARGETS := install freeze wheels sync sync-offline clean help
PKG := $(filter-out $(MAKECMDGOALS_TARGETS),$(MAKECMDGOALS))
.PHONY: install freeze wheels sync sync-offline clean help venv
help:
@echo "make install <pkg> [<pkg> ...] - установить пакет(ы) в $(VENV), обновить $(REQUIREMENTS), собрать wheel в $(WHEELS_DIR)/"
@echo "make sync - установить все пакеты из $(REQUIREMENTS) (из индекса)"
@echo "make sync-offline - установить все пакеты из $(REQUIREMENTS) только из $(WHEELS_DIR)/, без обращения к индексу"
@echo "make freeze - перезаписать $(REQUIREMENTS) текущим состоянием окружения"
@echo "make wheels - собрать wheel для всех пакетов из $(REQUIREMENTS)"
@echo "make clean - удалить $(WHEELS_DIR)/"
venv:
@test -d $(VENV) || python3 -m venv $(VENV)
install: venv
ifeq ($(strip $(PKG)),)
$(error Укажите пакет: make install <package>, например make install redis)
endif
$(PIP) install --upgrade $(PKG)
$(PIP) freeze > $(REQUIREMENTS)
@mkdir -p $(WHEELS_DIR)
$(PIP) wheel $(PKG) -w $(WHEELS_DIR)
@echo "Готово: $(PKG) установлен, $(REQUIREMENTS) обновлён, wheel-пакеты в $(WHEELS_DIR)/"
sync: venv
$(PIP) install -r $(REQUIREMENTS)
sync-offline: venv
$(PIP) install --no-index --find-links=$(WHEELS_DIR) -r $(REQUIREMENTS)
freeze: venv
$(PIP) freeze > $(REQUIREMENTS)
wheels: venv
@mkdir -p $(WHEELS_DIR)
$(PIP) wheel -r $(REQUIREMENTS) -w $(WHEELS_DIR)
clean:
rm -rf $(WHEELS_DIR)
# позволяет передавать имя пакета как аргумент: make install redis
%:
@:
+137
View File
@@ -0,0 +1,137 @@
# WebRobo API
Browser automation API with built-in anti-bot stealth. Send a URL and a sequence of declarative actions — fill a field,
click, wait for an element — and get back the rendered page, including sites behind Cloudflare-style JS challenges.
## Status
Early MVP (v0), under active development. Single browser engine, no queue, no billing yet — see [Roadmap](#roadmap).
## Architecture
```
Client → Caddy (TLS) → FastAPI → API-key auth → semaphore acquire
│
▼
Camoufox browser context
(run actions, collect result)
│
▼
release semaphore → response
```
- **Engine**: [Camoufox](https://camoufox.com/) (stealth Firefox, driven via Playwright). Chosen for v1 because
Firefox-based fingerprinting is less commoditized against anti-bot vendors than patched Chromium.
- **API**: FastAPI, single versioned endpoint (`POST /v1/solve`).
- **Concurrency**: bounded by an `asyncio.Semaphore` over browser contexts — no external job queue in v1. Simpler to
run, hard ceiling on throughput per instance (see Roadmap for scaling out).
- **Auth**: per-client API key (`X-API-Key` header), checked against a SQLite table. (SQLAlchemy ORM)
- **Logging**: every request's outcome (success/failure/duration) is persisted — this becomes the success-rate data used
to evaluate the service and talk to clients about reliability.
- **Proxies**: not managed by the service in v1 — the client supplies their own proxy per request if they need one.
- **Deployment**: single Docker container behind a Caddy reverse proxy.
### Planned project layout
```
src/
main.py # FastAPI app + router wiring
api/
solve.py # POST /v1/solve
deps.py # API-key auth dependency
core/
config.py # settings (env vars)
engine/
browser.py # Camoufox context pool + semaphore
actions.py # fill / click / wait_for execution
schemas.py # SolveRequest / SolveResponse / Action models
db/
models.py # api_keys, request_logs
session.py
tests/
wheels/ # whl библиотеки для оффлайн установки под Ubuntu Noble
Dockerfile
requirements.txt
env.example
```
## API (draft)
```
POST /v1/solve
X-API-Key: <key>
{
"captcha": "token",
"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_ms": 30000}
],
"proxy": "http://user:pass@host:port",
"timeout_ms": 120000
}
```
Response:
```json
{
"status": "ok",
"html": "<html>...</html>",
"cookies": [
{
"name": "session",
"value": "..."
}
],
"actions_result": [
{
"type": "fill",
"ok": true
},
{
"type": "fill",
"ok": true
},
{
"type": "click",
"ok": true
},
{
"type": "wait_for",
"ok": true
}
],
"error": null
}
```
On failure, `status` is `"error"` and `error` carries a machine-readable reason (`selector_not_found`, `timeout`,
`navigation_failed`, ...).
Fill команда включает 3 действия
1. `{"type": "locate", "selector": "#email"},` - навести мышку на элемент
2. `{"type": "click"},` - клик в текущие координаты
3. `{"type": "typing", "value": "user@example.com"},` - набор текста
## Roadmap
Deliberately out of scope for v1:
- Second engine (Patchright/Chromium) for targets where the Firefox fingerprint doesn't fit
- Job queue (Redis/RabbitMQ) + multiple worker VPS for horizontal scaling
- Real billing/usage plans beyond a flat API-key check
- Broader action vocabulary beyond fill/click/wait_for
## Local development
```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload
```
+1
View File
@@ -0,0 +1 @@
# Project Env
+61
View File
@@ -0,0 +1,61 @@
aiohappyeyeballs==2.7.1
aiohttp==3.14.3
aiosignal==1.4.0
annotated-doc==0.0.5
annotated-types==0.8.0
anyio==4.15.1
apify_fingerprint_datapoints==0.15.0
attrs==26.1.0
blessed==1.49.0
browserforge==1.2.4
camoufox==0.5.6
certifi==2026.7.22
charset-normalizer==3.5.1
click==8.5.0
editor==1.8.0
fastapi==0.141.1
frozenlist==1.8.0
geoip2==5.3.0
greenlet==3.5.5
h11==0.16.0
idna==3.19
inquirer==3.4.1
jinxed==2.1.0
language-tags==1.3.1
lxml==6.1.3
markdown-it-py==4.2.0
maxminddb==3.1.1
mdurl==0.1.2
multidict==6.7.1
numpy==2.5.3
orjson==3.12.0
platformdirs==4.11.7
playwright==1.62.0
propcache==0.5.2
pydantic==2.13.5
pydantic_core==2.46.5
pyee==13.0.1
Pygments==2.21.0
PySide6==6.11.2
PySide6_Addons==6.11.2
PySide6_Essentials==6.11.2
PySocks==1.7.1
PyYAML==6.0.3
readchar==4.2.2
redis==8.1.0
requests==2.34.2
rich==15.0.0
rich-click==1.9.9
runs==1.3.0
screeninfo==0.8.1
shiboken6==6.11.2
starlette==1.6.0
typing-inspection==0.4.4
typing_extensions==4.16.0
ua-parser==1.0.2
ua-parser-builtins==202606
urllib3==2.7.0
uvicorn==0.52.4
wcwidth==0.8.3
xmod==1.10.0
yarl==1.24.5
+13
View File
@@ -0,0 +1,13 @@
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/hello/{name}")
async def say_hello(name: str):
return {"message": f"Hello {name}"}
+11
View File
@@ -0,0 +1,11 @@
# Test your FastAPI endpoints
GET http://127.0.0.1:8000/
Accept: application/json
###
GET http://127.0.0.1:8000/hello/User
Accept: application/json
###