Skip to content

Instantly share code, notes, and snippets.

@Potherca
Last active July 20, 2026 18:08
Show Gist options
  • Select an option

  • Save Potherca/2361f154d296b9644d6b54a08346e2f6 to your computer and use it in GitHub Desktop.

Select an option

Save Potherca/2361f154d296b9644d6b54a08346e2f6 to your computer and use it in GitHub Desktop.
Cloudflare worker to preview GitHub Gists - https://gist.pother.ca/
const quotes = [
'A complete and utter technological meltdown.',
'A complete black hole of information.',
'A complete communication breakdown.',
'A massive distortion in the space-time continuum.',
'A massive glitch in the mainframe.',
'A massive tear in the fabric of reality.',
'A terrible fate has befallen this place.',
'A total disruption in the natural order.',
'A total eclipse of the system.',
'Ah, biscuits!',
'An absolute, unmitigated technical disaster.',
'An unknown error has disrupted the continuum.',
'Barnacles!',
'Blistering barnacles!',
'Curse this sudden but inevitable betrayal!',
'Everything is gone. The flame has faded.',
'Everything is in ruins. Total destruction.',
'Everything is spinning out of control.',
'Everything is spinning out of orbit.',
'Everything\'s gone pear-shaped.',
'Good grief.',
'Great Caesar\'s ghost!',
'Great Scott!',
'Houston, we have a problem.',
'It\'s a total blackout.',
'Mission failed. We\'ll get \'em next time.',
'Mistakes were made.',
'My coordinates are entirely gone.',
'Nooo! My ice cream!',
'Nothing but a bottomless pit of emptiness.',
'Nothing but absolute, chaotic static.',
'Nothing but an endless loop of errors.',
'Nothing but emptiness and despair.',
'Nothing but static and broken code.',
'Nothing but static. Total dead zone.',
'Nothing makes sense anymore.',
'Oh dear. Oh dear. I shall be too late!',
'Oh, bother.',
'Oh, tartarsauce!',
'Our princess is in another castle.',
'Oy vey.',
'Snake? Snake?! SNAAAAAAKE!!!',
'Something is amiss.',
'Something is rotten in the state of Denmark.',
'Sufferin\' succotash!',
'The bridge is gone! The tracks just stop!',
'The cake is a lie.',
'The circuit is broken.',
'The compass is spinning in circles.',
'The continuum is disrupted.',
'The coordinates are completely wrong. We are nowhere.',
'The core is destabilizing.',
'The data stream is completely frozen.',
'The destination has vanished from the map.',
'The entire grid has gone dark.',
'The entire mainframe is toast.',
'The entire matrix is dissolving.',
'The extraction point is gone. We are completely stranded.',
'The internal logic circuits are totally scrambled.',
'The link is broken. The timeline is fractured.',
'The logic circuits are entirely fried.',
'The machine is completely broken.',
'The machinery is completely uncooperative.',
'The mainframe is completely unresponsive.',
'The map is entirely blank. The path is lost.',
'The map is upside down and backwards.',
'The math is not mathing.',
'The matrix is degrading.',
'The pathway has vanished into thin air.',
'The radar is picking up absolutely nothing.',
'The server room is entirely engulfed in flames.',
'The signals are completely crossed.',
'The signals are completely gone. Dead air.',
'The simulation is collapsing!',
'The topology of this space is completely unmappable.',
'The tracks are completely washed away.',
'The transmission is completely lost.',
'The whole infrastructure is collapsing.',
'The whole operation is completely compromised.',
'The whole reality is falling apart.',
'The wilderness must be explored! ...Wait, this isn\'t the wilderness.',
'The wiring is completely tangled.',
'There is nothing here but dust.',
'This is a dark day.',
'This is an unmitigated disaster.',
'This is not a drill! Repeat, this is not a drill!',
'This is the end of the line.',
'This path leads straight into a brick wall.',
'Total blackout. The radar is picking up zero signals.',
'Total code red. The system is failing.',
'Total structural failure.',
'Well, this is a fine mess.',
]
function getRandomQuote() {
const randomIndex = Math.floor(Math.random() * quotes.length)
return quotes[randomIndex]
}
export { getRandomQuote }
export default getRandomQuote
import { marked } from './marked.js' // https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js
import { getRandomQuote } from './getRandomQuote.js'
function createErrorResponse(title, message, headers, status, statusText) {
headers = headers || {}
headers['Content-Type'] = 'text/html; charset=utf-8'
headers['Access-Control-Allow-Origin'] = '*'
if (!message) {
message = `<p>${statusText}</p>`
}
// If the HTML is less than 512 bytes Chrome doesn't show it
const padding = 1024
if (message.length < padding) {
const paddingString = '.'.repeat(padding - message.length)
message += `<!-- ${paddingString} -->`
}
let body = `<!doctype html><html lang="en"><meta charset="UTF-8">
<link rel="shortcut icon" href="data:image/svg+xml,<svg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'><text x='0' y='15'>⚠️</text></svg>" />
<style>title{display:inline;}h1:after{content:" (${status})"; color: #CBB}h1:before{content:"Error: "; color:crimson}</style><title>${title}</title><h1>${title}</h1>${message}</html>
`
return new Response(body, { headers, status, statusText })
}
export default {
async fetch(request, env) {
// Check if all required environment variables are set.
const requiredEnv = [
'GITHUB_TOKEN',
]
const missingEnv = requiredEnv.filter(key => !env[key])
if (missingEnv.length > 0) {
return createErrorResponse(
'Missing environment variables',
`<ul><li>${missingEnv.join('</li><li>')}</li></ul>`, {}, 500, 'Please set missing ENV vars.',
)
}
const { pathname } = new URL(request.url)
const baseUrl = new URL(request.url).origin
if (pathname === '/') {
// @TODO: Either redirect to the Gist that has the HTML form, or fetch and show the HTML form...
const message = `<!doctype html>
<html color-mode="user" lang="en">
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0" name="viewport" />
<link crossorigin href="https://cdn.jsdelivr.net/" rel="preconnect" />
<link href="https://cdn.jsdelivr.net/npm/mvp.css@1.17.3/mvp.css" rel="stylesheet" />
<link href="https://pother.ca/CssBase/css/created-by-potherca.css" rel="stylesheet" />
<style>
/* Generic tweaks to MVP.css styles */
::selection {
background-color: var(--color-link);
text-shadow: 0 0 3px white;
}
body, html {
min-height: 100vh;
}
footer {
align-items: center;
background-color: var(--color-bg-secondary);
display: flex;
flex-wrap: wrap;
justify-content: space-around;
padding: 0.35rem 1rem 1rem 0;
position: sticky;
top: 100%;
}
footer p {
text-align: center;
width: fit-content;
}
footer svg {
height: 2rem;
}
header {
padding-bottom: 0;
}
nav {
margin-bottom: 0;
}
title {
display: inline;
}
/* Page specific styles */
input {
width: calc(100% - 1.5rem);
}
</style>
<article>
<header>
<h1><title>Gist Preview</title></h1>
<p>Call this URL with the ID of the gist appended</p>
</header>
<main>
<section>
<form>
<p>For instance:</p>
<fieldset><legend>https://gist.github.com/</legend>
<label><input data-js="input" placeholder="2361f154d296b9644d6b54a08346e2f6" value="" /></label>
</fieldset>
<output>
<a href="${baseUrl}/2361f154d296b9644d6b54a08346e2f6">${baseUrl}/2361f154d296b9644d6b54a08346e2f6</a>
</output>
</form>
</section>
</main>
</article>
<footer>
<p>
<small>sponsored by</small><br/>
<a href="https://muze.nl/">
<svg data-name="Logo Muze" viewBox="0 0 245 50" xmlns="http://www.w3.org/2000/svg" fill="#1a1a1a">
<path d="M13 49a8 8 0 0 1-5-2c-2-1-2-3-2-6v-8a9 9 0 0 0-1-5 4 4 0 0 0-3-2v-2q4-1 4-6V9q0-4 2-6a8 8 0 0 1 5-2v3a4 4 0 0 0-3 1 8 8 0 0 0-1 5v8q0 6-4 7 4 1 4 8v7a15 15 0 0 0 0 3 5 5 0 0 0 1 2 3 3 0 0 0 1 1 5 5 0 0 0 2 0Z" stroke="#1a1a1a" stroke-miterlimit="10" />
<path d="M89 49h-7V20a15 15 0 0 0-3-9 10 10 0 0 0-8-4 13 13 0 0 0-9 3 11 11 0 0 0-4 9v30h-7V19a11 11 0 0 0-3-9 13 13 0 0 0-9-3 10 10 0 0 0-9 4 14 14 0 0 0-3 9v29h-6V21c0-6 1-11 5-14a18 18 0 0 1 12-6c7 0 13 3 17 9 4-6 9-9 16-9a19 19 0 0 1 13 6c3 3 5 8 5 14Z" />
<path d="M137 28a21 21 0 0 1-6 15 20 20 0 0 1-15 6 20 20 0 0 1-15-6 21 21 0 0 1-6-15V1h7v28a14 14 0 0 0 4 10 14 14 0 0 0 10 4 14 14 0 0 0 10-4 14 14 0 0 0 5-10V1h6Z" fill="#c2000b" />
<path d="M178 49h-35v-6l25-36h-24V1h32v6l-25 36h27Zm49-16a23 23 0 0 1-9 12 24 24 0 0 1-13 4 23 23 0 0 1-17-7 23 23 0 0 1-8-16 23 23 0 0 1 8-18 23 23 0 0 1 18-7 24 24 0 0 1 16 8q6 6 6 15v3h-41a17 17 0 0 0 6 12 18 18 0 0 0 12 4c7 0 12-3 15-10Zm-5-11a18 18 0 0 0-29-11 17 17 0 0 0-6 11Z" />
<path d="M232 46a5 5 0 0 0 2 0 3 3 0 0 0 1-1 5 5 0 0 0 1-2 15 15 0 0 0 0-3v-7q0-7 4-8-4-1-4-7v-8a8 8 0 0 0-1-5 4 4 0 0 0-3-1V1a8 8 0 0 1 5 2q2 2 2 6v9q0 5 4 6v2a4 4 0 0 0-3 2 8 8 0 0 0-1 5v8c0 3 0 5-2 6a8 8 0 0 1-5 2Z" stroke="#1a1a1a" stroke-miterlimit="10" />
</svg>
</a>
</p>
<p class="created-by">
The Source Code for this project is available
<a href="https://gist.github.com/potherca/2361f154d296b9644d6b54a08346e2f6/">as a Gist</a>
under a <a href="https://mozilla.org/MPL/2.0/" rel="license">Mozilla Public License 2.0 (MPL-2.0)</a>
&ndash; Created by <a class="potherca" href="https://pother.ca/">Potherca</a>
</p>
</footer>
<script>
const form = document.forms[0]
const output = form.querySelector('output > a')
form.querySelector('[data-js="input"]').addEventListener('input', (event) => {
event.preventDefault()
const element = event.currentTarget
output.href = '${baseUrl}/' + element.value
output.innerText = '${baseUrl}/' + element.value
})
</script>
</html>
`
return new Response(message, {
'headers': {
'Content-Type': 'text/html; charset=utf-8',
},
})
} else if (pathname === '/favicon.ico') {
return new Response(`<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 162 162">
<defs>
<mask id="a">
<path fill="#fff" d="M0 0h162v162H0z"/>
<path d="M54 34zm57 26-2-4h-5l1-3V28l-3-3-2-2a11 11 0 0 0-1 0l-1-1-3-3-1-2-1-1-3-3-3-4-2-2c-1-1-3 0-4 1l-1 1h-1l-4 2-2 1c-1 0-2-3-3-1l-1 4-3 2-2 2-3 2-1 3-3 4-1 2-2 6v13l-1 5 1 1v1h-5c-3 1-5 4-5 7 0 2 0 6 2 7 1 1 5 3 6 2l-1 13v22a38 38 0 0 0 4 13c2 3 6 6 10 7 3 2 6 1 10 2 3 1 7 0 10-1l3-2 4-3c3-3 5-6 6-11a84 84 0 0 0 4-27l1-6v-8l3 1 2-2c2-3 3-7 3-10zm-55-2-1-1 1 1z"/>
<path d="m102 77-2 14-2 12-2 11-1-1-4 9c-2-1-2-2-2-4h1v-9 2l1-4v1-3l1-2s-2-5-4-5c-1-3 0-1-2-2l-3-1-1-1-3 1c-3-2-3-1-6 1l-1-1-5 1a8 8 0 0 0-5 5l-2 4v-1 8a26 26 0 0 1-1 9c0-2-3-3-3-6v1l-2-4-3-12V81c-2 5-1 11-1 17v17l3 4 3 4c2 2 6 5 7 8h1l1 1v-1c1 1 5 2 6 0l2 2v-1c1 1 4 3 4 0l2 1v-1c2 0 4 1 5-1l3 1v-3l1 2 1-3v1-2l2 1 1-3v-2h1c1 1 3 0 3-2l1 1c2-6 1-13 3-19l2 1c0-3-2-5-1-8l1-6V77zm-14 35-1 3-1 4-1 1v1c-1 2-1 2-3 2h-2l-1-1-2-4-2 1h-2l-3-1-2 1v3l-1 1c-1 1-1 2-2 0l-2-6 1-2c-1-1-3-6 0-5v-3l1-3 1-1a7 7 0 0 0 3-1c1-1 4-3 5-2h-1l3-1h2l7 4c1 0 2 0 2 2v3c1 1 2 3 1 4z"/>
<path d="m106 167-2-6-3-9-5-11-6-8-1-4v2l-2-1v2l-2-1c-1 3-3 1-6 1v1h-2l-1 1v4l-1 1-3 2-3 2-2 3-4 4-3 4-1 2-1 1-1-5-1-4 1-5 1-4c0 2 3 0 3 0a7 7 0 0 0 3-4l1-7a5 5 0 0 0-1-2v-2l-2-5-5-3c0-2 2-5 4-6l3-2 2-1c2-1 1-4-1-4h-8l-5 6-1 3a10 10 0 0 0-2 2l-3 7-2 8-1 10 2 6 2 5v18c0 2-1 5 1 6l6 1h50c1-1 4 0 4-2l-2-6z"/>
</mask>
</defs>
<circle cx="81" cy="81" r="81" mask="url(#a)"/>
</svg>`, {
headers: {
'Content-Type': 'image/svg+xml',
},
})
}
const path = pathname.split('/')
// @FIXME: Add checks/support for $username, and/or $reference (optionally with `raw` in the URL)
let gistId = path[1]
let fileName = path[2] || ''
if (fileName) {
try {
fileName = decodeURIComponent(fileName)
} catch (error) {
// Just use the original
}
}
try {
gistId = decodeURIComponent(gistId)
} catch (error) {
// Just use the original
}
// Use alias if one is available
if (env.hasOwnProperty('GIST_ALIAS')) {
const aliases = env.GIST_ALIAS
if (aliases === null || typeof aliases !== 'object' || Array.isArray(aliases)) {
return createErrorResponse(
'GIST_ALIAS is not an object',
`<p>Environment variable "GIST_ALIAS" is set, but its value must be an object, ${typeof env.GIST_ALIAS} given.</p>`,
{},
500,
'Please correct the value of the "GIST_ALIAS" env var.',
)
} else if (typeof aliases[gistId] === 'string') {
gistId = aliases[gistId]
}
}
let url = 'https://api.github.com/gists/' + gistId
return fetch(url, {
cf: { cacheEverything: true },
headers: {
'Accept': 'application/vnd.github+json',
'Authorization': `Bearer ${env.GITHUB_TOKEN}`,
'User-Agent': 'Cloudflare Worker - Potherca Gist',
'X-GitHub-Api-Version': '2022-11-28',
},
})
.then(response => response.ok ? response.json() : Promise.reject(response))
.then(async (json) => {
if (fileName === '') {
if (json.files['index.html']) {
fileName = 'index.html'
} else if (json.files['README.md']) {
fileName = 'README.md'
} else {
fileName = Object.keys(json.files)[0]
}
}
if (json.files.hasOwnProperty(fileName) === false) {
return new Response(`File ${fileName} does not exist`, {
status: 404,
})
}
let content = json.files[fileName].content
let contentType = json.files[fileName].type
const rawUrl = json.files[fileName].raw_url
// @FIXME: Rather than have a template here, the template should come from ... ?
// @FIXME: Show public/private based on json.public
// @TODO: Also show comments count json.comments and entries from json.comments_url ?
// @TODO: Also show forks count from json.forks.length ?
// @TODO: Show commit count form json.history.length ?
// @TODO: Show last edited / created date(s)? json.created_at / json.updated_at
const [type, subType] = contentType.split('/', 2)
let action = 'redirect';
switch (type) {
case 'application':
case 'image':
if (subType.startsWith('vnd.')) {
action = 'redirect'
} else if (subType.endsWith('+json')
|| subType.endsWith('+xml')
|| subType.endsWith('+yaml')
) {
action = 'text'
} else {
action = 'binary'
}
break;
case 'audio':
case 'video':
action = 'binary'
break;
case 'text':
action = 'text'
if (subType === 'markdown') {
action = 'markdown'
}
break;
case 'example':
case 'font':
case 'haptics':
case 'message':
case 'model':
case 'multipart':
action = 'redirect'
break;
}
if (action === 'redirect') {
return Response.redirect(rawUrl, 302)
} else if (action === 'binary') {
content = await fetch(rawUrl, { cf: { cacheEverything: true } })
.then(response => response.ok
? response.arrayBuffer()
: Promise.reject(response)
)
} else if (json.files[fileName].truncated) {
content = await fetch(rawUrl, { cf: { cacheEverything: true } })
.then(response => response.ok
? response.text()
: Promise.reject(response)
)
}
if (action === 'markdown') {
// @FIXME: Add HTML headers, CSS etc. to make the rendered Markdown look good
content = marked.parse(content)
contentType = 'text/html; charset=utf-8'
}
// else action === 'text, use content as-is
return new Response(content, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Expose-Headers': 'Last-Modified, X-GIST-RAW-URL, X-GIST-URL, X-GIST-VISIBILITY',
'Content-Type': contentType,
'Last-Modified': new Date(json.updated_at).toUTCString(),
'X-GIST-RAW-URL': rawUrl,
'X-GIST-URL': json.html_url,
'X-GIST-VISIBILITY': json.public ? 'public' : 'private',
},
})
})
.catch(async (error) => {
if (typeof error.json === 'undefined') {
return new Response(String(error.message || error), {
status: 500,
statusText: getRandomQuote(),
})
} else {
let json = {}
try {
json = await error.json()
json.url = url
} catch (e) {
json = error
}
// @TODO: Prettier 404
let headers = error.headers || {}
headers = new Headers(headers)
headers.append('Warning', `119 - "${getRandomQuote()}" ${new Date().toUTCString()}`)
return new Response(JSON.stringify(json, null, 4), {
headers,
status: error.status || 500,
statusText: error.statusText || getRandomQuote(),
})
}
})
},
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment