Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions config/formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,23 @@ export const Formats: FormatList = [
'Lugia', 'Lunala', 'Magearna', 'Marshadow', 'Mewtwo', 'Naganadel', 'Necrozma-Dawn-Wings', 'Necrozma-Dusk-Mane', 'Palkia', 'Pheromosa',
'Rayquaza', 'Reshiram', 'Solgaleo', 'Xerneas', 'Yveltal', 'Zacian', 'Zamazenta', 'Zekrom', 'Zygarde-Base',
'Moody', 'Power Construct', 'Shadow Tag', 'Terrain Extender', 'Baton Pass',
'Damp Rock', 'Smooth Rock', 'Moody', 'Shadow Tag', 'Baton Pass',
],
},
{
name: "[Gen 8] Anything Goes",
threads: [
`&bullet; <a href="https://www.smogon.com/forums/threads/3656317/">Anything Goes</a>`,
],

mod: 'gen8',
searchShow: false,
ruleset: ['Obtainable', 'Team Preview', 'HP Percentage Mod', 'Cancel Mod', 'Endless Battle Clause'],
},
{
name: "[Gen 8] ZU",
threads: [
`&bullet; <a href="https://www.smogon.com/forums/threads/3661968/">ZU Metagame Discussion</a>`,
],
},
{
Expand Down
2 changes: 1 addition & 1 deletion data/abilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2816,7 +2816,7 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
}
},
name: "Quick Draw",
rating: 2.5,
rating: 2,
num: 259,
},
quickfeet: {
Expand Down
67 changes: 66 additions & 1 deletion lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
* - A lot of Chat functions are kind of iffy, but I'm going to say for now
* that if it's English-specific, it should be left out of here.
*/

import {Net} from './net';
import * as crypto from 'crypto';
type Comparable = number | string | boolean | Comparable[] | {reverse: Comparable};

export const Utils = new class Utils {
Expand Down Expand Up @@ -302,4 +303,68 @@ export const Utils = new class Utils {
// Step 7
return d[n][m];
}
async namecolor(name: string): Promise<string> {
const config = Net('https://play.pokemonshowdown.com/config/config.js');
const customcolors = await config.get().then(res => {
const idx = res.indexOf(' = {');
res = res.slice(idx + 4);
const colors: {[k: string]: string} = {};
for (const line of res.split('\n')) {
const [name, base] = line.split(':')
.map(item => item.split('//')[0])
.map(toID).filter(Boolean);
if (!base || !name) continue;
colors[name] = base;
}
return colors;
});
const HSLToRGB = (H: number, S: number, L: number) => {
let C = (100 - Math.abs(2 * L - 100)) * S / 100 / 100;
let X = C * (1 - Math.abs((H / 60) % 2 - 1));
let m = L / 100 - C / 2;

let R1;
let G1;
let B1;
switch (Math.floor(H / 60)) {
case 1: R1 = X; G1 = C; B1 = 0; break;
case 2: R1 = 0; G1 = C; B1 = X; break;
case 3: R1 = 0; G1 = X; B1 = C; break;
case 4: R1 = X; G1 = 0; B1 = C; break;
case 5: R1 = C; G1 = 0; B1 = X; break;
case 0: default: R1 = C; G1 = X; B1 = 0; break;
}
let R = R1 + m;
let G = G1 + m;
let B = B1 + m;
return {R, G, B};
}
if (customcolors[name]) return this.namecolor(customcolors[name]);
let hash = crypto.createHash('md5').update(name).digest('hex');
let H = parseInt(hash.substr(4, 4), 16) % 360; // 0 to 360
let S = parseInt(hash.substr(0, 4), 16) % 50 + 40; // 40 to 89
let L = Math.floor(parseInt(hash.substr(8, 4), 16) % 20 + 30); // 30 to 49

let {R, G, B} = HSLToRGB(H, S, L);
let lum = R * R * R * 0.2126 + G * G * G * 0.7152 + B * B * B * 0.0722; // 0.013 (dark blue) to 0.737 (yellow)

let HLmod = (lum - 0.2) * -150; // -80 (yellow) to 28 (dark blue)
if (HLmod > 18) HLmod = (HLmod - 18) * 2.5;
else if (HLmod < 0) HLmod = (HLmod - 0) / 3;
else HLmod = 0;
// let mod = ';border-right: ' + Math.abs(HLmod) + 'px solid ' + (HLmod > 0 ? 'red' : '#0088FF');
let Hdist = Math.min(Math.abs(180 - H), Math.abs(240 - H));
if (Hdist < 15) {
HLmod += (15 - Hdist) / 3;
}

L += HLmod;

let {R: r, G: g, B: b} = HSLToRGB(H, S, L);
const toHex = (x: number) => {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
},
"optionalDependencies": {
"cloud-env": "^0.2.3",
"githubhook": "^1.9.3",
"node-oom-heapdump": "^1.2.0",
"node-static": "^0.7.11",
"nodemailer": "^6.4.6",
"permessage-deflate": "^0.1.7",
Expand Down
1 change: 0 additions & 1 deletion server/chat-commands/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1623,7 +1623,6 @@ export const commands: ChatCommands = {
},

bugreport: 'bugs',
bugreports: 'bugs',
bugs(target, room, user) {
if (!this.runBroadcast()) return;
if (room?.battle) {
Expand Down
192 changes: 192 additions & 0 deletions server/chat-plugins/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/**
* Plugin to notify given rooms / the Development room on Pokemon Showdown
* of github updates.
* Some html / design from xfix's github bot, plugin by Mia.
* @author mia-pi-git
*/
// @ts-ignore old enough module it has no types
import * as githubhook from 'githubhook';
import {Utils} from '../../lib/utils';
import {FS} from '../../lib/fs';

const cooldown = 15 * 60 * 1000;
const rooms: RoomID[] = Config.github?.rooms ? Config.github.rooms : ['staff', 'upperstaff', 'development'];

export const github = githubhook({
port: (Config.github?.port || Config.port + 1),
secret: (Config.github?.secret || ''),
callback: '/server/chat-plugins/github.ts',
});

interface GithubData {
names: {[k: string]: string};
bans: string[];
repos: {[k: string]: string};
}

export let gitData: GithubData = {
bans: ['dependabot-preview[bot]'],
names: {},
repos: {
'pokemon-showdown': 'server',
'pokemon-showdown-client': 'client',
'Pokemon-Showdown-Dex': 'dex',
},
};

try {
gitData = JSON.parse(FS('config/chat-plugins/github.json').readIfExistsSync());
} catch (e) {};

github.on('push',
(repo: string, ref: string, result: AnyObject) => GithubParser.push(repo, ref, result)
);

github.on(
'pull_request',
(repo: string, ref: string, result: AnyObject) => GithubParser.pull(repo, ref, result)
);

export const GithubParser = new class {
pushes: AnyObject;
constructor() {
this.pushes = {};
}
shouldShow(id: string, repo: string) {
if (gitData.bans.includes(id)) return false;
if (!this.pushes[repo]) this.pushes[repo] = {};
if (Date.now() - this.pushes[repo][id] > cooldown) return true;
if (Date.now() - this.pushes[repo][id] < cooldown) this.pushes[repo][id] = Date.now();
return false;
}
repository(repo: string) {
if (gitData.repos[repo]) return gitData.repos[repo];
return repo;
}
save() {
FS('config/chat-plugins/github.json').writeUpdate(() => JSON.stringify(gitData));
}
report(html: string) {
Rooms.global.notifyRooms(rooms, `|c|&|/uhtml github,<div class="infobox">${html}</div>`);
}
push(repo: string, ref: string, result: AnyObject) {
const url = result.compare;
const buffer = result.commits.map((commit: AnyObject) => {
const message = commit.message;
const shortMessage = /.+/.exec(message)![0];
const id = commit.id.slice(0, 6);
const formattedRepo = `[<font color='FF00FF'>${this.repository(repo)}</font>]`;
const userName = Utils.html`<font color='909090'>(${this.getName(result.sender.login)})</font>`;
return `${formattedRepo} <a href="${url}"><font color='606060'>${id}</font></a> ${shortMessage} ${userName}`;
}).join('<br/ >');
if (!this.pushes[repo]) this.pushes[repo] = {};
if (!this.shouldShow(result.sender.login, repo)) return;
this.pushes[repo][result.pusher.name] = Date.now();
this.report(buffer);
}
pull(repo: string, ref: string, result: AnyObject) {
const committer = toID(result.sender.login);
if (gitData.bans.includes(committer)) return;
const num = result.pull_request.number;
const url = result.pull_request.html_url;
const title = result.pull_request.title;
const name = this.getName(result.sender.login);
if (!this.shouldShow(result.sender.login, repo)) return;
let action = result.action;
if (action === 'synchronize') {
action = 'updated';
}
if (action === 'review_requested') {
action = 'requested a review for';
}
const blacklisted = ['converted_to_draft', 'ready_for_review', 'converted_to_draft'];
if (blacklisted.includes(action)) {
return;
}
this.report(
`[<font color='FF00FF'>${repo}</font>] <font color='909090'>${name}</font> ` +
`${action} <a href="${url}">PR#${num}</a>: ${title}`
);
}
getName(name: string) {
if (gitData.names[name]) return gitData.names[name];
return name;
}
};

export const commands: ChatCommands = {
git: 'github',
gh: 'github',
github: {
ban(target, room, user) {
room = this.requireRoom('development');
this.checkCan('mute', null, room);
if (gitData.bans.includes(target)) return this.errorReply(`${target} is already gitbanned.`);
gitData.bans.push(target);
GithubParser.save();
this.privateModAction(`(${user.name} prevented ${target} from being reported by the plugin.)`);
return this.modlog('GITBAN', null, target);
},
unban(target, room, user) {
room = this.requireRoom('development');
this.checkCan('mute', null, room);
const index = gitData.bans.indexOf(target);
if (index < 0) return this.errorReply(`${target} is not gitbanned.`);
gitData.bans.splice(index, 1);
GithubParser.save();
this.privateModAction(`(${user.name} allowed ${target} to be reported by the plugin.)`);
return this.modlog('UNGITBAN', null, target);
},
bans(target, room, user) {
room = this.requireRoom('development');
this.checkCan('mute', null, room);
let buf = `<strong>IDs banned from being reported by the Github Plugin</strong>:<hr/ >`;
for (const id of gitData.bans) {
buf += `- ${id}<br/ >`;
}
return this.sendReplyBox(buf);
},
setname(target, room, user) {
room = this.requireRoom('development');
this.checkCan('mute', null, room);
const [oldID, newName] = target.split(',');
if (!target || !oldID || !newName) return this.errorReply(`Specify a GitHub username and new name.`);
if (gitData.names[oldID] === newName) return this.errorReply("That Git ID is already set to that name.");
gitData.names[oldID] = newName;
GithubParser.save();
this.privateModAction(`(${user.name} set ${oldID}'s name to be ${newName} in the Github Plugin.)`);
this.modlog(`GIT NAME`, null, `"${oldID}" to "${newName}"`);
},
resetname(target, room, user) {
room = this.requireRoom('development');
this.checkCan('mute', null, room);
if (!target) return this.errorReply(`Specify a name.`);
if (!gitData.names[target]) return this.errorReply("That Git ID does not have a name set.");
delete gitData.names[target];
GithubParser.save();
this.privateModAction(`(${user.name} reset ${target}'s name in the Github Plugin.)`);
return this.modlog(`GIT RESETNAME`, null, `${target}`);
},
reponame(target, room, user) {
room = this.requireRoom('development');
this.checkCan('mute', null, room);
const [oldRepo, newName] = Utils.splitFirst(target, ',');
if (!oldRepo || !newName) return this.errorReply("Specify a repo name and a new name to display.");
gitData.repos[oldRepo] = newName;
GithubParser.save();
this.privateModAction(`(${user.name} set the repo ${oldRepo}'s name in the Github Plugin to ${newName}.)`);
return this.modlog(`GIT REPONAME`, null, `${target}`);
},
resetreponame(target, room, user) {
room = this.requireRoom('development');
this.checkCan('mute', null, room);
if (!gitData.repos[target]) return this.errorReply(`${target} does not have a name in the Github Plugin.`);
delete gitData.repos[target];
GithubParser.save();
this.privateModAction(`(${user.name} reset the repo ${target}'s name in the Github Plugin.)`);
return this.modlog(`GIT RESETREPONAME`, null, `${target}`);
},
},
};

github.listen();
2 changes: 0 additions & 2 deletions server/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -803,10 +803,8 @@ export abstract class BasicRoom {
if (user.named) {
this.reportJoin('j', user.getIdentityWithStatus(this.roomid), user);
}

const staffIntro = this.getStaffIntroMessage(user);
if (staffIntro) this.sendUser(user, staffIntro);

this.users[user.id] = user;
this.userCount++;

Expand Down