/**
* A sample teams plugin
*
* @author Kris
*/
import { FS, Utils } from "../../lib";
const SAMPLE_TEAMS = 'config/chat-plugins/sample-teams.json';
interface SampleTeamsData {
whitelist: { [formatid: string]: RoomID[] };
/** Teams are stored in the packed format */
teams: {
[formatid: string]: {
uncategorized: { [k: string]: string },
[category: string]: { [teamName: string]: string },
},
};
}
export const teamData: SampleTeamsData = (() => {
try {
return JSON.parse(FS(SAMPLE_TEAMS).readIfExistsSync());
} catch {
return {
whitelist: {},
teams: {},
};
}
})();
function save() {
FS(SAMPLE_TEAMS).writeUpdate(() => JSON.stringify(teamData));
}
for (const formatid in teamData.teams) {
if (!teamData.teams[formatid].uncategorized) teamData.teams[formatid].uncategorized = {};
}
save();
export const SampleTeams = new class SampleTeams {
private isRoomStaff(user: User, roomids: RoomID[]) {
let matched = false;
if (!roomids?.length) return false;
for (const roomid of roomids) {
const room = Rooms.search(roomid);
// Malformed entry from botched room rename
if (!room) continue;
matched = room.auth.isStaff(user.id);
if (matched) break;
}
return matched;
}
isDevMod(user: User) {
return !!Rooms.get('development')?.auth.atLeast(user, '@');
}
checkPermissions(user: User, roomids: RoomID[]) {
// Give Development room mods access to help fix crashes
return this.isRoomStaff(user, roomids) || user.can('bypassall') || this.isDevMod(user);
}
whitelistedRooms(formatid: string, names = false) {
formatid = this.sanitizeFormat(formatid);
if (!teamData.whitelist[formatid]?.length) return null;
return Utils.sortBy(teamData.whitelist[formatid], x => {
if (!names) return x;
const room = Rooms.search(x);
if (!room) return x;
return room.title;
});
}
whitelistRooms(formatids: string[], roomids: string[]) {
for (const unsanitizedFormatid of formatids) {
const formatid = this.sanitizeFormat(unsanitizedFormatid);
if (!teamData.whitelist[formatid]) teamData.whitelist[formatid] = [];
for (const roomid of roomids) {
const targetRoom = Rooms.search(roomid);
if (!targetRoom?.persist) {
throw new Chat.ErrorMessage(`Room ${roomid} not found. Check spelling?`);
}
if (teamData.whitelist[formatid].includes(targetRoom.roomid)) {
throw new Chat.ErrorMessage(`Room ${targetRoom.title} is already added.`);
}
teamData.whitelist[formatid].push(targetRoom.roomid);
save();
}
}
}
unwhitelistRoom(formatid: string, roomid: string) {
formatid = this.sanitizeFormat(formatid, false);
const targetRoom = Rooms.search(roomid);
if (!targetRoom?.persist) throw new Chat.ErrorMessage(`Room ${roomid} not found. Check spelling?`);
if (!teamData.whitelist[formatid]?.length) throw new Chat.ErrorMessage(`No rooms are whitelisted for ${formatid}.`);
if (!teamData.whitelist[formatid].includes(targetRoom.roomid)) {
throw new Chat.ErrorMessage(`Room ${targetRoom.title} isn't whitelisted.`);
}
const index = teamData.whitelist[formatid].indexOf(targetRoom.roomid);
teamData.whitelist[formatid].splice(index, 1);
if (!teamData.whitelist[formatid].length) delete teamData.whitelist[formatid];
save();
}
sanitizeFormat(formatid: string, checkExists = false) {
const format = Dex.formats.get(formatid);
if (checkExists && format.effectType !== 'Format') {
throw new Chat.ErrorMessage(`Format "${formatid.trim()}" not found. Check spelling?`);
}
if (format.team) {
throw new Chat.ErrorMessage(`Formats with computer-generated teams can't have team storage.`);
}
return format.id;
}
initializeFormat(formatid: string) {
if (!teamData.teams[formatid]) {
teamData.teams[formatid] = { uncategorized: {} };
save();
}
}
addCategory(user: User, formatid: string, category: string) {
formatid = this.sanitizeFormat(formatid);
if (!this.checkPermissions(user, teamData.whitelist[formatid])) {
let rankNeeded = `a global administrator`;
if (teamData.whitelist[formatid]) {
rankNeeded = `staff in ${Chat.toListString(teamData.whitelist[formatid], "or")}`;
}
throw new Chat.ErrorMessage(`Access denied. You need to be ${rankNeeded} to add teams for ${formatid}`);
}
category = category.trim();
this.initializeFormat(formatid);
if (this.findCategory(formatid, category)) {
throw new Chat.ErrorMessage(`The category named ${category} already exists.`);
}
teamData.teams[formatid][category] = {};
save();
}
removeCategory(user: User, formatid: string, category: string) {
formatid = this.sanitizeFormat(formatid, false);
if (!this.checkPermissions(user, teamData.whitelist[formatid])) {
let rankNeeded = `a global administrator`;
if (teamData.whitelist[formatid]) {
rankNeeded = `staff in ${Chat.toListString(teamData.whitelist[formatid], "or")}`;
}
throw new Chat.ErrorMessage(`Access denied. You need to be ${rankNeeded} to add teams for ${formatid}`);
}
const categoryName = this.findCategory(formatid, category);
if (!categoryName) {
throw new Chat.ErrorMessage(`There's no category named "${category.trim()}" for the format ${formatid}.`);
}
delete teamData.teams[formatid][categoryName];
save();
}
/**
* @param user
* @param formatid
* @param teamName
* @param team - Can be a team in the packed, JSON, or exported format
* @param category - Category the team will go in, defaults to uncategorized
*/
addTeam(user: User, formatid: string, teamName: string, team: string, category = "uncategorized") {
formatid = this.sanitizeFormat(formatid);
if (!this.checkPermissions(user, teamData.whitelist[formatid])) {
let rankNeeded = `a global administrator`;
if (teamData.whitelist[formatid]?.length) {
rankNeeded = `staff in ${Chat.toListString(teamData.whitelist[formatid], "or")}`;
}
throw new Chat.ErrorMessage(`Access denied. You need to be ${rankNeeded} to add teams for ${formatid}`);
}
teamName = teamName.trim();
category = category.trim();
this.initializeFormat(formatid);
if (this.findTeamName(formatid, category, teamName)) {
throw new Chat.ErrorMessage(`There is already a team for ${formatid} with the name ${teamName} in the ${category} category.`);
}
if (!teamData.teams[formatid][category]) this.addCategory(user, formatid, category);
teamData.teams[formatid][category][teamName] = Teams.pack(Teams.import(team.trim()));
save();
return teamData.teams[formatid][category][teamName];
}
removeTeam(user: User, formatid: string, teamid: string, category: string) {
formatid = this.sanitizeFormat(formatid, false);
category = category.trim();
if (!this.checkPermissions(user, teamData.whitelist[formatid])) {
let required = `an administrator`;
if (teamData.whitelist[formatid]) {
required = `staff in ${Chat.toListString(teamData.whitelist[formatid], "or")}`;
}
throw new Chat.ErrorMessage(`Access denied. You need to be ${required} to add teams for ${formatid}`);
}
const categoryName = this.findCategory(formatid, category);
if (!categoryName) {
throw new Chat.ErrorMessage(`There are no teams for ${formatid} under the category ${category.trim()}. Check spelling?`);
}
const teamName = this.findTeamName(formatid, category, teamid);
if (!teamName) {
throw new Chat.ErrorMessage(`There is no team for ${formatid} with the name of "${teamid}". Check spelling?`);
}
const oldTeam = teamData.teams[formatid][categoryName][teamName];
delete teamData.teams[formatid][categoryName][teamName];
if (!Object.keys(teamData.teams[formatid][categoryName]).length) delete teamData.teams[formatid][categoryName];
if (!Object.keys(teamData.teams[formatid]).filter(x => x !== 'uncategorized').length) delete teamData.teams[formatid];
save();
return oldTeam;
}
formatTeam(teamName: string, teamStr: string, broadcasting = false) {
const team = Teams.unpack(teamStr);
if (!team) return `Team is not correctly formatted. PM room staff to fix the formatting.`;
let buf = ``;
if (!broadcasting) {
buf += `
${team.map(x => ``).join('')}`;
buf += `
${Utils.escapeHTML(teamName.toUpperCase())}`;
buf += Chat.getReadmoreCodeBlock(Teams.export(team).trim());
} else {
buf += `${team.map(x => ``).join('')} – ${Utils.escapeHTML(teamName)}
`;
buf += `${Teams.export(team).trim().replace(/\n/g, '
')}
`;
}
return buf;
}
modlog(context: Chat.CommandContext, formatid: string, action: string, note: string, log: string) {
formatid = this.sanitizeFormat(formatid);
const whitelistedRooms = this.whitelistedRooms(formatid);
if (whitelistedRooms?.length) {
for (const roomid of whitelistedRooms) {
const room = Rooms.get(roomid);
if (!room) continue;
context.room = room;
context.modlog(action, null, `${formatid}: ${note}`);
context.privateModAction(log);
}
} else {
context.room = Rooms.get('staff') || null;
context.globalModlog(action, null, `${formatid}: ${note}`);
context.privateGlobalModAction(log);
}
}
/**
* Returns the category name of the provided category ID if there is one.
*/
findCategory(formatid: string, categoryid: string) {
formatid = toID(formatid);
categoryid = toID(categoryid);
let match: string | null = null;
for (const categoryName in teamData.teams[formatid] || {}) {
if (toID(categoryName) === categoryid) {
match = categoryName;
break;
}
}
return match;
}
findTeamName(formatid: string, categoryid: string, teamid: string) {
const categoryName = this.findCategory(formatid, categoryid);
if (!categoryName) return null;
let match: string | null = null;
for (const teamName in teamData.teams[formatid][categoryName] || {}) {
if (toID(teamName) === teamid) {
match = teamName;
break;
}
}
return match;
}
getFormatName(formatid: string) {
return Dex.formats.get(formatid).exists ? Dex.formats.get(formatid).name : formatid;
}
destroy() {
for (const formatid in teamData.whitelist) {
for (const [i, roomid] of teamData.whitelist[formatid].entries()) {
const room = Rooms.search(roomid);
if (room) continue;
teamData.whitelist[formatid].splice(i, 1);
if (!teamData.whitelist[formatid].length) delete teamData.whitelist[formatid];
save();
}
}
}
};
export const destroy = SampleTeams.destroy;
export const handlers: Chat.Handlers = {
onRenameRoom(oldID, newID) {
for (const formatid in teamData.whitelist) {
if (!SampleTeams.whitelistedRooms(formatid)?.includes(oldID)) continue;
SampleTeams.unwhitelistRoom(formatid, oldID);
SampleTeams.whitelistRooms([formatid], [newID]);
}
},
};
export const commands: Chat.ChatCommands = {
sampleteams(target, room, user) {
this.sendReply(`Sample Teams are being deprecated. Please do \`\`/tier [formatid]\`\` to view their resources.`);
this.sendReply(`If you are room staff for a room and would like to access old samples for your room's formats, please contact an admin.`);
},
/* sampleteams: {
''(target, room, user) {
this.runBroadcast();
let [formatid, category] = target.split(',');
if (!this.broadcasting) {
if (!formatid) return this.parse(`/j view-sampleteams-view`);
formatid = SampleTeams.sanitizeFormat(formatid);
if (!category) return this.parse(`/j view-sampleteams-view-${formatid}`);
const categoryName = SampleTeams.findCategory(formatid, category);
return this.parse(`/j view-sampleteams-view-${formatid}${categoryName ? '-' + toID(categoryName) : ''}`);
}
if (!formatid) return this.parse(`/help sampleteams`);
formatid = SampleTeams.sanitizeFormat(formatid);
if (!teamData.teams[formatid]) {
throw new Chat.ErrorMessage(`No teams for ${SampleTeams.getFormatName(formatid)} found. Check spelling?`);
}
let buf = ``;
if (!category) {
for (const categoryName in teamData.teams[formatid]) {
if (!Object.keys(teamData.teams[formatid][categoryName]).length) continue;
if (buf) buf += `
`;
buf += `${categoryName.toUpperCase()}
`;
for (const [i, teamName] of Object.keys(teamData.teams[formatid][categoryName]).entries()) {
if (i) buf += `
`;
buf += SampleTeams.formatTeam(teamName, teamData.teams[formatid][categoryName][teamName], true);
}
buf += ` `;
}
if (!buf) {
throw new Chat.ErrorMessage(`No teams for ${SampleTeams.getFormatName(formatid)} found. Check spelling?`);
} else {
buf = `Sample Teams for ${SampleTeams.getFormatName(formatid)}
${buf}`;
}
} else {
const categoryName = SampleTeams.findCategory(formatid, category);
if (!categoryName) {
throw new Chat.ErrorMessage(`No teams for ${SampleTeams.getFormatName(formatid)} in the ${category} category found. Check spelling?`);
}
for (const teamName in teamData.teams[formatid][categoryName]) {
buf += SampleTeams.formatTeam(teamName, teamData.teams[formatid][categoryName][teamName], true);
}
}
this.sendReplyBox(buf);
},
addcategory(target, room, user) {
let [formatid, categoryName] = target.split(',');
if (!(formatid && categoryName)) return this.parse(`/help sampleteams`);
if (!categoryName.trim()) categoryName = "uncategorized";
SampleTeams.addCategory(user, formatid, categoryName.trim());
SampleTeams.modlog(
this, formatid, 'ADDTEAMCATEGORY', categoryName.trim(),
`${user.name} added ${categoryName.trim()} as a category for ${formatid}.`
);
this.sendReply(`Added ${categoryName.trim()} as a category for ${formatid}.`);
},
removecategory(target, room, user) {
const [formatid, categoryName] = target.split(',');
if (!(formatid && categoryName)) return this.parse(`/help sampleteams`);
if (toID(categoryName) === 'uncategorized') {
throw new Chat.ErrorMessage(`The uncategorized category cannot be removed.`);
}
SampleTeams.removeCategory(user, formatid, categoryName);
SampleTeams.modlog(
this, formatid, 'REMOVETEAMCATEGORY', categoryName.trim(),
`${user.name} removed ${categoryName.trim()} as a category for ${formatid}.`
);
this.sendReply(`Removed ${categoryName.trim()} as a category for ${formatid}.`);
},
add(target, room, user) {
const [formatid, category, teamName, team] = target.split(',');
if (!(formatid && category?.trim() && teamName && team)) return this.parse('/j view-sampleteams-add');
const packedTeam = SampleTeams.addTeam(user, formatid, teamName, team, category);
SampleTeams.modlog(
this, formatid, 'ADDTEAM', `${category}: ${teamName}: ${packedTeam}`,
`${user.name} added a team for ${formatid} in the ${category} category.`
);
this.sendReply(`Added a team for ${formatid} in the ${category} category.`);
},
remove(target, room, user) {
const [formatid, category, teamName] = target.split(',').map(x => x.trim());
if (!(formatid && category && teamName)) return this.parse(`/help sampleteams`);
const team = SampleTeams.removeTeam(user, formatid, toID(teamName), category);
SampleTeams.modlog(
this, formatid, 'REMOVETEAM', `${category}: ${teamName}: ${team}`,
`${user.name} removed a team from ${formatid} in the ${category} category.`
);
this.sendReply(`Removed a team from ${formatid} in the ${category} category.`);
},
whitelist: {
add(target, room, user) {
// Allow development roommods to whitelist to help debug
if (!SampleTeams.isDevMod(user)) {
this.checkCan('bypassall');
}
const [formatids, roomids] = target.split('|').map(x => x.split(','));
if (!(formatids?.length && roomids?.length)) return this.parse(`/help sampleteams`);
SampleTeams.whitelistRooms(formatids, roomids);
this.privateGlobalModAction(`${user.name} whitelisted ${Chat.toListString(roomids.map(x => Rooms.search(x)!.title))} to handle sample teams for ${Chat.toListString(formatids)}.`);
this.globalModlog(`SAMPLETEAMS WHITELIST`, null, roomids.join(', '));
this.sendReply(`Whitelisted ${Chat.toListString(roomids)} to handle sample teams for ${Chat.toListString(formatids)}.`);
},
remove(target, room, user) {
// Allow development roommods to whitelist to help debug
if (!SampleTeams.isDevMod(user)) {
this.checkCan('bypassall');
}
const [formatid, roomid] = target.split(',');
if (!(formatid && roomid)) return this.parse(`/help sampleteams`);
SampleTeams.unwhitelistRoom(formatid, roomid);
this.refreshPage('sampleteams-whitelist');
},
'': 'view',
view(target, room, user) {
// Allow development roommods to whitelist to help debug
if (!SampleTeams.isDevMod(user)) {
this.checkCan('bypassall');
}
this.parse(`/j view-sampleteams-whitelist`);
},
},
},
sampleteamshelp: [
`/sampleteams [format] - Lists the sample teams for [format], if there are any.`,
`/sampleteams addcategory/removecategory [format], [category] - Adds/removes categories for [format].`,
`/sampleteams add - Pulls up the interface to add sample teams. Requires: Room staff in dedicated tier room, ~`,
`/sampleteams remove [format], [category], [team name] - Removes a sample team for [format] in [category].`,
`/sampleteams whitelist add [formatid], [formatid] | [roomid], [roomid], ... - Whitelists room staff for the provided roomids to add sample teams. Requires: ~`,
`/sampleteams whitelist remove [formatid], [roomid] - Unwhitelists room staff for the provided room to add sample teams. Requires: ~`,
], */
};