first commit

This commit is contained in:
2025-02-06 15:15:51 +00:00
commit 826d9573d8
8 changed files with 279 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.DS_Store

0
README.md Normal file
View File

75
app/docs/index.html Normal file
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
fastapi
uvicorn
playwright
fastapi-limiter
redis