feat: add github command (#33)

This commit is contained in:
Jacob Nguyen
2023-04-29 18:16:28 -05:00
committed by GitHub
6 changed files with 438 additions and 8 deletions

View File

@@ -27,6 +27,7 @@
],
"license": "MIT",
"dependencies": {
"@octokit/rest": "^19.0.7",
"@sern/handler": "2.6.1",
"discord.js": "14.8.0",
"dotenv": "16.0.3",

216
src/commands/github.ts Normal file
View File

@@ -0,0 +1,216 @@
import { publish } from "#plugins";
import { CommandType, commandModule } from "@sern/handler";
import { ApplicationCommandOptionType } from "discord.js";
import { Timestamp } from "#utils";
import { Emojis } from "#constants";
import { useContainer } from "../index.js";
const prefix = (t: unknown) => (t ? "$" : "#");
export default commandModule({
type: CommandType.Slash,
description: "Get info about a PR or issue",
plugins: [publish({ dmPermission: false })],
options: [
{
name: "repo",
description: "The repo to get info from",
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
command: {
onEvent: [],
async execute(ctx) {
const [octokit] = useContainer("octokit");
const text = ctx.options.getFocused();
const org = await octokit.repos.listForOrg({ org: "sern-handler" });
if (!org) return ctx.respond([]);
const topRepos = org.data.sort(
(a, b) => (b.stargazers_count ?? 0) - (a.stargazers_count ?? 0)
);
const publicRepos = topRepos
.filter((r) => !r.private)
.map((repo) => ({ name: `sern/${repo.name}`, value: repo.name }));
if (!text.length) {
return ctx.respond(publicRepos.slice(0, 25)).catch(() => null);
}
return ctx.respond(
publicRepos
.filter((repo) =>
repo.name.toLowerCase().includes(text.toLowerCase())
)
.slice(0, 25)
).catch(() => null);
},
},
},
{
name: "number",
description: "The number of the PR or issue",
type: ApplicationCommandOptionType.Integer,
required: true,
autocomplete: true,
command: {
onEvent: [],
async execute(ctx) {
const [octokit] = useContainer("octokit");
const text = ctx.options.getFocused();
const repo = ctx.options.getString("repo");
if (!repo) return ctx.respond([]);
let search;
if (text.length) {
search = await octokit.search
.issuesAndPullRequests({
q: `repo:sern-handler/${repo} ${text} in:title`,
})
.catch(() => null);
}
if (!text.length) {
const issues = await octokit.issues
.listForRepo({
owner: "sern-handler",
repo,
state: "all",
per_page: 25,
})
.catch(() => null);
if (!issues) return ctx.respond([]);
const map = issues.data.map((issue) => ({
name: cutText(
`${prefix(issue.pull_request)}${issue.number} - ${issue.title}`
),
value: issue.number,
}));
return ctx.respond(map).catch(() => null);
}
return ctx.respond(
search?.data.items
.filter((i) => i.title.toLowerCase().includes(text.toLowerCase()))
.map((issue) => ({
name: cutText(
`${prefix(issue.pull_request)}${issue.number} - ${
issue.title
}`
),
value: issue.number,
}))
.slice(0, 25) ?? []
).catch(() => null);
},
},
},
{
name: "target",
description: "Who should I ping that should see this?",
type: ApplicationCommandOptionType.User,
required: false,
},
],
async execute(ctx, [, options]) {
const [octokit] = useContainer("octokit");
const repo = options.getString("repo", true);
const number = options.getInteger("number", true);
const target = options.getUser("target");
const issue = await octokit.issues
.get({
owner: "sern-handler",
repo,
issue_number: number,
})
.then((r) => r.data)
.catch(() => null);
if (!issue) {
return ctx.reply({
content: `I could not find [\`#${number} in sern/${repo}\`](https://github.com/sern-handler/${repo}/)`,
ephemeral: true,
});
}
const emoji = (i: typeof issue): string => {
if (i.pull_request) {
switch (i.state) {
case "open":
return i.draft ? Emojis.PRDraft : Emojis.PROpen;
case "closed":
return i.pull_request.merged_at ? Emojis.PRMerged : Emojis.PRClosed;
}
}
switch (i.state) {
case "open":
return Emojis.IssueOpen;
case "closed":
return i.state_reason === "completed"
? Emojis.IssueClosed
: i.state_reason === "not_planned"
? Emojis.IssueNotPlanned
: "";
}
return "";
};
const suffix = (i: typeof issue): string => {
let str = "";
let time = "";
if (i.pull_request) {
switch (i.state) {
case "open":
str = i.draft ? "drafted" : "opened";
time = i.created_at;
break;
case "closed":
str = i.pull_request.merged_at ? "merged" : "closed";
time = i.pull_request.merged_at ?? i.closed_at ?? "";
break;
}
} else {
switch (i.state) {
case "open":
str = "opened";
time = i.created_at;
break;
case "closed":
str = i.state_reason === "completed" ? "completed" : "closed";
time = i.closed_at ?? "";
break;
}
}
return `${str} ${new Timestamp(
new Date(time).getTime()
).getRelativeTime()}`;
};
let reply = target
? `*GitHub ${
issue.pull_request ? "Pull Request" : "Issue"
} data for ${target}*\n`
: "";
reply += `${emoji(issue)} [\`${prefix(
issue.pull_request
)}${number} sern/${repo}\`](<${issue.html_url}>)\n___${
issue.title
}___ by [*${issue.user!.login}*](<${issue.user?.html_url}>) ${suffix(
issue
)}`;
return ctx.reply(reply);
},
});
function cutText(text: string) {
return text.length > 99 ? text.slice(0, 97) + "..." : text;
}

View File

@@ -35,6 +35,7 @@ export async function cp(client: Client) {
const resp = await fetch(link).catch(() => null);
if (!resp) return null;
const dataArray = (await resp.json()) as Data[];
// TODO: use octokit instead of fetch
for (const data of dataArray) {
const name = data.name.replace(".ts", "");
data.rawData = await (await fetch(data.download_url))

View File

@@ -4,3 +4,12 @@ export const Ropox = "756393473430519849";
export const Mina = "504698587221852172";
export const ownerIDs = [Evo, Seren, Ropox];
export const forumID = "1019807803935825922";
export const enum Emojis {
PROpen = "<:pr_open:1101708598570143754>",
PRClosed = "<:pr_closed:1101708712072183819>",
PRMerged = "<:pr_merged:1101708800848830525>",
PRDraft = "<:pr_draft:1101708908747309126>",
IssueOpen = "<:issue_open:1101709315955511346>",
IssueClosed = "<:issue_closed:1101716515771920424>",
IssueNotPlanned = "<:issue_notplanned:1101719419434045540>"
}

View File

@@ -1,7 +1,8 @@
import { Client, GatewayIntentBits, Partials } from "discord.js";
import { Dependencies, Sern, single, Singleton } from "@sern/handler";
import "dotenv/config";
import { randomStatus, SernLogger, /*CommandSyncer*/ } from "#utils";
import { randomStatus, SernLogger /*CommandSyncer*/ } from "#utils";
import { Octokit } from "@octokit/rest";
const client = new Client({
intents: [
@@ -27,17 +28,21 @@ const client = new Client({
export interface BotDependencies extends Dependencies {
"@sern/client": Singleton<Client>;
"@sern/logger": Singleton<SernLogger>;
"octokit": Singleton<Octokit>;
}
export const useContainer = Sern.makeDependencies<BotDependencies>({
build: (root) =>
root
.add({ "@sern/client": single(() => client) })
.upsert({ "@sern/logger": single(() => new SernLogger("info")) })
.add({ process: single(() => process) })
// .add(ctx =>
// ({'sync' : single(() => new CommandSyncer(ctx['@sern/logger'], ctx['@sern/client'], ["941002690211766332"]))}
// ))
.add({ "@sern/client": single(() => client) })
.upsert({ "@sern/logger": single(() => new SernLogger("info")) })
.add({ process: single(() => process) })
.add({
octokit: single(() => new Octokit({ auth: process.env.GITHUB_TOKEN })),
}),
// .add(ctx =>
// ({'sync' : single(() => new CommandSyncer(ctx['@sern/logger'], ctx['@sern/client'], ["941002690211766332"]))}
// ))
});
Sern.init({
defaultPrefix: "sern",

200
yarn.lock
View File

@@ -145,6 +145,137 @@ __metadata:
languageName: node
linkType: hard
"@octokit/auth-token@npm:^3.0.0":
version: 3.0.3
resolution: "@octokit/auth-token@npm:3.0.3"
dependencies:
"@octokit/types": ^9.0.0
checksum: 9b3f569cec1b7e0aa88ab6da68aed4b49b6652261bd957257541fabaf6a4d4ed99f908153cc3dd2fe15b8b0ccaff8caaafaa50bb1a4de3925b0954a47cca1900
languageName: node
linkType: hard
"@octokit/core@npm:^4.1.0":
version: 4.2.0
resolution: "@octokit/core@npm:4.2.0"
dependencies:
"@octokit/auth-token": ^3.0.0
"@octokit/graphql": ^5.0.0
"@octokit/request": ^6.0.0
"@octokit/request-error": ^3.0.0
"@octokit/types": ^9.0.0
before-after-hook: ^2.2.0
universal-user-agent: ^6.0.0
checksum: 5ac56e7f14b42a5da8d3075a2ae41483521a78bee061a01f4a81d8c0ecd6a684b2e945d66baba0cd1fdf264639deedc3a96d0f32c4d2fc39b49ca10f52f4de39
languageName: node
linkType: hard
"@octokit/endpoint@npm:^7.0.0":
version: 7.0.5
resolution: "@octokit/endpoint@npm:7.0.5"
dependencies:
"@octokit/types": ^9.0.0
is-plain-object: ^5.0.0
universal-user-agent: ^6.0.0
checksum: 81c9e9eabf50e48940cceff7c4d7fbc9327190296507cfe8a199ea00cd492caf8f18a841caf4e3619828924b481996eb16091826db6b5a649bee44c8718ecaa9
languageName: node
linkType: hard
"@octokit/graphql@npm:^5.0.0":
version: 5.0.5
resolution: "@octokit/graphql@npm:5.0.5"
dependencies:
"@octokit/request": ^6.0.0
"@octokit/types": ^9.0.0
universal-user-agent: ^6.0.0
checksum: eb2d1a6305a3d1f55ff0ce92fb88b677f0bb789757152d58a79ef61171fb65ecf6fe18d6c27e236c0cee6a0c2600c2cb8370f5ac7184f8e9361c085aa4555bb1
languageName: node
linkType: hard
"@octokit/openapi-types@npm:^17.0.0":
version: 17.0.0
resolution: "@octokit/openapi-types@npm:17.0.0"
checksum: 64d8500891b18e9866f1e662a65041a37381c3f9e6e5ce070cd26416a85f1b93340feb068ec2e8a5d0c0f4196df02ee2de1e73c87234e3d17edf33e7483d9b69
languageName: node
linkType: hard
"@octokit/plugin-paginate-rest@npm:^6.0.0":
version: 6.0.0
resolution: "@octokit/plugin-paginate-rest@npm:6.0.0"
dependencies:
"@octokit/types": ^9.0.0
peerDependencies:
"@octokit/core": ">=4"
checksum: 4ad89568d883373898b733837cada7d849d51eef32157c11d4a81cef5ce8e509720d79b46918cada3c132f9b29847e383f17b7cd5c39ede7c93cdcd2f850b47f
languageName: node
linkType: hard
"@octokit/plugin-request-log@npm:^1.0.4":
version: 1.0.4
resolution: "@octokit/plugin-request-log@npm:1.0.4"
peerDependencies:
"@octokit/core": ">=3"
checksum: 2086db00056aee0f8ebd79797b5b57149ae1014e757ea08985b71eec8c3d85dbb54533f4fd34b6b9ecaa760904ae6a7536be27d71e50a3782ab47809094bfc0c
languageName: node
linkType: hard
"@octokit/plugin-rest-endpoint-methods@npm:^7.0.0":
version: 7.0.1
resolution: "@octokit/plugin-rest-endpoint-methods@npm:7.0.1"
dependencies:
"@octokit/types": ^9.0.0
deprecation: ^2.3.1
peerDependencies:
"@octokit/core": ">=3"
checksum: cdb8734ec960f75acc2405284920c58efac9a71b1c3b2a71662b9100ffbc22dac597150acff017a93459c57e9a492d9e1c27872b068387dbb90597de75065fcf
languageName: node
linkType: hard
"@octokit/request-error@npm:^3.0.0":
version: 3.0.3
resolution: "@octokit/request-error@npm:3.0.3"
dependencies:
"@octokit/types": ^9.0.0
deprecation: ^2.0.0
once: ^1.4.0
checksum: 5db0b514732686b627e6ed9ef1ccdbc10501f1b271a9b31f784783f01beee70083d7edcfeb35fbd7e569fa31fdd6762b1ff6b46101700d2d97e7e48e749520d0
languageName: node
linkType: hard
"@octokit/request@npm:^6.0.0":
version: 6.2.3
resolution: "@octokit/request@npm:6.2.3"
dependencies:
"@octokit/endpoint": ^7.0.0
"@octokit/request-error": ^3.0.0
"@octokit/types": ^9.0.0
is-plain-object: ^5.0.0
node-fetch: ^2.6.7
universal-user-agent: ^6.0.0
checksum: fef4097be8375d20bb0b3276d8a3adf866ec628f2b0664d334f3c29b92157da847899497abdc7a5be540053819b55564990543175ad48f04e9e6f25f0395d4d3
languageName: node
linkType: hard
"@octokit/rest@npm:^19.0.7":
version: 19.0.7
resolution: "@octokit/rest@npm:19.0.7"
dependencies:
"@octokit/core": ^4.1.0
"@octokit/plugin-paginate-rest": ^6.0.0
"@octokit/plugin-request-log": ^1.0.4
"@octokit/plugin-rest-endpoint-methods": ^7.0.0
checksum: 1f970c4de2cf3d1691d3cf5dd4bfa5ac205629e76417b5c51561e1beb5b4a7e6c65ba647f368727e67e5243418e06ca9cdafd9e733173e1529385d4f4d053d3d
languageName: node
linkType: hard
"@octokit/types@npm:^9.0.0":
version: 9.1.4
resolution: "@octokit/types@npm:9.1.4"
dependencies:
"@octokit/openapi-types": ^17.0.0
checksum: 354c555bbf6876858774b13508666daf2882ea1e958361f109dc9c4bad3753704f62a84bc9457661c8a3a0fba25eadf2f78fa24d6eaf95c852e3159b379ff8b8
languageName: node
linkType: hard
"@sapphire/async-queue@npm:^1.5.0":
version: 1.5.0
resolution: "@sapphire/async-queue@npm:1.5.0"
@@ -316,6 +447,13 @@ __metadata:
languageName: node
linkType: hard
"before-after-hook@npm:^2.2.0":
version: 2.2.3
resolution: "before-after-hook@npm:2.2.3"
checksum: a1a2430976d9bdab4cd89cb50d27fa86b19e2b41812bf1315923b0cba03371ebca99449809226425dd3bcef20e010db61abdaff549278e111d6480034bebae87
languageName: node
linkType: hard
"binary-extensions@npm:^2.0.0":
version: 2.2.0
resolution: "binary-extensions@npm:2.2.0"
@@ -557,6 +695,13 @@ __metadata:
languageName: node
linkType: hard
"deprecation@npm:^2.0.0, deprecation@npm:^2.3.1":
version: 2.3.1
resolution: "deprecation@npm:2.3.1"
checksum: f56a05e182c2c195071385455956b0c4106fe14e36245b00c689ceef8e8ab639235176a96977ba7c74afb173317fac2e0ec6ec7a1c6d1e6eaa401c586c714132
languageName: node
linkType: hard
"dir-glob@npm:^3.0.1":
version: 3.0.1
resolution: "dir-glob@npm:3.0.1"
@@ -1245,6 +1390,13 @@ __metadata:
languageName: node
linkType: hard
"is-plain-object@npm:^5.0.0":
version: 5.0.0
resolution: "is-plain-object@npm:5.0.0"
checksum: e32d27061eef62c0847d303125440a38660517e586f2f3db7c9d179ae5b6674ab0f469d519b2e25c147a1a3bc87156d0d5f4d8821e0ce4a9ee7fe1fcf11ce45c
languageName: node
linkType: hard
"is-stream@npm:^2.0.0":
version: 2.0.1
resolution: "is-stream@npm:2.0.1"
@@ -1551,6 +1703,20 @@ __metadata:
languageName: node
linkType: hard
"node-fetch@npm:^2.6.7":
version: 2.6.9
resolution: "node-fetch@npm:2.6.9"
dependencies:
whatwg-url: ^5.0.0
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
checksum: acb04f9ce7224965b2b59e71b33c639794d8991efd73855b0b250921382b38331ffc9d61bce502571f6cc6e11a8905ca9b1b6d4aeb586ab093e2756a1fd190d0
languageName: node
linkType: hard
"node-gyp@npm:latest":
version: 9.3.0
resolution: "node-gyp@npm:9.3.0"
@@ -1617,7 +1783,7 @@ __metadata:
languageName: node
linkType: hard
"once@npm:^1.3.0":
"once@npm:^1.3.0, once@npm:^1.4.0":
version: 1.4.0
resolution: "once@npm:1.4.0"
dependencies:
@@ -1873,6 +2039,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "sern-community@workspace:."
dependencies:
"@octokit/rest": ^19.0.7
"@sern/handler": 2.6.1
"@types/node": 18.11.18
"@types/string-similarity": 4.0.0
@@ -2130,6 +2297,13 @@ __metadata:
languageName: node
linkType: hard
"tr46@npm:~0.0.3":
version: 0.0.3
resolution: "tr46@npm:0.0.3"
checksum: 726321c5eaf41b5002e17ffbd1fb7245999a073e8979085dacd47c4b4e8068ff5777142fc6726d6ca1fd2ff16921b48788b87225cbc57c72636f6efa8efbffe3
languageName: node
linkType: hard
"tree-kill@npm:^1.2.2":
version: 1.2.2
resolution: "tree-kill@npm:1.2.2"
@@ -2262,6 +2436,13 @@ __metadata:
languageName: node
linkType: hard
"universal-user-agent@npm:^6.0.0":
version: 6.0.0
resolution: "universal-user-agent@npm:6.0.0"
checksum: 5092bbc80dd0d583cef0b62c17df0043193b74f425112ea6c1f69bc5eda21eeec7a08d8c4f793a277eb2202ffe9b44bec852fa3faff971234cd209874d1b79ef
languageName: node
linkType: hard
"util-deprecate@npm:^1.0.1":
version: 1.0.2
resolution: "util-deprecate@npm:1.0.2"
@@ -2276,6 +2457,13 @@ __metadata:
languageName: node
linkType: hard
"webidl-conversions@npm:^3.0.0":
version: 3.0.1
resolution: "webidl-conversions@npm:3.0.1"
checksum: c92a0a6ab95314bde9c32e1d0a6dfac83b578f8fa5f21e675bc2706ed6981bc26b7eb7e6a1fab158e5ce4adf9caa4a0aee49a52505d4d13c7be545f15021b17c
languageName: node
linkType: hard
"webidl-conversions@npm:^4.0.2":
version: 4.0.2
resolution: "webidl-conversions@npm:4.0.2"
@@ -2283,6 +2471,16 @@ __metadata:
languageName: node
linkType: hard
"whatwg-url@npm:^5.0.0":
version: 5.0.0
resolution: "whatwg-url@npm:5.0.0"
dependencies:
tr46: ~0.0.3
webidl-conversions: ^3.0.0
checksum: b8daed4ad3356cc4899048a15b2c143a9aed0dfae1f611ebd55073310c7b910f522ad75d727346ad64203d7e6c79ef25eafd465f4d12775ca44b90fa82ed9e2c
languageName: node
linkType: hard
"whatwg-url@npm:^7.0.0":
version: 7.1.0
resolution: "whatwg-url@npm:7.1.0"