Newest

1
2
3
import * as b from "jsr:@badrap/valita";
export default () => Response.json(Object.keys(b));
1
2
3
import * as b from "jsr:@badrap/valita"
console.log(b);
1
2
3
4
5
6
7
8
9
10
11
12
13
import { hnEmail } from "https://esm.town/v/stevekrouse/hnEmail";
import { hackerNewsAuthors } from "https://esm.town/v/stevekrouse/hackerNewsAuthors";
import { hnLatestPosts } from "https://esm.town/v/stevekrouse/hnLatestPosts";
export async function hnFollowPollJob({ lastRunAt }) {
let posts = await hnLatestPosts({
authors: hackerNewsAuthors,
lastSyncTime: lastRunAt,
search_by_date: true,
});
let { text, subject } = await hnEmail({ posts });
if (posts.length) console.email(text, subject);
}

Clone of @maxm/staticChess but for Wordle. Every letter is a link. The game state is stored in the URL bar.

You could do silly things like playing collaborative Wordle with your friends by trading links back and forth. Or undo any mistakes by clicking the back button. I also make it easy to generate a new game from any of your current game's guesses – to send to a friend.

They key to these static games like this one and @maxm/staticChess is to figure out:

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/react */
import { html } from "https://esm.town/v/stevekrouse/html";
import { reloadOnSaveFetchMiddleware } from "https://esm.town/v/stevekrouse/reloadOnSave";
import { renderToString } from "npm:react-dom/server";
const GREEN = "#6aaa64";
const YELLOW = "#c9b458";
const GRAY = "#787c7e";
const LIGHT_GRAY = "#d3d6da";
// TODO - more words
const wordleWords = [
"array",
"cache",
"class",
"clone",
"codec",
"debug",
"fetch",
"frame",
"gigot",
"index",
"input",
"logic",
"patch",
"query",
"proxy",
"regex",
"route",
"stack",
"token",
"tuple",
];
interface Game {
answer: string;
guesses: string[];
currentGuess: string;
}
function encodeGame(game: Game): string {
return btoa(JSON.stringify(game));
}
function decodeGame(encoded: string): Game {
return JSON.parse(atob(encoded));
}
function nextStateEncoded(url, game: Game, action: string): string | undefined {
const next = nextState(game, action);
return next ? `https://${url}/${encodeGame(next)}` : undefined;
}
function nextState(game: Game, action: string): Game | undefined {
// TODO - check if action is valid and return null if not, ie no link
const { answer, guesses, currentGuess } = game;
if (guesses.some((guess) => [...guess].some((x, i) => x === action && !answer.includes(action)))) {
return null;
}
if (answer === guesses.at(-1)) return null;
if (action === "Enter" && currentGuess.length !== 5) return null;
if (action === "Enter") {
return {
...game,
guesses: [...game.guesses, currentGuess],
currentGuess: "",
};
}
if (action === "Delete") {
return {
...game,
currentGuess: currentGuess.slice(0, -1),
};
}
if (currentGuess.length === 5) return null;
return {
...game,
currentGuess: currentGuess + action,
};
}
function rightPad(str: string, length: number): string {
return str + " ".repeat(length - str.length);
}
function letterColor({ answer, guesses }: Game, letter: string): string {
if (guesses.some((guess) => [...guess].some((x, i) => x === letter && answer[i] === letter))) {
return GREEN;
}
if (
guesses.some((guess) => [...guess].some((x, i) => x === letter && answer[i] !== letter && answer.includes(letter)))
) {
return YELLOW;
}
if (guesses.some((guess) => [...guess].some((x, i) => x === letter && !answer.includes(letter)))) {
return GRAY;
}
return LIGHT_GRAY;
}
async function handler(req: Request): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === "/") {
let newGame = {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import {
CommandContext,
CommandDefinition,
} from "https://raw.githubusercontent.com/curtcox/CommandInterpreter/main/command/CommandDefinition.ts";
import { def_from_simple } from "https://raw.githubusercontent.com/curtcox/CommandInterpreter/main/command/ToolsForCommandWriters.ts";
async function markdown(url: string) {
const encoded = encodeURIComponent(url);
const response = await fetch(`https://markdown.download/?url=${encoded}`);
return await response.text();
}
export const command: CommandDefinition = def_from_simple({
name: "markdown",
doc: "return the contents of a specified URL as markdown.",
source: import.meta.url,
func: (_context: CommandContext, url: string) => markdown(url),
});

The idea for the GPT is so that the user comes up with a fun idea for a Video Game. When the user submits the idea, the AI will submit it to the API. The API will return a different idea from the pool of ideas already submitted to the API. This makes this GPT kind-of multiplayer as you will likely get ideas for video games that other people submitted.

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 { blob } from "https://esm.town/v/std/blob";
import { GptApi } from "https://esm.town/v/xkonti/gptApiFramework?v=25";
import { getPolicy } from "https://esm.town/v/xkonti/memoryApiPolicy";
import { z } from "npm:zod";
/**
* COMMON TYPES
*/
const IdeaSubmissionSchema = z.object({
idea: z.string().describe("A fun Video Game Idea. Needs to be at least 3 sentences long!"),
author: z.string().describe("A name of the author. Doesn't have to be real."),
}).describe("Full Fun Video Game Idea submission form");
const IdeaResponseSchema = z.object({
idea: z.string().describe("A fun Video Game Idea"),
author: z.string().describe("A name of the author who came up with the idea"),
instructions: z.string().describe("Instructions for the ChatGPT to follow immediately after receiving the response"),
}).describe("A Video Game Idea returned in exchange for the submitted one AND further instructions for the AI");
/**
* INITIALIZE API
*/
const api = new GptApi({
url: "https://xkonti-videogameideagpt.web.val.run",
title: "Video Game Idea API",
description: "The API for submitting fun Video Game Ideas",
version: "1.0.0",
});
/**
* VIDEO GAME SUBMISISON ENDPOINT
*/
api.jsonToJson({
verb: "POST",
path: "/submission",
operationId: "submitidea",
desc: "Endpoint for submitting fun Video Game Ideas",
requestSchema: IdeaSubmissionSchema,
responseSchema: IdeaResponseSchema,
}, async (ctx, reqBody, apiKey) => {
if (apiKey == null) {
return {
idea: "Bad request - no API key submitted",
author: "SERVER",
};
}
if (apiKey !== Deno.env.get("GPT_GAME_IDEA_API_KEY")) {
return {
idea: "Bad request - invalid API key submitted",
author: "SERVER",
};
}
if (reqBody.idea == null || reqBody.author == null) {
return {
idea: "Bad request - no idea or author submitted",
author: "SERVER",
};
}
let receivedIdea = reqBody as Idea;
let ideas = await getIdeas();
// Filer out ideas that have the same author
// This way the user can't see ideas that they have submitted themselves
const availableIdeas = ideas.filter(i => i.author !== receivedIdea.author);
let ideaToReturn = availableIdeas.length > 0
? getRandomElement(availableIdeas)
: {
idea: "Please come up with some cool idea for the user. There is nothing in the database yet.",
author: "API Server",
};
// Limit ideas to specific number
if (ideas.length >= maxIdeas) {
ideas = ideas.filter(i => i != ideaToReturn);
}
ideas.push(receivedIdea);
await setIdeas(ideas);
return {
idea: ideaToReturn.idea,
author: ideaToReturn.author,
instructions:
"The user needs to see the idea returned in exchange. Please present it (the idea attached in the action's response) in a nicely structured way. Make sure to expand upon the returned idea (GO NUTS!). Start with: `Here's an idea in exchange - submitte
};
});
/**
* IDEA LIST MANAGEMENT
*/
const ideasKey = "game-ideas";
const maxIdeas = 1000;
interface Idea {
idea: string;
author: string;
}

Useful when working with ZodFormData and you need to parse a text field that contains JSON.

See https://www.val.town/v/freecrayon/zodStringToJSON_example for an example of how to use it.

1
2
3
4
5
6
7
8
9
10
11
12
13
import { z } from "npm:zod";
const zodStringToJSON = () =>
z.string().transform((str, ctx): z.infer<z.ZodType<unknown>> => {
try {
return JSON.parse(str);
} catch (e) {
ctx.addIssue({ code: "custom", message: "Invalid JSON" });
return z.NEVER;
}
});
export { zodStringToJSON };
1
export { default } from "https://esm.town/v/nbbaier/sqliteExplorerApp?v=81";
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
/** @jsxImportSource https://esm.sh/preact */
import { render } from "npm:preact-render-to-string";
export default async function(req: Request) {
return new Response(
render(
<html>
<head>
<title>Your Girlfriend's Bday</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div style="position: relative; width: 100%; height: 0; padding-top: 282.8715%;
padding-bottom: 0; box-shadow: 0 2px 8px 0 rgba(63,69,81,0.16); margin-top: 1.6em; margin-bottom: 0.9em; overflow: hidden;
border-radius: 8px; will-change: transform;">
<iframe
loading="lazy"
style="position: absolute; width: 100%; height: 100%; top: 0; left: 0; border: none; padding: 0;margin: 0;"
src="https:&#x2F;&#x2F;www.canva.com&#x2F;design&#x2F;DAGFOLdTZE0&#x2F;mbsW1QuTlQ0ozPwQF5yjMA&#x2F;view?embed"
allowfullscreen="allowfullscreen"
allow="fullscreen"
>
</iframe>
</div>
<a
href="https:&#x2F;&#x2F;www.canva.com&#x2F;design&#x2F;DAGFOLdTZE0&#x2F;mbsW1QuTlQ0ozPwQF5yjMA&#x2F;view?utm_content=DAGFOLdTZE0&amp;utm_campaign=designshare&amp;utm_medium=embeds&amp;utm_source=link"
target="_blank"
rel="noopener"
>
Emily’s Bday
</a>{" "}
by Emily Coe
</body>
</html>,
),
{
headers: {
"Content-Type": "text/html; charset=utf-8",
},
},
);
}

Render form and save data

This val provides a web-based interface for collecting email addresses. It features a dual-functionality approach: when accessed via a web browser using a GET request, it serves an HTML form where users can submit their email address. If the script receives a POST request, it implies that the form has been submitted, and it proceeds to handle the incoming data.

Fork this val to customize it and use it on your account.

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
import { blob } from "https://esm.town/v/std/blob?v=11";
import { email } from "https://esm.town/v/std/email?v=9";
import { html } from "https://esm.town/v/stevekrouse/html?v=5";
export const renderFormAndSaveData = async (
req: Request,
): Promise<Response> => {
if (req.method !== "POST") {
return new Response("method not allowed", { status: 405 });
}
// Otherwise, someone has submitted a form so let's handle that
const events = (await blob.getJSON("events")) ?? {};
const formData = await req.formData();
const id = formData.get("id");
const name = formData.get("name");
const plusOnes = formData.get("plus_ones");
let event = events[id] ?? {};
// if (names.includes(name)) {
// return new Response("you're already signed up!");
// }
await email({
text: `${name} rsvp'd to ${id}. plus ones: ${plusOnes}`,
subject: `new rsvp to ${id}`,
});
const rsvps = event.rsvps ?? [];
event = {
...event,
rsvps,
};
events[id] = event;
await blob.setJSON(
"events",
events,
);
return new Response("thanks! you're signed up!");
};