Back to APIs list

Slack API examples & templates

Use these vals as a playground to view and fork Slack API examples and templates on Val Town. Run any example below or find templates that can be used as a pre-built solution.

This is public so you can see what's in it. If it had real secrets, it should be private. See https://www.val.town/v/curtcox/reply_to_slack_message

1
2
3
4
5
6
7
8
9
10
11
12
import { processor } from "https://esm.town/v/curtcox/message_processor?v=17";
import { reply_to_slack_message, SlackConfig } from "https://esm.town/v/curtcox/reply_to_slack_message";
// This is for informational purposes only.
// It needs real tokens to work.
export const chatio = (req: Request) => {
const config: SlackConfig = {
slackToken: "See Val Town docs for how to get this token",
slackVerificationToken: "See Val Town docs for how to get this token",
};
return reply_to_slack_message(req, processor, config);
};

This val provides a way of readily having lots of different Slack bots that do different things and support different Slack workspaces without needing a bunch of Val Town accounts. If you only need a single bot on a single workspace, just go with the approach in the Val Town docs.

This bot is essentially the one described in the Val Town docs, but without any of the details. Those are externalized by the SlackConfig and SlackFunction interfaces. It is just glue. In order to make it work, you will need the following additional glue:

  • a public HTTP val to handle requests like this one
  • a private val to supply any info missing from the public one like this one

Actually, those could both be public or all in the same val, but the whole point of this scheme is to allow you to hide anything you want.

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
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON?v=41";
async function post_response(body, f: SlackFunction, config: SlackConfig) {
// Note: `body.event` has information about the event like the sender and the message text
const text = await f(body);
const result = await fetchJSON("https://slack.com/api/chat.postMessage", {
headers: { "Authorization": `Bearer ${config.slackToken}` },
method: "POST",
body: JSON.stringify({
channel: body.event.channel,
thread_ts: body.event.ts,
text: text,
}),
});
// Slack replies with information about the created message
console.log(result);
}
// This allows us to accept the keys we need
export interface SlackConfig {
slackToken: string;
slackVerificationToken: string;
}
// This will process the message and produce a response
export interface SlackFunction {
(body: any): Promise<string>;
}
export const reply_to_slack_message = async (req: Request, f: SlackFunction, config: SlackConfig) => {
let body = null;
try {
body = await req.json();
console.log(body);
} catch (e) {
console.error(e);
return new Response(`Request was not valid ${req}`, { status: 400 });
}
// Verify the request is genuine
if (body.token !== config.slackVerificationToken) {
return new Response(undefined, { status: 401 });
}
// Respond to the initial challenge (when events are enabled)
if (body.challenge) {
return Response.json({ challenge: body.challenge });
}
// Reply to app_mention events
if (body.event.type === "app_mention") {
post_response(body, f, config);
return new Response(undefined, { status: 202 });
}
return new Response(undefined, { status: 404 });
};

Twitter ๐• keyword Alerts

Custom notifications for when you, your company, or anything you care about is mentioned on Twitter.

1. Authentication

You'll need a Twitter Bearer Token. Follow these instructions to get one.

Unfortunately it costs $100 / month to have a Basic Twitter Developer account. If you subscribe to Val Town Pro, I can let you "borrow" my token. Just comment on this val and I'll hook you up.

2. Query

Change the query variable for what you want to get notified for.

You can use Twitter's search operators to customize your query, for some collection of keywords, filtering out others, and much more!

3. Notification

Below I'm sending these mentions to a private channel in our company Discord, but you can customize that to whatever you want, @std/email, Slack, Telegram, whatever.

Runs every 1 hrs
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
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
import { twitterSearch } from "https://esm.town/v/stevekrouse/twitterSearch";
const query = "\"val.town\" OR \"val town\" -_ValTown_";
export async function twitterAlert({ lastRunAt }: Interval) {
const results = await twitterSearch({
query,
start_time: lastRunAt,
bearerToken: Deno.env.get("twitter"),
});
if (!results.length) return;
// format results
let content = results
.map(({ author_name, author_username, text, id }) => `https://fxtwitter.com/${author_username}/status/${id}`)
.join("\n");
// notify
await discordWebhook({
url: Deno.env.get("mentionsDiscord"),
content,
});
}
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
/** @jsxImportSource npm:react **/
import { fetchText } from "https://esm.town/v/stevekrouse/fetchText?v=6";
import { load } from "npm:cheerio";
import { renderToString } from "npm:react-dom@18/server";
export default async (req: Request) => {
const html = await fetchText(
"https://www.moongiant.com/phase/today/",
);
const $ = load(html);
const todayContents = [...$("#today_").contents()].filter(e => e.type === "text" && $(e).text().trim())
.map(e => $(e).text().trim());
const moonIcon = `:${todayContents[0].toLowerCase().replace(" ", "_")}_moon:`;
console.log(moonIcon);
/**
*
* //This is the way Slack wants it.
* {
"status_text": "riding a train",
"status_emoji": ":mountain_railway:",
"status_expiration": 0
}
*
*
* */
return new Response(
renderToString(
<html>
<link rel="stylesheet" href="https://unpkg.com/missing.css@1.1.1" />
<main>
<h1>{moonIcon}</h1>
</main>
</html>,
),
{ headers: { "Content-Type": "text/html" } },
);
};

Daily Standup Bot

Every weekday at 9am EDT send a message to our team's #engineering Discord channel to start a thread to remind us to do our standup.

Screenshot 2024-03-14 at 09.27.26.png

Slack version: @mikker/dailySlackRoundup

1
2
3
4
5
6
7
8
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
export default async function() {
discordWebhook({
url: Deno.env.get("engDiscord"),
content: `<@&1081224342110736394> Daily updates thread ${new Date().toLocaleDateString()} ๐Ÿšจ`,
});
}

Twitter ๐• keyword Alerts

Custom notifications for when you, your company, or anything you care about is mentioned on Twitter.

1. Authentication

You'll need a Twitter Bearer Token. Follow these instructions to get one.

Unfortunately it costs $100 / month to have a Basic Twitter Developer account. If you subscribe to Val Town Pro, I can let you "borrow" my token. Just comment on this val and I'll hook you up.

2. Query

Change the query variable for what you want to get notified for.

You can use Twitter's search operators to customize your query, for some collection of keywords, filtering out others, and much more!

3. Notification

Below I'm sending these mentions to a private channel in our company Discord, but you can customize that to whatever you want, @std/email, Slack, Telegram, whatever.

Runs every 1 hrs
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
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
import { twitterSearch } from "https://esm.town/v/stevekrouse/twitterSearch";
const query = "\"val.town\" OR \"val town\" -_ValTown_";
export async function twitterAlert({ lastRunAt }: Interval) {
const results = await twitterSearch({
query,
start_time: lastRunAt,
bearerToken: Deno.env.get("twitter"),
});
if (!results.length) return;
// format results
let content = results
.map(({ author_name, author_username, text, id }) => `https://fxtwitter.com/${author_username}/status/${id}`)
.join("\n");
// notify
await discordWebhook({
url: Deno.env.get("mentionsDiscord"),
content,
});
}

Daily Standup Bot

Every weekday at 9am EDT send a message to our team's #engineering Discord channel to start a thread to remind us to do our standup.

Screenshot 2024-03-14 at 09.27.26.png

Slack version: @mikker/dailySlackRoundup

1
2
3
4
5
6
7
8
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
export default async function() {
discordWebhook({
url: Deno.env.get("engDiscord"),
content: `<@&1081224342110736394> Daily updates thread ${new Date().toLocaleDateString()} ๐Ÿšจ`,
});
}

image.png

You know how when you paste a URL in Twitter or Slack it shows you a nice preview? This val gives you that data.
Given a URL, this will return metadata about the website like title, description, imageURL, image as base64 etc.

Sample input - paste this in your URL bar

https://dvsj-GetWebsiteMetadata.web.val.run?targetURL=https://dvsj.in
https://dvsj-GetWebsiteMetadata.web.val.run?targetURL=<your-target-url-here>

Sample output:

{
   status: 200,
   url: "https://dvsj.in",
   title: "Dav-is-here โžœ",
   description: "Davis' not-so-secret stash",
   imgUrl: "https://www.dvsj.in/cover-picture.png",
   imgData: "data:image/png;base64,qwertyblahblah"
}

FAQ:
Why is imgData sent when imgUrl is already present?
Because you shouldn't hotlink images from 3rd parties. Store the base64 image on your server and use it in your app.
It's unfair to use their server bandwidth and could be a security issue for you if they change the content of the link later.

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
// Although you probably want this, you can take a peek at the implementation at https://www.val.town/v/dvsj/getOpengraphMetadata too.
import getOpengraphMetadata from "https://esm.town/v/dvsj/getOpengraphMetadata";
export default async function(req: Request): Promise<Response> {
// First extract query param from the URL
const url = new URL(req.url);
// People forget capitalization all the time. Let's go easy on them and check a few queryParam keys. :)
const targetUrlKeys = [
"targetURL",
"TargetURL",
"targetUrl",
"TargetUrl",
"Targeturl",
"targeturl",
"GIMME_THE_META_DAMMIT",
];
let targetURL = null;
for (let i = 0; i < targetUrlKeys.length; i++) {
targetURL = url.searchParams.get(targetUrlKeys[i]);
if (targetURL != null) {
break;
}
}
// URL isn't present. Oopsie!
if (targetURL == null || targetURL.trim() == "") {
return Response.json({
"error":
"targetURL is missing in query params. If you want to get the metadata for `https://dvsj.in`, call this function in this format: `https://fn-url?targetURL=https://dvsj.in`",
});
}
// Let's go!
return Response.json(await getOpengraphMetadata(targetURL));
}
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
import { fetchJSON } from "https://esm.town/v/stevekrouse/fetchJSON?v=41";
export const slackReplyToMessage = async (req: Request) => {
const body = await req.json();
// Verify the request is genuine
if (body.token !== Deno.env.get("slackVerificationToken")) {
return new Response(undefined, { status: 401 });
}
// Respond to the initial challenge (when events are enabled)
if (body.challenge) {
return Response.json({ challenge: body.challenge });
}
// Reply to app_mention events
if (body.event.type === "app_mention") {
// Note: `body.event` has information about the event
// like the sender and the message text
const result = await fetchJSON(
"https://slack.com/api/chat.postMessage",
{
headers: {
"Authorization": `Bearer ${Deno.env.get("slackToken")}`,
},
method: "POST",
body: JSON.stringify({
channel: body.event.channel,
thread_ts: body.event.ts,
text: "Hello, ~World~ from Val Town!",
}),
},
);
// Slack replies with information about the created message
console.log(result);
}
};

Daily Standup Bot

Every weekday at 9am EDT send a message to our team's #engineering Discord channel to start a thread to remind us to do our standup.

Screenshot 2024-03-14 at 09.27.26.png

Slack version: @mikker/dailySlackRoundup

1
2
3
4
5
6
7
8
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
export default async function() {
discordWebhook({
url: Deno.env.get("engDiscord"),
content: `<@&1081224342110736394> Daily updates thread ${new Date().toLocaleDateString()} ๐Ÿšจ`,
});
}

Twitter ๐• keyword Alerts

Custom notifications for when you, your company, or anything you care about is mentioned on Twitter.

1. Authentication

You'll need a Twitter Bearer Token. Follow these instructions to get one.

Unfortunately it costs $100 / month to have a Basic Twitter Developer account. If you subscribe to Val Town Pro, I can let you "borrow" my token. Just comment on this val and I'll hook you up.

2. Query

Change the query variable for what you want to get notified for.

You can use Twitter's search operators to customize your query, for some collection of keywords, filtering out others, and much more!

3. Notification

Below I'm sending these mentions to a private channel in our company Discord, but you can customize that to whatever you want, @std/email, Slack, Telegram, whatever.

Runs every 1 hrs
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
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
import { twitterSearch } from "https://esm.town/v/stevekrouse/twitterSearch";
const query = "\"val.town\" OR \"val town\" -_ValTown_";
export async function twitterAlert({ lastRunAt }: Interval) {
const results = await twitterSearch({
query,
start_time: lastRunAt,
bearerToken: Deno.env.get("twitter"),
});
if (!results.length) return;
// format results
let content = results
.map(({ author_name, author_username, text, id }) => `https://fxtwitter.com/${author_username}/status/${id}`)
.join("\n");
// notify
await discordWebhook({
url: Deno.env.get("mentionsDiscord"),
content,
});
}

Twitter ๐• keyword Alerts

Custom notifications for when you, your company, or anything you care about is mentioned on Twitter.

1. Authentication

You'll need a Twitter Bearer Token. Follow these instructions to get one.

Unfortunately it costs $100 / month to have a Basic Twitter Developer account. If you subscribe to Val Town Pro, I can let you "borrow" my token. Just comment on this val and I'll hook you up.

2. Query

Change the query variable for what you want to get notified for.

You can use Twitter's search operators to customize your query, for some collection of keywords, filtering out others, and much more!

3. Notification

Below I'm sending these mentions to a private channel in our company Discord, but you can customize that to whatever you want, @std/email, Slack, Telegram, whatever.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
import { twitterSearch } from "https://esm.town/v/stevekrouse/twitterSearch";
const query = "\"val.town\" OR \"val town\" -_ValTown_";
export async function twitterAlert({ lastRunAt }: Interval) {
const results = await twitterSearch({
query,
start_time: lastRunAt,
bearerToken: Deno.env.get("twitter"),
});
if (!results.length) return;
// format results
let content = results
.map(({ author_name, author_username, text, id }) => `https://fxtwitter.com/${author_username}/status/${id}`)
.join("\n");
// notify
await discordWebhook({
url: Deno.env.get("mentionsDiscord"),
content,
});
}
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
import { slackPost } from "https://esm.town/v/glommer/sendMsgToSlack";
import { sqlite } from "https://esm.town/v/std/sqlite";
import { sql } from "npm:drizzle-orm";
import { drizzle } from "npm:drizzle-orm/libsql";
import { integer, sqliteTable, text } from "npm:drizzle-orm/sqlite-core";
const db = drizzle(sqlite as any);
const threadsTbl = sqliteTable("threads", {
id: text("id").primaryKey(),
last_message_id: text("last_message_id"),
});
async function getKnownThreads() {
const result = await db.select().from(threadsTbl);
return result.reduce((r, { id, last_message_id }) => {
r[id] = parseInt(last_message_id ?? "0");
return r;
}, {});
}
async function getNewThreads() {
const resp = await fetch(`https://discord.com/api/guilds/${guild}/threads/active`, {
headers: {
"Authorization": `Bot ${token}`,
},
});
if (!resp.ok) {
const err = await resp.json();
console.log(`response failed: ${err}`);
return;
}
const j = await resp.json();
return j.threads;
}
export async function getThreadsActivity(
token: string,
guild: string,
slackToken: string,
slackChannel: string,
) {
const knownThreads = await getKnownThreads();
const threads = await getNewThreads();
const ops = [db.delete(threadsTbl)];
for (let i = 0; i < threads.length; i++) {
const t = threads[i];
const id = t.id;
const name = t.name;
// don't do parseInt here, need to be text, so we can save text back to sqlite
const last_message_id = t.last_message_id;
const parent_id = t.parent_id;
const lastKnown = knownThreads[id] ?? 0;
if (parseInt(last_message_id) > lastKnown) {
await slackPost(
slackToken,
slackChannel,
`New activity for "${name}: https://discord.com/channels/${guild}/${id}/${id}"`,
);
}
ops.push(db.insert(threadsTbl).values({ id, last_message_id }));
}
await db.batch(ops);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export const queryParams = (req: Request) => {
const searchParams = new URL(req.url).searchParams;
return Response.json({
"headers": Object.fromEntries(req.headers.entries()),
"url": req.url,
"method": req.method,
"mode": req.mode,
"cache": req.cache,
"credentials": req.credentials,
"body": req.body,
"destination": req.destination,
"integrity": req.integrity,
"referrer": req.referrer,
"redirect": req.redirect,
"searchParams": Object.fromEntries(searchParams.entries()),
"docs": "https://developer.mozilla.org/en-US/docs/Web/API/Request",
});
};

Sends a daily message to the team Slack channel. Similar to 37signals' What did you work on today?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { fetch } from "https://esm.town/v/std/fetch";
import { getDayName } from "https://esm.town/v/stevekrouse/getDayName?v=2";
import process from "node:process";
export const dailySlackRoundup = (async () => {
const res = await fetch(process.env.BRAINBOT_WEBHOOK_URL, {
method: "POST",
body: JSON.stringify({
"text":
`โœŒ๏ธ *Time for the ${getDayName()} round-up!* If you've worked today, what did you work on? Anything in your way?`,
}),
});
return res.statusText;
});