Implement v2 API for stateful sessions and add engagement metrics support

This commit is contained in:
ImBenji
2026-01-02 00:45:14 +00:00
parent 48fce1564a
commit 48e6c7e3c1
7 changed files with 1325 additions and 160 deletions

266
README.md
View File

@@ -6,12 +6,15 @@ Generate Twitter-style quote images programmatically. Perfect for creating socia
## Features
- 🎨 Twitter-authentic styling
- 🖼️ Support for images and text-only tweets
- 📸 High-resolution output (3240x3240)
- ⚡ Built-in caching (24-hour TTL)
- 🔒 Base64 image support
- 🐳 Docker-ready
- Twitter-authentic styling
- Support for images and text-only tweets
- Verified badge (blue checkmark)
- Engagement metrics (likes, retweets, replies, views)
- High-resolution output (3240x3240)
- Built-in caching (24-hour TTL)
- Base64 image support
- Docker-ready
- v2 API with stateful sessions for incremental updates
## API Endpoints
@@ -51,6 +54,8 @@ curl "https://quotes.imbenji.net/generate?displayName=John%20Doe&username=@johnd
| `text` | string | No | Tweet text content |
| `imageUrl` | string | No | Tweet image URL, base64 data URI, or `null` |
| `timestamp` | integer | No | Unix epoch timestamp in seconds |
| `verified` | boolean | No | Show verified badge (blue checkmark) next to name |
| `engagement` | object | No | Engagement metrics (likes, retweets, replies, views) |
## Response
@@ -145,6 +150,251 @@ curl -G "https://quotes.imbenji.net/generate" \
--output quote.png
```
### Verified badge
```javascript
fetch('https://quotes.imbenji.net/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
displayName: 'Elon Musk',
username: '@elonmusk',
text: 'Just bought Twitter!',
verified: true,
timestamp: 1735574400
})
});
```
### Engagement metrics
```javascript
fetch('https://quotes.imbenji.net/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
displayName: 'Popular User',
username: '@viral',
text: 'This tweet went viral!',
verified: true,
engagement: {
replies: 1234,
retweets: 5678,
likes: 98765,
views: 1500000
},
timestamp: 1735574400
})
});
```
**Note:** All four engagement fields (replies, retweets, likes, views) must be provided for the engagement bar to appear. Numbers are automatically formatted (e.g., 1234 → 1.2K, 1500000 → 1.5M).
---
## v2 API (Stateful Sessions)
The v2 API lets you build quote images incrementally. Instead of sending everything in one request, you create a session and update fields as needed. This is more efficient when making multiple edits since you dont need to resend large images every time.
### How it works
1. Create a session with `POST /v2/quote`
2. Update fields with `PATCH /v2/quote/:id` (only send whats changed)
3. Render the image with `GET /v2/quote/:id/image`
Sessions expire after 24 hours of inactivity.
### POST /v2/quote
Create a new session.
**Request:**
```bash
curl -X POST https://quotes.imbenji.net/v2/quote \
-H "Content-Type: application/json" \
-d '{
"displayName": "John Doe",
"username": "@johndoe",
"text": "Hello world"
}'
```
**Response (201):**
```json
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"displayName": "John Doe",
"username": "@johndoe",
"text": "Hello world",
"avatarUrl": null,
"imageUrl": null,
"timestamp": null,
"verified": false,
"engagement": null,
"createdAt": 1735600000,
"updatedAt": 1735600000
}
```
### GET /v2/quote/:id
Get current session state.
```bash
curl https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890
```
### PATCH /v2/quote/:id
Update specific fields. Only send the fields you want to change.
```bash
curl -X PATCH https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Content-Type: application/json" \
-d '{
"text": "Updated text!",
"avatarUrl": "data:image/png;base64,..."
}'
```
**Engagement updates:**
```bash
# Set all engagement metrics
curl -X PATCH https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Content-Type: application/json" \
-d '{
"engagement": {
"replies": 100,
"retweets": 250,
"likes": 5000,
"views": 50000
}
}'
# Partial update (update only likes, keep other fields)
curl -X PATCH https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Content-Type: application/json" \
-d '{
"engagement": {
"likes": 10000
}
}'
# Clear engagement entirely (removes engagement bar)
curl -X PATCH https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Content-Type: application/json" \
-d '{
"engagement": null
}'
```
**Verified badge:**
```bash
# Add verified badge
curl -X PATCH https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Content-Type: application/json" \
-d '{
"verified": true
}'
```
### GET /v2/quote/:id/image
Render the current session state as a PNG image.
```bash
curl https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890/image --output quote.png
```
**Response headers:**
- `Content-Type: image/png`
- `X-Session-Id: <session-id>`
- `X-Cache: HIT` or `MISS`
### DELETE /v2/quote/:id
Delete a session.
```bash
curl -X DELETE https://quotes.imbenji.net/v2/quote/a1b2c3d4-e5f6-7890-abcd-ef1234567890
```
Returns `204 No Content` on success.
### v2 Example Workflow
```javascript
// 1. Create session with initial data
const res = await fetch('https://quotes.imbenji.net/v2/quote', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
displayName: 'Jane Doe',
username: '@janedoe',
text: 'Working on something cool'
})
});
const session = await res.json();
const sessionId = session.id;
// 2. Later, add an avatar (only sends the avatar, not everything again)
await fetch(`https://quotes.imbenji.net/v2/quote/${sessionId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
avatarUrl: 'data:image/png;base64,iVBORw0KGgo...'
})
});
// 3. Update the text
await fetch(`https://quotes.imbenji.net/v2/quote/${sessionId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: 'Finished building something cool!'
})
});
// 4. Generate the final image
const imgRes = await fetch(`https://quotes.imbenji.net/v2/quote/${sessionId}/image`);
const blob = await imgRes.blob();
```
### v2 Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `displayName` | string | Display name |
| `username` | string | Twitter handle with @ |
| `avatarUrl` | string | Avatar image URL or base64 |
| `text` | string | Tweet text content |
| `imageUrl` | string | Tweet image URL or base64 |
| `timestamp` | integer | Unix epoch in seconds |
| `verified` | boolean | Show verified badge (blue checkmark) |
| `engagement` | object/null | Engagement metrics (see below) |
**Engagement object:**
```json
{
"engagement": {
"replies": 89,
"retweets": 567,
"likes": 1234,
"views": 50000
}
}
```
**Engagement behavior:**
- All four fields must be provided for the engagement bar to appear
- Partial updates: Only update specific fields, others remain unchanged
- Set to `null` to clear all engagement and hide the engagement bar
- Numbers are auto-formatted (1234 → 1.2K, 1000000 → 1M)
---
## Caching
The API automatically caches generated images for 24 hours from the last request. Identical requests will be served from cache instantly.
@@ -217,7 +467,9 @@ Response:
- **Format:** PNG
- **Max tweet width:** 450px (auto-scaled to fit)
- **Cache TTL:** 24 hours from last access
- **Session TTL:** 24 hours from last update (v2 API)
- **Cleanup interval:** Every hour
- **Database:** SQLite (for v2 sessions)
## Limitations
@@ -232,4 +484,4 @@ For issues or questions, please contact the maintainer.
---
**Built with:** Node.js, Express, node-html-to-image, Puppeteer
**Built with:** Node.js, Express, Puppeteer, SQLite