/** * 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: ~`, ], */ };