full first commit

This commit is contained in:
Gordon Pedersen 2024-05-14 10:31:45 +10:00
parent 80f6b69d04
commit 3849dfa92c
6 changed files with 141 additions and 148 deletions

35
package-lock.json generated
View file

@ -7,6 +7,11 @@
"": { "": {
"name": "status", "name": "status",
"version": "0.0.0", "version": "0.0.0",
"dependencies": {
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"yaml": "^2.4.2"
},
"devDependencies": { "devDependencies": {
"wrangler": "^3.0.0" "wrangler": "^3.0.0"
} }
@ -923,6 +928,25 @@
"node": ">=16.13" "node": ">=16.13"
} }
}, },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"engines": {
"node": "*"
}
},
"node_modules/moment-timezone": {
"version": "0.5.45",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz",
"integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==",
"dependencies": {
"moment": "^2.29.4"
},
"engines": {
"node": "*"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -1256,6 +1280,17 @@
"integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==", "integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==",
"dev": true "dev": true
}, },
"node_modules/yaml": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
"integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/youch": { "node_modules/youch": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/youch/-/youch-3.3.3.tgz", "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.3.tgz",

View file

@ -9,5 +9,10 @@
}, },
"devDependencies": { "devDependencies": {
"wrangler": "^3.0.0" "wrangler": "^3.0.0"
},
"dependencies": {
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"yaml": "^2.4.2"
} }
} }

View file

@ -1,23 +0,0 @@
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const proxyUrl = url.searchParams.get('proxyUrl'); // get a query param value (?proxyUrl=...)
const modify = url.searchParams.has('modify'); // check if a query param is set (?proxyUrl=...&modify)
if (!proxyUrl) {
return new Response('Bad request: Missing `proxyUrl` query param', { status: 400 });
}
// make subrequests with the global `fetch()` function
let res = await fetch(proxyUrl, request);
// optionally, modify the respone
if (modify) {
res = new Response(res.body, res)
res.headers.set('X-My-Header', 'My Header Value');
}
return res;
},
};

View file

@ -1,13 +0,0 @@
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const redirectUrl = url.searchParams.get('redirectUrl'); // get a query param value (?redirectUrl=...)
if (!redirectUrl) {
return new Response('Bad request: Missing `redirectUrl` query param', { status: 400 });
}
// The Response class has static methods to create common Response objects as a convenience
return Response.redirect(redirectUrl);
},
};

View file

@ -1,90 +0,0 @@
// An example 'toy' implementation of a router using URLPattern: https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API
// If you're interested in more production-ready routing, you may want to check out one of the following:
// itty-router: https://www.npmjs.com/package/itty-router
// Hono: https://www.npmjs.com/package/hono
class Router {
routes = [];
handle(request) {
for (const route of this.routes) {
const match = route[0](request);
if (match) {
return route[1]({ ...match, request });
}
}
const match = this.routes.find(([matcher]) => matcher(request));
if (match) {
return match[1](request);
}
}
register(handler, path, method) {
const urlPattern = new URLPattern({ pathname: path });
this.routes.push([
(request) => {
if (method === undefined || request.method.toLowerCase() === method) {
const match = urlPattern.exec({
pathname: new URL(request.url).pathname,
});
if (match) {
return { params: match.pathname.groups };
}
}
},
(args) => handler(args),
]);
}
options(path, handler) {
this.register(handler, path, "options");
}
head(path, handler) {
this.register(handler, path, "head");
}
get(path, handler) {
this.register(handler, path, "get");
}
post(path, handler) {
this.register(handler, path, "post");
}
put(path, handler) {
this.register(handler, path, "put");
}
patch(path, handler) {
this.register(handler, path, "patch");
}
delete(path, handler) {
this.register(handler, path, "delete");
}
all(path, handler) {
this.register(handler, path);
}
}
// Setting up our application:
const router = new Router();
// GET collection index
router.get("/api/todos", () => new Response("Todos Index!"));
// GET item
router.get(
"/api/todos/:id",
({ params }) => new Response(`Todo #${params.id}`)
);
// POST to the collection (we'll use async here)
router.post("/api/todos", async ({ request }) => {
const content = await request.json();
return new Response("Creating Todo: " + JSON.stringify(content));
});
// 404 for everything else
router.all("*", () => new Response("Not Found.", { status: 404 }));
export default router;

View file

@ -8,40 +8,119 @@
* Learn more at https://developers.cloudflare.com/workers/ * Learn more at https://developers.cloudflare.com/workers/
*/ */
import handleProxy from './proxy.js'; import YAML from 'yaml'
import handleRedirect from './redirect.js'; import moment from 'moment-timezone'
import apiRouter from './router.js';
// Export a default object containing event handlers // Export a default object containing event handlers
export default { export default {
// The fetch handler is invoked when this worker receives a HTTP(S) request // The fetch handler is invoked when this worker receives a HTTP(S) request
// and should return a Response (optionally wrapped in a Promise) // and should return a Response (optionally wrapped in a Promise)
async fetch(request, env, ctx) { async fetch(req, env, ctx) {
// You'll find it helpful to parse the request.url string into a URL object. Learn more at https://developer.mozilla.org/en-US/docs/Web/API/URL // You'll find it helpful to parse the request.url string into a URL object. Learn more at https://developer.mozilla.org/en-US/docs/Web/API/URL
const url = new URL(request.url); const url = new URL(req.url);
// You can get pretty far with simple logic like if/switch-statements // log the incoming request info
switch (url.pathname) { console.info(`${new Date().toISOString()} 📥 ${req.method} ${req.url}`)
case '/redirect':
return handleRedirect.fetch(request, env, ctx);
case '/proxy': // CORS route (for now, any domain has access)
return handleProxy.fetch(request, env, ctx); if(req.method === "OPTIONS") {
const headers = {
'Access-Control-Allow-Origin': req.headers.get('Origin') || '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, DELETE',
'Access-Control-Max-Age': '86400',
'Access-Control-Allow-Credentials': true
}
let h
if (h = req.headers.get('Access-Control-Request-Headers'))
headers['Access-Control-Allow-Headers'] = 'Accept, Content-Type, Authorization, Signature, Digest, Date, Host'
return new Response('', { status: 204, headers })
} }
else if(req.method === "POST" && url.pathname.startsWith("/status.lol")) {
if (url.pathname.startsWith('/api/')) { const body = await req.json()
// You can also use more robust routing return handleLol(body, env)
return apiRouter.handle(request);
} }
else if(req.method === "GET" && url.pathname.startsWith("/apget")) {
const href = url.searchParams.get('url')
if(!href) return new Response('"url" parameter required', { status: 400, headers: { "Content-Type": "text/plain" }})
return fetch(url, {
method: "GET",
headers: {
'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
}
})
}
return new Response( return new Response(
`Try making requests to: `This is a worker to accept POST requests at /status.lol from a status.lol webhook`,
<ul>
<li><code><a href="/redirect?redirectUrl=https://example.com/">/redirect?redirectUrl=https://example.com/</a></code>,</li>
<li><code><a href="/proxy?modify&proxyUrl=https://example.com/">/proxy?modify&proxyUrl=https://example.com/</a></code>, or</li>
<li><code><a href="/api/todos">/api/todos</a></code></li>`,
{ headers: { "Content-Type": "text/html" } } { headers: { "Content-Type": "text/html" } }
); );
}, },
}; };
async function handleLol(body, env) {
/* incoming: {
status_emoji: '🧪',
status_text: 'This is a test.',
posted: 1715234767,
id: '663c67cf3344e',
url: 'https://status.lol/deathau/663c67cf3344e'
} */
// get the url (and username) from the incoming data
const url = new URL(body.url);
const username = url.pathname.substring(1).split('/')[0]
// get the full data of the status.lol post
const statusres = (await fetch(`https://api.omg.lol/address/${username}/statuses/${body.id}`))
if(!statusres.ok) return statusres
const status = (await statusres.json()).response.status
// check that the incoming data matches what we got from status.lol
if(status.id != body.id) {
console.error("Error: Incoming status does not match retrieved status!", body, status)
return new Response(JSON.stringify({body,status}, null, 2), {status:500})
}
// create a "title" for the status
const title = status.emoji + ' ' + status.content.match(/((\s*\S+){10})([\s\S]*)/)[1] + '...'
// this is the frontmatter data we're adding to the markdown file
let frontmatter = {
id: status.id,
title,
date: moment(status.created*1000).tz('Australia/Melbourne').format(),
location: `/${(status.created * 1000).toString(36)}`,
emoji: status.emoji,
background: status.background,
external_url: status.external_url,
status_url: `https://${status.address}.status.lol/${status.id}`
}
// this is the content
let content = status.content
// this is the actual markdown
let markdown = `---\n${YAML.stringify(frontmatter)}---\n\n${content}\n`
// make sure we have the env variables defined
if(env.FORGEJO_TOKEN && env.FORGEJO_URL && env.GIT_REPO) {
// the url to push the file to the repo in `/weblog/status/{whatever the location is}.md`
let pushurl = `${env.FORGEJO_URL}api/v1/repos/${env.GIT_REPO}/contents/weblog/status${frontmatter.location}.md`
// just return the result of this request
return await fetch(pushurl, {
method: "POST",
body: JSON.stringify({
content: btoa(unescape(encodeURIComponent(markdown))), // the markdown content as base64
message: `Added status ${frontmatter.location}` // the commit message
}),
headers: {
'Authorization': `token ${env.FORGEJO_TOKEN}`, // require the auth token
'Content-Type': 'application/json' // the content type is required for this api
}
})
}
return new Response(markdown, { status: 400 })
}