chore: init
This commit is contained in:
commit
85b28835d1
8 files changed
+294
No files matched your search
@@ -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
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
extra/
|
||||||
|
.venv/
|
||||||
|
.idea/
|
||||||
|
wheels/
|
||||||
@@ -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
|
||||||
|
%:
|
||||||
|
@:
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Project Env
|
||||||
@@ -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
@@ -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}"}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
###
|
||||||
Reference in new issue
Block a user