mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-22 01:35:31 -05:00
279 lines
10 KiB
TypeScript
279 lines
10 KiB
TypeScript
/**
|
|
* Chat plugin to view GitHub events in a chatroom.
|
|
* By Mia, with design / html from xfix's original bot, https://github.com/xfix/GitHub-Bot-Legacy/
|
|
* @author mia-pi-git
|
|
*/
|
|
|
|
import {FS, Utils} from '../../lib';
|
|
|
|
const STAFF_REPOS = Config.staffrepos || [
|
|
'pokemon-showdown', 'pokemon-showdown-client', 'Pokemon-Showdown-Dex', 'pokemon-showdown-loginserver',
|
|
];
|
|
const COOLDOWN = 10 * 60 * 1000;
|
|
|
|
export const gitData: GitData = JSON.parse(FS("config/chat-plugins/github.json").readIfExistsSync() || "{}");
|
|
|
|
interface GitHookHandler {
|
|
on(event: 'pull_request', callback: (repo: string, ref: string | undefined, result: PullRequest) => void): void;
|
|
on(event: 'push', callback: (repo: string, ref: string, result: Push) => void): void;
|
|
on(event: string, callback: (repo: string, ref: string | undefined, result: any) => void): void;
|
|
listen(): void;
|
|
server: import('http').Server;
|
|
}
|
|
|
|
interface Push {
|
|
commits: Commit[];
|
|
sender: {login: string};
|
|
compare: string;
|
|
}
|
|
|
|
interface PullRequest {
|
|
action: string;
|
|
number: number;
|
|
pull_request: {
|
|
url: string,
|
|
html_url: string,
|
|
title: string,
|
|
user: {
|
|
login: string,
|
|
html_url: string,
|
|
},
|
|
merge_commit_sha: string,
|
|
};
|
|
sender: {login: string};
|
|
}
|
|
|
|
interface Commit {
|
|
id: string;
|
|
message: string;
|
|
author: {name: string, avatar_url: string};
|
|
url: string;
|
|
}
|
|
|
|
interface GitData {
|
|
usernames?: {[username: string]: string};
|
|
bans?: {[username: string]: string};
|
|
}
|
|
|
|
export const GitHub = new class {
|
|
readonly hook: GitHookHandler | null = null;
|
|
updates: {[k: string]: number} = Object.create(null);
|
|
constructor() {
|
|
// config.github: https://github.com/nlf/node-github-hook#readme
|
|
if (!Config.github) return;
|
|
|
|
try {
|
|
this.hook = require('githubhook')({
|
|
logger: {
|
|
log: (line: string) => Monitor.debug(line),
|
|
error: (line: string) => Monitor.notice(line),
|
|
},
|
|
...Config.github,
|
|
});
|
|
} catch (err) {
|
|
Monitor.crashlog(err, "GitHub hook");
|
|
}
|
|
this.listen();
|
|
}
|
|
listen() {
|
|
if (!this.hook) return;
|
|
this.hook.listen();
|
|
this.hook.on('push', (repo, ref, result) => this.handlePush(repo, ref, result));
|
|
this.hook.on('pull_request', (repo, ref, result) => this.handlePull(repo, ref, result));
|
|
}
|
|
private getRepoName(repo: string) {
|
|
switch (repo) {
|
|
case 'pokemon-showdown':
|
|
return 'server';
|
|
case 'pokemon-showdown-client':
|
|
return 'client';
|
|
case 'Pokemon-Showdown-Dex':
|
|
return 'dex';
|
|
default:
|
|
return repo.toLowerCase();
|
|
}
|
|
}
|
|
handlePush(repo: string, ref: string, result: Push) {
|
|
const branch = /[^/]+$/.exec(ref)?.[0] || "";
|
|
if (branch !== 'master') return;
|
|
const messages: {[k: string]: string[]} = {
|
|
staff: [],
|
|
development: [],
|
|
};
|
|
for (const commit of result.commits) {
|
|
const {message, url} = commit;
|
|
const [shortMessage] = message.split('\n\n');
|
|
const username = this.getUsername(commit.author.name);
|
|
const repoName = this.getRepoName(repo);
|
|
const id = commit.id.substring(0, 6);
|
|
messages.development.push(
|
|
Utils.html`[<span style="color:#FF00FF">${repoName}</span>] <a href="${url}" style="color:#606060">${id}</a> ${shortMessage} <span style="color:#909090">(${username})</span>`
|
|
);
|
|
messages.staff.push(Utils.html`[<span style="color:#FF00FF">${repoName}</span>] <a href="${url}">${shortMessage}</a> <span style="color:#909090">(${username})</span>`);
|
|
}
|
|
for (const k in messages) {
|
|
this.report(k as RoomID, repo, messages[k as RoomID]);
|
|
}
|
|
}
|
|
handlePull(repo: string, ref: string | undefined, result: PullRequest) {
|
|
if (this.isRateLimited(result.number)) return;
|
|
if (this.isGitbanned(result)) return;
|
|
const url = result.pull_request.html_url;
|
|
const action = this.isValidAction(result.action);
|
|
if (!action) return;
|
|
const repoName = this.getRepoName(repo);
|
|
const userName = this.getUsername(result.sender.login);
|
|
const title = result.pull_request.title;
|
|
let buf = Utils.html`[<span style="color:#FF00FF">${repoName}</span>] <span style="color:#909090">${userName}</span> `;
|
|
buf += Utils.html`${action} <a href="${url}">PR#${result.number}</a>: ${title}`;
|
|
this.report('development', repo, buf);
|
|
}
|
|
report(roomid: RoomID, repo: string, messages: string[] | string) {
|
|
if (!STAFF_REPOS.includes(repo) && roomid === 'staff') return;
|
|
if (Array.isArray(messages)) messages = messages.join('<br />');
|
|
Rooms.get(roomid)?.add(`|html|<div class="infobox">${messages}</div>`).update();
|
|
}
|
|
isGitbanned(result: PullRequest) {
|
|
if (!gitData.bans) return false;
|
|
return gitData.bans[result.sender.login] || gitData.bans[result.pull_request.user.login];
|
|
}
|
|
isRateLimited(prNumber: number) {
|
|
if (this.updates[prNumber]) {
|
|
if (this.updates[prNumber] + COOLDOWN > Date.now()) return true;
|
|
this.updates[prNumber] = Date.now();
|
|
return false;
|
|
}
|
|
this.updates[prNumber] = Date.now();
|
|
return false;
|
|
}
|
|
isValidAction(action: string) {
|
|
if (action === 'synchronize') return 'updated';
|
|
if (action === 'review_requested') {
|
|
return 'requested a review for';
|
|
} else if (action === 'review_request_removed') {
|
|
return 'removed a review request for';
|
|
}
|
|
if (['ready_for_review', 'labeled', 'unlabeled', 'converted_to_draft'].includes(action)) {
|
|
return null;
|
|
}
|
|
return action;
|
|
}
|
|
getUsername(name: string) {
|
|
return gitData.usernames?.[toID(name)] || name;
|
|
}
|
|
save() {
|
|
FS("config/chat-plugins/github.json").writeUpdate(() => JSON.stringify(gitData));
|
|
}
|
|
};
|
|
|
|
export const commands: Chat.ChatCommands = {
|
|
gh: 'github',
|
|
github: {
|
|
''() {
|
|
return this.parse('/help github');
|
|
},
|
|
ban(target, room, user) {
|
|
room = this.requireRoom('development');
|
|
this.checkCan('mute', null, room);
|
|
const [username, reason] = Utils.splitFirst(target, ',').map(u => u.trim());
|
|
if (!toID(target)) return this.parse(`/help github`);
|
|
if (!toID(username)) return this.errorReply("Provide a username.");
|
|
if (room.auth.has(toID(GitHub.getUsername(username)))) {
|
|
return this.errorReply("That user is Dev roomauth. If you need to do this, demote them and try again.");
|
|
}
|
|
if (!gitData.bans) gitData.bans = {};
|
|
if (gitData.bans[toID(username)]) {
|
|
return this.errorReply(`${username} is already gitbanned.`);
|
|
}
|
|
gitData.bans[toID(username)] = reason || " "; // to ensure it's truthy
|
|
GitHub.save();
|
|
this.privateModAction(`${user.name} banned the GitHub user ${username} from having their GitHub actions reported to this server.`);
|
|
this.modlog('GITHUB BAN', username, reason);
|
|
},
|
|
unban(target, room, user) {
|
|
room = this.requireRoom('development');
|
|
this.checkCan('mute', null, room);
|
|
target = toID(target);
|
|
if (!target) return this.parse('/help github');
|
|
if (!gitData.bans?.[target]) return this.errorReply("That user is not gitbanned.");
|
|
delete gitData.bans[target];
|
|
if (!Object.keys(gitData.bans).length) delete gitData.bans;
|
|
GitHub.save();
|
|
this.privateModAction(`${user.name} allowed the GitHub user ${target} to have their GitHub actions reported to this server.`);
|
|
this.modlog('GITHUB UNBAN', target);
|
|
},
|
|
bans() {
|
|
const room = this.requireRoom('development');
|
|
this.checkCan('mute', null, room);
|
|
return this.parse('/j view-github-bans');
|
|
},
|
|
setname: 'addusername',
|
|
addusername(target, room, user) {
|
|
room = this.requireRoom('development');
|
|
this.checkCan('mute', null, room);
|
|
const [gitName, username] = Utils.splitFirst(target, ',').map(u => u.trim());
|
|
if (!toID(gitName) || !toID(username)) return this.parse(`/help github`);
|
|
if (!gitData.usernames) gitData.usernames = {};
|
|
gitData.usernames[toID(gitName)] = username;
|
|
GitHub.save();
|
|
this.privateModAction(`${user.name} set ${gitName}'s name on reported GitHub actions to be ${username}.`);
|
|
this.modlog('GITHUB SETNAME', null, `'${gitName}' to '${username}'`);
|
|
},
|
|
clearname: 'removeusername',
|
|
removeusername(target, room, user) {
|
|
room = this.requireRoom('development');
|
|
this.checkCan('mute', null, room);
|
|
target = toID(target);
|
|
if (!target) return this.parse(`/help github`);
|
|
const name = gitData.usernames?.[target];
|
|
if (!name) return this.errorReply(`${target} is not a GitHub username on our list.`);
|
|
delete gitData.usernames?.[target];
|
|
if (!Object.keys(gitData.usernames || {}).length) delete gitData.usernames;
|
|
GitHub.save();
|
|
this.privateModAction(`${user.name} removed ${target}'s name from the GitHub username list.`);
|
|
this.modlog('GITHUB CLEARNAME', target, `from the name ${name}`);
|
|
},
|
|
names() {
|
|
return this.parse('/j view-github-names');
|
|
},
|
|
},
|
|
githubhelp: [
|
|
`/github ban [username], [reason] - Bans a GitHub user from having their GitHub actions reported to Dev room. Requires: % @ # &`,
|
|
`/github unban [username] - Unbans a GitHub user from having their GitHub actions reported to Dev room. Requires: % @ # &`,
|
|
`/github bans - Lists all GitHub users that are currently gitbanned. Requires: % @ # &`,
|
|
`/github setname [username], [name] - Sets a GitHub user's name on reported GitHub actions to be [name]. Requires: % @ # &`,
|
|
`/github clearname [username] - Removes a GitHub user's name from the GitHub username list. Requires: % @ # &`,
|
|
`/github names - Lists all GitHub usernames that are currently on our list.`,
|
|
],
|
|
};
|
|
|
|
export const pages: Chat.PageTable = {
|
|
github: {
|
|
bans(query, user) {
|
|
const room = Rooms.get('development');
|
|
if (!room) return this.errorReply("No Development room found.");
|
|
this.checkCan('mute', null, room);
|
|
if (!gitData.bans) return this.errorReply("There are no gitbans at this time.");
|
|
let buf = `<div class="pad"><h2>Current Gitbans:</h2><hr /><ol>`;
|
|
for (const [username, reason] of Object.entries(gitData.bans)) {
|
|
buf += `<li><strong>${username}</strong> - ${reason.trim() || '(No reason found)'}</li>`;
|
|
}
|
|
buf += `</ol>`;
|
|
return buf;
|
|
},
|
|
names() {
|
|
if (!gitData.usernames) return this.errorReply("There are no GitHub usernames in the list.");
|
|
let buf = `<div class="pad"><h2>Current GitHub username mappings:</h2><hr /><ol>`;
|
|
for (const [username, name] of Object.entries(gitData.usernames)) {
|
|
buf += `<li><strong>${username}</strong> - ${name}</li>`;
|
|
}
|
|
buf += `</ol>`;
|
|
return buf;
|
|
},
|
|
},
|
|
};
|
|
|
|
export function destroy() {
|
|
GitHub.hook?.server.close();
|
|
}
|