Avatar

@jamiedubs

5 likes18 public vals
Joined March 27, 2023
i move away from the mic to breathe in
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 { blob } from "https://esm.town/v/std/blob?v=10";
function isJSON(input: string | null | undefined) {
if (!input || input === null) return false;
try {
JSON.parse(input);
return true;
} catch (e) {
return false;
}
}
function processInputHtml(html: string) {
let output = html;
// strip out backticks which gpt loves generating
output = output.replaceAll("```html", "");
output = output.replaceAll("```", "");
return output;
}
function formatPage(key: string, data: string) {
return `
${data}
<br />
<br />
hosted by
<a href="https://www.val.town/v/jamiedubs/valtownGeocities" target="_blank">
valtownGeocities
</a>
| key: ${key}
`;
}
export const valtownGeocities = async (req: Request) => {
const searchParams = new URL(req.url).searchParams;
const format = searchParams.get("format") ?? "html";
const key = searchParams.get("key") ?? "default";
// if (!key) throw new Error("missing ?key=");
console.log("hello", { format, key });
let data;
const oldData = await blob.getJSON(key);
if (req.method == "GET") {
data = { data: oldData };
} else if (req.method == "POST") {
const rawData = await req.text();
console.log("received rawData...", rawData);

returns image URL (and only image URL) for a given NFT contract + tokenId. Uses Alchemy's NFT API

to this use val, copy the Web API endpoint and use ?query params to specify the contract address and tokenId you want:

https://jamiedubs-nftimage.web.val.run/?contractAddress=0x3769c5700Da07Fe5b8eee86be97e061F961Ae340&tokenId=666 - FIXME valtown is turning & into "&", you need to fix it. even like this broken

plain text by default. for JSON add &format=json, for an <img> tag use &format=html

for other NFT metadata: https://www.val.town/v/jamiedubs.nftMetadata

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 { fetchNftMetadata } from "https://esm.town/v/jamiedubs/nftMetadata";
export const nftImage = async (req: Request) => {
const searchParams = new URL(req.url).searchParams;
const contractAddress = searchParams.get("contractAddress");
const tokenId = searchParams.get("tokenId");
const format = searchParams.get("format")?.toLowerCase() ?? "text";
console.log("nftImage", { contractAddress, tokenId });
const json = await fetchNftMetadata(contractAddress, tokenId);
console.log("nftMetadata response =>", json);
const imageUrl = json["metadata"] && json["metadata"]["image"];
if (format == "json") {
return Response.json({ imageUrl });
}
else if (format == "html") {
return new Response(`<img src="${imageUrl}"/>`, {
headers: {
"Content-Type": "text/html",
},
});
}
else {
return new Response(imageUrl, {
headers: {
"Content-Type": "text/plain",
},
});
}
};

use by copying web API endpoint and appending "?contractAddress=...&tokenId..." - like this

uses Alchemy for indexed NFT data: https://docs.alchemy.com/reference/getnftmetadata

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
import { fetch } from "https://esm.town/v/std/fetch";
import process from "node:process";
export async function fetchNftMetadata(contractAddress: string, tokenId: string) {
const apiKey = process.env.ALCHEMY_API_KEY;
const rpcUrl = `https://eth-mainnet.g.alchemy.com/nft/v2/${apiKey}`;
const url = `${rpcUrl}/getNFTMetadata?contractAddress=${contractAddress}&tokenId=${tokenId}`;
const response = await fetch(url, {
method: "GET",
headers: {
"accept": "application/json",
},
});
console.log("Alchemy response", response);
if (!response) {
return { error: "no response from Alchemy" };
} else if (!response.ok) {
return { error: `failed with status: ${response.statusText}` };
}
else {
const json = await response.json();
console.log("response OK", json);
return json;
}
}
export default async function nftMetadata(req: Request): Promise<Response> {
const searchParams = new URL(req.url).searchParams;
const contractAddress = searchParams.get("contractAddress");
const tokenId = searchParams.get("tokenId");
const format = searchParams.get("format")?.toLowerCase() ?? "text";
if (!contractAddress || !tokenId) {
return Response.json({ error: "you must specify ?contractAddress...&tokenId=..." });
}
const json = await fetchNftMetadata(contractAddress, tokenId);
return Response.json(json);
}

returns the last 100 featured glifs on Glif but with a simplified response shape; I use this in other glifs with the WebFetcher block

use like: https://jamiedubs-glifs.web.val.run/

to fetch info for a single glif, try @jamiedubs/glifJson

#glif #glifs

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
import { fetch } from "https://esm.town/v/std/fetch";
export const glifs = async (id: string) => {
const url = `https://glif.app/api/glifs?featured=1`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
const data = json.map((glif) => {
return { id: glif.id, name: glif.name, description: glif.description };
});
return Response.json(data);
// return text
// const data = json.map((glif) => {
// return `${glif.id} ${glif.name}`;
// }).join("\n");
// console.log("data", data);
// return new Response(data, { headers: { "Content-Type": "text/plain" }});
}
catch (error) {
return Response.json({ error: error.message });
}
};

fetches a simplified version of a Glif response object; I use this in other glifs with the WebFetcher block

use like: https://jamiedubs-glifjson.web.val.run/?id=clgh1vxtu0011mo081dplq3xs

to fetch only the raw glif JSON, specify ?data=1: https://jamiedubs-glifjson.web.val.run/?id=clgh1vxtu0011mo081dplq3xs&data=1

to fetch a list of recently featured glifs try @jamiedubs/glifs

#glif #glifs

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
import { fetch } from "https://esm.town/v/std/fetch";
export const glifJson = async (req: Reqeust) => {
const searchParams = new URL(req.url).searchParams;
const id = searchParams.get("id");
const url = `https://glif.app/api/glifs?id=${id}`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
const data = searchParams.get("data") ? json[0]?.data : json[0];
if (data) {
return Response.json(data);
}
else {
throw new Error("bad JSON response");
}
}
catch (error) {
return Response.json({ error: error.message });
}
};

fetch contents of a specific wikipedia page using ?title=param. will return nothing if page doesn't exactly match. example: https://jamiedubs-wikipediapage.web.val.run/?title=Berlin

for a more search-oriented approach, try https://www.val.town/v/jamiedubs/searchWikipedia

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
40
export const fetchWikipediaContent = async (req: Request) => {
const url = new URL(req.url);
const title = url.searchParams.get("title");
if (!title) {
return new Response("Title parameter is required", {
status: 400, // Bad Request
headers: { "Content-Type": "text/plain" },
});
}
try {
const apiUrl =
`https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&format=json&origin=*&titles=${
encodeURIComponent(title)
}`;
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const pageId = Object.keys(data.query.pages)[0];
if (pageId === "-1") {
return new Response("No content found for the provided title", {
status: 404, // Not Found
headers: { "Content-Type": "text/plain" },
});
}
const content = data.query.pages[pageId].extract;
return new Response(content, {
status: 200, // OK
headers: { "Content-Type": "text/plain" },
});
} catch (error) {
return new Response(`Error fetching Wikipedia content: ${error.message}`, {
status: 500, // Internal Server Error
headers: { "Content-Type": "text/plain" },
});
}
};

dark greetings cryptoadventurers. This val will print the contents of a given Ethereum wallet's Synthetic Loot, which is procedurally generated from your wallet address. To look at your sLoot in a browser with some fun pixel art, check out timshel's Synthetic Loot Viewer

to use this endpoint, pass ?address=0x... e.g. https://jamiedubs-syntheticloot.web.val.run/?account=0xf296178d553c8ec21a2fbd2c5dda8ca9ac905a00

the default response type is JSON. You can also get a simple list of the loot bag contents using ?format=text. e.g. https://jamiedubs-syntheticloot.web.val.run/?account=0xf296178d553c8ec21a2fbd2c5dda8ca9ac905a00&format=text

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
40
41
42
43
44
45
46
47
48
49
50
import { fetch } from "https://esm.town/v/std/fetch";
import process from "node:process";
import { DOMParser } from "https://deno.land/x/deno_dom/deno-dom-wasm.ts";
// I <3 ether.actor, very convenient for fetching on-chain data
// it's a little slow - Alchemy or another RPC/API provider would be faster
const url = `https://ether.actor/0x869ad3dfb0f9acb9094ba85228008981be6dbdde/tokenURI`;
// example data:
// const account = "0xf296178d553c8ec21a2fbd2c5dda8ca9ac905a00";
// const svg = `<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: serif; font-size: 14px; }</style><rect width="100%" height="100%" fill="black" /><text x="10" y="20" class="base">Grave Wand</text><text x="10" y="40" class="base">Ornate Chestplate</text><text x="10" y="60" class="base">Dragon's Crown of Protection</text><text x="10" y="80" class="base">War Belt</text><text x="10" y="100" class="base">"Fate Sun" Divine Slippers of Power</text><text x="10" y="120" class="base">Silk Gloves</text><text x="10" y="140" class="base">Necklace</text><text x="10" y="160" class="base">Bronze Ring</text></svg>`;
async function fetchAndParseSvgFromJson(account: string) {
try {
const response = await fetch(`${url}/${account}`);
const dataUri = await response.text();
// Decode the JSON from the base64 encoded dataURI
const base64Json = dataUri.split(",")[1];
const decodedJsonString = atob(base64Json);
const json = JSON.parse(decodedJsonString);
console.log({ json });
// Extract the SVG dataURI from the JSON's `image` field
const svgDataUri = json.image;
const base64Svg = svgDataUri.split(",")[1];
const decodedSvg = atob(base64Svg);
console.log(decodedSvg);
return decodedSvg; // or manipulate as needed
} catch (error) {
console.error("Error fetching or decoding SVG from JSON:", error);
}
}
type SvgTextElement = {
content: string;
x: string;
y: string;
class: string;
};
function parseElementsFromSvg(svgString: string): SvgTextElement[] {
const parser = new DOMParser();
// deno-dom only supports HTML
// https://deno.land/x/deno_dom@v0.1.45
// const doc = parser.parseFromString(svgString, "image/svg+xml");
const doc = parser.parseFromString(svgString, "text/html");
const elements = doc.querySelectorAll("text");

get weather forecast for a city. pass ?city=Cleveland,OH. e.g.: https://jamiedubs-weatherInCity.web.val.run/?city=brooklyn,ny

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
40
41
42
43
44
export const getWeatherForecast = async (req: Request) => {
const url = new URL(req.url);
const city = url.searchParams.get("city");
if (!city) {
return new Response("City parameter is required", {
status: 400, // Bad Request
headers: { "Content-Type": "text/plain" },
});
}
const weatherApiUrl = `https://wttr.in/${encodeURIComponent(city)}?format=j1`;
try {
const weatherResponse = await fetch(weatherApiUrl);
if (!weatherResponse.ok) {
throw new Error(`Weather API request failed with status: ${weatherResponse.status}`);
}
const weatherData = await weatherResponse.json();
const currentTempC = weatherData.current_condition[0].temp_C;
const currentTempF = weatherData.current_condition[0].temp_F;
const forecast = weatherData.weather[0].hourly.slice(0, 8).map(hour => ({
time: hour.time,
tempC: hour.tempC,
tempF: hour.tempF,
desc: hour.weatherDesc[0].value,
}));
const forecastString = forecast.reduce((acc, hour) => {
return `${acc}Time: ${hour.time}, Temp: ${hour.tempC}°C/${hour.tempF}°F, Description: ${hour.desc}\n`;
}, `Current Temperature: ${currentTempC}°C/${currentTempF}°F\nForecast for the next 8 hours:\n`);
return new Response(forecastString, {
status: 200, // OK
headers: { "Content-Type": "text/plain" },
});
} catch (err) {
return new Response("Error fetching weather data: " + err.message, {
status: 500, // Internal Server Error
headers: { "Content-Type": "text/plain" },
});
}
};
Fork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// a very slow and sleepy function
// pass it ?sleep=33 to make it sleep for that amount of sleep
export default async function (req: Request): Promise<Response> {
const url = new URL(req.url);
const sleepQueryParam = url.searchParams.get('sleep');
let sleepTime = 1;
if (sleepQueryParam) {
sleepTime = parseInt(sleepQueryParam, 10);
if (!isNaN(sleepTime) && sleepTime > 0) {
await new Promise(resolve => setTimeout(resolve, sleepTime * 1000));
}
}
return new Response(JSON.stringify({ ok: true, sleepTime }), {
headers: { 'Content-Type': 'application/json' },
});
}

fetch the contents of the Wikipedia "On this day in history" page. defaults to JSON output, but specify ?format=textor ?format=html for other outputs. e.g.

#wikipedia

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 { fetchText } from "https://esm.town/v/stevekrouse/fetchText?v=6";
import { load } from "npm:cheerio";
const fetchToday = async () => {
const html = await fetchText(
"https://en.wikipedia.org/wiki/Wikipedia:On_this_day/Today",
);
const $ = load(html);
// Cheerio accepts a CSS selector, here we pick the second <p>
// const intro = $("p:nth-of-type(2)").first().text();
const body = $("#mw-content-text").first().text();
// TODO trim body a bit; what is all this other stuff?
let parsedBody = body.split(".mw-parser-output")[0];
parsedBody = parsedBody.split("\n").slice(1, -1).join("\n").trim();
return parsedBody;
};
export const wikipediaToday = async (req: Request) => {
const searchParams = new URL(req.url).searchParams;
const format = searchParams.get("format") ?? "html";
const data = await fetchToday();
if (format == "json") {
return Response.json({ data });
} else if (format == "html" || format == "text") {
// return new Response(`<pre>${JSON.stringify(data, null, 2)}</pre>`, { headers: { 'Content-Type': 'text/html' } });
return new Response(data, { headers: { "Content-Type": "text/html" } });
} else {
throw new Error("unsupported format");
}
};