first commit
This commit is contained in:
75
app/docs/index.html
Normal file
75
app/docs/index.html
Normal file
@@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Screenshot API Tester</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📸 Screenshot API Tester</h1>
|
||||
|
||||
<label>URL:</label>
|
||||
<input type="text" id="url" value="https://example.com">
|
||||
|
||||
<label>Width:</label>
|
||||
<input type="number" id="width" value="1280">
|
||||
|
||||
<label>Height:</label>
|
||||
<input type="number" id="height" value="720">
|
||||
|
||||
<label>Device:</label>
|
||||
<select id="device">
|
||||
<option value="desktop">Desktop</option>
|
||||
<option value="mobile">Mobile</option>
|
||||
<option value="tablet">Tablet</option>
|
||||
</select>
|
||||
|
||||
<button onclick="startScreenshot()">Take Screenshot</button>
|
||||
|
||||
<h3>Real-Time Updates:</h3>
|
||||
<div id="log"></div>
|
||||
|
||||
<h3>Screenshot:</h3>
|
||||
<img id="screenshot" src="" style="display:none; max-width: 100%;">
|
||||
<a id="download" style="display:none;" download>Download Screenshot</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let socket = new WebSocket("ws://yourserver.com/ws");
|
||||
|
||||
socket.onmessage = function(event) {
|
||||
let log = document.getElementById("log");
|
||||
let message = JSON.parse(event.data);
|
||||
|
||||
if (message.screenshot_url) {
|
||||
let img = document.getElementById("screenshot");
|
||||
img.src = message.screenshot_url;
|
||||
img.style.display = "block";
|
||||
|
||||
let download = document.getElementById("download");
|
||||
download.href = message.screenshot_url;
|
||||
download.style.display = "block";
|
||||
} else {
|
||||
log.innerHTML += `<p>${event.data}</p>`;
|
||||
}
|
||||
};
|
||||
|
||||
function startScreenshot() {
|
||||
let params = {
|
||||
url: document.getElementById("url").value,
|
||||
width: parseInt(document.getElementById("width").value),
|
||||
height: parseInt(document.getElementById("height").value),
|
||||
full_height: false,
|
||||
format: "png",
|
||||
click_selectors: [],
|
||||
delay: 1000,
|
||||
device: document.getElementById("device").value
|
||||
};
|
||||
|
||||
socket.send(JSON.stringify(params));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
44
app/docs/style.css
Normal file
44
app/docs/style.css
Normal file
@@ -0,0 +1,44 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
margin: 20px auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1, h3 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
input, select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 15px;
|
||||
padding: 10px;
|
||||
background-color: #0073e6;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #005bb5;
|
||||
}
|
||||
102
app/main.py
Normal file
102
app/main.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
import redis
|
||||
import json
|
||||
from fastapi import FastAPI, Query, HTTPException, WebSocket, WebSocketDisconnect, Request, Depends
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi_limiter import FastAPILimiter
|
||||
from fastapi_limiter.depends import RateLimiter
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Secret API Key
|
||||
SECRET_PHRASE = os.getenv("API_SECRET", "my_secret_key")
|
||||
|
||||
# Redis for Rate Limiting
|
||||
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379")
|
||||
redis_client = redis.from_url(REDIS_URL)
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
"""Initialize FastAPI Limiter."""
|
||||
await FastAPILimiter.init(redis_client)
|
||||
|
||||
# Setup Static Files for Documentation
|
||||
app.mount("/", StaticFiles(directory="app/docs", html=True), name="docs")
|
||||
|
||||
# WebSocket Connections Dictionary
|
||||
active_connections = {}
|
||||
|
||||
# Device Profiles for Emulation
|
||||
DEVICE_PROFILES = {
|
||||
"mobile": {"width": 375, "height": 667, "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)"},
|
||||
"tablet": {"width": 768, "height": 1024, "user_agent": "Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X)"},
|
||||
"desktop": {"width": 1280, "height": 720, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},
|
||||
}
|
||||
|
||||
def generate_md5_hash(url: str, secret: str) -> str:
|
||||
"""Generate MD5 hash for authentication."""
|
||||
return hashlib.md5(f"{url}{secret}".encode()).hexdigest()
|
||||
|
||||
async def take_screenshot(websocket: WebSocket, url: str, width: int, height: int, full_height: bool, format: str, click_selectors: list, delay: int, device: str):
|
||||
"""Capture a screenshot using Playwright and return the file path."""
|
||||
file_ext = format.lower()
|
||||
screenshot_path = f"app/docs/screenshots/{uuid.uuid4()}.{file_ext}"
|
||||
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(headless=True)
|
||||
await websocket.send_text("Browser launched...")
|
||||
|
||||
viewport = {"width": width, "height": height}
|
||||
user_agent = None
|
||||
if device in DEVICE_PROFILES:
|
||||
viewport = {"width": DEVICE_PROFILES[device]["width"], "height": DEVICE_PROFILES[device]["height"]}
|
||||
user_agent = DEVICE_PROFILES[device]["user_agent"]
|
||||
|
||||
context = await browser.new_context(viewport=viewport, user_agent=user_agent)
|
||||
page = await context.new_page()
|
||||
await page.goto(url, wait_until="load")
|
||||
await websocket.send_text("Page loaded...")
|
||||
|
||||
for selector in click_selectors:
|
||||
try:
|
||||
await page.click(selector, timeout=2000)
|
||||
await websocket.send_text(f"Clicked element: {selector}")
|
||||
except Exception:
|
||||
pass # Ignore if element is not found
|
||||
|
||||
if delay > 0:
|
||||
await websocket.send_text(f"Waiting for {delay}ms...")
|
||||
await page.wait_for_timeout(delay)
|
||||
|
||||
if full_height:
|
||||
height = await page.evaluate("() => document.body.scrollHeight")
|
||||
await page.set_viewport_size({"width": viewport["width"], "height": height})
|
||||
|
||||
await page.screenshot(path=screenshot_path, full_page=full_height, type=file_ext)
|
||||
await websocket.send_text("Screenshot captured.")
|
||||
|
||||
await browser.close()
|
||||
|
||||
return screenshot_path
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
"""WebSocket Endpoint for Real-Time Updates."""
|
||||
await websocket.accept()
|
||||
active_connections[websocket] = True
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
params = json.loads(data)
|
||||
|
||||
screenshot_path = await take_screenshot(websocket, **params)
|
||||
await websocket.send_text(json.dumps({"screenshot_url": f"/screenshots/{os.path.basename(screenshot_path)}"}))
|
||||
|
||||
except WebSocketDisconnect:
|
||||
active_connections.pop(websocket, None)
|
||||
Reference in New Issue
Block a user