Avatar

@stevekrouse

135 likes715 public vals
Joined July 11, 2022
mayor of val town
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
import { lowercaseKeys } from "https://esm.town/v/stevekrouse/lowercaseKeys";
import { normalizeURL } from "https://esm.town/v/stevekrouse/normalizeURL";
export const fetchJSON = async (
url: string,
options?: RequestInit & {
bearer?: string;
},
) => {
let f = await fetch(normalizeURL(url), {
redirect: "follow",
...options,
headers: {
"content-type": "application/json",
authorization: options?.bearer ? `Bearer ${options.bearer}` : undefined,
...(lowercaseKeys(options?.headers ?? {})),
},
});
let t = await f.text();
try {
return JSON.parse(t);
}
catch (e) {
throw new Error(`fetchJSON error: ${e.message} in ${url}\n\n"${t}"`);
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import process from "node:process";
export const langchainEx = (async () => {
const { OpenAI } = await import("https://esm.sh/langchain/llms/openai");
const { PromptTemplate } = await import("https://esm.sh/langchain/prompts");
const { LLMChain } = await import("https://esm.sh/langchain/chains");
const model = new OpenAI({
temperature: 0.9,
openAIApiKey: process.env.openai,
maxTokens: 100,
});
const template = "What is a good name for a company that makes {product}?";
const prompt = new PromptTemplate({
template: template,
inputVariables: ["product"],
});
const chain = new LLMChain({ llm: model, prompt: prompt });
const res = await chain.call({ product: "colorful socks" });
return res;
})();

Blob Admin

This is a lightweight Blob Admin interface to view and debug your Blob data.

Screenshot 2023-12-13 at 12.51.53.png

To use it on your own Val Town Blob Storage, fork it to your account.

It uses basic authentication with your Val Town API Token as the password (leave the username field blank).

TODO

  • /new - render a page to write a new blob key and value
    • or new blob by file upload
  • /edit/:blob - render a page to edit a blob (prefilled with the existing content)
    • json validation when the existing content is json
      • checkbox to disable that
  • /delete/:blob - delete a blob and render success
Readme
Fork
1
2
3
4
5
6
7
8
9
10
11
12
import { basicAuth } from "https://esm.town/v/pomdtr/basicAuth";
import { blob_admin_blob } from "https://esm.town/v/stevekrouse/blob_admin_blob";
import { blob_admin_home } from "https://esm.town/v/stevekrouse/blob_admin_home";
import { Hono } from "npm:hono@3.9.2";
const app = new Hono();
app.get("/", async (c) => c.html(await blob_admin_home()));
app.get("/new", (c) => c.html("TODO: New Blob"));
app.get("/:blob", async (c) => c.html(await blob_admin_blob(c.req.param("blob"))));
app.get("/edit/:blob", async (c) => c.html(`TODO: Edit ${c.req.param("blob")}`));
app.get("/delete/:blob", async (c) => c.html(`TODO: Delete ${c.req.param("blob")}`));
export default basicAuth(app.fetch);

Email with GPT-3

Send an email to stevekrouse.emailGPT3@valtown.email, it will forward it to gpt3, and email you back the response.

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
import { thisEmail } from "https://esm.town/v/stevekrouse/thisEmail";
import { mail } from "https://esm.town/v/stevekrouse/mail";
import { runVal } from "https://esm.town/v/std/runVal";
export async function emailGPT3(email) {
let response = await runVal("patrickjm.gpt3", { prompt: email.text });
return mail({
to: email.from,
from: thisEmail(),
subject: "Re: " + email.subject,
text: response,
});
}

dlock - free distributed lock as a service

https://dlock.univalent.net/

Usage

API

Acquire a lock.

The id path segment is the lock ID - choose your own.

https://dlock.univalent.net/lock/arbitrary-string/acquire?ttl=60

{"lease":1,"deadline":1655572186}

Another attempt to acquire the same lock within its TTL will fail with HTTP status code 409.

https://dlock.univalent.net/lock/01899dc0-2742-44f9-9c7b-01830851b299/acquire?ttl=60

{"error":"lock is acquired by another client","deadline":1655572186}

The previous lock can be renewed with its lease number, like a heartbeat

https://dlock.univalent.net/lock/01899dc0-2742-44f9-9c7b-01830851b299/acquire?ttl=60&lease=1

{"lease":1,"deadline":1655572824}

Release a lock

https://dlock.univalent.net/lock/01899dc0-2742-44f9-9c7b-01830851b299/release?lease=42

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
import { searchParams } from "https://esm.town/v/stevekrouse/searchParams";
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON";
import { parentReference } from "https://esm.town/v/stevekrouse/parentReference";
export async function dlock({ id, ttl, release, lease }: {
id?: string;
ttl?: number;
release?: boolean;
lease?: number;
} = {}): Promise<{
lease?: number;
deadline: number;
error?: "string";
}> {
id = id ??
parentReference().userHandle + "-" +
parentReference().valName;
ttl = ttl ?? 3; // seconds
let method = release ? "release" : "acquire";
return fetchJSON(
`https://dlock.univalent.net/lock/${id}/${method}?${
searchParams({ ttl, lease })
}`,
);
}
Fork
1
2
3
4
5
6
import { karma } from "https://esm.town/v/stevekrouse/karma";
export const whatIsValTown = karma.replaceAll(
/karma/gi,
"Val Town",
);
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 process from "node:process";
import OpenAI from "npm:openai";
const openai = new OpenAI({ apiKey: process.env.openai });
async function main() {
const response = await openai.chat.completions.create({
model: "gpt-4-vision-preview",
messages: [
{
role: "user",
content: [
{
type: "text",
text:
"I am trying to find an emoji. I took a selfie that's trying to evoke this emoji. Give me a list of potential emojis this photo evokes. Reply ONLY with emoji. No other text explaining your choices.",
},
{
type: "image_url",
image_url:
"https://media.cleanshot.cloud/media/60976/KXVUGiSb0DD4jYqnaASkHYliQLpYPUnTBIiylySQ.jpeg?Expires=1699376932&Signature=XR1~tmcXRwHgQXh1BRZh3pa0RQrq00nSGTw3w-YHbNm6kCJXpCq13J6eORE1XDdZlPWq9yy5B~h6~nR889GmtuA67E5Fno839LyxPXA4RIBMIySVNF1py55grAba4JGZkt1oLqNeoKIcyUJYBOTcf41kNq0cJfEv0PSYrCpJbWiB1pcIAVg-WrgIF-R7MNdKQMwEUfSbbTSECRiTIIVU9GtBqqnGaSSot~2r28Tpj7~mACnyk9gNhewqeGOpYReCgw6cmmkrmh-5tNsqMtxeWoxXcT5EuXXEcX908grVzlLxLJQBQ5Tv2gmJprNsAIAgo6u01gWHAZL-OTpXn3firg__&Key-Pair-Id=K269JMAT9ZF4GZ",
},
],
},
],
max_tokens: 10,
});
return response.choices[0].message.content;
}
export let gpt4vDemo = await main();

☔️ Umbrella reminder if there's rain today

Screenshot 2023-09-14 at 12.31.32.png

Setup

  1. Fork this val 👉 https://val.town/v/stevekrouse.umbrellaReminder/fork
  2. Customize the location (line 8). You can supply any free-form description of a location.

⚠️ Only works for US-based locations (where weather.gov covers).

How it works

  1. Geocodes an free-form description of a location to latitude and longitude – @stevekrouse.nominatimSearch
  2. Converts a latitude and longitude to weather.gov grid – @stevekrouse.weatherGovGrid
  3. Gets the hourly forecast for that grid
  4. Filters the forecast for periods that are today and >30% chance of rain
  5. If there are any, it formats them appropriately, and sends me an email
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
import { email } from "https://esm.town/v/std/email?v=9";
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON";
import { nominatimSearch } from "https://esm.town/v/stevekrouse/nominatimSearch";
import { weatherGovGrid } from "https://esm.town/v/stevekrouse/weatherGovGrid";
export const umbrellaReminder = async (arg) => {
if (arg.method) return Response.json("");
let location = "prospect heights, brooklyn"; // <---- customize this line
let [{ lat, lon }] = await nominatimSearch({
q: location,
});
let { properties: grid } = await weatherGovGrid({
lat,
lon,
});
let { properties: { periods } } = await fetchJSON(
grid.forecastHourly,
);
let { DateTime } = await import("npm:luxon");
let parse = (iso) => DateTime.fromISO(iso).setZone(grid.timeZone);
let today = periods.filter((x) =>
parse(x.startTime).toLocaleString()
=== DateTime.now().setZone(grid.timeZone).toLocaleString()
);
if (today.every((x) => x.probabilityOfPrecipitation.value < 30))
return today;
let format = (iso) => parse(iso).toFormat("ha").toLowerCase();
let html = `The probabilities of rain in <b>${location}</b> today:<br><br>`
+ today.map((
{ startTime, endTime, probabilityOfPrecipitation: { value: p } },
) => `${format(startTime)}-${format(endTime)}: ${p}%`).join("<br>");
return email({ html, subject: "☔️ Carry an umbrella today!" });
};

Planes Above Me

Inspired by https://louison.substack.com/p/i-built-a-plane-spotter-for-my-son

A little script that grabs that planes above you, just change line 4 to whatever location you want and it'll pull the lat/log for it and query.

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
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON";
import { round } from "https://esm.town/v/stevekrouse/round";
import { nominatimSearch } from "https://esm.town/v/stevekrouse/nominatimSearch";
export let planesAboveMe = (async () => {
let [{ lat, lon, display_name }] = await nominatimSearch({
q: "atlantic terminal", // <---- change me
});
let epsilon = .1;
let query = new URLSearchParams({
lamax: String(round(lat, 2) + epsilon),
lamin: String(round(lat, 2) - epsilon),
lomax: String(round(lon, 2) + epsilon),
lomin: String(round(lon, 2) - epsilon),
});
console.log({ lat, lon, display_name });
let url = `https://opensky-network.org/api/states/all?${query}`;
let data = await fetchJSON(url);
return data?.states?.map((f) => ({
icao24: f[0],
callsign: f[1],
origin_country: f[2],
time_position: f[3],
last_contact: f[4],
longitude: f[5],
latitude: f[6],
baro_altitude: f[7],
on_ground: f[8],
velocity: f[9],
true_track: f[10],
vertical_rate: f[11],
sensors: f[12],
geo_altitude: f[13],
squawk: f[14],
spi: f[15],
position_source: f[16],
category: f[17],
}));
})();

Message yourself on Telegram

Inspired by console.email, this val lets you send yourself Telegram messages via the Val Town Telegram Bot. You can use it to make custom Telegram bots, such as this DallE one.

Usage

@stevekrouse.telegram(@me.secrets.telegram, "hi to me on telegram!")
@me.t("hello telegram!")

Installation

It takes less than a minute to set up!

  1. Start a conversation with ValTownBot

  2. Copy the secret it gives you

  3. Save it in your Val Town secrets under telegram

  4. Send a message!

@stevekrouse.telegram(@me.secrets.telegram, "hi to me on telegram!")

Example: https://www.val.town/v/stevekrouse.exampleTelegramMessage

  1. (Bonus) Make a helper function

If you want to make it even easier to message yourself on telegram, ie @me.t("hello!") then you can setup a helper function:

let t = (message, options) => @stevekrouse.telegram(@me.secrets.telegram, message, options);

Ensure you keep this function private otherwise anyone can message you on Telegram!

Commands

  • /roll - Roll your secret in case you accidentally leak it.
  • /webhook - Set a webhook to receive messages you send to @ValTownBot

Receiving Messages

If you send /webhook to @ValTownBot, it will let you specify a webhook URL. It will then forward on any messages (that aren't recognized @ValTownBot commands) to that webhook. It's particularly useful for creating personal chatbots, like my telegram <-> DallE bot.

How it works

Telegram has a lovely API.

  1. I created a @ValTownBot via Bot Father.
  2. I created a webhook and registered it with telegram
  3. Whenever someone new messages @ValTownBot, I generate a secret and save it along with their Chat Id in @stevekrouse.telegramValTownBotSecrets (a private val), and message it back to them
  4. Now whenever you call this val, it calls telegramValTownBot via the Run API (api is a helper), which looks up your Chat Id via your secret and sends you a message

Telegram Resources

Credits

This val was originally made by pomdtr.

Readme
1
2
3
4
5
6
import { runVal } from "https://esm.town/v/std/runVal";
export async function telegram(secret: string, text: string, options?) {
return runVal("stevekrouse.telegramValTownBot", secret, text, options);
}
// Forked from @pomdtr.telegram