reviewbot

Source code for the is-a.dev r...
Log | Files | Refs | README | LICENSE

commit 88b3155208ee41ccd97a7771a670328a91685f81
parent d05c8dff3b81da5836d39b23c7017c8fa5c1d689
Author: iostpa <iostpa@iostpa.com>
Date:   Sat,  6 Jun 2026 12:54:39 +0300

Split the code so that it isn't in one index.js file

Diffstat:
M.dockerignore | 5++---
Dindex.js | 498-------------------------------------------------------------------------------
Mpackage.json | 4++--
Asrc/index.js | 225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rmessage/draft.md -> src/message/draft.md | 0
Rmessage/invalidnolabel.md -> src/message/invalidnolabel.md | 0
Rmessage/label/abuse-risk.md -> src/message/label/abuse-risk.md | 0
Rmessage/label/commercial-usage.md -> src/message/label/commercial-usage.md | 0
Rmessage/label/impersonation.md -> src/message/label/impersonation.md | 0
Rmessage/label/inaccessible-website.md -> src/message/label/inaccessible-website.md | 0
Rmessage/label/incompatible-records.md -> src/message/label/incompatible-records.md | 0
Rmessage/label/incomplete-pr.md -> src/message/label/incomplete-pr.md | 0
Rmessage/label/incomplete-website.md -> src/message/label/incomplete-website.md | 0
Rmessage/label/invalid-file.md -> src/message/label/invalid-file.md | 0
Rmessage/label/invalid-records.md -> src/message/label/invalid-records.md | 0
Rmessage/label/invalid-social.md -> src/message/label/invalid-social.md | 0
Rmessage/label/lowpriority.md -> src/message/label/lowpriority.md | 0
Rmessage/label/merge-conflict.md -> src/message/label/merge-conflict.md | 0
Rmessage/label/needs-preview.md -> src/message/label/needs-preview.md | 0
Rmessage/label/not-dev-related.md -> src/message/label/not-dev-related.md | 0
Rmessage/label/nsfw.md -> src/message/label/nsfw.md | 0
Rmessage/label/other.md -> src/message/label/other.md | 0
Rmessage/label/unauthorized.md -> src/message/label/unauthorized.md | 0
Rmessage/merged.md -> src/message/merged.md | 0
Rmessage/opened.md -> src/message/opened.md | 0
Asrc/tools/numberofdays.js | 16++++++++++++++++
Asrc/webhooks/closed.js | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/webhooks/labeled.js | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/webhooks/opened.js | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/webhooks/unlabeled.js | 21+++++++++++++++++++++
30 files changed, 545 insertions(+), 503 deletions(-)

diff --git a/.dockerignore b/.dockerignore @@ -7,5 +7,4 @@ README.me LICENSE .env.example .github -CONTRIBUTING.md -renovate.json- \ No newline at end of file +CONTRIBUTING.md+ \ No newline at end of file diff --git a/index.js b/index.js @@ -1,497 +0,0 @@ -import * as Sentry from '@sentry/bun'; -import dotenv from 'dotenv'; -import fs from 'fs'; -import middie from '@fastify/middie'; -import Fastify from 'fastify'; -import { App } from 'octokit'; -import { Octokit } from "@octokit/core"; -import { createNodeMiddleware } from '@octokit/webhooks'; -import { createAppAuth } from "@octokit/auth-app"; -import { CronJob } from 'cron'; -const mariadb = require('mariadb'); - -// Load environment variables from .env file -dotenv.config(); - -// Set configured values -const sentryDsn = process.env.SENTRY; -const appId = process.env.APP_ID; -const privateKeyPath = process.env.PRIVATE_KEY_PATH; -const installationId = process.env.INSTALLATION_ID; -const privateKey = fs.readFileSync(privateKeyPath, 'utf8'); -const secret = process.env.WEBHOOK_SECRET; -const numberOfDays = 3; -const newPRs = fs.readFileSync('./message/opened.md', 'utf8'); -const mergedPRs = fs.readFileSync('./message/merged.md', 'utf8'); -const draftPRs = fs.readFileSync('./message/draft.md', 'utf8'); -const lowPriorityMessage = fs.readFileSync('./message/label/lowpriority.md', 'utf8'); -const ignoreLabels = ["maintainer"]; -const reasonLabels = ["reason: abuse risk", "reason: commercial usage", "reason: impersonation", "reason: inaccessible website", "reason: incomplete pr", "reason: incomplete website", "reason: invalid file", "reason: invalid records", "reason: invalid social", "reason: merge conflict", "reason: not dev related", "reason: nsfw", "reason: other", "reason: unauthorized", "reason: incompatible records"]; -const unremovableLabels = ["maintainer", "ci: bypass-owner-check", "no-stale", "r: william"]; -const reviewerUsernames = ["DEV-DIBSTER", "dragsbruh", "iostpa", "notamitgamer", "omsenjalia", "orangci", "satr14washere", "Stef-00012", "STICKnoLOGIC", "wdhdev", "Yunexiz"]; - -Sentry.init({ - dsn: sentryDsn, - // Tracing - tracesSampleRate: 1.0, // Capture 100% of the transactions - // Enable logs to be sent to Sentry - enableLogs: true, - integrations: [Sentry.bunRuntimeMetricsIntegration()], -}); - -// Database -// Following order of columns: username, prnumber, time, repoowner, repo -const pool = mariadb.createPool({ - user: process.env.SQL_USER, - password: process.env.SQL_PASSWORD, - host: process.env.SQL_HOST, - port: process.env.SQL_PORT, - database: process.env.SQL_DB_NAME, - ssl: false, - connectionLimit: 5 -}); - -// Create an authenticated Octokit client authenticated as a GitHub App -const app = new App({ - appId, - privateKey, - webhooks: { - secret - } -}); - -// for cron -const installationOctokit = new Octokit({ - authStrategy: createAppAuth, - auth: { - appId, - privateKey, - installationId, - }, -}); - -// Get & log the authenticated app's name -const { data } = await app.octokit.request('/app'); - -// https://github.com/octokit/core.js#logging -app.octokit.log.debug(`Authenticated as '${data.name}'`); - - -function getNumberOfDays(start, end) { - const date1 = new Date(start); - const date2 = new Date(end); - - // One day in milliseconds - const oneDay = 1000 * 60 * 60 * 24; - - // Calculating the time difference between two dates - const diffInTime = date2.getTime() - date1.getTime(); - - // Calculating the no. of days between two dates - const diffInDays = Math.round(diffInTime / oneDay); - - return diffInDays; -}; - - -// Check if a low priority pull request has been in the database for over 3 days -let job = new CronJob( - '0 * * * *', // cronTime - async function () { - let date = new Date(); - let conn; - try { - conn = await pool.getConnection(); - let res = await conn.query(`SELECT * FROM LIST`); - let resJson = JSON.stringify(res); - if (resJson !== '[]') { - let parsed = JSON.parse(resJson); - for (let i in parsed) { - if (getNumberOfDays(parsed[i].time, date) >= numberOfDays) { - await conn.query(`DELETE FROM LIST WHERE time=(?)`, [parsed[i].time]); - await installationOctokit.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}', { - owner: parsed[i].repoowner, - repo: parsed[i].repo, - issue_number: parsed[i].prnumber, - name: "status: low priority", - }); - console.log(`Removed #${parsed[i].prnumber} from the database since it has existed for 3 or more days.`); - } - } - } else if (resJson === '[]') { - return; - } - } catch (error) { - Sentry.captureException(error); - fastify.log.error(error); - if (error.response) { - console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); - } else { - console.error(error); - } - } finally { - if (conn) conn.end(); - } - }, // onTick - null, // onComplete - true, // start - 'Europe/Bucharest' // timeZone -); - -// https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request -app.webhooks.on('pull_request.opened', async ({ octokit, payload }) => { - console.log(`Received a open pull request event for #${payload.pull_request.number} on https://github.com/${payload.repository.full_name}`); - let conn; - try { - const labels = await octokit.rest.issues.listLabelsOnIssue({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number - }); - const allLabels = labels.data.map((label) => label.name); - if (ignoreLabels.some((label) => allLabels.includes(label))) { - console.log(`#${payload.pull_request.number} from https://github.com/${payload.repository.full_name} is by a maintainer, skipping pull request.`); - return; - } - if (payload.pull_request.draft === true) { - await octokit.rest.issues.createComment({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number, - body: draftPRs - }); - console.log(`Sent a draft message to #${payload.pull_request.number} on https://github.com/${payload.repository.full_name}`); - } else { - await octokit.rest.issues.createComment({ - owner:payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number, - body: newPRs - }); - console.log(`Sent a opened message to #${payload.pull_request.number} on https://github.com/${payload.repository.full_name}`); - }; - // low priority check - conn = await pool.getConnection(); - let res = await conn.query(`SELECT * FROM LIST WHERE username=(?)`, [payload.pull_request.user.login]); - let resJson = JSON.stringify(res); - if (resJson !== "[]") { - let parsed = JSON.parse(resJson); - let date = new Date(); - if (parsed[0].username === payload.pull_request.user.login && getNumberOfDays(parsed[0].time, date) <= numberOfDays) { - let lowPriority = ` -# Low priority - -You're attempting to create a new pull request to bypass the low priority label placed on your previous pull request, #${parsed[0].prnumber}. Unfortunately, we've noticed this attempt, and we're applying the label you were trying to escape on this pull request, too. - -If you think this is a mistake then please contact [iostpa](https://github.com/iostpa). - `; - await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/labels', { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number, - labels: [ - 'status: low priority' - ] - }); - await octokit.rest.issues.createComment({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number, - body: lowPriority - }); - await conn.query(`UPDATE LIST SET prnumber=(?) WHERE prnumber=(?)`, [payload.pull_request.number, parsed[0].prnumber]); - await conn.query(`UPDATE LIST SET time=(?) WHERE prnumber=(?)`, [payload.pull_request.created_at, parsed[0].prnumber]); - console.log(`Auto-added, replaced with new PR number and sent low priority message to #${payload.pull_request.number} on https://github.com/${payload.repository.full_name} because it was found in the database.`); - } else if (parsed[0].username === payload.pull_request.user.login && getNumberOfDays(parsed[0].time, date) >= numberOfDays) { - await octokit.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}', { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number, - name: "status: low priority", - }); - res = await conn.query("DELETE FROM LIST WHERE username=(?)", [parsed[0].username]); - console.log(`Removed #${payload.pull_request.number} from https://github.com/${payload.repository.full_name} from the low priority database as well as the label.`); - } - } else if (resJson === '[]') { - return; - } - } catch (error) { - Sentry.captureException(error); - fastify.log.error(error); - if (error.response) { - console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); - } else { - console.error(error); - } - } finally { - if (conn) conn.end(); - } -}); - -// Label system -app.webhooks.on('pull_request.labeled', async ({ octokit, payload }) => { - let denied, invalid, lowpriority; - if (payload.label.name === "status: denied") { - denied = true; - } else if (payload.label.name === "status: invalid") { - invalid = true; - } else if (payload.label.name === "status: low priority") { - lowpriority = true; - } - - if (denied === true || invalid === true) { - try { - const listOfLabels = []; - // timeout so that it creates a little time window for the maintainer to add the rest of the labels - await new Promise(resolve => setTimeout(resolve, 5000)); - const data = await octokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', { - owner: payload.repository.owner.login, - repo: payload.repository.name, - pull_number: payload.pull_request.number - }); - const labelData = data.data.labels; - for (let i in labelData) { - if (labelData[i].name) { - listOfLabels.push(labelData[i].name); - } - } - - // start adding the messages - const allMessages = []; - for (let i in listOfLabels) { - if (reasonLabels.includes(listOfLabels[i])) { - let initialReason = listOfLabels[i].toString().replace(/reason:\s/i, ''); - let finalReason = initialReason.replace(/\s+/g, '-'); - let message = fs.readFileSync(`./message/label/${finalReason}.md`, 'utf8'); - allMessages.push(message); - } else if (listOfLabels[i] === "status: needs preview") { - let message = fs.readFileSync(`./message/label/needs-preview.md`, 'utf8'); - allMessages.push(message); - }; - } - - const labelMessages = allMessages.join('\n\n'); - let body; - if (!labelMessages.length) { - body = fs.readFileSync('./message/invalidnolabel.md', 'utf8'); - } else { - body = ` -# Invalid Pull Request - -This pull request is invalid due to the following reason(s): - ---- -${labelMessages} - ---- - -If you need any help, please create an issue or ask our team in the [Discord server](https://discord.gg/is-a-dev-830872854677422150) - -`; - }; - await octokit.rest.issues.createComment({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number, - body: body - }); - console.log(`Sent reason messages at #${payload.pull_request.number} from https://github.com/${payload.repository.full_name}`); - if (denied === true) { - await octokit.request('PATCH /repos/{owner}/{repo}/pulls/{pull_number}', { - owner: payload.repository.owner.login, - repo: payload.repository.name, - pull_number: payload.pull_request.number, - state: 'closed', - }); - console.log(`Closed pull request at #${payload.pull_request.number} from https://github.com/${payload.repository.full_name}`); - }; - } catch (error) { - Sentry.captureException(error); - fastify.log.error(error); - if (error.response) { - console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); - } else { - console.error(error); - } - }; - } else if (lowpriority === true) { - let conn; - try { - conn = await pool.getConnection(); - let res = await conn.query(`SELECT * FROM LIST WHERE username=(?)`, [payload.pull_request.user.login]); - let resJson = JSON.stringify(res); - if (resJson === "[]") { - await octokit.rest.issues.createComment({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number, - body: lowPriorityMessage - }); - console.log(`Sent low priority message to #${payload.pull_request.number} from https://github.com/${payload.repository.full_name}`); - await conn.query("INSERT INTO LIST VALUES (?, ?, ?, ?, ?)", [payload.pull_request.user.login, payload.pull_request.number, payload.pull_request.updated_at, payload.repository.owner.login, payload.repository.name]); - console.log(`Logged #${payload.pull_request.number} from https://github.com/${payload.repository.full_name} to the low priority database.`); - } - } catch (error) { - Sentry.captureException(error); - fastify.log.error(error); - if (error.response) { - console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); - } else { - console.error(error); - } - } finally { - if (conn) conn.end(); - }; - } -}); - -// https://github.com/octokit/webhooks.js/?tab=readme-ov-file#webhook-events -app.webhooks.on('pull_request.closed', async ({ octokit, payload }) => { - console.log(`Received a closed pull request event for #${payload.pull_request.number} on https://github.com/${payload.repository.full_name}`); - let conn; - try { - if (payload.pull_request.merged === true) { - const labels = await octokit.rest.issues.listLabelsOnIssue({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number - }); - const allLabels = labels.data.map((label) => label.name); - if (ignoreLabels.some((label) => allLabels.includes(label))) { - return; - } - await octokit.rest.issues.createComment({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number, - body: mergedPRs - }); - console.log(`Sent a merged message to #${payload.pull_request.number} on https://github.com/${payload.repository.full_name}`); - - // remove almost all labels if there are any - const listOfLabels = []; - const data = await octokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', { - owner: payload.repository.owner.login, - repo: payload.repository.name, - pull_number: payload.pull_request.number - }); - - const labelData = data.data.labels; - for (let i in labelData) { - if (labelData[i].name) { - listOfLabels.push(labelData[i].name); - } - } - - for (let i in listOfLabels) { - if (!listOfLabels.includes(unremovableLabels)) { - await octokit.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}', { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number, - name: listOfLabels[i], - }); - } else if (listOfLabels[i] == "status: low priority") { - return; - } else if (unremovableLabels.includes(listOfLabels[i])) { - return; - } - } - console.log(`Removed all labels from #${payload.pull_request.number} on https://github.com/${payload.repository.full_name}`); - } else { - conn = await pool.getConnection(); - let res = await conn.query(`SELECT * FROM LIST WHERE username=(?)`, [payload.pull_request.user.login]); - let resJson = JSON.stringify(res); - if (resJson !== "[]") { - let parsed = JSON.parse(resJson); - if (reviewerUsernames.includes(payload.sender.login)) { - res = await conn.query("DELETE FROM LIST WHERE username=(?)", [parsed[0].username]); - await octokit.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}', { - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.pull_request.number, - name: "status: low priority", - }); - console.log(`Removed #${payload.pull_request.number} from https://github.com/${payload.repository.full_name} from the low priority database as well as the label.`); - } - } - }; - } catch (error) { - Sentry.captureException(error); - fastify.log.error(error); - if (error.response) { - console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); - } else { - console.error(error); - } - } finally { - if (conn) conn.end(); - } -}); - -app.webhooks.on('pull_request.unlabeled', async ({ payload }) => { - let conn; - try { - if (payload.label.name === "status: low priority") { - conn = await pool.getConnection(); - let res = await conn.query(`SELECT * FROM LIST WHERE username=(?)`, [payload.pull_request.user.login]); - let resJson = JSON.stringify(res); - if (resJson !== "[]") { - res = await conn.query("DELETE FROM LIST WHERE username=(?)", [payload.pull_request.user.login]); - console.log(`Removed #${payload.pull_request.number} from https://github.com/${payload.repository.full_name} from the low priority database.`); - } else { - return; - } - } - } catch (error) { - Sentry.captureException(error); - fastify.log.error(error); - if (error.response) { - console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); - } else { - console.error(error); - } - } finally { - if (conn) conn.end(); - } -}); - -// Handle errors -app.webhooks.onError((error) => { - Sentry.captureException(error); - fastify.log.error(error); - if (error.name === 'AggregateError') { - // Log Secret verification errors - console.log(`Error processing request: ${error.event}`); - } else { - console.log(error); - } -}); - -// Launch a web server to listen for GitHub webhooks -const port = process.env.PORT || 3000; -const host = process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost'; -const webhookPath = '/api/webhook'; -const localWebhookUrl = `http://${host}:${port}${webhookPath}`; - -// https://github.com/octokit/webhooks.js/#createnodemiddleware -const middleware = createNodeMiddleware(app.webhooks, { path: webhookPath }); - -const fastify = Fastify({ - logger: false -}); -await fastify.register(middie); -fastify.use(middleware); - -await fastify.listen({ port, host }); - -console.log(`Server is listening for events at: ${localWebhookUrl}`); -console.log('Press Ctrl + C to quit.'); - -/* -Standard node http server (use "import http from 'http' if you want to use this instead of Fastify") -http.createServer(middleware).listen(port, () => { - console.log(`Server is listening for events at: ${localWebhookUrl}`) - console.log('Press Ctrl + C to quit.') -}) -*/- \ No newline at end of file diff --git a/package.json b/package.json @@ -6,8 +6,8 @@ "description": "An app to assist in the review process of is-a-dev", "author": "iostpa", "scripts": { - "server": "bun index.js", - "lint": "eslint index.js" + "server": "bun src/index.js", + "lint": "eslint src" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/src/index.js b/src/index.js @@ -0,0 +1,224 @@ +import * as Sentry from '@sentry/bun'; +import fs from 'fs'; +import path from 'path'; +import dotenv from 'dotenv'; +import middie from '@fastify/middie'; +import Fastify from 'fastify'; +import { App } from 'octokit'; +import { createNodeMiddleware } from '@octokit/webhooks'; +import { CronJob } from 'cron'; +import { getNumberOfDays } from './tools/numberofdays'; +import { unlabeled } from './webhooks/unlabeled'; +import { opened } from './webhooks/opened'; +import { closed } from './webhooks/closed'; +import { labeled } from './webhooks/labeled'; +const mariadb = require('mariadb'); + +// Load environment variables from .env file +dotenv.config(); + +// Set configured values +const sentryDsn = process.env.SENTRY; +const appId = process.env.APP_ID; +const installationId = process.env.INSTALLATION_ID; +const privateKey = fs.readFileSync(path.resolve(process.env.PRIVATE_KEY_PATH), 'utf8'); +const secret = process.env.WEBHOOK_SECRET; +export const numberOfDays = 3; + + +Sentry.init({ + dsn: sentryDsn, + // Tracing + tracesSampleRate: 1.0, // Capture 100% of the transactions + // Enable logs to be sent to Sentry + enableLogs: true, + integrations: [Sentry.bunRuntimeMetricsIntegration()], +}); + +// Database +// Following order of columns: username, prnumber, time, repoowner, repo +export const pool = mariadb.createPool({ + user: process.env.SQL_USER, + password: process.env.SQL_PASSWORD, + host: process.env.SQL_HOST, + port: process.env.SQL_PORT, + database: process.env.SQL_DB_NAME, + ssl: false, + connectionLimit: 5 +}); + +// Create an authenticated Octokit client authenticated as a GitHub App +const app = new App({ + appId, + privateKey, + webhooks: { + secret + } +}); + +const appOctokit = await app.getInstallationOctokit(installationId); + +/* +saving this for incase + +const installationOctokit = new Octokit({ + authStrategy: createAppAuth, + auth: { + appId, + privateKey, + installationId, + }, +}); +*/ + +// Get & log the authenticated app's name +const { data } = await app.octokit.request('/app'); + +// https://github.com/octokit/core.js#logging +app.octokit.log.debug(`Authenticated as '${data.name}'`); + + +// Check if a low priority pull request has been in the database for over 3 days +let job = new CronJob( + '0 * * * *', // cronTime + async function () { + let date = new Date(); + let conn; + try { + conn = await pool.getConnection(); + let res = await conn.query(`SELECT * FROM LIST`); + let resJson = JSON.stringify(res); + if (resJson !== '[]') { + let parsed = JSON.parse(resJson); + for (let i in parsed) { + if (getNumberOfDays(parsed[i].time, date) >= numberOfDays) { + await conn.query(`DELETE FROM LIST WHERE time=(?)`, [parsed[i].time]); + await appOctokit.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}', { + owner: parsed[i].repoowner, + repo: parsed[i].repo, + issue_number: parsed[i].prnumber, + name: "status: low priority", + }); + console.log(`Removed #${parsed[i].prnumber} from the database since it has existed for 3 or more days.`); + } + } + } else if (resJson === '[]') { + return; + } + } catch (error) { + Sentry.captureException(error); + fastify.log.error(error); + if (error.response) { + console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); + } else { + console.error(error); + } + } finally { + if (conn) conn.end(); + } + }, // onTick + null, // onComplete + true, // start + 'Europe/Bucharest' // timeZone +); + +app.webhooks.on('pull_request.opened', async ({ payload }) => { + try { + await opened(appOctokit, payload.repository.owner.login, payload.repository.name, payload.repository.full_name, payload.pull_request.number, payload.pull_request.user.login, payload.pull_request.draft); + } catch (error) { + Sentry.captureException(error); + fastify.log.error(error); + if (error.response) { + console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); + } else { + console.error(error); + } + } +}); + +// https://github.com/octokit/webhooks.js/?tab=readme-ov-file#webhook-events +app.webhooks.on('pull_request.closed', async ({ payload }) => { + console.log(`Received a closed pull request event for #${payload.pull_request.number} on https://github.com/${payload.repository.full_name}`); + try { + await closed(appOctokit, payload.pull_request.merged, payload.repository.owner.login, payload.repository.name, payload.repository.full_name, payload.pull_request.number, payload.pull_request.user.login, payload.sender.login); + } catch (error) { + Sentry.captureException(error); + fastify.log.error(error); + if (error.response) { + console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); + } else { + console.error(error); + } + } +}); + +// Label system +app.webhooks.on('pull_request.labeled', async ({ payload }) => { + try { + await labeled(appOctokit, payload.label.name, payload.repository.owner.login, payload.repository.name, payload.repository.full_name, payload.pull_request.number, payload.pull_request.updated_at, payload.pull_request.user.login); + } catch (error) { + Sentry.captureException(error); + fastify.log.error(error); + if (error.response) { + console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); + } else { + console.error(error); + } + } +}); + +app.webhooks.on('pull_request.unlabeled', async ({ payload }) => { + try { + await unlabeled(payload.label.name, payload.pull_request.user.login, payload.pull_request.number, payload.repository.full_name); + } catch (error) { + Sentry.captureException(error); + fastify.log.error(error); + if (error.response) { + console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`); + } else { + console.error(error); + } + } +}); + + +// Handle errors +app.webhooks.onError((error) => { + Sentry.captureException(error); + fastify.log.error(error); + if (error.name === 'AggregateError') { + // Log Secret verification errors + console.log(`Error processing request: ${error.event}`); + } else { + console.log(error); + } +}); + +// Launch a web server to listen for GitHub webhooks +const port = process.env.PORT || 3000; +const host = process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost'; +const webhookPath = '/api/webhook'; +const localWebhookUrl = `http://${host}:${port}${webhookPath}`; + +// https://github.com/octokit/webhooks.js/#createnodemiddleware +const middleware = createNodeMiddleware(app.webhooks, { path: webhookPath }); + +const fastify = Fastify({ + logger: false +}); +await fastify.register(middie); +fastify.use(middleware); + +await fastify.listen({ port, host }); + +console.log(`Server is listening for events at: ${localWebhookUrl}`); +console.log('Press Ctrl + C to quit.'); + +/* +Standard node http server (use "import http from 'http' if you want to use this instead of Fastify") + +http.createServer(middleware).listen(port, () => { + console.log(`Server is listening for events at: ${localWebhookUrl}`) + console.log('Press Ctrl + C to quit.') +}) +*/+ \ No newline at end of file diff --git a/message/draft.md b/src/message/draft.md diff --git a/message/invalidnolabel.md b/src/message/invalidnolabel.md diff --git a/message/label/abuse-risk.md b/src/message/label/abuse-risk.md diff --git a/message/label/commercial-usage.md b/src/message/label/commercial-usage.md diff --git a/message/label/impersonation.md b/src/message/label/impersonation.md diff --git a/message/label/inaccessible-website.md b/src/message/label/inaccessible-website.md diff --git a/message/label/incompatible-records.md b/src/message/label/incompatible-records.md diff --git a/message/label/incomplete-pr.md b/src/message/label/incomplete-pr.md diff --git a/message/label/incomplete-website.md b/src/message/label/incomplete-website.md diff --git a/message/label/invalid-file.md b/src/message/label/invalid-file.md diff --git a/message/label/invalid-records.md b/src/message/label/invalid-records.md diff --git a/message/label/invalid-social.md b/src/message/label/invalid-social.md diff --git a/message/label/lowpriority.md b/src/message/label/lowpriority.md diff --git a/message/label/merge-conflict.md b/src/message/label/merge-conflict.md diff --git a/message/label/needs-preview.md b/src/message/label/needs-preview.md diff --git a/message/label/not-dev-related.md b/src/message/label/not-dev-related.md diff --git a/message/label/nsfw.md b/src/message/label/nsfw.md diff --git a/message/label/other.md b/src/message/label/other.md diff --git a/message/label/unauthorized.md b/src/message/label/unauthorized.md diff --git a/message/merged.md b/src/message/merged.md diff --git a/message/opened.md b/src/message/opened.md diff --git a/src/tools/numberofdays.js b/src/tools/numberofdays.js @@ -0,0 +1,15 @@ +export function getNumberOfDays(start, end) { + const date1 = new Date(start); + const date2 = new Date(end); + + // One day in milliseconds + const oneDay = 1000 * 60 * 60 * 24; + + // Calculating the time difference between two dates + const diffInTime = date2.getTime() - date1.getTime(); + + // Calculating the no. of days between two dates + const diffInDays = Math.round(diffInTime / oneDay); + + return diffInDays; +};+ \ No newline at end of file diff --git a/src/webhooks/closed.js b/src/webhooks/closed.js @@ -0,0 +1,82 @@ +import { pool } from '../index'; +import fs from 'fs'; +import path from 'path'; + +const ignoreLabels = ["maintainer"]; +const mergedPRs = fs.readFileSync(path.join(__dirname, '../message/merged.md'), 'utf8'); +const unremovableLabels = ["maintainer", "ci: bypass-owner-check", "no-stale", "r: william"]; +const reviewerUsernames = ["DEV-DIBSTER", "dragsbruh", "iostpa", "notamitgamer", "omsenjalia", "orangci", "satr14washere", "Stef-00012", "STICKnoLOGIC", "wdhdev", "Yunexiz"]; + +export async function closed(appOctokit, prMerged, repoOwner, repoName, repoFullName, prNumber, prUsername, prSender) { + let conn; + try { + if (prMerged === true) { + const labels = await appOctokit.rest.issues.listLabelsOnIssue({ + owner: repoOwner, + repo: repoName, + issue_number: prNumber + }); + const allLabels = labels.data.map((label) => label.name); + if (ignoreLabels.some((label) => allLabels.includes(label))) { + return; + } + await appOctokit.rest.issues.createComment({ + owner: repoOwner, + repo: repoName, + issue_number: prNumber, + body: mergedPRs + }); + console.log(`Sent a merged message to #${prNumber} on https://github.com/${repoFullName}`); + + // remove almost all labels if there are any + const listOfLabels = []; + const data = await appOctokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', { + owner: repoOwner, + repo: repoName, + pull_number: prNumber + }); + + const labelData = data.data.labels; + for (let i in labelData) { + if (labelData[i].name) { + listOfLabels.push(labelData[i].name); + } + } + + for (let i in listOfLabels) { + if (!listOfLabels.includes(unremovableLabels)) { + await appOctokit.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}', { + owner: repoOwner, + repo: repoName, + issue_number: prNumber, + name: listOfLabels[i], + }); + } else if (listOfLabels[i] == "status: low priority") { + return; + } else if (unremovableLabels.includes(listOfLabels[i])) { + return; + } + } + console.log(`Removed all labels from #${prNumber} on https://github.com/${repoFullName}`); + } else { + conn = await pool.getConnection(); + let res = await conn.query(`SELECT * FROM LIST WHERE username=(?)`, [prUsername]); + let resJson = JSON.stringify(res); + if (resJson !== "[]") { + let parsed = JSON.parse(resJson); + if (reviewerUsernames.includes(prSender)) { + res = await conn.query("DELETE FROM LIST WHERE username=(?)", [parsed[0].username]); + await appOctokit.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}', { + owner: repoOwner, + repo: repoName, + issue_number: prNumber, + name: "status: low priority", + }); + console.log(`Removed #${prNumber} from https://github.com/${repoFullName} from the low priority database as well as the label.`); + } + } + }; + } finally { + if (conn) conn.end; + } +}+ \ No newline at end of file diff --git a/src/webhooks/labeled.js b/src/webhooks/labeled.js @@ -0,0 +1,104 @@ +import { pool } from '../index'; +import fs from 'fs'; +import path from 'path'; + +const reasonLabels = ["reason: abuse risk", "reason: commercial usage", "reason: impersonation", "reason: inaccessible website", "reason: incomplete pr", "reason: incomplete website", "reason: invalid file", "reason: invalid records", "reason: invalid social", "reason: merge conflict", "reason: not dev related", "reason: nsfw", "reason: other", "reason: unauthorized", "reason: incompatible records"]; +const lowPriorityMessage = fs.readFileSync(path.join(__dirname, '../message/label/lowpriority.md'), 'utf8'); + +export async function labeled(appOctokit, prLabelName, repoOwner, repoName, repoFullName, prNumber, prUpdatedAt, prUsername) { + let denied, invalid, lowpriority; + if (prLabelName === "status: denied") { + denied = true; + } else if (prLabelName === "status: invalid") { + invalid = true; + } else if (prLabelName === "status: low priority") { + lowpriority = true; + } + + if (denied === true || invalid === true) { + const listOfLabels = []; + // timeout so that it creates a little time window for the maintainer to add the rest of the labels + await new Promise(resolve => setTimeout(resolve, 5000)); + const data = await appOctokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', { + owner: repoOwner, + repo: repoName, + pull_number: prNumber + }); + const labelData = data.data.labels; + for (let i in labelData) { + if (labelData[i].name) { + listOfLabels.push(labelData[i].name); + } + } + + // start adding the messages + const allMessages = []; + for (let i in listOfLabels) { + if (reasonLabels.includes(listOfLabels[i])) { + let initialReason = listOfLabels[i].toString().replace(/reason:\s/i, ''); + let finalReason = initialReason.replace(/\s+/g, '-'); + let message = fs.readFileSync(path.join(__dirname, `../message/label/${finalReason}.md`), 'utf8'); + allMessages.push(message); + } else if (listOfLabels[i] === "status: needs preview") { + let message = fs.readFileSync(path.join(__dirname, `../message/label/needs-preview.md`), 'utf8'); + allMessages.push(message); + }; + } + + const labelMessages = allMessages.join('\n\n'); + let body; + if (!labelMessages.length) { + body = fs.readFileSync(path.join(__dirname, `../message/invalidnolabel.md`), 'utf8'); + } else { + body = ` +# Invalid Pull Request + +This pull request is invalid due to the following reason(s): + +--- +${labelMessages} + +--- + +If you need any help, please create an issue or ask our team in the [Discord server](https://discord.gg/is-a-dev-830872854677422150) + +`; + }; + await appOctokit.rest.issues.createComment({ + owner: repoOwner, + repo: repoName, + issue_number: prNumber, + body: body + }); + console.log(`Sent reason messages at #${prNumber} from https://github.com/${repoFullName}`); + if (denied === true) { + await appOctokit.request('PATCH /repos/{owner}/{repo}/pulls/{pull_number}', { + owner: repoOwner, + repo: repoName, + pull_number: prNumber, + state: 'closed', + }); + console.log(`Closed pull request at #${prNumber} from https://github.com/${repoFullName}`); + }; + } else if (lowpriority === true) { + let conn; + try { + conn = await pool.getConnection(); + let res = await conn.query(`SELECT * FROM LIST WHERE username=(?)`, [prUsername]); + let resJson = JSON.stringify(res); + if (resJson === "[]") { + await appOctokit.rest.issues.createComment({ + owner: repoOwner, + repo: repoName, + issue_number: prNumber, + body: lowPriorityMessage + }); + console.log(`Sent low priority message to #${prNumber} from https://github.com/${repoFullName}`); + await conn.query("INSERT INTO LIST VALUES (?, ?, ?, ?, ?)", [prUsername, prNumber, prUpdatedAt, repoOwner, repoName]); + console.log(`Logged #${prNumber} from https://github.com/${repoFullName} to the low priority database.`); + } + } finally { + if (conn) conn.end(); + } + } +}+ \ No newline at end of file diff --git a/src/webhooks/opened.js b/src/webhooks/opened.js @@ -0,0 +1,90 @@ +import { pool, numberOfDays } from '../index'; +import { getNumberOfDays } from '../tools/numberofdays'; +import fs from 'fs'; +import path from 'path'; + +const ignoreLabels = ["maintainer"]; +const newPRs = fs.readFileSync(path.join(__dirname, '../message/opened.md'), 'utf8'); +const draftPRs = fs.readFileSync(path.join(__dirname, '../message/draft.md'), 'utf8'); + +// https://docs.github.com/en/webhooks/webhook-events-and-payloads#pull_request +export async function opened(appOctokit, repoOwner, repoName, repoFullName, prNumber, prCreatedAt, prUsername, prDraft) { + console.log(`Received a open pull request event for #${prNumber} on https://github.com/${repoFullName}`); + let conn; + try { + const labels = await appOctokit.rest.issues.listLabelsOnIssue({ + owner: repoOwner, + repo: repoName, + issue_number: prNumber + }); + const allLabels = labels.data.map((label) => label.name); + if (ignoreLabels.some((label) => allLabels.includes(label))) { + console.log(`#${prNumber} from https://github.com/${repoFullName} is by a maintainer, skipping pull request.`); + return; + } + if (prDraft === true) { + await appOctokit.rest.issues.createComment({ + owner: repoOwner, + repo: repoName, + issue_number: prNumber, + body: draftPRs + }); + console.log(`Sent a draft message to #${prNumber} on https://github.com/${repoFullName}`); + } else { + await appOctokit.rest.issues.createComment({ + owner: repoOwner, + repo: repoName, + issue_number: prNumber, + body: newPRs + }); + console.log(`Sent a opened message to #${prNumber} on https://github.com/${repoFullName}`); + }; + // low priority check + conn = await pool.getConnection(); + let res = await conn.query(`SELECT * FROM LIST WHERE username=(?)`, [prUsername]); + let resJson = JSON.stringify(res); + if (resJson !== "[]") { + let parsed = JSON.parse(resJson); + let date = new Date(); + if (parsed[0].username === prUsername && getNumberOfDays(parsed[0].time, date) <= numberOfDays) { + let lowPriority = ` +# Low priority + +You're attempting to create a new pull request to bypass the low priority label placed on your previous pull request, #${parsed[0].prnumber}. Unfortunately, we've noticed this attempt, and we're applying the label you were trying to escape on this pull request, too. + +If you think this is a mistake then please contact [iostpa](https://github.com/iostpa). + `; + await appOctokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/labels', { + owner: repoOwner, + repo: repoName, + issue_number: prNumber, + labels: [ + 'status: low priority' + ] + }); + await appOctokit.rest.issues.createComment({ + owner: repoOwner, + repo: repoName, + issue_number: prNumber, + body: lowPriority + }); + await conn.query(`UPDATE LIST SET prnumber=(?) WHERE prnumber=(?)`, [prNumber, parsed[0].prnumber]); + await conn.query(`UPDATE LIST SET time=(?) WHERE prnumber=(?)`, [prCreatedAt, parsed[0].prnumber]); + console.log(`Auto-added, replaced with new PR number and sent low priority message to #${prNumber} on https://github.com/${repoFullName} because it was found in the database.`); + } else if (parsed[0].username === prUsername && getNumberOfDays(parsed[0].time, date) >= numberOfDays) { + await appOctokit.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}', { + owner: repoOwner, + repo: repoName, + issue_number: prNumber, + name: "status: low priority", + }); + res = await conn.query("DELETE FROM LIST WHERE username=(?)", [parsed[0].username]); + console.log(`Removed #${prNumber} from https://github.com/${repoFullName} from the low priority database as well as the label.`); + } + } else if (resJson === '[]') { + return; + } + } finally { + if (conn) conn.end(); + } +}+ \ No newline at end of file diff --git a/src/webhooks/unlabeled.js b/src/webhooks/unlabeled.js @@ -0,0 +1,20 @@ +import { pool } from '../index'; + +export async function unlabeled(labelName, username, prNumber, repoFullName) { + let conn; + try { + if (labelName === "status: low priority") { + conn = await pool.getConnection(); + let res = await conn.query(`SELECT * FROM LIST WHERE username=(?)`, [username]); + let resJson = JSON.stringify(res); + if (resJson !== "[]") { + res = await conn.query("DELETE FROM LIST WHERE username=(?)", [username]); + console.log(`Removed #${prNumber} from https://github.com/${repoFullName} from the low priority database.`); + } else { + return; + } + } + } finally { + if (conn) conn.end(); + } +}+ \ No newline at end of file
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror