Spaces:
Running
Running
/** | |
* 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 += `<center><strong style="letter-spacing:1.2pt">${team.map(x => `<psicon pokemon="${x.species}" />`).join('')}`; | |
buf += `<br />${Utils.escapeHTML(teamName.toUpperCase())}</strong></center>`; | |
buf += Chat.getReadmoreCodeBlock(Teams.export(team).trim()); | |
} else { | |
buf += `<details><summary>${team.map(x => `<psicon pokemon="${x.species}" />`).join('')} – ${Utils.escapeHTML(teamName)}</summary>`; | |
buf += `<code style="white-space: pre-wrap; display: table; tab-size: 3">${Teams.export(team).trim().replace(/\n/g, '<br />')}</code></details>`; | |
} | |
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 += `<hr />`; | |
buf += `<details${Object.keys(teamData.teams[formatid]).length < 2 ? ` open` : ``}><summary><strong style="letter-spacing:1.2pt">${categoryName.toUpperCase()}</strong></summary>`; | |
for (const [i, teamName] of Object.keys(teamData.teams[formatid][categoryName]).entries()) { | |
if (i) buf += `<hr />`; | |
buf += SampleTeams.formatTeam(teamName, teamData.teams[formatid][categoryName][teamName], true); | |
} | |
buf += `</details>`; | |
} | |
if (!buf) { | |
throw new Chat.ErrorMessage(`No teams for ${SampleTeams.getFormatName(formatid)} found. Check spelling?`); | |
} else { | |
buf = `<center><h3>Sample Teams for ${SampleTeams.getFormatName(formatid)}</h3></center><hr />${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: ~`, | |
], */ | |
}; | |