Add separate challenge template
This commit is contained in:
parent
76dd07f82b
commit
1123d09530
185
og-image/api/_lib/challenge-template.ts
Normal file
185
og-image/api/_lib/challenge-template.ts
Normal file
|
@ -0,0 +1,185 @@
|
|||
import { sanitizeHtml } from './sanitizer'
|
||||
import { ParsedRequest } from './types'
|
||||
|
||||
function getCss(theme: string, fontSize: string) {
|
||||
let background = 'white'
|
||||
let foreground = 'black'
|
||||
let radial = 'lightgray'
|
||||
|
||||
if (theme === 'dark') {
|
||||
background = 'black'
|
||||
foreground = 'white'
|
||||
radial = 'dimgray'
|
||||
}
|
||||
// To use Readex Pro: `font-family: 'Readex Pro', sans-serif;`
|
||||
return `
|
||||
@import url('https://fonts.googleapis.com/css2?family=Major+Mono+Display&family=Readex+Pro:wght@400;700&display=swap');
|
||||
|
||||
body {
|
||||
background: ${background};
|
||||
background-image: radial-gradient(circle at 25px 25px, ${radial} 2%, transparent 0%), radial-gradient(circle at 75px 75px, ${radial} 2%, transparent 0%);
|
||||
background-size: 100px 100px;
|
||||
height: 100vh;
|
||||
font-family: "Readex Pro", sans-serif;
|
||||
}
|
||||
|
||||
code {
|
||||
color: #D400FF;
|
||||
font-family: 'Vera';
|
||||
white-space: pre-wrap;
|
||||
letter-spacing: -5px;
|
||||
}
|
||||
|
||||
code:before, code:after {
|
||||
content: '\`';
|
||||
}
|
||||
|
||||
.logo-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0 75px;
|
||||
}
|
||||
|
||||
.plus {
|
||||
color: #BBB;
|
||||
font-family: Times New Roman, Verdana;
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
margin: 150px;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
margin: 0 .05em 0 .1em;
|
||||
vertical-align: -0.1em;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-family: 'Major Mono Display', monospace;
|
||||
font-size: ${sanitizeHtml(fontSize)};
|
||||
font-style: normal;
|
||||
color: ${foreground};
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.font-major-mono {
|
||||
font-family: "Major Mono Display", monospace;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #11b981;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export function getChallengeHtml(parsedReq: ParsedRequest) {
|
||||
const {
|
||||
theme,
|
||||
fontSize,
|
||||
question,
|
||||
creatorName,
|
||||
// creatorUsername,
|
||||
creatorAvatarUrl,
|
||||
challengerAmount,
|
||||
challengerOutcome,
|
||||
creatorAmount,
|
||||
creatorOutcome,
|
||||
} = parsedReq
|
||||
const MAX_QUESTION_CHARS = 85
|
||||
const truncatedQuestion =
|
||||
question.length > MAX_QUESTION_CHARS
|
||||
? question.slice(0, MAX_QUESTION_CHARS) + '...'
|
||||
: question
|
||||
const hideAvatar = creatorAvatarUrl ? '' : 'hidden'
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Generated Image</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<style>
|
||||
${getCss(theme, fontSize)}
|
||||
</style>
|
||||
<body>
|
||||
<div class="px-24">
|
||||
|
||||
|
||||
<div class="flex flex-col justify-between gap-16 pt-2">
|
||||
<div class="flex flex-col text-indigo-700 mt-4 text-5xl leading-tight text-center">
|
||||
${truncatedQuestion}
|
||||
</div>
|
||||
<div class="flex flex-row grid grid-cols-3">
|
||||
<div class="flex flex-col justify-center items-center ${
|
||||
creatorOutcome === 'YES' ? 'text-primary' : 'text-red-500'
|
||||
}">
|
||||
<div class="flex flex-row align-bottom gap-6">
|
||||
<img
|
||||
class="h-24 w-24 rounded-full bg-white flex items-center justify-center ${hideAvatar}"
|
||||
src="${creatorAvatarUrl}"
|
||||
alt=""
|
||||
/>
|
||||
<div class="flex flex-col gap-2 items-center justify-center">
|
||||
<p class="text-gray-900 text-4xl">${creatorName}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-6xl mt-8">${'M$' + creatorAmount}</div>
|
||||
<div class="text-3xl">${'on'}</div>
|
||||
<div class="text-6xl ">${creatorOutcome}</div>
|
||||
</div>
|
||||
<div class="flex flex-col text-gray-900 text-6xl mt-8 text-center">
|
||||
⚔️
|
||||
</div>
|
||||
<div class="flex flex-col justify-center items-center ${
|
||||
challengerOutcome === 'YES' ? 'text-primary' : 'text-red-500'
|
||||
}">
|
||||
<div class="flex flex-row align-bottom gap-6 mr-20">
|
||||
<img
|
||||
class="h-24 w-24 rounded-full bg-white flex items-center justify-center ${hideAvatar}"
|
||||
src="https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png"
|
||||
alt=""
|
||||
/>
|
||||
<div class="flex flex-col gap-2 items-center justify-center">
|
||||
<p class="text-gray-900 text-4xl">You</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-6xl mt-8">${'M$' + challengerAmount}</div>
|
||||
<div class="text-3xl">${'on'}</div>
|
||||
<div class="text-6xl ">${challengerOutcome}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- Manifold logo -->
|
||||
<div class="flex flex-row justify-center mt-14 ">
|
||||
<a class="flex flex-row gap-3" href="/">
|
||||
<img
|
||||
class="sm:h-12 sm:w-12"
|
||||
src="https://manifold.markets/logo.png"
|
||||
width="40"
|
||||
height="40"
|
||||
alt=''
|
||||
/>
|
||||
<div
|
||||
class="hidden sm:flex font-major-mono lowercase mt-1 sm:text-3xl md:whitespace-nowrap"
|
||||
>
|
||||
Manifold Markets
|
||||
</div></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
|
@ -22,8 +22,10 @@ export function parseRequest(req: IncomingMessage) {
|
|||
creatorAvatarUrl,
|
||||
|
||||
// Challenge attributes:
|
||||
challengeAmount,
|
||||
challengeOutcome,
|
||||
challengerAmount,
|
||||
challengerOutcome,
|
||||
creatorAmount,
|
||||
creatorOutcome,
|
||||
} = query || {}
|
||||
|
||||
if (Array.isArray(fontSize)) {
|
||||
|
@ -71,8 +73,10 @@ export function parseRequest(req: IncomingMessage) {
|
|||
creatorName: getString(creatorName) || 'Manifold Markets',
|
||||
creatorUsername: getString(creatorUsername) || 'ManifoldMarkets',
|
||||
creatorAvatarUrl: getString(creatorAvatarUrl) || '',
|
||||
challengeAmount: getString(challengeAmount) || '',
|
||||
challengeOutcome: getString(challengeOutcome) || '',
|
||||
challengerAmount: getString(challengerAmount) || '',
|
||||
challengerOutcome: getString(challengerOutcome) || '',
|
||||
creatorAmount: getString(creatorAmount) || '',
|
||||
creatorOutcome: getString(creatorOutcome) || '',
|
||||
}
|
||||
parsedRequest.images = getDefaultImages(parsedRequest.images)
|
||||
return parsedRequest
|
||||
|
|
|
@ -91,16 +91,13 @@ export function getHtml(parsedReq: ParsedRequest) {
|
|||
creatorName,
|
||||
creatorUsername,
|
||||
creatorAvatarUrl,
|
||||
challengeAmount,
|
||||
challengeOutcome,
|
||||
} = parsedReq
|
||||
const MAX_QUESTION_CHARS = challengeAmount ? 85 : 100
|
||||
const MAX_QUESTION_CHARS = 100
|
||||
const truncatedQuestion =
|
||||
question.length > MAX_QUESTION_CHARS
|
||||
? question.slice(0, MAX_QUESTION_CHARS) + '...'
|
||||
: question
|
||||
const hideAvatar = creatorAvatarUrl ? '' : 'hidden'
|
||||
const hideNonChallengeElements = challengeAmount !== '' ? 'hidden' : ''
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
@ -147,29 +144,17 @@ export function getHtml(parsedReq: ParsedRequest) {
|
|||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between gap-12 pt-36">
|
||||
<div class="flex flex-col text-gray-900 text-6xl">
|
||||
${challengeAmount ? '⚔️ Bet against me ⚔️' : ''}
|
||||
<div class="text-indigo-700 leading-tight">
|
||||
${truncatedQuestion}
|
||||
</div>
|
||||
<div class="text-indigo-700 text-6xl leading-tight">
|
||||
${truncatedQuestion}
|
||||
</div>
|
||||
<div class="flex flex-col text-primary">
|
||||
<div class="text-8xl">${
|
||||
challengeAmount ? 'M$' + challengeAmount : probability
|
||||
}</div>
|
||||
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div class="text-4xl">${challengeOutcome ? 'on' : ''}</div>
|
||||
<div class="text-8xl ">${challengeOutcome ?? ''}</div>
|
||||
</div>
|
||||
<div class="text-4xl ${hideNonChallengeElements}">${
|
||||
probability !== '' ? 'chance' : ''
|
||||
}</div>
|
||||
<div class="text-8xl">${probability}</div>
|
||||
<div class="text-4xl">${probability !== '' ? 'chance' : ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="absolute bottom-16 ${hideNonChallengeElements}">
|
||||
<div class="absolute bottom-16">
|
||||
<div class="text-gray-500 text-3xl">
|
||||
${metadata}
|
||||
</div>
|
||||
|
|
|
@ -18,6 +18,9 @@ export interface ParsedRequest {
|
|||
creatorName: string
|
||||
creatorUsername: string
|
||||
creatorAvatarUrl: string
|
||||
challengeAmount: string
|
||||
challengeOutcome: string
|
||||
// Challenge attributes:
|
||||
challengerAmount: string
|
||||
challengerOutcome: string
|
||||
creatorAmount: string
|
||||
creatorOutcome: string
|
||||
}
|
||||
|
|
|
@ -1,36 +1,38 @@
|
|||
import { IncomingMessage, ServerResponse } from "http";
|
||||
import { parseRequest } from "./_lib/parser";
|
||||
import { getScreenshot } from "./_lib/chromium";
|
||||
import { getHtml } from "./_lib/template";
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import { parseRequest } from './_lib/parser'
|
||||
import { getScreenshot } from './_lib/chromium'
|
||||
import { getHtml } from './_lib/template'
|
||||
import { getChallengeHtml } from './_lib/challenge-template'
|
||||
|
||||
const isDev = !process.env.AWS_REGION;
|
||||
const isHtmlDebug = process.env.OG_HTML_DEBUG === "1";
|
||||
const isDev = !process.env.AWS_REGION
|
||||
const isHtmlDebug = process.env.OG_HTML_DEBUG === '1'
|
||||
|
||||
export default async function handler(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse
|
||||
) {
|
||||
try {
|
||||
const parsedReq = parseRequest(req);
|
||||
const html = getHtml(parsedReq);
|
||||
const parsedReq = parseRequest(req)
|
||||
let html = getHtml(parsedReq)
|
||||
if (parsedReq.challengerOutcome) html = getChallengeHtml(parsedReq)
|
||||
if (isHtmlDebug) {
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.end(html);
|
||||
return;
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.end(html)
|
||||
return
|
||||
}
|
||||
const { fileType } = parsedReq;
|
||||
const file = await getScreenshot(html, fileType, isDev);
|
||||
res.statusCode = 200;
|
||||
res.setHeader("Content-Type", `image/${fileType}`);
|
||||
const { fileType } = parsedReq
|
||||
const file = await getScreenshot(html, fileType, isDev)
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Type', `image/${fileType}`)
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
'Cache-Control',
|
||||
`public, immutable, no-transform, s-maxage=31536000, max-age=31536000`
|
||||
);
|
||||
res.end(file);
|
||||
)
|
||||
res.end(file)
|
||||
} catch (e) {
|
||||
res.statusCode = 500;
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.end("<h1>Internal Error</h1><p>Sorry, there was a problem</p>");
|
||||
console.error(e);
|
||||
res.statusCode = 500
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.end('<h1>Internal Error</h1><p>Sorry, there was a problem</p>')
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user