1import { Octokit } from "@octokit/rest";
2import { IncomingWebhook } from "@slack/webhook";
3
4/**
5 * The maximum length of the `text` in a section block.
6 *
7 * [Slack Docs](https://api.slack.com/reference/block-kit/blocks#section)
8 */
9const SECTION_BLOCK_TEXT_LIMIT = 3000;
10
11async function main() {
12 const octokit = new Octokit({
13 auth: process.env["ISSUE_RESPONSE_GITHUB_TOKEN"],
14 });
15
16 if (!process.env["SLACK_ISSUE_RESPONSE_WEBHOOK_URL"]) {
17 throw new Error("SLACK_ISSUE_RESPONSE_WEBHOOK_URL is not set");
18 }
19
20 const webhook = new IncomingWebhook(
21 process.env["SLACK_ISSUE_RESPONSE_WEBHOOK_URL"],
22 );
23
24 const owner = "zed-industries";
25 const repo = "zed";
26 const staff = await octokit.paginate(octokit.rest.teams.listMembersInOrg, {
27 org: owner,
28 team_slug: "staff",
29 per_page: 100,
30 });
31 let staffHandles = staff.map((member) => member.login);
32 let commenterFilters = staffHandles.map((name) => `-commenter:${name}`);
33 let authorFilters = staffHandles.map((name) => `-author:${name}`);
34
35 const q = [
36 `repo:${owner}/${repo}`,
37 "is:issue",
38 "state:open",
39 "created:>=2025-02-01",
40 "sort:created-asc",
41 ...commenterFilters,
42 ...authorFilters,
43 ];
44
45 const response = await octokit.rest.search.issuesAndPullRequests({
46 q: q.join("+"),
47 per_page: 100,
48 });
49
50 let issues = response.data.items;
51 let issueLines = issues.map((issue, index) => {
52 const formattedDate = new Date(issue.created_at).toLocaleDateString(
53 "en-US",
54 {
55 year: "numeric",
56 month: "short",
57 day: "numeric",
58 },
59 );
60 const sanitizedTitle = issue.title
61 .replaceAll("&", "&")
62 .replaceAll("<", "<")
63 .replaceAll(">", ">");
64
65 return `${index + 1}. ${formattedDate}: <${issue.html_url}|${sanitizedTitle}>\n`;
66 });
67
68 const sections = [];
69 /** @type {string[]} */
70 let currentSection = [];
71 let currentSectionLength = 0;
72
73 for (const issueLine of issueLines) {
74 if (currentSectionLength + issueLine.length <= SECTION_BLOCK_TEXT_LIMIT) {
75 currentSection.push(issueLine);
76 currentSectionLength += issueLine.length;
77 } else {
78 sections.push(currentSection);
79 currentSection = [];
80 currentSectionLength = 0;
81 }
82 }
83
84 if (currentSection.length > 0) {
85 sections.push(currentSection);
86 }
87
88 const blocks = sections.map((section) => ({
89 type: "section",
90 text: {
91 type: "mrkdwn",
92 text: section.join("").trimEnd(),
93 },
94 }));
95
96 await webhook.send({ blocks });
97}
98
99main().catch((error) => {
100 console.error("An error occurred:", error);
101 process.exit(1);
102});