Avatar

@jordan

3 likes11 public vals
Joined February 2, 2023

Bluesky RSS bot

This is a bot that polls an RSS feed for a new item every hour and posts it to Bluesky.

It's split into three parts:

  1. bsky_rss_poll
    • This function runs every hour and polls the provided RSS feed, turns it into XML and runs the check. If there is a new post, it tell rss_to_bskyto post a link (and the title) to Bluesky
  2. latest_rss
    • This is a stored object that keeps the latest object for the poll to test against
  3. rss_to_bsky
    • This function turns the text post into a rich text post and posts it to Bluesky
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function bsky_rss_poll() {
const { parseFeed } = await import("https://deno.land/x/rss/mod.ts");
const res = await fetch("https://v8.dev/blog.atom")
.then(res => res.text())
.then(res => parseFeed(res));
const title = res.entries[0].title.value, url = res.entries[0].id;
const latest_rss = JSON.stringify({ "title": title, "url": url });
if(@me.latest_rss !== latest_rss) {
@me.latest_rss = latest_rss;
const post = `${title}
${url} `;
await Promise.all([
@jordan.rss_to_bsky(post)
]);
}
}
1
1

fold(1)

A JavaScript implementation of the UNIX command fold(1).

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export const fold = (str: string, len?: number) => {
len = len !== undefined ? len - 1 : 79;
const split = str.split(" ");
let strarr = [];
let strlen = 0;
do {
let w = split.shift(), wlen = w.length;
if (strlen + wlen > len) {
strarr.push('\n' + w);
strlen = wlen;
continue;
} else if (strlen + wlen == len) {
strarr.push(w + '\n');
strlen = wlen;
continue;
} else {
strarr.push(w);
strlen += wlen + 1;
}
} while (split.length > 0);
return strarr.join(" ");
}
0
5

wikitxt

Converts Wikipedia articles (passed via link: e.g. /TypeScript) into plaintext. You can pass in a URL param ?width={width} to have it fold to your width. See below for an example.

Example: https://jordan-wikitxt.web.val.run/TypeScript?width=60

Readme
1
2
3
4
5
6
7
8
9
const wikitxt = async (req: Request) => {
const url = new URL(req.url), article = url.pathname.replace("/", ""), width = parseInt(url.searchParams.get("width"));
const wiki = await fetch(`https://en.wikipedia.org/w/api.php?action=query&format=json&titles=${article}&prop=extracts&explaintext`).then(res => res.json());
if (!wiki.query) return new Response("Please input a correct title")
const pages = wiki.query.pages;
const txt = pages[Object.keys(pages)[0]].extract;
const res = width ? @me.fold(txt, width) : @me.fold(txt);
return new Response(res);
}
0
0
1
2
// set by jordan.bsky_rss_poll at 2023-08-24T17:04:27.928Z
let latest_rss = "{\"title\":\"Speeding up V8 heap snapshots\",\"url\":\"https://v8.dev/blog/speeding-up-v8-heap-snapshots\"}";
0
2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
async function rss_to_bsky(text) {
import bsky from "npm:@atproto/api";
const { BskyAgent, RichText } = bsky;
const agent = new BskyAgent({ service: "https://bsky.social" });
await agent.login({
identifier: @me.secrets.BLUESKY_USERNAME!,
password: @me.secrets.BLUESKY_PASSWORD!,
});
const post = new RichText({ text: text });
await post.detectFacets(agent);
const postRecord = {
$type: 'app.bsky.feed.post',
text: post.text,
facets: post.facets,
createdAt: new Date().toISOString()
};
const latest_rss = @me.latest_rss;
if(latest_rss !== undefined) {
await agent.post(postRecord);
}
};
0
2
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
async function raw(req: Request) {
const { esm } = await import("https://esm.sh/build");
const url = new URL(req.url);
const [owner, val] = url.pathname.slice(1).split("/") as string[];
if (!owner || !val) {
return new Response("author and name are required!", {
status: 400,
});
}
const token = new URLSearchParams(url.search).get("token");
const resp = await fetch(`https://api.val.town/v1/alias/${owner.slice(1)}/${val.slice(0, -3)}`, {
headers: {
Authorization: token ? `Bearer ${token}` : undefined,
},
});
if (resp.status != 200) {
return resp;
}
if (val.includes(".ts") || val.includes(".js")) {
let { code } = await resp.json();
const res = await esm`${code}`;
return Response.redirect(res._build.url, 301);
}
return new Response("Not Found", { status: 404 });
}
// Forked from @pomdtr.raw
0
0
1
const paste_srht_example = @jordan.paste_srht("public", "paste_srht", @jordan.paste_srht, @me.secrets.srhtOauth)
0
0

paste.sr.ht

This val allows you to create a new paste on paste.sr.ht by passing in the following information:

  • visibility (public, unlisted, private)
  • filename
  • contents (just reference a val - e.g. @me.testVal)
  • token (@me.secrets.srhtOauth - generate this at meta.sr.ht/oauth; yes this is the legacy dashboard, paste.sr.ht only works with the legacy oauth) It will then return the link to your paste! Check out an example here.

You can see this val saved to paste.sr.ht here.

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
async function paste_srht(visibility: "public" | "unlisted" | "private", filename: string, fn: Function, token: string) {
const body = {
"visibility": visibility,
"files": [
{
"filename": `${filename}.js`,
"contents": fn.toString()
}
]
};
const res = await fetch("https://paste.sr.ht/api/pastes", {
method: "POST",
body: JSON.stringify(body),
headers: {
"Authorization": `token ${token}`,
"Content-Type": "application/json"
}
}).then(res => res.json());
const url = `https://paste.sr.ht/api/pastes/${res.sha}`;
const paste = await fetch(url, {
method: "GET",
headers: {
"Authorization": `token ${token}`,
}
}).then(res => res.json());
const username = paste.user.canonical_name, sha = paste.sha;
return `https://paste.sr.ht/${username}/${sha}`;
}
0
1

Tornado Bot

This is a rough mirror of the NWS Tornado Twitter bot but it's built to be distributed to multiple platforms. It's purely a tool for social media platforms and should not be relied upon for safety. Please see the official NWS website for time- (and life-) critical information.

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function tornado_bot() {
const latest_tornado_warning = await @nws.latest_tornado_warning();
// set @me.latest_tornado_warning to @nws.latest_tornado_warning
if (@me.latest_tornado_warning.properties.headline !== latest_tornado_warning.properties.headline) {
@me.latest_tornado_warning = latest_tornado_warning;
// if there's a tornado warning, setup the post
if (latest_tornado_warning.properties.headline !== "No current tornado warnings") {
const post = `${latest_tornado_warning.properties.parameters.AWIPSidentifier[0].slice(-3)} - ${latest_tornado_warning.properties.areaDesc}
${latest_tornado_warning.properties.headline}`;
// post to all the services
await Promise.all([
@me.nwstornado_bsky(post),
@me.nwstornado_discord(post)
]);
}
}
}
0
2

Screenshot of Discord message from NWS Tornado bot

Readme
1
2
3
4
5
6
7
8
9
async function nwstornado_discord(post) {
const latest_tornado_warning = @me.latest_tornado_warning;
if(latest_tornado_warning !== undefined) {
@stevekrouse.discordWebhook({
url: @me.secrets.nwstornadoWebhook,
content: post,
})
}
};
0
2