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 });
}
}