From 1b7f83f33583be5fd580eecd2cfcfa07c7cd4dc2 Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Wed, 27 Aug 2025 17:58:28 -0700 Subject: [PATCH] feat: auto-assign issues and prs to naomi --- package.json | 1 + pnpm-lock.yaml | 305 ++++++++++++++++++ prod.env | 5 +- src/index.ts | 17 +- src/interfaces/amari.ts | 2 + src/interfaces/github.ts | 508 +++++++++++++++++++++++++++++- src/modules/processGitHubEvent.ts | 32 +- src/server/serve.ts | 13 +- 8 files changed, 874 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 0b34bf7..a01b23c 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "discord.js": "14.22.0", "fastify": "5.5.0", "node-schedule": "2.1.1", + "octokit": "5.0.3", "rss-parser": "3.13.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27fc5f8..fe89fa1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: node-schedule: specifier: 2.1.1 version: 2.1.1 + octokit: + specifier: 5.0.3 + version: 5.0.3 rss-parser: specifier: 3.13.0 version: 3.13.0 @@ -374,6 +377,113 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@octokit/app@16.1.0': + resolution: {integrity: sha512-OdKHnm0CYLk8Setr47CATT4YnRTvWkpTYvE+B/l2B0mjszlfOIit3wqPHVslD2jfc1bD4UbO7Mzh6gjCuMZKsA==} + engines: {node: '>= 20'} + + '@octokit/auth-app@8.1.0': + resolution: {integrity: sha512-6bWhyvLXqCSfHiqlwzn9pScLZ+Qnvh/681GR/UEEPCMIVwfpRDBw0cCzy3/t2Dq8B7W2X/8pBgmw6MOiyE0DXQ==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-app@9.0.1': + resolution: {integrity: sha512-TthWzYxuHKLAbmxdFZwFlmwVyvynpyPmjwc+2/cI3cvbT7mHtsAW9b1LvQaNnAuWL+pFnqtxdmrU8QpF633i1g==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-device@8.0.1': + resolution: {integrity: sha512-TOqId/+am5yk9zor0RGibmlqn4V0h8vzjxlw/wYr3qzkQxl8aBPur384D1EyHtqvfz0syeXji4OUvKkHvxk/Gw==} + engines: {node: '>= 20'} + + '@octokit/auth-oauth-user@6.0.0': + resolution: {integrity: sha512-GV9IW134PHsLhtUad21WIeP9mlJ+QNpFd6V9vuPWmaiN25HEJeEQUcS4y5oRuqCm9iWDLtfIs+9K8uczBXKr6A==} + engines: {node: '>= 20'} + + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/auth-unauthenticated@7.0.1': + resolution: {integrity: sha512-qVq1vdjLLZdE8kH2vDycNNjuJRCD1q2oet1nA/GXWaYlpDxlR7rdVhX/K/oszXslXiQIiqrQf+rdhDlA99JdTQ==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.3': + resolution: {integrity: sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.0': + resolution: {integrity: sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.1': + resolution: {integrity: sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==} + engines: {node: '>= 20'} + + '@octokit/oauth-app@8.0.1': + resolution: {integrity: sha512-QnhMYEQpnYbEPn9cae+wXL2LuPMFglmfeuDJXXsyxIXdoORwkLK8y0cHhd/5du9MbO/zdG/BXixzB7EEwU63eQ==} + engines: {node: '>= 20'} + + '@octokit/oauth-authorization-url@8.0.0': + resolution: {integrity: sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==} + engines: {node: '>= 20'} + + '@octokit/oauth-methods@6.0.0': + resolution: {integrity: sha512-Q8nFIagNLIZgM2odAraelMcDssapc+lF+y3OlcIPxyAU+knefO8KmozGqfnma1xegRDP4z5M73ABsamn72bOcA==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@25.1.0': + resolution: {integrity: sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==} + + '@octokit/openapi-webhooks-types@12.0.3': + resolution: {integrity: sha512-90MF5LVHjBedwoHyJsgmaFhEN1uzXyBDRLEBe7jlTYx/fEhPAk3P3DAJsfZwC54m8hAIryosJOL+UuZHB3K3yA==} + + '@octokit/plugin-paginate-graphql@6.0.0': + resolution: {integrity: sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-paginate-rest@13.1.1': + resolution: {integrity: sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@16.0.0': + resolution: {integrity: sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-retry@8.0.1': + resolution: {integrity: sha512-KUoYR77BjF5O3zcwDQHRRZsUvJwepobeqiSSdCJ8lWt27FZExzb0GgVxrhhfuyF6z2B2zpO0hN5pteni1sqWiw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=7' + + '@octokit/plugin-throttling@11.0.1': + resolution: {integrity: sha512-S+EVhy52D/272L7up58dr3FNSMXWuNZolkL4zMJBNIfIxyZuUcczsQAU4b5w6dewJXnKYVgSHSV5wxitMSW1kw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': ^7.0.0 + + '@octokit/request-error@7.0.0': + resolution: {integrity: sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.3': + resolution: {integrity: sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==} + engines: {node: '>= 20'} + + '@octokit/types@14.1.0': + resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} + + '@octokit/webhooks-methods@6.0.0': + resolution: {integrity: sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==} + engines: {node: '>= 20'} + + '@octokit/webhooks@14.1.3': + resolution: {integrity: sha512-gcK4FNaROM9NjA0mvyfXl0KPusk7a1BeA8ITlYEZVQCXF5gcETTd4yhAU0Kjzd8mXwYHppzJBWgdBVpIR9wUcQ==} + engines: {node: '>= 20'} + '@pkgr/core@0.1.2': resolution: {integrity: sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -499,6 +609,9 @@ packages: peerDependencies: eslint: '>=8.40.0' + '@types/aws-lambda@8.10.152': + resolution: {integrity: sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw==} + '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -784,6 +897,12 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1128,6 +1247,9 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} + fast-content-type-parse@3.0.0: + resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -1619,6 +1741,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + octokit@5.0.3: + resolution: {integrity: sha512-+bwYsAIRmYv30NTmBysPIlgH23ekVDriB07oRxlPIAH5PI0yTMSxg5i5Xy0OetcnZw+nk/caD4szD7a9YZ3QyQ==} + engines: {node: '>= 20'} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -2103,6 +2229,12 @@ packages: resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} engines: {node: '>=18.17'} + universal-github-app-jwt@2.2.2: + resolution: {integrity: sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==} + + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -2538,6 +2670,153 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@octokit/app@16.1.0': + dependencies: + '@octokit/auth-app': 8.1.0 + '@octokit/auth-unauthenticated': 7.0.1 + '@octokit/core': 7.0.3 + '@octokit/oauth-app': 8.0.1 + '@octokit/plugin-paginate-rest': 13.1.1(@octokit/core@7.0.3) + '@octokit/types': 14.1.0 + '@octokit/webhooks': 14.1.3 + + '@octokit/auth-app@8.1.0': + dependencies: + '@octokit/auth-oauth-app': 9.0.1 + '@octokit/auth-oauth-user': 6.0.0 + '@octokit/request': 10.0.3 + '@octokit/request-error': 7.0.0 + '@octokit/types': 14.1.0 + toad-cache: 3.7.0 + universal-github-app-jwt: 2.2.2 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-app@9.0.1': + dependencies: + '@octokit/auth-oauth-device': 8.0.1 + '@octokit/auth-oauth-user': 6.0.0 + '@octokit/request': 10.0.3 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-device@8.0.1': + dependencies: + '@octokit/oauth-methods': 6.0.0 + '@octokit/request': 10.0.3 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-oauth-user@6.0.0': + dependencies: + '@octokit/auth-oauth-device': 8.0.1 + '@octokit/oauth-methods': 6.0.0 + '@octokit/request': 10.0.3 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/auth-token@6.0.0': {} + + '@octokit/auth-unauthenticated@7.0.1': + dependencies: + '@octokit/request-error': 7.0.0 + '@octokit/types': 14.1.0 + + '@octokit/core@7.0.3': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.1 + '@octokit/request': 10.0.3 + '@octokit/request-error': 7.0.0 + '@octokit/types': 14.1.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@11.0.0': + dependencies: + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@9.0.1': + dependencies: + '@octokit/request': 10.0.3 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/oauth-app@8.0.1': + dependencies: + '@octokit/auth-oauth-app': 9.0.1 + '@octokit/auth-oauth-user': 6.0.0 + '@octokit/auth-unauthenticated': 7.0.1 + '@octokit/core': 7.0.3 + '@octokit/oauth-authorization-url': 8.0.0 + '@octokit/oauth-methods': 6.0.0 + '@types/aws-lambda': 8.10.152 + universal-user-agent: 7.0.3 + + '@octokit/oauth-authorization-url@8.0.0': {} + + '@octokit/oauth-methods@6.0.0': + dependencies: + '@octokit/oauth-authorization-url': 8.0.0 + '@octokit/request': 10.0.3 + '@octokit/request-error': 7.0.0 + '@octokit/types': 14.1.0 + + '@octokit/openapi-types@25.1.0': {} + + '@octokit/openapi-webhooks-types@12.0.3': {} + + '@octokit/plugin-paginate-graphql@6.0.0(@octokit/core@7.0.3)': + dependencies: + '@octokit/core': 7.0.3 + + '@octokit/plugin-paginate-rest@13.1.1(@octokit/core@7.0.3)': + dependencies: + '@octokit/core': 7.0.3 + '@octokit/types': 14.1.0 + + '@octokit/plugin-rest-endpoint-methods@16.0.0(@octokit/core@7.0.3)': + dependencies: + '@octokit/core': 7.0.3 + '@octokit/types': 14.1.0 + + '@octokit/plugin-retry@8.0.1(@octokit/core@7.0.3)': + dependencies: + '@octokit/core': 7.0.3 + '@octokit/request-error': 7.0.0 + '@octokit/types': 14.1.0 + bottleneck: 2.19.5 + + '@octokit/plugin-throttling@11.0.1(@octokit/core@7.0.3)': + dependencies: + '@octokit/core': 7.0.3 + '@octokit/types': 14.1.0 + bottleneck: 2.19.5 + + '@octokit/request-error@7.0.0': + dependencies: + '@octokit/types': 14.1.0 + + '@octokit/request@10.0.3': + dependencies: + '@octokit/endpoint': 11.0.0 + '@octokit/request-error': 7.0.0 + '@octokit/types': 14.1.0 + fast-content-type-parse: 3.0.0 + universal-user-agent: 7.0.3 + + '@octokit/types@14.1.0': + dependencies: + '@octokit/openapi-types': 25.1.0 + + '@octokit/webhooks-methods@6.0.0': {} + + '@octokit/webhooks@14.1.3': + dependencies: + '@octokit/openapi-webhooks-types': 12.0.3 + '@octokit/request-error': 7.0.0 + '@octokit/webhooks-methods': 6.0.0 + '@pkgr/core@0.1.2': {} '@rollup/rollup-android-arm-eabi@4.46.3': @@ -2623,6 +2902,8 @@ snapshots: - supports-color - typescript + '@types/aws-lambda@8.10.152': {} + '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 @@ -2994,6 +3275,10 @@ snapshots: balanced-match@1.0.2: {} + before-after-hook@4.0.0: {} + + bottleneck@2.19.5: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -3520,6 +3805,8 @@ snapshots: expect-type@1.2.2: {} + fast-content-type-parse@3.0.0: {} + fast-decode-uri-component@1.0.1: {} fast-deep-equal@3.1.3: {} @@ -4030,6 +4317,20 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + octokit@5.0.3: + dependencies: + '@octokit/app': 16.1.0 + '@octokit/core': 7.0.3 + '@octokit/oauth-app': 8.0.1 + '@octokit/plugin-paginate-graphql': 6.0.0(@octokit/core@7.0.3) + '@octokit/plugin-paginate-rest': 13.1.1(@octokit/core@7.0.3) + '@octokit/plugin-rest-endpoint-methods': 16.0.0(@octokit/core@7.0.3) + '@octokit/plugin-retry': 8.0.1(@octokit/core@7.0.3) + '@octokit/plugin-throttling': 11.0.1(@octokit/core@7.0.3) + '@octokit/request-error': 7.0.0 + '@octokit/types': 14.1.0 + '@octokit/webhooks': 14.1.3 + on-exit-leak-free@2.1.2: {} optionator@0.9.4: @@ -4563,6 +4864,10 @@ snapshots: undici@6.21.3: {} + universal-github-app-jwt@2.2.2: {} + + universal-user-agent@7.0.3: {} + update-browserslist-db@1.1.3(browserslist@4.25.3): dependencies: browserslist: 4.25.3 diff --git a/prod.env b/prod.env index 1db584d..4140d91 100644 --- a/prod.env +++ b/prod.env @@ -1,2 +1,5 @@ LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth" -BOT_TOKEN="op://Environment Variables - Naomi/Amari/bot token" \ No newline at end of file +BOT_TOKEN="op://Environment Variables - Naomi/Amari/bot token" +GH_CLIENT_ID="op://Environment Variables - Naomi/Amari/gh client id" +GH_CLIENT_SECRET="op://Environment Variables - Naomi/Amari/gh client secret" +GH_PRIVATE_KEY="op://Environment Variables - Naomi/Amari/gh private key" \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 16fd5db..1faa3c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { Client, GatewayIntentBits, Events, Partials } from "discord.js"; import { scheduleJob } from "node-schedule"; +import { App } from "octokit"; import { handleMessageCreate } from "./events/handleMessageCreate.js"; import { cacheData } from "./modules/cacheData.js"; import { @@ -18,6 +19,19 @@ import { instantiateServer } from "./server/serve.js"; import { logger } from "./utils/logger.js"; import type { Amari } from "./interfaces/amari.js"; +if (process.env.GH_CLIENT_ID === undefined + || process.env.GH_PRIVATE_KEY === undefined) { + throw new Error("Cannot initialise GitHub!"); +} + +const githubApp = new App({ + appId: process.env.GH_CLIENT_ID, + privateKey: process.env.GH_PRIVATE_KEY.replaceAll("\\n", "\n"), +}); +const octokit = await githubApp.getInstallationOctokit(83_119_105); +const { data } = await octokit.rest.apps.getAuthenticated(); +await logger.log("debug", `Authenticated to GitHub as ${data?.name ?? "unknown"}`); + const amari: Amari = { discord: new Client({ intents: [ GatewayIntentBits.Guilds, @@ -27,6 +41,7 @@ const amari: Amari = { GatewayIntentBits.DirectMessages, ], partials: [ Partials.Channel ] }), + github: octokit, lastRssItems: { freeCodeCamp: null, hackerNews: null, @@ -66,4 +81,4 @@ amari.discord.on(Events.UserUpdate, (_oldUser, updatedUser) => { }); await amari.discord.login(process.env.BOT_TOKEN); -instantiateServer(); +instantiateServer(amari); diff --git a/src/interfaces/amari.ts b/src/interfaces/amari.ts index 6fd727f..fa071c3 100644 --- a/src/interfaces/amari.ts +++ b/src/interfaces/amari.ts @@ -5,9 +5,11 @@ */ import type { Client } from "discord.js"; +import type { App } from "octokit"; export interface Amari { discord: Client; + github: App["octokit"]; lastRssItems: { freeCodeCamp: string | null; hackerNews: string | null; diff --git a/src/interfaces/github.ts b/src/interfaces/github.ts index 6506c60..6dd839e 100644 --- a/src/interfaces/github.ts +++ b/src/interfaces/github.ts @@ -6,6 +6,508 @@ /* eslint-disable @typescript-eslint/naming-convention, max-lines -- These are API interfaces. */ +interface Repository { + id: number; + node_id: string; + name: string; + full_name: string; + owner: { + login: string; + id: number; + node_id: string; + avatar_url: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: string; + site_admin: boolean; + }; + private: boolean; + html_url: string; + description: string; + fork: boolean; + url: string; + archive_url: string; + assignees_url: string; + blobs_url: string; + branches_url: string; + collaborators_url: string; + comments_url: string; + commits_url: string; + compare_url: string; + contents_url: string; + contributors_url: string; + deployments_url: string; + downloads_url: string; + events_url: string; + forks_url: string; + git_commits_url: string; + git_refs_url: string; + git_tags_url: string; + git_url: string; + issue_comment_url: string; + issue_events_url: string; + issues_url: string; + keys_url: string; + labels_url: string; + languages_url: string; + merges_url: string; + milestones_url: string; + notifications_url: string; + pulls_url: string; + releases_url: string; + ssh_url: string; + stargazers_url: string; + statuses_url: string; + subscribers_url: string; + subscription_url: string; + tags_url: string; + teams_url: string; + trees_url: string; + clone_url: string; + mirror_url: string; + hooks_url: string; + svn_url: string; + homepage: string; + forks_count: number; + forks: number; + stargazers_count: number; + watchers_count: number; + watchers: number; + size: number; + default_branch: string; + open_issues_count: number; + open_issues: number; + is_template: boolean; + topics: Array; + has_issues: boolean; + has_projects: boolean; + has_wiki: boolean; + has_pages: boolean; + has_downloads: boolean; + has_discussions: boolean; + archived: boolean; + disabled: boolean; + visibility: string; + pushed_at: string; + created_at: string; + updated_at: string; + permissions: { + pull: boolean; + push: boolean; + admin: boolean; + }; + allow_rebase_merge: boolean; + template_repository: { + id: number; + node_id: string; + name: string; + full_name: string; + owner: { + login: string; + id: number; + node_id: string; + avatar_url: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: string; + site_admin: boolean; + }; + private: boolean; + html_url: string; + description: string; + fork: boolean; + url: string; + archive_url: string; + assignees_url: string; + blobs_url: string; + branches_url: string; + collaborators_url: string; + comments_url: string; + commits_url: string; + compare_url: string; + contents_url: string; + contributors_url: string; + deployments_url: string; + downloads_url: string; + events_url: string; + forks_url: string; + git_commits_url: string; + git_refs_url: string; + git_tags_url: string; + git_url: string; + issue_comment_url: string; + issue_events_url: string; + issues_url: string; + keys_url: string; + labels_url: string; + languages_url: string; + merges_url: string; + milestones_url: string; + notifications_url: string; + pulls_url: string; + releases_url: string; + ssh_url: string; + stargazers_url: string; + statuses_url: string; + subscribers_url: string; + subscription_url: string; + tags_url: string; + teams_url: string; + trees_url: string; + clone_url: string; + mirror_url: string; + hooks_url: string; + svn_url: string; + homepage: string; + language: unknown; + forks: number; + forks_count: number; + stargazers_count: number; + watchers_count: number; + watchers: number; + size: number; + default_branch: string; + open_issues: number; + open_issues_count: number; + is_template: boolean; + license: { + key: string; + name: string; + url: string; + spdx_id: string; + node_id: string; + html_url: string; + }; + topics: Array; + has_issues: boolean; + has_projects: boolean; + has_wiki: boolean; + has_pages: boolean; + has_downloads: boolean; + archived: boolean; + disabled: boolean; + visibility: string; + pushed_at: string; + created_at: string; + updated_at: string; + permissions: { + admin: boolean; + push: boolean; + pull: boolean; + }; + allow_rebase_merge: boolean; + temp_clone_token: string; + allow_squash_merge: boolean; + allow_auto_merge: boolean; + delete_branch_on_merge: boolean; + allow_merge_commit: boolean; + subscribers_count: number; + network_count: number; + }; + temp_clone_token: string; + allow_squash_merge: boolean; + allow_auto_merge: boolean; + delete_branch_on_merge: boolean; + allow_merge_commit: boolean; + allow_forking: boolean; + subscribers_count: number; + network_count: number; + license: { + key: string; + name: string; + spdx_id: string; + url: string; + node_id: string; + }; + organization: { + login: string; + id: number; + node_id: string; + avatar_url: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: string; + site_admin: boolean; + }; + parent: { + id: number; + node_id: string; + name: string; + full_name: string; + owner: { + login: string; + id: number; + node_id: string; + avatar_url: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: string; + site_admin: boolean; + }; + private: boolean; + html_url: string; + description: string; + fork: boolean; + url: string; + archive_url: string; + assignees_url: string; + blobs_url: string; + branches_url: string; + collaborators_url: string; + comments_url: string; + commits_url: string; + compare_url: string; + contents_url: string; + contributors_url: string; + deployments_url: string; + downloads_url: string; + events_url: string; + forks_url: string; + git_commits_url: string; + git_refs_url: string; + git_tags_url: string; + git_url: string; + issue_comment_url: string; + issue_events_url: string; + issues_url: string; + keys_url: string; + labels_url: string; + languages_url: string; + merges_url: string; + milestones_url: string; + notifications_url: string; + pulls_url: string; + releases_url: string; + ssh_url: string; + stargazers_url: string; + statuses_url: string; + subscribers_url: string; + subscription_url: string; + tags_url: string; + teams_url: string; + trees_url: string; + clone_url: string; + mirror_url: string; + hooks_url: string; + svn_url: string; + homepage: string; + language: unknown; + forks_count: number; + stargazers_count: number; + watchers_count: number; + size: number; + default_branch: string; + open_issues_count: number; + is_template: boolean; + topics: Array; + has_issues: boolean; + has_projects: boolean; + has_wiki: boolean; + has_pages: boolean; + has_downloads: boolean; + archived: boolean; + disabled: boolean; + visibility: string; + pushed_at: string; + created_at: string; + updated_at: string; + permissions: { + admin: boolean; + push: boolean; + pull: boolean; + }; + allow_rebase_merge: boolean; + temp_clone_token: string; + allow_squash_merge: boolean; + allow_auto_merge: boolean; + delete_branch_on_merge: boolean; + allow_merge_commit: boolean; + subscribers_count: number; + network_count: number; + license: { + key: string; + name: string; + url: string; + spdx_id: string; + node_id: string; + html_url: string; + }; + forks: number; + open_issues: number; + watchers: number; + }; + source: { + id: number; + node_id: string; + name: string; + full_name: string; + owner: { + login: string; + id: number; + node_id: string; + avatar_url: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: string; + site_admin: boolean; + }; + private: boolean; + html_url: string; + description: string; + fork: boolean; + url: string; + archive_url: string; + assignees_url: string; + blobs_url: string; + branches_url: string; + collaborators_url: string; + comments_url: string; + commits_url: string; + compare_url: string; + contents_url: string; + contributors_url: string; + deployments_url: string; + downloads_url: string; + events_url: string; + forks_url: string; + git_commits_url: string; + git_refs_url: string; + git_tags_url: string; + git_url: string; + issue_comment_url: string; + issue_events_url: string; + issues_url: string; + keys_url: string; + labels_url: string; + languages_url: string; + merges_url: string; + milestones_url: string; + notifications_url: string; + pulls_url: string; + releases_url: string; + ssh_url: string; + stargazers_url: string; + statuses_url: string; + subscribers_url: string; + subscription_url: string; + tags_url: string; + teams_url: string; + trees_url: string; + clone_url: string; + mirror_url: string; + hooks_url: string; + svn_url: string; + homepage: string; + forks_count: number; + stargazers_count: number; + watchers_count: number; + size: number; + default_branch: string; + open_issues_count: number; + is_template: boolean; + topics: Array; + has_issues: boolean; + has_projects: boolean; + has_wiki: boolean; + has_pages: boolean; + has_downloads: boolean; + archived: boolean; + disabled: boolean; + visibility: string; + pushed_at: string; + created_at: string; + updated_at: string; + permissions: { + admin: boolean; + push: boolean; + pull: boolean; + }; + allow_rebase_merge: boolean; + temp_clone_token: string; + allow_squash_merge: boolean; + allow_auto_merge: boolean; + delete_branch_on_merge: boolean; + allow_merge_commit: boolean; + subscribers_count: number; + network_count: number; + license: { + key: string; + name: string; + url: string; + spdx_id: string; + node_id: string; + html_url: string; + }; + forks: number; + open_issues: number; + watchers: number; + security_and_analysis: { + advanced_security: { + status: string; + }; + secret_scanning: { + status: string; + }; + secret_scanning_push_protection: { + status: string; + }; + secret_scanning_non_provider_patterns: { + status: string; + }; + }; + }; +} + interface Issue { id: number; node_id: string; @@ -162,8 +664,9 @@ interface Issue { } interface IssueCreated { - action: "opened"; - issue: Issue; + action: "opened"; + issue: Issue; + repository: Repository; } interface PullRequest { @@ -669,6 +1172,7 @@ interface PullRequestCreated { action: "opened"; number: number; pull_request: PullRequest; + repository: Repository; } type Ping = Record; diff --git a/src/modules/processGitHubEvent.ts b/src/modules/processGitHubEvent.ts index d9d666f..2558510 100644 --- a/src/modules/processGitHubEvent.ts +++ b/src/modules/processGitHubEvent.ts @@ -4,6 +4,7 @@ * @author Naomi Carrigan */ +import type { Amari } from "../interfaces/amari.js"; import type { IssueCreated, PullRequestCreated, GithubPayload } from "../interfaces/github.js"; @@ -19,10 +20,12 @@ const isPull = (body: GithubPayload): body is PullRequestCreated => { /** * Handles a payload from a GitHub webhook. + * @param amari - Amari's instance. * @param request - The Fastify request payload. * @param response - The Fastify reply class. */ export const processGithubEvent = async( + amari: Amari, // eslint-disable-next-line @typescript-eslint/naming-convention -- Fastify standard. request: FastifyRequest<{ Body: GithubPayload }>, response: FastifyReply, @@ -37,7 +40,30 @@ export const processGithubEvent = async( await response.status(200).send({ message: "Pong!" }); return; } - const { action: _action } = request.body; - isIssue(request.body); - isPull(request.body); + const { action } = request.body; + await response.status(200).send({ message: "Payload received!" }); + if (action === "opened" && event === "issue" && isIssue(request.body)) { + const { issue, repository } = request.body; + const { number } = issue; + const { owner, name } = repository; + await amari.github.rest.issues.addAssignees({ + assignees: [ "naomi-lgbt" ], + // eslint-disable-next-line @typescript-eslint/naming-convention -- Github SDK requirement. + issue_number: number, + owner: owner.login, + repo: name, + }); + } + if (action === "opened" && event === "pull_request" && isPull(request.body)) { + const { pull_request: pr, repository } = request.body; + const { number } = pr; + const { owner, name } = repository; + await amari.github.rest.pulls.requestReviewers({ + owner: owner.login, + // eslint-disable-next-line @typescript-eslint/naming-convention -- Github SDK requirement. + pull_number: number, + repo: name, + reviewers: [ "naomi-lgbt" ], + }); + } }; diff --git a/src/server/serve.ts b/src/server/serve.ts index 36ea927..ed135d9 100644 --- a/src/server/serve.ts +++ b/src/server/serve.ts @@ -7,6 +7,7 @@ import fastify from "fastify"; import { processGithubEvent } from "../modules/processGitHubEvent.js"; import { logger } from "../utils/logger.js"; +import type { Amari } from "../interfaces/amari.js"; import type { GithubPayload } from "../interfaces/github.js"; const html = ` @@ -52,8 +53,9 @@ const html = ` /** * Starts up a web server for health monitoring. + * @param amari - Amari's instance. */ -export const instantiateServer = (): void => { +export const instantiateServer = (amari: Amari): void => { try { const server = fastify({ logger: false, @@ -67,7 +69,14 @@ export const instantiateServer = (): void => { server. // eslint-disable-next-line @typescript-eslint/naming-convention -- Fastify standard. post<{ Body: GithubPayload }>("/github", async(request, response) => { - await processGithubEvent(request, response); + try { + await processGithubEvent(amari, request, response); + } catch (error) { + if (!(error instanceof Error)) { + return; + } + await logger.error("/github route", error); + } }); server.listen({ port: 7044 }, (error) => {