Search

Results include substring matches and semantically similar vals. Learn more
rwev avatar
polygonPriceOnWeekdayMsAgo
@rwev
An interactive, runnable TypeScript val by rwev
Script
import { adjustMsToWeekday } from "https://esm.town/v/rwev/adjustMsToWeekday";
import { msToIsoDate } from "https://esm.town/v/rwev/msToIsoDate";
export async function polygonPriceOnWeekdayMsAgo(ticker, msAgo) {
const date = msToIsoDate(
adjustMsToWeekday(Date.now() - msAgo)
vladimyr avatar
libmultibase
@vladimyr
// SPDX-License-Identifier: 0BSD
Script
0xeb51: KeyType["JWK-JCS"],
} as const;
export function multibaseToBytes(input: string) {
const bytes = base58btc.decode(input);
const [codec, prefixLength] = varint.decode(bytes);
throw new Error("error: invalid key size");
return { keyBytes, keyType: keyType.name };
export function bytesToMultibase(keyBytes: Uint8Array, keyType: string) {
const type = KeyType[keyType];
if (!type) throw new TypeError("error: unsupported key type");
stevekrouse avatar
newStripeSubscriber
@stevekrouse
New Stripe Subscription Handler This val processes new Stripe subscribers to Val Town Pro. It sends our team a Discord notifcation. It takes a couple of steps if you'd like to set up something similar for your own Stripe account. Setup Fork this HTTP val Create a new webhook in Stripe Add your val's HTTP endpoint URL into the Stripe webhook Select customer.subscription.updated as the only event to listen to (more on this below) Add your stripe_sk_customer_readonly to your Val Town Env Variables Add your webhook's signing secret as STRIPE_WEBHOOK_SECRET to you Val Town Env Variables How the code is structured Verifies the signature to make sure it's really from Stripe Filters out only newly created subscriptions Sends off the Discord message & email Which Stripe event type to listen to Stripe sends webhooks for many different kinds of events. Relevant for us here are: customer.subscription.created (what we used to listen for) customer.subscription.updated (what we're currently listening for) The issue with customer.subscription.created is that it triggers too early, before the user's payment actually goes through. This is a problem because in early Nov 2024 we started getting credit card fraudsters testing cards using our service. We started getting lots of notifications for new subscriptions that never actually became active. Note: if anyone knows good ways to prevent that sort of behavior at the root, please let me know by commenting on this val! In order to only get notified on a valid subscription, we now subscribe to customer.subscription.updated . This event happens on any subscription change, including renewals and cancellations, so now we have to filter those events to only get new subscriptions, ie where: event.data.previous_attributes.status === 'incomplete' && event.data.object.status === 'active'
HTTP
const stripe = new Stripe(Deno.env.get("stripe_sk_customer_readonly") as string, {
apiVersion: "2020-08-27",
function getStripeCustomer(customerId: string) {
return stripe.customers.retrieve(customerId);
export let newStripeEvent = async (req: Request) => {
emarref avatar
HttpMiddlewareChain
@emarref
An implementation of chainable http middleware. Wrap your http handlers in middleware to add functionality and reduce code duplication. export default function chain() .then(add(requireHttpMethod("post"))) .then(add(requireAuthentication())) .then(send())
Script
An implementation of chainable http middleware.
Wrap your http handlers in middleware to add functionality and reduce code duplication.
```ts
export default function chain()
.then(add(requireHttpMethod("post")))
send: (response: Response) => HttpHandler;
export function chain(): Promise<Chain> {
const stack = new Set<HttpMiddleware>();
async function getChain(): Promise<Chain> {
return { add, send };
let happy = true;
async function loop() {
middleware = queue.shift();
return getChain();
export function add(middleware: HttpMiddleware) {
return function({ add }: Chain) {
return add(middleware);
export function send(response?: Response) {
return function({ send }: Chain) {
return send(response ?? success());
dotim avatar
checkTomorrowTempoEdf
@dotim
Ce script permet de vérifier le statut du jour Tempo de demain (Bleu, Blanc ou Rouge) en utilisant l'API publique Couleur Tempo. S'il détecte un jour Rouge ou blanc, il envoie une notification pour avertir l'utilisateur. En cas d'erreur lors de la récupération des données, une notification d'erreur est également envoyée.
Cron
day: "numeric",
} as const,
export default async function checkTempoStatus() {
try {
const tempoData = await TempoService.getTomorrow();
ktodaz avatar
sendDiscordMessage
@ktodaz
Send Chunked Discord Message This function is used to send a message to a Discord webhook. If the message exceeds the maximum character limit (2000 characters), it will be divided into chunks and sent as multiple messages. Parameters message (string): The message to be sent to the Discord webhook. Return Value This function does not return any value. Example Usage: const message = "This is a long message that needs to be sent to Discord. It may exceed the character limit, so it will be divided into smaller chunks."; await @ktodaz.sendDiscordMessage(message); In the above example, the sendDiscordMessage function is used to send the message to the Discord webhook. If the message exceeds 2000 characters, it will be split into smaller chunks and sent as separate messages. Required Secrets: Ensure you have added the secret for discord_webhook in your val.town secrets settings (Profile -> Secrets)
Script
# Send Chunked Discord Message
This function is used to send a message to a Discord webhook. If the message exceeds the maximum character limit (2000 charac
### Parameters
### Return Value
This function does not return any value.
## Example Usage:
await @ktodaz.sendDiscordMessage(message);
In the above example, the sendDiscordMessage function is used to send the message to the Discord webhook. If the message exce
## Required Secrets:
webhook_url: string = process.env.discord_webhook,
function chunkString(str) {
const chunks = [];
maxencelav avatar
datetimeFormatterTool
@maxencelav
@jsxImportSource https://esm.sh/react
HTTP
{ label: "Custom", value: "custom" },
function App() {
const [dateTime, setDateTime] = useState("2023-06-15T14:30:00");
</div>
function client() {
createRoot(document.getElementById("root")).render(<App />);
client();
export default async function server(request: Request): Promise<Response> {
return new Response(
neverstew avatar
setCorsHeaders
@neverstew
An interactive, runnable TypeScript val by neverstew
Script
"Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
} as const;
export default function setCorsHeaders(
response: Response,
headers: Partial<Record<keyof typeof DEFAULT_CORS_HEADERS, string>> = DEFAULT_CORS_HEADERS,
postpostscript avatar
multiFormat
@postpostscript
multiFormat: easily create outputs of multiple formats (e.g. text/html for Email) using tagged templates Examples import { format } from "https://esm.town/v/postpostscript/multiFormat"; console.log(format.Plain` ${format.Heading1`Some Heading`} Lorem ipsum dolor ${format.Strong`sit`} amet `); { "text": "\nSome Heading\n\nLorem ipsum dolor sit amet\n", "html": "\n<br><h1>Some Heading</h1>\n<br>\n<br>Lorem ipsum dolor <strong>sit</strong> amet\n<br>" } Create Your Own Formatters import { createFormatMethod, wrapHTMLTag, type MultiFormatWithHTML } from "https://esm.town/v/postpostscript/multiFormat"; export const Red = createFormatMethod<MultiFormatWithHTML>((value) => { return wrapHTMLTag("span", value, { style: "color: red;", }); }); console.log(Red`red text!`) { "text": "red text!", "html": "<span style=\"color: red;\">red text!</span>" } Sanitization Text is automatically sanitized when converted to HTML: console.log(format.Trim` ${format.Heading1`Some Heading`} <script>alert(1)</script> `.html.toString()) <h1>Some Heading</h1> <br> <br>&lt;script&gt;alert(1)&lt;/script&gt;
Script
export function multiFormat<R extends MultiFormat = { text: string }>(
export function combineMultiFormat<R extends MultiFormat>(
export function joinMultiFormat<R extends MultiFormat>(values: (R | string)[], join: R | string) {
export function normalizeMultiFormat<R extends MultiFormat>(value: R | string | number | boolean | null | undefined) {
export function getMultiFormatValue<R extends MultiFormat, K extends keyof R>(
function isString(value: unknown): value is string {
function toString(value: unknown) {
export function addStrings(valueA: unknown, valueB: unknown) {
export function wrapHTMLTag(
export function createFormatMethod<R extends MultiFormat>(method: (value: R) => R) {
iamseeley avatar
dateHelpers
@iamseeley
An interactive, runnable TypeScript val by iamseeley
Script
export function formatDateRange(startDateString, endDateString, format) {
const startDate = new Date(startDateString);
const endDate = new Date(endDateString);
return startFormatted;
return `${startFormatted} - ${endFormatted}`;
function formatSingleDate(date, format) {
const options = {};
switch (format) {
emarref avatar
HttpMiddlewareRequireValidHmacSignature
@emarref
Middleware to ensure the signed request is valid. export default chain() .then(add(requireValidHmacSignature({ // The secret value shared between you and the originating party signingKey: "my_super_secret_key", // The name of the header containing the signature to compare with our value signatureHeaderName: "x-signature", }))) .then(send())
Script
type Hmac = ReturnType<HmacFactory>;
type DigestFunction = Hmac["digest"];
type Algorithm = Parameters<HmacFactory>[0];
type Encoding = Parameters<DigestFunction>[0];
type Options = {
encoding?: Encoding;
export function requireValidHmacSignature({
algorithm = "sha256",
}: Options): HttpMiddleware {
return function(next) {
return async function(request) {
const providedSignature = request.headers.get(signatureHeaderName) ?? "";
kailhus avatar
checkMixcloudIsLive
@kailhus
Server/client to check whether a mixcloud channel is live. Uses browserless.io (add your own 'browserless' key in the Val Town env variables),
HTTP
import React, { useState } from "https://esm.sh/react@18.2.0";
function App() {
const [isLive, setIsLive] = useState(false);
const [submittedChannel, setSubmittedChannel] = React.useState<string | null>(null);
async function checkLiveStatus(channel: string) {
try {
</div>
function client() {
createRoot(document.getElementById("root")).render(<App />);
if (typeof document !== "undefined") { client(); }
export default async function server(request: Request): Promise<Response> {
const url = new URL(request.url);
headers: { "Content-Type": "text/html" },
async function checkMixcloudLiveStatus(
channelName: string,
arfan avatar
myBookmarkManager
@arfan
@jsxImportSource https://esm.sh/react@18.2.0
HTTP
function MyBookmarkManager() {
function getSiteIconURL(bookmark) {
async function fetchSavedBookmarks() {
function showAddNewBookmarkOverlay() {
function handleRightClickOnBookmark(e, bookmark) {
function closeAddBookmarkOverlay() {
function closeSettingsOverlay() {
async function handleBookmarkFormSubmit(e) {
async function saveAllBookmarks() {
function importBookmarksFromFile(event) {
luizhrios avatar
btcPriceAlert
@luizhrios
BTC Price Alert This val monitors the price of Bitcoin (BTC) and sends an email alert if the price fluctuates significantly. Specifically, it checks the current BTC price against the last recorded price and triggers an email notification if the change exceeds 20%. The email includes the new price, formatted as currency. Fork this val to get these notifications on your inbox.
Cron
import { email } from "https://esm.town/v/std/email?v=9";
import { currency } from "https://esm.town/v/stevekrouse/currency";
export async function btcPriceAlert() {
const lastBtcPrice: number = await blob.getJSON("lastBtcPrice");
let btcPrice = await currency("usd", "btc");
chet avatar
diffHtml
@chet
An interactive, runnable TypeScript val by chet
Script
import { cleanHtml } from "https://esm.town/v/chet/cleanHtml";
import { diffLines } from "https://esm.town/v/chet/diffLines";
export function diffHtml(before: string, after: string) {
return diffLines(cleanHtml(before), cleanHtml(after));