first commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
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)
|
||||
28
docker-compose.yaml
Normal file
28
docker-compose.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
screenshot-api:
|
||||
build: .
|
||||
container_name: screenshot-api
|
||||
ports:
|
||||
- "80:80"
|
||||
environment:
|
||||
- API_SECRET=my_secret_key
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
- redis
|
||||
networks:
|
||||
- screenshot-network
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
container_name: screenshot-redis
|
||||
restart: always
|
||||
ports:
|
||||
- "6379:6379"
|
||||
networks:
|
||||
- screenshot-network
|
||||
|
||||
networks:
|
||||
screenshot-network:
|
||||
driver: bridge
|
||||
24
dockerfile
Normal file
24
dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
# Use official Python image
|
||||
FROM python:3.10
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependency files first for caching
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Install Playwright and required browsers
|
||||
RUN pip install playwright
|
||||
RUN playwright install --with-deps chromium
|
||||
|
||||
# Copy application files
|
||||
COPY app ./app
|
||||
|
||||
# Expose ports
|
||||
EXPOSE 80
|
||||
|
||||
# Run FastAPI with Uvicorn
|
||||
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port 80 --reload"]
|
||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
fastapi
|
||||
uvicorn
|
||||
playwright
|
||||
fastapi-limiter
|
||||
redis
|
||||
Reference in New Issue
Block a user