Avatar

@maxm

10 likes33 public vals
Joined January 12, 2023

Render a PNG

This tiny smiley face is rendered by this val:

The image response is cached by the CDN, so make sure to change the image name if you make code changes. Any name with a .png extension will work: https://maxm-smileypng.web.val.run/-.png

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export const smileyPNG = async (request: Request) => {
const { encode } = await import("https://deno.land/x/pngs/mod.ts");
// Dimensions of the image
const [width, height] = [10, 10];
// Create our PNG canvas
const imageData = new Uint8Array(width * height * 4);
const y = [255, 255, 0, 255]; // Yellow
const b = [0, 0, 0, 255]; // Black
const t = [0, 0, 0, 0]; // Transparent
// dprint-ignore
const smileyFace = [
t, t, y, y, y, y, y, y, t, t,
t, y, y, y, y, y, y, y, y, t,
y, y, y, y, y, y, y, y, y, y,
y, y, b, y, y, y, y, b, y, y,
y, y, y, y, y, y, y, y, y, y,
y, y, y, y, y, y, y, y, y, y,
y, y, b, y, y, y, y, b, y, y,
y, y, y, b, b, b, b, y, y, y,
t, y, y, y, y, y, y, y, y, t,
t, t, y, y, y, y, y, y, t, t,
];
// Move around the bytes and encode the image
const smileyFaceData = [].concat(...smileyFace);
for (let i = 0; i < width * height * 4; i++) {
imageData[i] = smileyFaceData[i];
}
const png = encode(imageData, 10, 10);
return new Response(png, { headers: { "Content-Type": "image/png" } });
};

Self Editing Website

Visit and edit at: https://maxm-selfeditingwebsite.web.val.run/

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import { set } from "https://esm.town/v/std/set?v=11";
let { selfEditingWebsiteBody } = await import("https://esm.town/v/maxm/selfEditingWebsiteBody");
export const selfEditingWebsite = async (request: Request) => {
if (request == null) {
return "not invoked correctly";
}
// Otherwise, someone has submitted a form so let's handle that
if (selfEditingWebsiteBody === undefined) {
let defaultBody = `<h1>Edit Me!</h1>`;
selfEditingWebsiteBody = defaultBody;
}
if (request.method == "POST") {
let formData = await request.formData();
if (formData.get("source") !== undefined) {
selfEditingWebsiteBody = formData.get("source").toString();
await set("selfEditingWebsiteBody", selfEditingWebsiteBody);
}
}
let body = selfEditingWebsiteBody;
const form = `
<div style="border: 1px solid #999;">
<form action="" method="POST">
<label for="source">Edit the source of this web page:</label>
<textarea id="source" name="source" required>${body}</textarea>
<br>
<input type="submit" value="Submit">
</form>
</div>`;
return new Response(
`<!DOCTYPE html>
<html>
<head>
<title>I am a self-editing website!</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.js" integrity="sha512-2359y3bpxFfJ9xZw1r2IHM0WlZjZLI8gjLuhTGOVtRPzro3dOFy4AyEEl9ECwVbQ/riLXMeCNy0h6HMt2WUtYw==" crossorigin="anonymous" referrerpolicy="no-referrer"></s
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.css" integrity="sha512-uf06llspW44/LZpHzHT6qBOIVODjWtv4MxCricRxkzvopAlSWnTf6hpZTFxuuZcuNE9CBQhqE0Seu1CoRk84nQ==" crossorigin="anonymous" referrerpolicy=
</head>
<body>
${body}
${form}
<script>
var editor = CodeMirror.fromTextArea(document.getElementById('source'), {
mode: "html",
lineNumbers: true,
});
editor.save();
</script>
</body>
</html>`,
{ headers: { "Content-Type": "text/html" } },
);
};

Retro Visit Counter

You are visitor number:

How special!

Want a retro visitor counter for your myspace page or geocities website? Fork this val and add the image link to your website:

<img src="https://maxm-retrovisitcounter.web.val.run/counter.png">
<!-- Make sure you swap this subdomain out with the subdomain of your forked val --> 
<img src="https://[CHANGE ME!].web.val.run/counter.png">
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { sqlite } from "https://esm.town/v/std/sqlite";
await sqlite.execute(`create table if not exists counter(
name text,
counter integer
)`);
async function getCurrentCount(): Promise<number> {
let counter = await sqlite.execute(`select counter from counter where name = 'retro'`);
if (counter.rows.length === 0) {
await sqlite.execute(`insert into counter (name, counter) values ('retro', 1)`);
}
let [currentCount] = (counter.rows[0] as unknown) as number[];
await sqlite.execute(`update counter set counter = counter + 1 where name = 'retro'`);
return currentCount;
}
export const retroVisitCounter = async (request: Request) => {
const count = await getCurrentCount();
const { encode } = await import("https://deno.land/x/pngs/mod.ts");
if (request.url.endsWith("favicon.ico"))
return new Response("not found", { status: 404 });
// Set up all of our configured constants.
const numberPixelHeight = 7;
const numberPixelWidth = 5;
const pixelMultiplier = 3;
const numberPlaces = 6;
const padding = 1;
const B = [0, 0, 0, 255]; // Black
const _ = [153, 153, 153, 255]; // Transparent
// dprint-ignore
const numbers = [
[
_, B, B, B, _,
B, _, _, _, B,
B, _, _, B, B,
B, _, B, _, B,
B, B, _, _, B,
B, _, _, _, B,
_, B, B, B, _,
],
[
_, _, B, _, _,
_, B, B, _, _,
_, _, B, _, _,
_, _, B, _, _,
_, _, B, _, _,
_, _, B, _, _,
B, B, B, B, B,
],
[
_, B, B, B, _,
B, _, _, _, B,
_, _, _, _, B,
_, _, B, B, _,
_, B, _, _, _,
B, _, _, _, _,
B, B, B, B, B,
],
[
_, B, B, B, _,
B, _, _, _, B,
_, _, _, _, B,
_, _, B, B, _,
_, _, _, _, B,
B, _, _, _, B,
_, B, B, B, _,
],
[
_, _, _, B, B,
_, _, B, _, B,
_, B, _, _, B,
B, _, _, _, B,
B, B, B, B, B,
_, _, _, _, B,
_, _, _, _, B,
],
[
B, B, B, B, B,
B, _, _, _, _,
B, B, B, B, _,
_, _, _, _, B,
_, _, _, _, B,
B, _, _, _, B,
_, B, B, B, _,
],
[
_, _, B, B, _,
_, B, _, _, _,
B, _, _, _, _,
B, B, B, B, _,
B, _, _, _, B,
B, _, _, _, B,
_, B, B, B, _,
],
[
B, B, B, B, B,
B, _, _, _, B,
_, _, _, _, B,
_, _, _, B, _,
_, _, B, _, _,
1
2
3
4
5
6
7
import { Router, json } from "npm:itty-router@4";
export const ittyRouterExample = async (request: Request) => {
const router = Router();
router.get("/", () => "Hi from itty-router!");
return router.handle(request).then(json);
};
1
2
3
4
5
6
7
8
9
10
11
import { newClient } from "https://esm.town/v/maxm/libraryThatNeedsValidation";
try {
let client = await newClient("maxm", "wrong");
} catch (err) {
console.log(err);
}
let client = await newClient("maxm", "password");
console.log("logged in and validated", client.getUsername());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export const newClient = async (username: string, password: string): Client => {
const url = `https://${username}-userspaceauth.web.val.run/validate/${password}`;
let resp = await fetch(url);
if (resp.status !== 200) {
throw new Error("Invliad response. Have you set up your fork? https://www.val.town/v/maxm/userspaceauth/fork?");
}
let data = await resp.json();
if (!data) {
throw new Error("Incorrect password");
}
return new Client(username);
};
class Client {
private username: string;
constructor(username: string) {
this.username = username;
}
getUsername() {
return this.username;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource https://esm.sh/preact */
import { blob } from "https://esm.town/v/std/blob";
// import * as opaque from "npm:@cloudflare/opaque-ts";
import { Hono } from "npm:hono@3";
import { render } from "npm:preact-render-to-string";
const resp = (content, status) =>
new Response(render(content), {
status,
headers: {
"Content-Type": "text/html",
},
});
const trimSuffix = (s: string, suffix: string): string => {
if (!s.endsWith(suffix)) {
return s;
}
return s.slice(0, s.length - suffix.length);
};
const getPassword = async (): Promise<string | undefined> => {
try {
let resp = await blob.get(PASSWORD_BLOB_KEY);
return await resp.text();
} catch (e) {
return undefined;
}
};
const KNOWN_HOSTNAME = ".web.val.run";
const PASSWORD_BLOB_KEY = "password"; // hah
const app = new Hono();
app.get("/", async (c) => {
const password = await getPassword();
const u = new URL(c.req.url);
if (!u.host.endsWith(KNOWN_HOSTNAME)) {
return resp((<div>Hostname does not end with {KNOWN_HOSTNAME}, cannot continue</div>), 400);
}
const userAndValName = trimSuffix(u.host, KNOWN_HOSTNAME);
const [username, valName] = userAndValName.split("-");
if (username === "") {
return resp((<div>Username not found</div>), 400);
}
if (valName !== "userspaceauth") {
return resp((<div>Val must be called "userspaceAuth"</div>), 400);
}
let content = password
? <div>Your password has been set up and this val is ready to authenticate requests for you</div>
: (
<form method="post">
<p>
Enter a password that you'll use to authenticate:
</p>
<div>
<input type="password" name="password"></input>
<button type="submit">Submit</button>
</div>
</form>
);
return resp(
(<>
<div>Username: {username}</div>
<div>Val Name: {valName}</div>
<div>{content}</div>
</>),
200,
);
});
app.post("/", async (c) => {
const fd = await c.req.formData();
const password = fd.get("password");
// TODO: more validation
if (typeof password !== "string" || password.length < 8) {
return resp((<div>Invalid password!</div>), 400);
}
await blob.set(PASSWORD_BLOB_KEY, "password");
return c.redirect("/");
});
app.get("/reset", async (c) => {
// TODO: this can be abused
await blob.delete(PASSWORD_BLOB_KEY);
});
app.post("/validate", async (c) => {
const realPassword = await getPassword();
const fd = await c.req.formData();
const password = fd.get("password");
const match = password === realPassword;
return c.json(match, match ? 200 : 401);
});
app.get("/validate/:password", async (c) => {
const realPassword = await getPassword();
const password = c.req.param("password");
const match = password === realPassword;
return c.json(match, match ? 200 : 401);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { dataToRSS } from "https://esm.town/v/stevekrouse/dataToRSS";
import { valTownBlogJSON } from "https://esm.town/v/stevekrouse/valTownBlogJSON";
export async function valTownBlogRSS() {
const json = await valTownBlogJSON();
return new Response(dataToRSS(
json.map((blog) => ({
title: blog.title,
link: blog.url,
pubDate: blog.date,
})),
{
title: "Val Town Blog",
link: "https://blog.val.town",
rssLink: "https://stevekrouse-blogrss.web.val.run/",
},
));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { generateInvoicePDF } from "https://esm.town/v/vtdocs/generateInvoicePDF?v=1";
export const examplePDF = async (req: Request) => {
const invoicePDF = await generateInvoicePDF({
invoiceNumber: "001",
date: new Date().toDateString(),
customerName: "Alice Bar",
customerEmail: "alice@bar.com",
items: [{ description: "Arabica Beans 500g", quantity: 2, price: 10 }, {
description: "Robusta Beans 500g",
quantity: 1,
price: 11,
}],
currencySymbol: "$",
});
return new Response(invoicePDF, {
headers: { "Content-Type": "application/pdf" },
});
};
1
2
3
4
5
6
7
8
9
import { jsPDF } from "npm:jspdf";
export const helloWorldPDF = async (req: Request) => {
const doc = new jsPDF();
doc.text("Hello world!", 10, 10);
return new Response(doc.output(), {
headers: { "Content-Type": "application/pdf" },
});
};