Avatar

@jdan

11 likes70 public vals
Joined July 22, 2023

Renders a Lichess game in ascii, with some formatting to show the opponents.

Readme
Fork
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
import { lichessPgn } from "https://esm.town/v/jdan/lichessPgn";
export const lichessGameAscii = async (id: string) => {
const { Chess } = await import("npm:chess.js@1.0.0-beta.6");
const chess = new Chess();
const pgn = await lichessPgn(id);
chess.loadPgn(pgn);
const ascii = chess.ascii()
// .replace(/\./g, " ")
.replace(/K/g, "♔")
.replace(/Q/g, "♕")
.replace(/R/g, "♖")
.replace(/B/g, "♗")
.replace(/N/g, "♘")
.replace(/P/g, "♙")
.replace(/k/g, "♚")
.replace(/q/g, "♛")
.replace(/r/g, "♜")
// Don't turn the b in "a b" into a bishop lol
.replace(/(?<!a )b/g, "♝")
.replace(/n/g, "♞")
.replace(/p/g, "♟");
// Append the players to the ascii
const lines = ascii.split("\n");
lines[4] = lines[4] +
` ♙ ${chess._header["White"]} (${chess._header["WhiteElo"]})`;
lines[5] = lines[5] +
` ♟ ${chess._header["Black"]} (${chess._header["BlackElo"]})`;
return lines.join("\n");
};

A web interface for viewing a bunch of Lichess TV games.

https://jdan-lichessDashboard.web.val.run

Readme
Fork
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
import { lichessGameAscii } from "https://esm.town/v/jdan/lichessGameAscii";
import { lichessTVGames } from "https://esm.town/v/jdan/lichessTVGames";
export const lichessDashboard = async () => {
const games = await lichessTVGames();
const validGameTypes = [
"Bot",
"UltraBullet",
"Bullet",
"Computer",
"Rapid",
"Top Rated",
"Blitz",
"Classical",
];
const boards = await Promise.all(validGameTypes.map(async (gameType) => {
const gameId = games[gameType].gameId;
return `
<a href="https://lichess.org/tv/${
gameType === "Top Rated" ? "best" : gameType.toLowerCase()
}">${gameType}</a>
<pre>${await lichessGameAscii(gameId)}</pre>
`;
}));
const html = `
<!doctype html>
<head>
<style>body { margin: 24px }</style>
</head>
<body>
${boards.join("\n")}
</body>
`;
return new Response(html, {
headers: {
"Content-Type": "text/html",
},
});
};
Fork
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
import { htmlOfEmoji } from "https://esm.town/v/jdan/htmlOfEmoji";
import { emojiByCodepoints } from "https://esm.town/v/jdan/emojiByCodepoints";
import { emojiByAlias } from "https://esm.town/v/jdan/emojiByAlias";
import { emojiFlagTree } from "https://esm.town/v/jdan/emojiFlagTree";
import { detailsTree } from "https://esm.town/v/jdan/detailsTree";
import { emojiByGroup } from "https://esm.town/v/jdan/emojiByGroup";
export const emojiWeb = async (req: Request) => {
const { Hono } = await import("npm:hono");
const app = new Hono();
app.get("/", (c) => {
const toc = `<ul>
${
Object.keys(emojiByGroup).map((group) =>
`<li><a href="#${group}">${group}</a></li>`
).join("")
}
</ul>`;
const sections = Object.entries(emojiByGroup).map(
([group, emojis]) => {
return `
<h2 id="${group}">${group}</h2>
<ul>
${
emojis.map((emoji) => {
const alias = emoji.aliases[0];
return `
<li>${emoji.emoji} – :<a href="/alias/${alias}">${alias}</a>:</li>
`;
}).join("\n")
}
</ul>
`;
},
).join("\n");
return c.html(`
${toc}
${sections}
`);
});
app.get("/flags", (c) => {
return c.html(
`<style>
body { font-family: monospace }
details > details { margin-left: 24px }
details > div { margin-left: 24px; font-size: 18px }
</style>` +
detailsTree(emojiFlagTree, (emoji) => {
const alias = emoji.aliases[0];
Fork
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
import { countryData } from "https://esm.town/v/jdan/countryData";
import { svgMapOfCountry } from "https://esm.town/v/jdan/svgMapOfCountry";
import { Hono } from "npm:hono@3";
const app = new Hono();
app.get("/", async (c) => {
const countries = await countryData();
console.log(countries);
return c.html(`
<ol>
${
countries.map(({ countryLabel }) => `
<li>
<a href="/${countryLabel}">${countryLabel}</a>
</li>
`).join("")
}
</ol>
`);
});
app.get("/:countryName", async (c) => {
const param = c.req.param("countryName");
if (param.endsWith(".svg")) {
const withoutSvg = param.replace(".svg", "");
const label = decodeURIComponent(withoutSvg);
const svg = await svgMapOfCountry(label) as string;
c.header("Content-Type", "image/svg+xml");
return c.body(svg);
} else {
const label = decodeURIComponent(param);
const svg = await svgMapOfCountry(label);
return c.html(`<html><div style="width: 600px">${svg}</div></html>`);
}
});
export default app.fetch;

hashmail

This allows you to send me an email if the text you send hashes to a string that starts with seven (7) zeroes.

Details

Inspiration

https://en.wikipedia.org/wiki/Hashcash

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
import { email } from "https://esm.town/v/std/email?v=9";
import { sha256OfText } from "https://esm.town/v/jdan/sha256OfText";
export async function hashmail(e: {
from: string;
to: string[];
subject: string;
text: string;
html: string;
}) {
const trimmedText = e.text.trim();
const hash = await sha256OfText(trimmedText);
if (hash.startsWith("0000000")) {
return email({
text: `
You received a message from ${e.from}.
${e.subject}
${trimmedText}
which hashes to ${hash}
`,
subject: "Emailed received at @jdan.hashmail",
});
}
console.log(`
HASH FAILED!
You received a message from ${e.from}.
${e.subject}
${trimmedText}
JSON: ${JSON.stringify(trimmedText)}
which hashes to ${hash}
`);
}
Fork
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
import { messages } from "https://esm.town/v/jdan/messages";
export function messageBoard(req, res) {
// make sure u change this or else i'll get your messages
const action = "https://jdan-writeMessage.express.val.run";
res.send(`
<table border="2" cellpadding="4">
<thead>
<tr>
<th>Name</th>
<th>Message</th>
</tr>
</thead>
<tbody>
${
messages.filter((m) => m.approved).reverse().map((m) => {
return `
<tr>
<td>
${m.name}
</td>
<td>
${m.message}
</td>
</tr>
`;
}).join("")
}
</tbody>
</table><br><br><br>
<form action="${action}" method="POST">
<label>
Name: <br/>
<input name="name" />
</label>
<br />
<label>
Message:<br/>
<textarea name="message"></textarea>
</label><br /><br />
<input type="submit" />
</form>
<br><br>
Powered by <a href="https://val.town">val.town</a>. Check me out: <a href="https://www.val.town/v/jdan.messageBoard">https://www.val.town/v/jdan.messageBoard</a>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { email } from "https://esm.town/v/std/email?v=9";
import { set } from "https://esm.town/v/std/set?v=11";
import { messages } from "https://esm.town/v/jdan/messages";
export const writeMessage = async (req: express.Request, res: express.Response) => {
const name = req.body.name.slice(0, 100);
const message = req.body.message.slice(0, 500);
messages.push({
name,
message,
approved: false,
});
await set("messages", messages);
await email({
text:
`${name}:\n\n${message}\n\nApprove here: https://www.val.town/v/jdan.messages`,
});
return res.send("thx");
};
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
import { countryData } from "https://esm.town/v/jdan/countryData";
import { blob } from "https://esm.town/v/std/blob?v=10";
export async function svgMapOfCountry(country: string) {
const mapUrl = (await countryData())
.find((value) => value.countryLabel === country)
.mapLabel
// Don't love this
.replaceAll("+", "_");
if (!mapUrl) {
throw new Error(`Unable to find map for country: ${country}`);
}
const cacheKey = `cache-jdan/svgMapOfCountry/${country}`;
const cached = await blob.getJSON(cacheKey);
if (cached) {
return cached;
}
const res = await fetch(mapUrl);
if (res.status !== 200) {
throw new Error(`[${res.status}] Error fetching ${mapUrl}`);
}
const mapData = await res.json();
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
// Calculate bounds and include logic for holes
mapData.data.features.forEach(feature => {
const processRing = (ring) => {
ring.forEach(([x, y_]) => {
// flip y
const y = 90 - y_;
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
});
};
const geometry = feature.geometry;
if (geometry.type === "Polygon" || geometry.type === "MultiPolygon") {
const polygons = geometry.type === "Polygon" ? [geometry.coordinates] : geometry.coordinates;
polygons.forEach(polygon => {
polygon.forEach(ring => processRing(ring));
});
}

This val queries Wikidata for basic information on "countries" – name, map data, and flag data. It is cached.

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
import { sparql } from "https://esm.town/v/jdan/sparql";
import { blob } from "https://esm.town/v/std/blob?v=10";
interface Entry {
country: string;
countryLabel: string;
mapLabel: string;
flagLabel: string;
}
const CACHE_KEY = "cache-jdan/countryData";
export async function countryData(): Promise<Entry[]> {
const cached = await blob.getJSON(CACHE_KEY);
if (cached) {
return cached;
}
const res = await sparql(`
SELECT DISTINCT ?country ?countryLabel ?mapLabel ?flagLabel
WHERE
{
{ ?country wdt:P31 wd:Q6256 } # Countries
UNION { ?country wdt:P31 wd:Q3624078 } # Soverign states
MINUS { ?country wdt:P31 wd:Q3024240 } # Historical countries
MINUS { ?country wdt:P31 wd:Q417175 } # Kingdoms
?country wdt:P41 ?flag.
?country wdt:P3896 ?map.
# When maps are optional, Chinland, Palestine, and Transnistria appear
# OPTIONAL { ?country wdt:P3896 ?map }
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
ORDER BY ?countryLabel
`);
const values = res.results.bindings.map(({ country, countryLabel, mapLabel, flagLabel }) => {
return {
country: country.value,
countryLabel: countryLabel.value,
mapLabel: mapLabel.value,
flagLabel: flagLabel.value,
};
});
await blob.setJSON(CACHE_KEY, values);
1
2
3
4
5
6
7
8
9
export async function sparql(query) {
const url = `https://query.wikidata.org/sparql?query=${encodeURIComponent(query)}`;
const res = await fetch(url, {
headers: {
"Accept": "application/sparql-results+json",
},
});
return await res.json();
}