![GitHub](https://camo.githubusercontent.com/c003beec627d2194209ee3ff45ac1497299ead75de4bed3248a7d70b78cdacf5/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f627973616765732f6c656e73) ![GitHub Actions](https://camo.githubusercontent.com/1dee2ee70ee4a18f9ca055d66a2eac771963838dd5accc260501dba99f7df7ca/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f627973616765732f6c656e732f646f636b65722d6275696c642e796d6c) ![Contributor Covenant](https://camo.githubusercontent.com/4ae39ae593b602cf0ae07972b61c73728b77ec8e2cf40f579a2441948208036b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436f6e7472696275746f72253230436f76656e616e742d322e312d3462616161612e737667)

> A high-performance image proxy and web services toolkit built with modern TypeScript. Lens provides a comprehensive suite of web utilities including image processing, screenshot capture, font serving, and more.



## 🚀 Features



### Core Services

- **🖼️ Image Proxy**: IPX-powered image processing with resize, format conversion, optimization
- **📸 Screenshot Capture**: Fast website screenshot service with resource optimization
- **📄 Reader**: Article content extraction (HTML/Markdown) with Readability
- **🅰️ Font Service**: Google Fonts compatible API with multiple providers
- **🎨 Open Graph Images**: Dynamic OG image generation
- **🎯 Favicon Extraction**: Smart favicon extraction from websites
- **👤 Gravatar Proxy**: Cached Gravatar avatar proxy with email or hash input
- **🤖 MCP Server**: Model Context Protocol server exposing all services as tools via JSON-RPC 2.0



### Performance & Scalability

- **⚡ Redis Caching**: Redis-powered caching with 24-hour intelligent caching
- **🚀 Resource Optimization**: Optimized page loading with blocked unnecessary resources
- **🛡️ Rate Limiting**: Rate limiting for expensive operations
- **🔄 Graceful Degradation**: Automatic fallbacks for missing services



## 📦 Quick Start



### Prerequisites

- Node.js 22+
- pnpm 10+ (recommended) or npm



### Installation

```
# Clone the repository
git clone https://github.com/bysages/lens.git
cd lens

# Install dependencies
pnpm install

# Copy environment configuration
cp .env.example .env
```



### Development

```
# Start development server
pnpm dev

# Build for production
pnpm build

# Preview production build
pnpm preview
```

The server will start on `http://localhost:3000` by default.



## 🛠️ Configuration



### Configuration

All configurations are optional and will gracefully degrade:

```
# Image proxy security
ALLOWED_DOMAINS=example.com,cdn.example.com

# Caching (significantly improves performance)
REDIS_URL=redis://localhost:6379
```

See `.env.example` for all available options.



## 📡 API Reference

All cached responses include `X-Cache` headers (HIT/MISS) and `ETag` headers for conditional 304 responses.



### Images



#### Image Proxy

Transform and optimize images on-the-fly:

```
GET /img/{modifiers}/{image_url}

```

**Examples:**

```
# Resize to 300px width, convert to WebP
https://api.bysages.com/img/w_300,f_webp/https://example.com/image.jpg

# Create 200x200 square thumbnail
https://api.bysages.com/img/s_200x200,q_80/https://example.com/image.png

# Smart crop with high quality
https://api.bysages.com/img/w_400,h_300,c_fill,q_95/https://example.com/photo.jpg
```

**Supported Modifiers:**

- `w_XXX` - Width
- `h_XXX` - Height
- `s_XXXxYYY` - Size (width x height)
- `f_FORMAT` - Format (webp, jpg, png, avif)
- `q_XXX` - Quality (1-100)
- `c_MODE` - Crop mode (fill, fit, pad)

**Performance Features:**

- Redis caching for optimal performance
- Automatic format optimization and compression



#### Screenshot Capture

Capture website screenshots with Playwright-aligned options:

```
GET /screenshot?url={website_url}&options

```

**Examples:**

```
# Basic screenshot
https://api.bysages.com/screenshot?url=https://example.com

# JPEG with custom quality
https://api.bysages.com/screenshot?url=https://example.com&type=jpeg&quality=80

# Mobile screenshot with custom viewport
https://api.bysages.com/screenshot?url=https://example.com&width=375&height=667&mobile=true

# Full page capture
https://api.bysages.com/screenshot?url=https://example.com&fullPage=true

# Clip region (x,y,width,height)
https://api.bysages.com/screenshot?url=https://example.com&clip=0,0,400,300

# Transparent background
https://api.bysages.com/screenshot?url=https://example.com&omitBackground=true

# Disable animations for clean capture
https://api.bysages.com/screenshot?url=https://example.com&animations=disabled

# Custom CSS scale
https://api.bysages.com/screenshot?url=https://example.com&scale=css

# Inject custom stylesheet
https://api.bysages.com/screenshot?url=https://example.com&style=body{background:red}
```

**Parameters:**


| Parameter        | Description                                                           | Default            |
| ---------------- | --------------------------------------------------------------------- | ------------------ |
| `url`            | Website URL (required)                                                | -                  |
| `width`          | Viewport width (100-2560)                                             | 1280               |
| `height`         | Viewport height (100-1440)                                            | 720                |
| `type`           | Output format (`png`, `jpeg`)                                         | `png`              |
| `quality`        | Image quality, 1-100 (jpeg only)                                      | -                  |
| `fullPage`       | Capture full scrollable page (`true`/`false`)                         | `false`            |
| `clip`           | Clip region as `x,y,width,height`                                     | -                  |
| `omitBackground` | Hide default white background (`true`/`false`, png only)              | `false`            |
| `scale`          | Screenshot scale (`css` for CSS pixels, `device` for device pixels)   | `device`           |
| `animations`     | Animation handling (`disabled` or `allow`)                            | `allow`            |
| `caret`          | Text caret (`hide` or `initial`)                                      | `hide`             |
| `style`          | Custom CSS stylesheet to apply                                        | -                  |
| `timeout`        | Navigation timeout in ms (1000-30000)                                 | 10000              |
| `mobile`         | Mobile viewport (`true`/`false`)                                      | `false`            |
| `darkMode`       | Dark mode (`true`/`false`)                                            | `false`            |
| `waitUntil`      | Navigation wait condition (`load`, `domcontentloaded`, `networkidle`) | `domcontentloaded` |
| `delay`          | Additional delay in ms after page load (0-10000)                      | -                  |


**Performance Notes:**

- Screenshots are cached for 1 day with rate limiting
- Identical requests return cached results with sub-second response times
- Resource optimization (blocking fonts, media, websockets) speeds up screenshot generation by 60-80%



#### Open Graph Images

Generate dynamic OG images:

```
GET /og?title={title}&description={description}

```

**Examples:**

```
# Basic OG image
https://api.bysages.com/og?title=Welcome&description=Get%20started%20with%20Lens

# Custom styling
https://api.bysages.com/og?title=Hello%20World&theme=dark&fontSize=72&width=1200&height=630
```

**Caching:**

- Generated OG images are cached for 24 hours
- Identical requests with same parameters return cached results instantly



### Web Services



#### Reader (Content Extraction)

Extract rendered HTML or Markdown content from any URL:

```
GET /reader?url={website_url}&options

```

**Examples:**

```
# Extract rendered HTML
https://api.bysages.com/reader?url=https://github.com/bysages/lens

# Convert to Markdown
https://api.bysages.com/reader?url=https://github.com/bysages/lens&format=markdown

# Wait for full page load
https://api.bysages.com/reader?url=https://github.com/bysages/lens&waitUntil=load
```

**Parameters:**


| Parameter   | Description                                                           | Default            |
| ----------- | --------------------------------------------------------------------- | ------------------ |
| `url`       | Website URL (required)                                                | -                  |
| `format`    | Output format (`html`, `markdown`)                                    | `html`             |
| `waitUntil` | Navigation wait condition (`load`, `domcontentloaded`, `networkidle`) | `domcontentloaded` |
| `delay`     | Additional delay in ms after page load (0-10000)                      | -                  |
| `timeout`   | Navigation timeout in ms (1000-30000)                                 | 10000              |




#### Favicon Extraction

Extract high-quality favicons:

```
GET /favicon?url={website_url}&size={size}

```

**Examples:**

```
# Extract favicon
https://api.bysages.com/favicon?url=github.com

# Custom size
https://api.bysages.com/favicon?url=github.com&size=64
```

**Features:**

- Smart favicon extraction from multiple sources (PWA manifests, Apple touch icons, HTML tags)
- 30-day server-side caching with ETag/304 support
- Automatic fallback to generated favicon if none found



#### Gravatar Proxy

Get Gravatar avatars by email, MD5 hash, or path. All query parameters (except `email`/`hash`) are forwarded directly to Gravatar. You can switch from Gravatar by simply replacing the URL prefix.

```
GET /gravatar/{md5}?{gravatar_params}
GET /gravatar?email={email}
GET /gravatar?hash={md5}&{gravatar_params}

```

**Examples:**

```
# By path (drop-in replacement for Gravatar URLs)
https://api.bysages.com/gravatar/{md5}?size=200

# By email
https://api.bysages.com/gravatar?email=user@example.com

# By MD5 hash
https://api.bysages.com/gravatar?hash={md5}

# With Gravatar parameters
https://api.bysages.com/gravatar?email=user@example.com&size=200&default=identicon&rating=pg
```

**Parameters:**

- `{md5}` - MD5 hash in URL path (drop-in replacement mode)
- `email` - Email address (will be MD5 hashed automatically)
- `hash` - Pre-computed MD5 hash (alternative to email)
- All other parameters are forwarded to [Gravatar API](https://docs.gravatar.com/api/images/)



#### MCP Server

Model Context Protocol server exposing all Lens services as tools via JSON-RPC 2.0 over HTTP.

```
POST /mcp

```

**Setup with Claude Desktop** (`claude_desktop_config.json`):

```
{
  "mcpServers": {
    "lens": {
      "url": "https://api.bysages.com/mcp"
    }
  }
}
```

**Available Tools:**


| Tool         | Description                                             | Returns |
| ------------ | ------------------------------------------------------- | ------- |
| `reader`     | Extract article content from a URL (HTML/Markdown)      | text    |
| `screenshot` | Capture a screenshot of a website                       | image   |
| `favicon`    | Extract a website's favicon                             | image   |
| `og`         | Generate a dynamic Open Graph image                     | image   |
| `gravatar`   | Get a Gravatar avatar by email or hash                  | image   |
| `img`        | Transform and optimize an image (resize, crop, convert) | image   |




### Fonts



#### Font Service

Google Fonts compatible API with both v1 and v2 endpoints:

```
GET /css?family={font_family}&display=swap
GET /css2?family={font_family}&display=swap

```

**API Differences:**


| Feature        | `/css` (v1)                   | `/css2` (v2)                                 |
| -------------- | ----------------------------- | -------------------------------------------- |
| Multiple fonts | `family=A|B` (pipe separator) | `family=A&family=B` (repeated parameter)     |
| Style syntax   | `FontName:400,700` (old)      | `FontName:wght@400;700` (new, strict)        |
| Variable fonts | ❌ Not supported               | ✅ Full support with ranges (`wght@200..900`) |
| Base URL       | `fonts.googleapis.com/css`    | `fonts.googleapis.com/css2`                  |


**Parameters:**

- `family` - Font family name (required)
- `display` - Font display strategy (default: swap)
- `subset` - Font subset (default: latin)
- `provider` - Font provider (google, bunny, fontshare, fontsource, default: google)
- `proxy` - Use proxy for font files (true/false, default: false)

**Examples:**

```
# Basic font (v1 API - pipe separator)
https://api.bysages.com/css?family=Roboto:wght@400;700|Open+Sans:wght@300;400;600&display=swap

# Multiple fonts (v2 API - repeated family parameter)
https://api.bysages.com/css2?family=Inter:wght@400;700&family=Roboto:wght@300;400&display=swap

# Variable fonts with weight range (v2 only)
https://api.bysages.com/css2?family=Inter:wght@200..800&display=swap

# Font metadata
https://api.bysages.com/webfonts?sort=popularity&category=sans-serif
```

**Recommendations:**

- Use `/css` for **legacy compatibility** with old Google Fonts syntax
- Use `/css2` for **modern applications** with variable fonts and better optimization
- Both endpoints support all font providers (Google, Bunny, Fontshare, Fontsource)



## 🏗️ Architecture

Lens is built with modern web standards and follows clean architecture principles:



### Core Technologies

- **Runtime**: Node.js 22+ with Nitro
- **Language**: TypeScript with strict type safety
- **Image Proxy**: IPX with Sharp
- **Browser Automation**: Playwright with Crawlee BrowserPool
- **Caching**: Redis with unstorage



### Design Principles

- **KISS (Keep It Simple)**: Simple, focused solutions over complex abstractions
- **DRY (Don't Repeat Yourself)**: Shared utilities and configurations
- **Graceful Degradation**: Fallbacks for missing dependencies
- **Type Safety**: Comprehensive TypeScript coverage
- **Performance First**: Optimized for speed and efficiency



## 🚀 Deployment



### Prerequisites

- **Node.js** 22+ runtime
- **pnpm** 10+ package manager
- **Redis** (optional, for distributed caching)



### Production Deployment

```
# Build the application
pnpm run build

# Start production server
pnpm run preview
```



### Docker Deployment (Recommended)



#### Option 1: Using Docker Compose

```
# Copy environment variables
cp .env.example .env

# Edit .env file to configure your settings
# nano .env

# Start the application
docker-compose up -d

# View logs
docker-compose logs -f app

# Stop the application
docker-compose down
```

The application will be available at `http://localhost:3000`

The stack includes:

- **Lens** application on port 3000
- **Redis** for distributed caching (optional but recommended)



#### Option 2: Using Docker Run

```
# Pull the official image
docker pull bysages/lens:latest

# Run container with host network (recommended for accurate IP logging)
docker run -d \
  --name lens \
  --network host \
  --env-file .env \
  --restart unless-stopped \
  bysages/lens:latest

# View logs
docker logs -f lens

# Stop the container
docker stop lens
docker rm lens
```



#### Option 3: Build from Source

```
# Build Docker image
docker build -t lens .

# Run container
docker run -d \
  --name lens \
  --network host \
  --env-file .env \
  --restart unless-stopped \
  lens
```



### Environment Variables

All configurations are optional:

```
# Image Proxy Security
ALLOWED_DOMAINS=example.com,cdn.example.com

# Caching (significantly improves performance)
REDIS_URL=redis://localhost:6379
```

See `.env.example` for all available options.



## 📄 License

MIT License - see [LICENSE](/bysages/lens/blob/main/LICENSE) file for details.

---

Built with ❤️ by [By Sages](https://github.com/bysages)