summaryrefslogtreecommitdiffstats
path: root/src/index.ts
blob: 938b5f0e244df5b740ca3ecee84aa4116bbcb734 (plain)
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { addTask, getTask, numOfTasksInQueue } from "./tasks";
import { runDownloadInstagramTask } from "./instagram_task";
import { runWithRetries, wait } from "./utils";
import { AppOptions, App as SlackApp } from "@slack/bolt";
import fs from "fs";
import dotenv from "dotenv";
dotenv.config();

const QUEUE_MAX_LENGTH = process.env.QUEUE_MAX_LENGTH
  ? Number(process.env.QUEUE_MAX_LENGTH)
  : 3;

const slackAppConfig: AppOptions = {
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  socketMode: true,
  appToken: process.env.SLACK_APP_TOKEN,
  clientId: process.env.SLACK_CLIENT_ID,
  clientSecret: process.env.SLACK_CLIENT_SECRET,
};

const app = new SlackApp(slackAppConfig);

app.command("/veeti", async ({ command, ack, respond, client }) => {
  await ack();

  if (numOfTasksInQueue() >= QUEUE_MAX_LENGTH) {
    void respond({
      text: "Too many tasks in queue, please try again later.",
      response_type: "ephemeral",
    });
    return;
  }

  console.log("Received command: /veeti", command.text);
  const url = command.text.trim();

  if (!url.includes("instagram.com")) {
    void respond({
      text: "Only instagram.com URLs are supported at the moment",
      response_type: "ephemeral",
    });
    return;
  }

  // send a message only visible to the user who sent the command
  void respond({
    text: "Sharing instagram video, this can take a minute...",
    thread_ts: command.ts,
    response_type: "ephemeral",
  });

  const respondWithFile = async (filePath: string) => {
    try {
      console.log("Uploading file", filePath);
      const fileContent = fs.readFileSync(filePath);
      const extension = filePath.split(".").pop();
      const fileName = `veeti.${extension}`;
      console.log("Sending message to channel", command.channel_id);
      await client.filesUploadV2({
        channel_id: command.channel_id,
        initial_comment: `By: @${command.user_name}`,
        file: fileContent,
        filename: fileName,
      });
      console.log("Success");
    } catch (e) {
      console.error("Failed to upload file", e);
      void respond({
        text:
          "Failed to share file, please try again later. Error: " + String(e),
        response_type: "ephemeral",
      });
    }
  };

  const respondTaskFailed = async (err: unknown) => {
    console.error("Task failed", err);
    void respond({
      text:
        "Failed to download video, please try again later. Error: " +
        String(err),
      response_type: "ephemeral",
    });
  };

  addTask({
    type: "download_instagram",
    url,
    respondWithFile,
    respondTaskFailed,
  });
});

const taskLoop = async () => {
  const MAX_RETRIES = 5;
  // Main loop
  while (true) {
    const task = getTask();
    if (task) {
      try {
        await runWithRetries(async () => {
          if (task.type === "download_instagram") {
            await runDownloadInstagramTask(task);
          } else {
            console.log("Unknown task type, skipping", task);
          }
        }, MAX_RETRIES);
      } catch (e) {
        void task.respondTaskFailed(e);
        console.error("Task failed after max retries, skipping task.", task, e);
      }
    } else {
      await wait(1000);
    }
  }
};

// run Puppeteer tasks in background
void taskLoop();

// start listening for Slack events
app.start();