diff --git a/config/config-example.js b/config/config-example.js
index 08b9e8f216..d53152b675 100644
--- a/config/config-example.js
+++ b/config/config-example.js
@@ -486,7 +486,8 @@ exports.chatlogreader = 'fs';
* - makeroom: Create/delete chatrooms, and set modjoin/roomdesc/privacy
* - editroom: Editing properties of rooms
* - editprivacy: Set modjoin/privacy only for battles
- * - ban: Banning and unbanning.
+ * - globalban: Banning and unbanning from the entire server.
+ * - ban: Banning and unbanning in rooms.
* - mute: Muting and unmuting.
* - lock: locking (ipmute) and unlocking.
* - receivemutedpms: Receive PMs from muted users.
@@ -494,7 +495,8 @@ exports.chatlogreader = 'fs';
* - ip: IP checking.
* - alts: Alt checking.
* - modlog: view the moderator logs.
- * - broadcast: Broadcast informational commands.
+ * - show: Show command output to other users.
+ * - showmedia: Show images and videos to other users.
* - declare: /declare command.
* - announce: /announce command.
* - modchat: Set modchat.
@@ -509,19 +511,17 @@ exports.chatlogreader = 'fs';
*/
exports.grouplist = [
{
- symbol: '~',
+ symbol: '&',
id: "admin",
name: "Administrator",
- root: true,
- globalonly: true,
- },
- {
- symbol: '&',
- id: "leader",
- name: "Leader",
inherit: '@',
jurisdiction: 'u',
- promote: 'u',
+ globalonly: true,
+
+ console: true,
+ bypassall: true,
+ lockdown: true,
+ promote: '&u',
roomowner: true,
roombot: true,
roommod: true,
@@ -531,12 +531,12 @@ exports.grouplist = [
rangeban: true,
makeroom: true,
editroom: true,
+ editprivacy: true,
potd: true,
disableladder: true,
- globalonly: true,
+ gdeclare: true,
gamemanagement: true,
exportinputlog: true,
- editprivacy: true,
},
{
symbol: '#',
@@ -544,47 +544,57 @@ exports.grouplist = [
name: "Room Owner",
inherit: '@',
jurisdiction: 'u',
+ roomonly: true,
+
roombot: true,
roommod: true,
roomdriver: true,
editroom: true,
declare: true,
addhtml: true,
- roomonly: true,
gamemanagement: true,
},
+ {
+ symbol: '*',
+ id: "bot",
+ name: "Bot",
+ inherit: '%',
+ jurisdiction: 'u',
+
+ addhtml: true,
+ declare: true,
+ bypassafktimer: true,
+
+ ip: false,
+ globalban: false,
+ lock: false,
+ alts: false,
+ },
{
symbol: '\u2605',
id: "host",
name: "Host",
inherit: '@',
jurisdiction: 'u',
- declare: true,
- addhtml: true,
- modchat: true,
roomonly: true,
+
+ declare: true,
+ modchat: true,
gamemanagement: true,
joinbattle: true,
},
- {
- symbol: '*',
- id: "bot",
- name: "Bot",
- inherit: '@',
- jurisdiction: 'u',
- declare: true,
- addhtml: true,
- bypassafktimer: true,
- },
{
symbol: '@',
id: "mod",
name: "Moderator",
inherit: '%',
jurisdiction: 'u',
+
+ globalban: true,
ban: true,
modchatall: true,
roomvoice: true,
+ roomwhitelist: true,
forcerename: true,
ip: true,
alts: '@u',
@@ -597,10 +607,12 @@ exports.grouplist = [
name: "Driver",
inherit: '+',
jurisdiction: 'u',
+ globalGroupInPersonalRoom: '@',
+
announce: true,
- warn: '\u2606u',
+ warn: '\u2605u',
kick: true,
- mute: '\u2606u',
+ mute: '\u2605u',
lock: true,
forcerename: true,
timer: true,
@@ -619,22 +631,29 @@ exports.grouplist = [
id: "player",
name: "Player",
inherit: '+',
+ battleonly: true,
+
roomvoice: true,
modchat: true,
- roomonly: true,
+ editprivacy: true,
+ gamemanagement: true,
+ tournaments: true,
joinbattle: true,
nooverride: true,
- editprivacy: true,
- exportinputlog: true,
},
{
symbol: '+',
id: "voice",
name: "Voice",
inherit: ' ',
+
alts: 's',
- broadcast: true,
+ makegroupchat: true,
+ joinbattle: true,
+ show: true,
showmedia: true,
+ exportinputlog: true,
+ importinputlog: true,
},
{
symbol: 'whitelist',
@@ -643,12 +662,14 @@ exports.grouplist = [
inherit: ' ',
roomonly: true,
alts: 's',
- broadcast: true,
- importinputlog: true,
+ show: true,
showmedia: true,
+ exportinputlog: true,
+ importinputlog: true,
},
{
symbol: ' ',
+ ip: 's',
},
{
name: 'Locked',
diff --git a/server/chat-commands/info.ts b/server/chat-commands/info.ts
index 3236854ac9..96954369ac 100644
--- a/server/chat-commands/info.ts
+++ b/server/chat-commands/info.ts
@@ -19,10 +19,12 @@ export const commands: ChatCommands = {
alt: 'whois',
alts: 'whois',
whoare: 'whois',
+ altsnorecurse: 'whois',
whois(target, room: Room | null, user, connection, cmd) {
if (room?.roomid === 'staff' && !this.runBroadcast()) return;
const targetUser = this.targetUserOrSelf(target, user.group === ' ');
- const showAll = (cmd === 'ip' || cmd === 'whoare' || cmd === 'alt' || cmd === 'alts');
+ const showAll = (cmd === 'ip' || cmd === 'whoare' || cmd === 'alt' || cmd === 'alts' || cmd === 'altsnorecurse');
+ const showRecursiveAlts = showAll && (cmd !== 'altsnorecurse');
if (!targetUser) {
if (showAll) return this.parse('/offlinewhois ' + target);
return this.errorReply("User " + this.targetUsername + " not found.");
@@ -159,7 +161,7 @@ export const commands: ChatCommands = {
ips = ips.map(ip => {
const status = [];
const punishment = Punishments.ips.get(ip);
- if (user.can('ip') && punishment) {
+ if (user.can('globalban') && punishment) {
const [punishType, userid] = punishment;
let punishMsg = Punishments.punishmentTypes.get(punishType) || 'punished';
if (userid !== targetUser.id) punishMsg += ` as ${userid}`;
@@ -227,6 +229,15 @@ export const commands: ChatCommands = {
}
}
this.sendReplyBox(buf);
+
+ if (showRecursiveAlts && canViewAlts) {
+ const targetId = toID(target);
+ for (const alt of Users.users.values()) {
+ if (alt !== targetUser && targetId in alt.prevNames) {
+ this.parse(`/altsnorecurse ${alt.name}`);
+ }
+ }
+ }
},
whoishelp: [
`/whois - Get details on yourself: alts, group, IP address, and rooms.`,
@@ -351,7 +362,7 @@ export const commands: ChatCommands = {
'!host': true,
host(target, room, user, connection, cmd) {
if (!target) return this.parse('/help host');
- if (!this.can('ip')) return;
+ if (!this.can('globalban')) return;
target = target.trim();
if (!net.isIPv4(target)) return this.errorReply('You must pass a valid IPv4 IP to /host.');
void IPTools.lookup(target).then(({dnsbl, host, hostType}) => {
diff --git a/server/chat-commands/moderation.ts b/server/chat-commands/moderation.ts
index 1e7e86a7ca..bb9a6fd96a 100644
--- a/server/chat-commands/moderation.ts
+++ b/server/chat-commands/moderation.ts
@@ -844,7 +844,7 @@ export const commands: ChatCommands = {
unlockip(target, room, user) {
target = target.trim();
if (!target) return this.parse('/help unlock');
- if (!this.can('ban')) return false;
+ if (!this.can('globalban')) return false;
const range = target.charAt(target.length - 1) === '*';
if (range && !this.can('rangeban')) return false;
@@ -890,7 +890,7 @@ export const commands: ChatCommands = {
if (!target && REQUIRE_REASONS) {
return this.errorReply("Global bans require a reason.");
}
- if (!this.can('ban', targetUser)) return false;
+ if (!this.can('globalban', targetUser)) return false;
const name = targetUser.getLastName();
const userid = targetUser.getLastId();
@@ -963,7 +963,7 @@ export const commands: ChatCommands = {
globalunban: 'unglobalban',
unglobalban(target, room, user) {
if (!target) return this.parse(`/help unglobalban`);
- if (!this.can('ban')) return false;
+ if (!this.can('globalban')) return false;
const name = Punishments.unban(target);
@@ -1851,7 +1851,7 @@ export const commands: ChatCommands = {
}
}
- if (user.can('ban')) {
+ if (user.can('globalban')) {
const roomIps = Punishments.roomIps.get(room.roomid);
if (roomIps) {
@@ -1888,7 +1888,7 @@ export const commands: ChatCommands = {
markshared(target, room, user) {
if (!target) return this.parse('/help markshared');
- if (!this.can('ban')) return false;
+ if (!this.can('globalban')) return false;
let [ip, note] = this.splitOne(target);
if (!/^[0-9.*]+$/.test(ip)) return this.errorReply("Please enter a valid IP address.");
@@ -1916,7 +1916,7 @@ export const commands: ChatCommands = {
unmarkshared(target, room, user) {
if (!target) return this.parse('/help unmarkshared');
- if (!this.can('ban')) return false;
+ if (!this.can('globalban')) return false;
if (!/^[0-9.*]+$/.test(target)) return this.errorReply("Please enter a valid IP address.");
if (!Punishments.sharedIps.has(target)) return this.errorReply("This IP isn't marked as shared.");
diff --git a/server/chat-plugins/lottery.ts b/server/chat-plugins/lottery.ts
index ce98b2c256..847c6208fa 100644
--- a/server/chat-plugins/lottery.ts
+++ b/server/chat-plugins/lottery.ts
@@ -234,7 +234,7 @@ export const commands: ChatCommands = {
if (!lottery) {
return this.errorReply('This room does not have a lottery running.');
}
- const canSeeIps = user.can('ban');
+ const canSeeIps = user.can('globalban');
const participants = Object.entries(lottery.participants).map(([ip, participant]) => {
return `- ${participant}${canSeeIps ? ' (IP: ' + ip + ')' : ''}`;
});
diff --git a/server/chat-plugins/mafia.ts b/server/chat-plugins/mafia.ts
index eb5edc372e..0f7652a239 100644
--- a/server/chat-plugins/mafia.ts
+++ b/server/chat-plugins/mafia.ts
@@ -1939,7 +1939,7 @@ export const commands: ChatCommands = {
if (!this.canTalk()) return;
if (!room || room.type !== 'chat') return this.errorReply(`This command is only meant to be used in chat rooms.`);
if (room.game) return this.errorReply(`There is already a game of ${room.game.title} in progress in this room.`);
- if (!user.can('broadcast', null, room)) return this.errorReply(`/mafia ${cmd} - Access denied.`);
+ if (!user.can('show', null, room)) return this.errorReply(`/mafia ${cmd} - Access denied.`);
let nextHost = false;
if (room.roomid === 'mafia') {
@@ -2006,7 +2006,7 @@ export const commands: ChatCommands = {
if (room.roomid !== 'mafia') return this.errorReply(`This command can only be used in the Mafia room.`);
const args = target.split(',').map(toID);
if (['forceadd', 'add', 'remove', 'del', 'delete'].includes(args[0])) {
- const permission = (user.id === args[1]) ? 'broadcast' : 'mute';
+ const permission = (user.id === args[1]) ? 'show' : 'mute';
if (['forceadd', 'add'].includes(args[0]) && !this.can(permission, null, room)) return;
if (['remove', 'del', 'delete'].includes(args[0]) && user.id !== args[1] && !this.can('mute', null, room)) return;
} else {
@@ -2220,7 +2220,7 @@ export const commands: ChatCommands = {
idea(target, room, user) {
const game = room.getGame(MafiaTracker);
if (!game) return this.errorReply(`There is no game of mafia running in this room.`);
- if (!this.can('broadcast', null, room)) return;
+ if (!this.can('show', null, room)) return;
if (!user.can('mute', null, room) && game.hostid !== user.id && !game.cohosts.includes(user.id)) {
return this.errorReply(`/mafia idea - Access denied.`);
}
@@ -3127,7 +3127,7 @@ export const commands: ChatCommands = {
}
const game = targetRoom.getGame(MafiaTracker);
if (!game) return user.sendTo(targetRoom, `|error|There is no game of mafia running in this room.`);
- if (game.hostid !== user.id && !game.cohosts.includes(user.id) && !this.can('broadcast', null, room)) return;
+ if (game.hostid !== user.id && !game.cohosts.includes(user.id) && !this.can('show', null, room)) return;
game.end();
this.room = targetRoom;
this.modlog('MAFIAEND', null);
diff --git a/server/chat-plugins/scavengers.ts b/server/chat-plugins/scavengers.ts
index 42607328f9..1abc68acd7 100644
--- a/server/chat-plugins/scavengers.ts
+++ b/server/chat-plugins/scavengers.ts
@@ -1537,7 +1537,7 @@ const ScavengerCommands: ChatCommands = {
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (
- (!game.hosts.some(h => h.id === user.id) || !user.can('broadcast', null, room)) &&
+ (!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot edit the hints and answers if you are not the host.");
@@ -1554,7 +1554,7 @@ const ScavengerCommands: ChatCommands = {
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (
- (!game.hosts.some(h => h.id === user.id) || !user.can('broadcast', null, room)) &&
+ (!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot add more hints if you are not the host.");
@@ -1575,7 +1575,7 @@ const ScavengerCommands: ChatCommands = {
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (
- (!game.hosts.some(h => h.id === user.id) || !user.can('broadcast', null, room)) &&
+ (!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot remove hints if you are not the host.");
@@ -1598,7 +1598,7 @@ const ScavengerCommands: ChatCommands = {
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);
if (
- (!game.hosts.some(h => h.id === user.id) || !user.can('broadcast', null, room)) &&
+ (!game.hosts.some(h => h.id === user.id) || !user.can('show', null, room)) &&
game.staffHostId !== user.id
) {
return this.errorReply("You cannot edit hints if you are not the host.");
diff --git a/server/chat-plugins/trivia.ts b/server/chat-plugins/trivia.ts
index 7baeb9e473..658b5de2c0 100644
--- a/server/chat-plugins/trivia.ts
+++ b/server/chat-plugins/trivia.ts
@@ -1064,7 +1064,7 @@ class TriumvirateModeTrivia extends Trivia {
const commands: ChatCommands = {
new(target, room, user) {
if (!isTriviaRoom(room)) return this.errorReply("This command can only be used in the Trivia room.");
- if (!this.can('broadcast', null, room)) return false;
+ if (!this.can('show', null, room)) return false;
if (!this.canTalk()) return;
if (room.game) {
return this.errorReply(`There is already a game of ${room.game.title} in progress.`);
@@ -1166,7 +1166,7 @@ const commands: ChatCommands = {
start(target, room) {
if (!isTriviaRoom(room)) return this.errorReply("This command can only be used in Trivia.");
- if (!this.can('broadcast', null, room)) return false;
+ if (!this.can('show', null, room)) return false;
if (!this.canTalk()) return;
const game = getTriviaGame(this);
if (!game) return;
@@ -1196,7 +1196,7 @@ const commands: ChatCommands = {
end(target, room, user) {
if (!isTriviaRoom(room)) return this.errorReply("This command can only be used in Trivia.");
- if (!this.can('broadcast', null, room)) return false;
+ if (!this.can('show', null, room)) return false;
if (!this.canTalk()) return;
const game = getTriviaGame(this);
if (!game) return;
@@ -1240,7 +1240,7 @@ const commands: ChatCommands = {
add(target, room, user, connection, cmd) {
if (room.roomid !== 'questionworkshop') return this.errorReply('This command can only be used in Question Workshop.');
if (cmd === 'add' && !this.can('mute', null, room)) return false;
- if (cmd === 'submit' && !this.can('broadcast', null, room)) return false;
+ if (cmd === 'submit' && !this.can('show', null, room)) return false;
if (!target) return false;
if (!this.canTalk()) return false;
@@ -1565,7 +1565,7 @@ const commands: ChatCommands = {
search(target, room, user) {
if (room.roomid !== 'questionworkshop') return this.errorReply("This command can only be used in Question Workshop.");
- if (!this.can('broadcast', null, room)) return false;
+ if (!this.can('show', null, room)) return false;
if (!target.includes(',')) return this.errorReply("No valid search arguments entered.");
let [type, ...query] = target.split(',');
diff --git a/server/chat-plugins/wifi.ts b/server/chat-plugins/wifi.ts
index 5f2037e44a..7abaefc724 100644
--- a/server/chat-plugins/wifi.ts
+++ b/server/chat-plugins/wifi.ts
@@ -635,7 +635,7 @@ const cmds: ChatCommands = {
if (isNaN(parseInt(tid)) || tid.length < 5 || tid.length > 6) return this.errorReply("Invalid TID");
const targetUser = Users.get(giver);
if (!targetUser || !targetUser.connected) return this.errorReply(`User '${giver}' is not online.`);
- if (!user.can('warn', null, room) && !(user.can('broadcast', null, room) && user === targetUser)) {
+ if (!user.can('warn', null, room) && !(user.can('show', null, room) && user === targetUser)) {
return this.errorReply("/qg - Access denied.");
}
if (!targetUser.autoconfirmed) {
@@ -693,7 +693,7 @@ const cmds: ChatCommands = {
if (isNaN(parseInt(tid)) || tid.length < 5 || tid.length > 6) return this.errorReply("Invalid TID");
const targetUser = Users.get(giver);
if (!targetUser || !targetUser.connected) return this.errorReply(`User '${giver}' is not online.`);
- if (!user.can('warn', null, room) && !(user.can('broadcast', null, room) && user === targetUser)) {
+ if (!user.can('warn', null, room) && !(user.can('show', null, room) && user === targetUser)) {
return this.errorReply("/lg - Access denied.");
}
if (!targetUser.autoconfirmed) {
@@ -910,7 +910,7 @@ const cmds: ChatCommands = {
let reply = '';
switch (target) {
case 'staff':
- if (!this.can('broadcast', null, room)) return;
+ if (!this.can('show', null, room)) return;
reply = 'Staff commands:
' +
'- question or qg User | OT | TID | Prize | Question | Answer[ | Answer2 | Answer3] - Start a new question giveaway (voices can only host for themselves, staff can for all users) (Requires: + % @ # &)
' +
'- lottery or lg User | OT | TID | Prize[| Number of Winners] - Starts a lottery giveaway (voices can only host for themselves, staff can for all users) (Requires: + % @ # &)
' +
@@ -922,7 +922,7 @@ const cmds: ChatCommands = {
'- count Mon - Displays how often a certain mon has been given away. Use !giveaway count to broadcast this to the entire room
';
break;
case 'gts':
- if (!this.can('broadcast', null, room)) return;
+ if (!this.can('show', null, room)) return;
reply = 'GTS giveaway commands:
' +
'- gts start User | Amount | Summary of given mon | What to deposit | What to look for - Starts a gts giveaway (Requires: % @ # &)
' +
'- gts left Amount - Updates the amount left for the current GTS giveaway. Without an amount specified, shows how many Pokémon are left, and who the latest winners are.
' +
diff --git a/server/chat-plugins/youtube.ts b/server/chat-plugins/youtube.ts
index 4e047fba9c..6566802f57 100644
--- a/server/chat-plugins/youtube.ts
+++ b/server/chat-plugins/youtube.ts
@@ -168,7 +168,7 @@ export const commands: ChatCommands = {
const data = await YouTube.randChannel();
if (!data) return this.errorReply(`Error in getting channel data.`);
if (this.broadcasting) {
- if (!this.can('broadcast', null, room)) return false;
+ if (!this.can('show', null, room)) return false;
this.addBox(data);
room.update();
} else {
diff --git a/server/chat.ts b/server/chat.ts
index 2b61a52713..60c99741d7 100644
--- a/server/chat.ts
+++ b/server/chat.ts
@@ -593,7 +593,7 @@ export class CommandContext extends MessageContext {
checkSlowchat(room: Room | null | undefined, user: User) {
if (!room || !room.settings.slowchat) return true;
- if (user.can('broadcast', null, room)) return true;
+ if (user.can('show', null, room)) return true;
const lastActiveSeconds = (Date.now() - user.lastMessageTime) / 1000;
if (lastActiveSeconds < room.settings.slowchat) return false;
return true;
@@ -787,7 +787,7 @@ export class CommandContext extends MessageContext {
return true;
}
- if (this.room && !this.user.can('broadcast', null, this.room)) {
+ if (this.room && !this.user.can('show', null, this.room)) {
this.errorReply(`You need to be voiced to broadcast this command's information.`);
this.errorReply(`To see it for yourself, use: /${this.message.slice(1)}`);
return false;
@@ -1034,7 +1034,7 @@ export class CommandContext extends MessageContext {
if (room?.settings.highTraffic &&
toID(message).replace(/[^a-z]+/, '').length < 2 &&
- !user.can('broadcast', null, room)) {
+ !user.can('show', null, room)) {
this.errorReply(
this.tr('Due to this room being a high traffic room, your message must contain at least two letters.')
);
diff --git a/server/punishments.ts b/server/punishments.ts
index 7392610ba7..2618e82f46 100644
--- a/server/punishments.ts
+++ b/server/punishments.ts
@@ -1424,7 +1424,7 @@ export const Punishments = new class {
buf += `