Public
HTTP (deprecated)
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Readme

Dub Shortlink Slackbot

We started using dub.co for shortlinks at Mux, so we made a quick Slackbot to make it easier!

Usage

  1. Fork this thing to your account
  2. Set up a Slack bot/app and have the webhooks point at your forked Val
  3. Add the following environment variables to Val.town:
    • DUB_API_KEY your Dub API key
    • DUB_WORKSPACE_ID your Dub workspace ID
    • SLACK_MUX_LINK_SIGNING_SECRET Signing secret for the app you created
  4. Update the LINK_DOMAIN variable to be the one you want to use (needs to be set up in Dub, of course)
  5. Use the command you created in your Slack app! For us, it looks like this:
    • /mux.link https://example.com neat-example This is a quick link for an example.
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// example slack command: /mux.link [destination-url] [short-slug] [comments]
import { hmac } from "https://deno.land/x/crypto@v0.10.1/hmac.ts";
// UPDATE THESE VARIABLES FOR YOURSELF
const LINK_DOMAIN = "mux.link";
// Make sure to set these environment variables
const token = Deno.env.get("DUB_API_KEY");
const dubWorkspaceId = Deno.env.get("DUB_WORKSPACE_ID");
const signingSecret = Deno.env.get("SLACK_MUX_LINK_SIGNING_SECRET");
const encoder = new TextEncoder();
function uint8ArrayToHexString(uint8Array: Uint8Array): string {
return Array.from(uint8Array)
.map(byte => byte.toString(16).padStart(2, "0"))
.join("");
}
function parseFormData(formDataString: string): Record<string, string> {
const params = new URLSearchParams(formDataString);
const formData: Record<string, string> = {};
for (const [key, value] of params.entries()) {
formData[key] = value;
}
return formData;
}
export default async function(req: Request): Promise<Response> {
if (req.method !== "POST") {
return Response.json({ message: "This val responds to POST requests." }, {
status: 400,
});
}
// Read the raw request body as a Uint8Array
const rawBody = new Uint8Array(await req.arrayBuffer());
// Convert Uint8Array to a string if needed (assuming utf-8 encoding)
const bodyText = new TextDecoder("utf-8").decode(rawBody);
const timestamp = req.headers.get("x-slack-request-timestamp");
const reqSignature = req.headers.get("x-slack-signature");
const sigBaseString = "v0:" + timestamp + ":" + bodyText;
const digest = hmac("sha256", encoder.encode(signingSecret), encoder.encode(sigBaseString));
const sig = "v0=" + uint8ArrayToHexString(digest);
if (reqSignature !== sig) {
return Response.json({ message: "Invalid Slack signature" }, { status: 401 });
}
const payload = parseFormData(bodyText);
const [destination, slug, comments] = payload.text.split(" ");
// If the destination is `list`, return a list of all the existing short links
if (destination === "list") {
const req = await fetch(`https://api.dub.co/links?workspaceId=${dubWorkspaceId}`, {
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
});
const body = await req.json();
return Response.json({
mrkdown: true,
text: `No prob, friend. Here are the last 100 links created.
${body.map(link => `- ${link.shortLink} -> ${link.url} (last clicked: ${link.lastClicked})`).join("\n")}`,
});
}
const body = {
url: destination,
domain: LINK_DOMAIN,
key: slug,
comments,
};
const options = {
method: "POST",
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
};
try {
const resp = await fetch(`https://api.dub.co/links?workspaceId=${dubWorkspaceId}`, options).then(
response => response.json(),
);
console.log(resp);
if (resp.error) {
return Response.json({ ok: false, error: resp.error });
} else {
return Response.json({ ok: true, url: resp.shortLink });
}
} catch (err) {
console.error(err);
return Response.json({ ok: false });
}
}
mux-dublinkmaker.web.val.run
June 26, 2024