snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1 @@
import './server';

View File

@@ -0,0 +1,86 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { isRegisterUser } from '@rocket.chat/core-typings';
import { Users, Rooms } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { RoomMemberActions } from '../../../definition/IRoomTypeConfig';
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
import { i18n } from '../../lib/i18n';
import { archiveRoom } from '../../lib/rooms/archiveRoom';
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
import { slashCommands } from '../../lib/utils/slashCommand';
import { settings } from '../../settings';
slashCommands.add({
command: 'archive',
callback: async function Archive({ params, message, userId }: SlashCommandCallbackParams<'archive'>): Promise<void> {
let channel = params.trim();
let room;
if (channel === '') {
room = await Rooms.findOneById(message.rid);
if (room?.name) {
channel = room.name;
}
} else {
channel = channel.replace('#', '');
room = await Rooms.findOneByName(channel);
}
if (!userId) {
return;
}
const user = await Users.findOneById(userId, { projection: { username: 1, name: 1 } });
if (!user || !isRegisterUser(user)) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'archiveRoom' });
}
if (!room) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Channel_doesnt_exist', {
interpolation: { escapeValue: false },
channelName: channel,
lng: settings.get('Language') || 'en',
}),
});
return;
}
if (!(await roomCoordinator.getRoomDirectives(room.t).allowMemberAction(room, RoomMemberActions.ARCHIVE, userId))) {
throw new Meteor.Error('error-room-type-not-archivable', `Room type: ${room.t} can not be archived`);
}
if (!(await hasPermissionAsync(userId, 'archive-room', room._id))) {
throw new Meteor.Error('error-not-authorized', 'Not authorized');
}
if (room.archived) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Duplicate_archived_channel_name', {
interpolation: { escapeValue: false },
channelName: channel,
lng: settings.get('Language') || 'en',
}),
});
return;
}
await archiveRoom(room._id, user);
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Channel_Archived', {
interpolation: { escapeValue: false },
channelName: channel,
lng: settings.get('Language') || 'en',
}),
});
},
options: {
description: 'Archive',
params: '#channel',
permission: 'archive-room',
},
});

View File

@@ -0,0 +1,24 @@
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { slashCommands } from '../../lib/utils/slashCommand';
import { executeSendMessage } from '../../meteor-methods/messages/sendMessage';
/*
* Gimme is a named function that will replace /gimme commands
* @param {Object} message - The message object
*/
async function Gimme({ message, params, userId }: SlashCommandCallbackParams<'gimme'>): Promise<void> {
const msg = message;
msg.msg = `༼ つ ◕_◕ ༽つ ${params}`;
await executeSendMessage(userId, msg);
}
slashCommands.add({
command: 'gimme',
callback: Gimme,
options: {
description: 'Slash_Gimme_Description',
params: 'your_message_optional',
clientOnly: true,
},
});

View File

@@ -0,0 +1,5 @@
import './gimme';
import './lenny';
import './shrug';
import './tableflip';
import './unflip';

View File

@@ -0,0 +1,24 @@
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { slashCommands } from '../../lib/utils/slashCommand';
import { executeSendMessage } from '../../meteor-methods/messages/sendMessage';
/*
* Lenny is a named function that will replace /lenny commands
* @param {Object} message - The message object
*/
async function LennyFace({ message, params, userId }: SlashCommandCallbackParams<'lenny'>): Promise<void> {
const msg = message;
msg.msg = `${params} ( ͡° ͜ʖ ͡°)`;
await executeSendMessage(userId, msg);
}
slashCommands.add({
command: 'lennyface',
callback: LennyFace,
options: {
description: 'Slash_LennyFace_Description',
params: 'your_message_optional',
clientOnly: true,
},
});

View File

@@ -0,0 +1,22 @@
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { slashCommands } from '../../lib/utils/slashCommand';
import { executeSendMessage } from '../../meteor-methods/messages/sendMessage';
/*
* Shrug is a named function that will replace /shrug commands
* @param {Object} message - The message object
*/
slashCommands.add({
command: 'shrug',
callback: async ({ message, params, userId }: SlashCommandCallbackParams<'shrug'>): Promise<void> => {
const msg = message;
msg.msg = `${params} ¯\\\\_(ツ)_/¯`;
await executeSendMessage(userId, msg);
},
options: {
description: 'Slash_Shrug_Description',
params: 'your_message_optional',
clientOnly: true,
},
});

View File

@@ -0,0 +1,22 @@
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { slashCommands } from '../../lib/utils/slashCommand';
import { executeSendMessage } from '../../meteor-methods/messages/sendMessage';
/*
* Tableflip is a named function that will replace /Tableflip commands
* @param {Object} message - The message object
*/
slashCommands.add({
command: 'tableflip',
callback: async ({ message, params, userId }: SlashCommandCallbackParams<'tableflip'>): Promise<void> => {
const msg = message;
msg.msg = `${params} (╯°□°)╯︵ ┻━┻`;
await executeSendMessage(userId, msg);
},
options: {
description: 'Slash_Tableflip_Description',
params: 'your_message_optional',
clientOnly: true,
},
});

View File

@@ -0,0 +1,22 @@
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { slashCommands } from '../../lib/utils/slashCommand';
import { executeSendMessage } from '../../meteor-methods/messages/sendMessage';
/*
* Unflip is a named function that will replace /unflip commands
* @param {Object} message - The message object
*/
slashCommands.add({
command: 'unflip',
callback: async ({ message, params, userId }: SlashCommandCallbackParams<'unflip'>): Promise<void> => {
const msg = message;
msg.msg = `${params} ┬─┬ ノ( ゜-゜ノ)`;
await executeSendMessage(userId, msg);
},
options: {
description: 'Slash_TableUnflip_Description',
params: 'your_message_optional',
clientOnly: true,
},
});

View File

@@ -0,0 +1,39 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import { banUserFromRoomMethod } from '../../lib/banUserFromRoom';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { sanitizeUsername } from '../../meteor-methods/rooms/addUsersToRoom';
import { settings } from '../../settings';
slashCommands.add({
command: 'ban',
callback: async ({ params, message, userId }: SlashCommandCallbackParams<'ban'>): Promise<void> => {
const username = sanitizeUsername(params.trim());
if (!username) {
return;
}
const user = await Users.findOneByUsernameIgnoringCase(username);
if (!user) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Username_doesnt_exist', {
interpolation: { escapeValue: false },
username,
lng: settings.get('Language') || 'en',
}),
});
return;
}
await banUserFromRoomMethod(userId, { rid: message.rid, username });
},
options: {
description: 'Ban_user_from_room',
params: '@username',
permission: 'ban-user',
},
});

View File

@@ -0,0 +1,2 @@
import './ban';
import './unban';

View File

@@ -0,0 +1,39 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import { i18n } from '../../lib/i18n';
import { unbanUserFromRoom } from '../../lib/unbanUserFromRoom';
import { slashCommands } from '../../lib/utils/slashCommand';
import { sanitizeUsername } from '../../meteor-methods/rooms/addUsersToRoom';
import { settings } from '../../settings';
slashCommands.add({
command: 'unban',
callback: async ({ params, message, userId }: SlashCommandCallbackParams<'unban'>): Promise<void> => {
const username = sanitizeUsername(params.trim());
if (!username) {
return;
}
const user = await Users.findOneByUsernameIgnoringCase(username);
if (!user) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Username_doesnt_exist', {
interpolation: { escapeValue: false },
username,
lng: settings.get('Language') || 'en',
}),
});
return;
}
await unbanUserFromRoom(userId, { rid: message.rid, username });
},
options: {
description: 'Unban_user_from_room',
params: '@username',
permission: 'ban-user',
},
});

View File

@@ -0,0 +1 @@
import './server';

View File

@@ -0,0 +1,68 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Rooms, Users } from '@rocket.chat/models';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { createChannelMethod } from '../../meteor-methods/rooms/createChannel';
import { createPrivateGroupMethod } from '../../meteor-methods/rooms/createPrivateGroup';
import { settings } from '../../settings';
slashCommands.add({
command: 'create',
callback: async function Create({ params, message, userId }: SlashCommandCallbackParams<'create'>): Promise<void> {
function getParams(str: string): string[] {
const regex = /(--(\w+))+/g;
const result = [];
let m;
while ((m = regex.exec(str)) !== null) {
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
result.push(m[2]);
}
return result;
}
const regexp = new RegExp(settings.get('UTF8_Channel_Names_Validation') as string);
const channel = regexp.exec(params.trim());
if (!channel) {
return;
}
const channelStr: string = channel ? channel[0] : '';
if (channelStr === '') {
return;
}
const room = await Rooms.findOneByName(channelStr);
if (room != null) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Channel_already_exist', {
interpolation: { escapeValue: false },
channelName: channelStr,
lng: settings.get('Language') || 'en',
}),
});
return;
}
if (getParams(params).indexOf('private') > -1) {
const user = await Users.findOneById(userId);
if (!user) {
return;
}
await createPrivateGroupMethod(user, channelStr, []);
return;
}
await createChannelMethod(userId, channelStr, []);
},
options: {
description: 'Create_A_New_Channel',
params: '#channel',
permission: ['create-c', 'create-p'],
},
});

View File

@@ -0,0 +1 @@
import './server';

View File

@@ -0,0 +1,74 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { settings } from '../../settings';
/*
* Help is a named function that will replace /help commands
* @param {Object} message - The message object
*/
interface IHelpCommand {
key: string;
command: string;
}
slashCommands.add({
command: 'help',
callback: async function Help({ message, userId }: SlashCommandCallbackParams<'help'>): Promise<void> {
const user = await Users.findOneById(userId);
const keys: IHelpCommand[] = [
{
key: 'Open_channel_user_search',
command: 'Command (or Ctrl) + p OR Command (or Ctrl) + k',
},
{
key: 'Mark_all_as_read',
command: 'Shift (or Ctrl) + ESC',
},
{
key: 'Edit_previous_message',
command: 'Up Arrow',
},
{
key: 'Move_beginning_message',
command: 'Command (or Alt) + Left Arrow',
},
{
key: 'Move_beginning_message',
command: 'Command (or Alt) + Up Arrow',
},
{
key: 'Move_end_message',
command: 'Command (or Alt) + Right Arrow',
},
{
key: 'Move_end_message',
command: 'Command (or Alt) + Down Arrow',
},
{
key: 'New_line_message_compose_input',
command: 'Shift + Enter',
},
];
let msg = '';
keys.forEach((key) => {
msg = `${msg}\n${i18n.t(key.key, {
interpolation: { escapeValue: false },
shortcut: key.command,
lng: user?.language || settings.get('language') || 'en',
})}`;
});
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg,
...(message.tmid && { tmid: message.tmid }),
});
},
options: {
description: 'Show_the_keyboard_shortcut_list',
},
});

View File

@@ -0,0 +1,75 @@
import { api } from '@rocket.chat/core-services';
import type { IRoom, SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Rooms, Subscriptions, Users } from '@rocket.chat/models';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { hideRoomMethod } from '../../meteor-methods/rooms/hideRoom';
import { settings } from '../../settings';
/*
* Hide is a named function that will replace /hide commands
* @param {Object} message - The message object
*/
slashCommands.add({
command: 'hide',
callback: async ({ params, message, userId }: SlashCommandCallbackParams<'hide'>): Promise<void> => {
const room = params.trim();
if (!userId) {
return;
}
const user = await Users.findOneById(userId);
if (!user) {
return;
}
const lng = user.language || settings.get('Language') || 'en';
// if there is not a param, hide the current room
let { rid } = message;
if (room !== '') {
const [strippedRoom] = room.replace(/#|@/, '').split(' ');
const [type] = room;
const roomObject: IRoom | null =
type === '#'
? await Rooms.findOneByName(strippedRoom)
: await Rooms.findOne({
t: 'd',
usernames: { $all: [user.username, strippedRoom] },
});
if (!roomObject) {
void api.broadcast('notify.ephemeralMessage', user._id, message.rid, {
msg: i18n.t('Channel_doesnt_exist', {
interpolation: { escapeValue: false },
channelName: strippedRoom,
lng,
}),
});
}
if (!(await Subscriptions.findOneByRoomIdAndUserId(roomObject ? roomObject._id : '', user._id, { projection: { _id: 1 } }))) {
void api.broadcast('notify.ephemeralMessage', user._id, message.rid, {
msg: i18n.t('error-logged-user-not-in-room', {
interpolation: { escapeValue: false },
roomName: room,
lng,
}),
});
return;
}
rid = roomObject?._id || message.rid;
}
try {
await hideRoomMethod(userId, rid);
} catch (error: any) {
await api.broadcast('notify.ephemeralMessage', user._id, message.rid, {
msg: i18n.t(error, { lng }),
});
}
},
options: { description: 'Hide_room', params: '#room' },
});

View File

@@ -0,0 +1 @@
import './hide';

View File

@@ -0,0 +1 @@
import './server';

View File

@@ -0,0 +1,153 @@
import { api, FederationMatrix, isMeteorError } from '@rocket.chat/core-services';
import type { IUser, SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { isBannedSubscription } from '@rocket.chat/core-typings';
import { validateFederatedUsername } from '@rocket.chat/federation-matrix';
import { Subscriptions, Users, Rooms } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { addUsersToRoomMethod, sanitizeUsername } from '../../meteor-methods/rooms/addUsersToRoom';
import { FederationActions } from '../../services/room/hooks/BeforeFederationActions';
import { settings } from '../../settings';
// Type guards for the error
function isStringError(error: unknown): error is { error: string } {
return typeof (error as any)?.error === 'string';
}
/*
* Invite is a named function that will replace /invite commands
* @param {Object} message - The message object
*/
slashCommands.add({
command: 'invite',
callback: async ({ params, message, userId }: SlashCommandCallbackParams<'invite'>): Promise<void> => {
let usernames = params
.split(/[\s,]/)
.map((username) => sanitizeUsername(username))
.filter((a) => a !== '');
if (usernames.length === 0) {
return;
}
// Get room information for federation check
const room = await Rooms.findOneById(message.rid);
if (!room) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('error-invalid-room', { lng: settings.get('Language') || 'en' }),
});
return;
}
// Ensure federated users exist locally before looking them up
const federatedUsernames = usernames.filter((u) => validateFederatedUsername(u)) as string[];
if (federatedUsernames.length > 0) {
if (FederationActions.shouldPerformFederationAction(room)) {
await FederationMatrix.ensureFederatedUsersExistLocally(federatedUsernames);
} else {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('You_cannot_add_external_users_to_non_federated_room', { lng: settings.get('Language') || 'en' }),
});
// These federated users shouldn't be invited and we already broadcasted the error message
usernames = usernames.filter((username) => {
return !federatedUsernames.includes(username);
});
if (usernames.length === 0) {
return;
}
}
}
const users = await Users.findByUsernames(usernames).toArray();
if (users.length === 0) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('User_doesnt_exist', {
interpolation: { escapeValue: false },
username: usernames.join(' @'),
lng: settings.get('Language') || 'en',
}),
});
return;
}
const usersFiltered: IUser[] = [];
for await (const user of users) {
const subscription = await Subscriptions.findOneByRoomIdAndUserId(message.rid, user._id, {
projection: { _id: 1, status: 1 },
});
if (subscription == null || isBannedSubscription(subscription)) {
usersFiltered.push(user);
continue;
}
const usernameStr = user.username as string;
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Username_is_already_in_here', {
interpolation: { escapeValue: false },
username: usernameStr,
lng: settings.get('Language') || 'en',
}),
});
}
const inviter = await Users.findOneById(userId);
if (!inviter) {
throw new Meteor.Error('error-user-not-found', 'Inviter not found', {
method: 'slashcommand-invite',
});
}
await Promise.all(
usersFiltered.map(async (user) => {
try {
// TODO: Refactor this to return an error if some user fails to be added
return await addUsersToRoomMethod(
userId,
{
rid: message.rid,
users: [user.username || ''],
},
inviter,
);
} catch (e: unknown) {
if (isMeteorError(e)) {
if (e.error === 'error-only-compliant-users-can-be-added-to-abac-rooms') {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t(e.error, { lng: settings.get('Language') || 'en' }),
});
} else {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t(e.message, { lng: settings.get('Language') || 'en' }),
});
}
return;
}
if (isStringError(e)) {
const { error } = e;
if (error === 'error-federated-users-in-non-federated-rooms') {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('You_cannot_add_external_users_to_non_federated_room', { lng: settings.get('Language') || 'en' }),
});
} else if (error === 'cant-invite-for-direct-room') {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Cannot_invite_users_to_direct_rooms', { lng: settings.get('Language') || 'en' }),
});
} else {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t(error, { lng: settings.get('Language') || 'en' }),
});
}
}
}
}),
);
},
options: {
description: 'Invite_user_to_join_channel',
params: '@username',
permission: 'add-user-to-joined-room',
},
});

View File

@@ -0,0 +1 @@
import './server';

View File

@@ -0,0 +1,128 @@
/*
* Invite is a named function that will replace /invite commands
* @param {Object} message - The message object
*/
import { api } from '@rocket.chat/core-services';
import type { ISubscription, SlashCommand, SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Rooms, Subscriptions, Users } from '@rocket.chat/models';
import { isTruthy } from '@rocket.chat/tools';
import { Meteor } from 'meteor/meteor';
import { canAccessRoomAsync } from '../../lib/authorization';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { addUsersToRoomMethod } from '../../meteor-methods/rooms/addUsersToRoom';
import { createChannelMethod } from '../../meteor-methods/rooms/createChannel';
import { createPrivateGroupMethod } from '../../meteor-methods/rooms/createPrivateGroup';
import { settings } from '../../settings';
function inviteAll<T extends string>(type: T): SlashCommand<T>['callback'] {
return async function inviteAll({ command, params, message, userId }: SlashCommandCallbackParams<T>): Promise<void> {
if (!/invite\-all-(to|from)/.test(command)) {
return;
}
let channel = params.trim();
if (channel === '') {
return;
}
channel = channel.replace('#', '');
if (!channel) {
return;
}
if (!userId) {
return;
}
const user = await Users.findOneById(userId);
if (!user) {
return;
}
const lng = user?.language || settings.get('Language') || 'en';
const baseChannel = type === 'to' ? await Rooms.findOneById(message.rid) : await Rooms.findOneByName(channel);
const targetChannel = type === 'from' ? await Rooms.findOneById(message.rid) : await Rooms.findOneByName(channel);
if (!baseChannel) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Channel_doesnt_exist', {
interpolation: { escapeValue: false },
channelName: channel,
lng,
}),
});
return;
}
if (!(await canAccessRoomAsync(baseChannel, user))) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Room_not_exist_or_not_permission', { lng }),
});
return;
}
try {
const APIsettings = settings.get<number>('API_User_Limit');
if (!APIsettings) {
return;
}
if ((await Subscriptions.countByRoomIdWhenUsernameExists(baseChannel._id)) > APIsettings) {
throw new Meteor.Error('error-user-limit-exceeded', 'User Limit Exceeded', {
method: 'addAllToRoom',
});
}
const cursor = Subscriptions.findByRoomIdWhenUsernameExists(baseChannel._id, {
projection: { 'u.username': 1 },
});
const users = (await cursor.toArray()).map((s: ISubscription) => s.u.username).filter(isTruthy);
if (!targetChannel && ['c', 'p'].indexOf(baseChannel.t) > -1) {
baseChannel.t === 'c' ? await createChannelMethod(userId, channel, users) : await createPrivateGroupMethod(user, channel, users);
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Channel_created', {
interpolation: { escapeValue: false },
channelName: channel,
lng,
}),
});
} else {
await addUsersToRoomMethod(userId, {
rid: targetChannel?._id ?? '',
users,
});
}
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Users_added', { lng }),
});
} catch (e: any) {
const msg = e.error === 'cant-invite-for-direct-room' ? 'Cannot_invite_users_to_direct_rooms' : e.error;
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t(msg, { lng }),
});
}
};
}
slashCommands.add({
command: 'invite-all-to',
callback: inviteAll('to'),
options: {
description: 'Invite_user_to_join_channel_all_to',
params: '#room',
permission: ['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room'],
},
});
slashCommands.add({
command: 'invite-all-from',
callback: inviteAll('from'),
options: {
description: 'Invite_user_to_join_channel_all_from',
params: '#room',
permission: 'add-user-to-joined-room',
},
});
module.exports = inviteAll;

View File

@@ -0,0 +1 @@
import './server';

View File

@@ -0,0 +1,59 @@
import { api, Room } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Rooms, Subscriptions, Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { settings } from '../../settings';
slashCommands.add({
command: 'join',
callback: async ({ params, message, userId }: SlashCommandCallbackParams<'join'>): Promise<void> => {
let channel = params.trim();
if (channel === '') {
return;
}
if (!userId) {
return;
}
channel = channel.replace('#', '');
const room = await Rooms.findOneByNameAndType(channel, 'c');
if (!room) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Channel_doesnt_exist', {
interpolation: { escapeValue: false },
channelName: channel,
lng: settings.get('Language') || 'en',
}),
});
return;
}
const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, userId, {
projection: { _id: 1 },
});
if (subscription) {
throw new Meteor.Error('error-user-already-in-room', 'You are already in the channel', {
method: 'slashCommands',
});
}
const user = await Users.findOneById(userId);
if (!user) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'slashCommands',
});
}
await Room.join({ room, user });
},
options: {
description: 'Join_the_given_channel',
params: '#channel',
permission: 'view-c-room',
},
});

View File

@@ -0,0 +1 @@
import './server';

View File

@@ -0,0 +1,44 @@
// Kick is a named function that will replace /kick commands
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { sanitizeUsername } from '../../meteor-methods/rooms/addUsersToRoom';
import { removeUserFromRoomMethod } from '../../meteor-methods/rooms/removeUserFromRoom';
import { settings } from '../../settings';
slashCommands.add({
command: 'kick',
callback: async ({ params, message, userId }: SlashCommandCallbackParams<'kick'>): Promise<void> => {
const username = sanitizeUsername(params.trim());
if (username === '') {
return;
}
const user = await Users.findOneById(userId);
const lng = user?.language || settings.get('Language') || 'en';
const kickedUser = await Users.findOneByUsernameIgnoringCase(username);
if (kickedUser == null) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Username_doesnt_exist', {
interpolation: { escapeValue: false },
username,
lng,
}),
});
return;
}
const { rid } = message;
await removeUserFromRoomMethod(userId, { rid, username });
},
options: {
description: 'Remove_someone_from_room',
params: '@username',
permission: 'remove-user',
},
});

View File

@@ -0,0 +1 @@
import './leave';

View File

@@ -0,0 +1,49 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { leaveRoomMethod } from '../../meteor-methods/rooms/leaveRoom';
import { settings } from '../../settings';
/*
* Leave is a named function that will replace /leave commands
* @param {Object} message - The message object
*/
const Leave = async function Leave({ message, userId }: SlashCommandCallbackParams<'leave'>): Promise<void> {
try {
const user = await Users.findOneById(userId);
if (!user) {
return;
}
await leaveRoomMethod(user, message.rid);
} catch (error: any) {
if (typeof error.error !== 'string') {
throw error;
}
const user = await Users.findOneById(userId);
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t(error, { lng: user?.language || settings.get('Language') || 'en' }),
});
throw new Meteor.Error(error.error, error.message);
}
};
slashCommands.add({
command: 'leave',
callback: Leave,
options: {
description: 'Leave_the_current_channel',
permission: ['leave-c', 'leave-p'],
},
});
slashCommands.add({
command: 'part',
callback: Leave,
options: {
description: 'Leave_the_current_channel',
permission: ['leave-c', 'leave-p'],
},
});

View File

@@ -0,0 +1 @@
import './me';

View File

@@ -0,0 +1,23 @@
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { slashCommands } from '../../lib/utils/slashCommand';
import { executeSendMessage } from '../../meteor-methods/messages/sendMessage';
/*
* Me is a named function that will replace /me commands
* @param {Object} message - The message object
*/
slashCommands.add({
command: 'me',
callback: async function Me({ params, message, userId }: SlashCommandCallbackParams<'me'>): Promise<void> {
if (params.trim()) {
const msg = message;
msg.msg = `_${params}_`;
await executeSendMessage(userId, msg);
}
},
options: {
description: 'Displays_action_text',
params: 'your_message',
},
});

View File

@@ -0,0 +1 @@
import './server';

View File

@@ -0,0 +1,55 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import { Random } from '@rocket.chat/random';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { createDirectMessage } from '../../meteor-methods/messages/createDirectMessage';
import { executeSendMessage } from '../../meteor-methods/messages/sendMessage';
import { settings } from '../../settings';
/*
* Msg is a named function that will replace /msg commands
*/
slashCommands.add({
command: 'msg',
callback: async function Msg({ params, message: item, userId }: SlashCommandCallbackParams<'msg'>): Promise<void> {
const trimmedParams = params.trim();
const separator = trimmedParams.indexOf(' ');
if (separator === -1) {
void api.broadcast('notify.ephemeralMessage', userId, item.rid, {
msg: i18n.t('Username_and_message_must_not_be_empty', { lng: settings.get('Language') || 'en' }),
});
return;
}
const message = trimmedParams.slice(separator + 1);
const targetUsernameOrig = trimmedParams.slice(0, separator);
const targetUsername = targetUsernameOrig.replace('@', '');
const targetUser = await Users.findOneByUsernameIgnoringCase(targetUsername);
if (targetUser == null) {
const user = await Users.findOneById(userId, { projection: { language: 1 } });
void api.broadcast('notify.ephemeralMessage', userId, item.rid, {
msg: i18n.t('Username_doesnt_exist', {
interpolation: { escapeValue: false },
username: targetUsernameOrig,
lng: user?.language || settings.get('Language') || 'en',
}),
});
return;
}
const { rid } = await createDirectMessage([targetUsername], userId);
const msgObject = {
_id: Random.id(),
rid,
msg: message,
};
await executeSendMessage(userId, msgObject);
},
options: {
description: 'Direct_message_someone',
params: '@username <message>',
permission: 'create-d',
},
});

View File

@@ -0,0 +1,2 @@
import './mute';
import './unmute';

View File

@@ -0,0 +1,43 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { muteUserInRoom } from '../../meteor-methods/rooms/muteUserInRoom';
import { settings } from '../../settings';
/*
* Mute is a named function that will replace /mute commands
*/
slashCommands.add({
command: 'mute',
callback: async function Mute({ params, message, userId }: SlashCommandCallbackParams<'mute'>): Promise<void> {
const username = params.trim().replace('@', '');
if (username === '') {
return;
}
const mutedUser = await Users.findOneByUsernameIgnoringCase(username);
if (mutedUser == null) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Username_doesnt_exist', {
interpolation: { escapeValue: false },
username,
lng: settings.get('Language') || 'en',
}),
});
}
await muteUserInRoom(userId, {
rid: message.rid,
username,
});
},
options: {
description: 'Mute_someone_in_room',
params: '@username',
permission: 'mute-user',
},
});

View File

@@ -0,0 +1,43 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { unmuteUserInRoom } from '../../meteor-methods/rooms/unmuteUserInRoom';
import { settings } from '../../settings';
/*
* Unmute is a named function that will replace /unmute commands
*/
slashCommands.add({
command: 'unmute',
callback: async function Unmute({ params, message, userId }: SlashCommandCallbackParams<'unmute'>): Promise<void> {
const username = params.trim().replace('@', '');
if (username === '') {
return;
}
const unmutedUser = await Users.findOneByUsernameIgnoringCase(username);
if (unmutedUser == null) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Username_doesnt_exist', {
interpolation: { escapeValue: false },
username,
lng: settings.get('Language') || 'en',
}),
});
return;
}
await unmuteUserInRoom(userId, {
rid: message.rid,
username,
});
},
options: {
description: 'Unmute_someone_in_room',
params: '@username',
permission: 'mute-user',
},
});

View File

@@ -0,0 +1 @@
import './status';

View File

@@ -0,0 +1,47 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams, IUser } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import { i18n } from '../../lib/i18n';
import { slashCommands } from '../../lib/utils/slashCommand';
import { setUserStatusMethod } from '../../meteor-methods/users/setUserStatus';
import { settings } from '../../settings';
slashCommands.add({
command: 'status',
callback: async function Status({ params, message, userId }: SlashCommandCallbackParams<'status'>): Promise<void> {
if (!userId) {
return;
}
const user = await Users.findOneById<
Pick<IUser, '_id' | 'username' | 'name' | 'status' | 'statusDefault' | 'roles' | 'statusText' | 'language'>
>(userId, {
projection: { language: 1, username: 1, name: 1, status: 1, statusDefault: 1, roles: 1, statusText: 1 },
});
const lng = user?.language || settings.get('Language') || 'en';
if (!user) {
return;
}
try {
await setUserStatusMethod(user, undefined, params);
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('StatusMessage_Changed_Successfully', { lng }),
});
} catch (err: any) {
if (err.error === 'error-not-allowed') {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('StatusMessage_Change_Disabled', { lng }),
});
}
throw err;
}
},
options: {
description: 'Slash_Status_Description',
params: 'Slash_Status_Params',
},
});

View File

@@ -0,0 +1 @@
import './topic';

View File

@@ -0,0 +1,19 @@
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
import { slashCommands } from '../../lib/utils/slashCommand';
import { saveRoomSettings } from '../../meteor-methods/rooms/saveRoomSettings';
slashCommands.add({
command: 'topic',
callback: async ({ params, message, userId }: SlashCommandCallbackParams<'topic'>): Promise<void> => {
if (userId && (await hasPermissionAsync(userId, 'edit-room', message.rid))) {
await saveRoomSettings(userId, message.rid, 'roomTopic', params);
}
},
options: {
description: 'Slash_Topic_Description',
params: 'Slash_Topic_Params',
permission: 'edit-room',
},
});

View File

@@ -0,0 +1 @@
import './server';

View File

@@ -0,0 +1,85 @@
import { api } from '@rocket.chat/core-services';
import { isRegisterUser } from '@rocket.chat/core-typings';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
import { Users, Rooms } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import { RoomMemberActions } from '../../../definition/IRoomTypeConfig';
import { hasPermissionAsync } from '../../lib/authorization/hasPermission';
import { i18n } from '../../lib/i18n';
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
import { unarchiveRoom } from '../../lib/rooms/unarchiveRoom';
import { slashCommands } from '../../lib/utils/slashCommand';
import { settings } from '../../settings';
slashCommands.add({
command: 'unarchive',
callback: async function Unarchive({ params, message, userId }: SlashCommandCallbackParams<'unarchive'>): Promise<void> {
let channel = params.trim();
let room;
if (channel === '') {
room = await Rooms.findOneById(message.rid);
if (room?.name) {
channel = room.name;
}
} else {
channel = channel.replace('#', '');
room = await Rooms.findOneByName(channel);
}
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'archiveRoom' });
}
const user = await Users.findOneById(userId, { projection: { username: 1, name: 1 } });
if (!user || !isRegisterUser(user)) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'archiveRoom' });
}
if (!room) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Channel_doesnt_exist', {
interpolation: { escapeValue: false },
channelName: channel,
lng: settings.get('Language') || 'en',
}),
});
return;
}
if (!(await roomCoordinator.getRoomDirectives(room.t).allowMemberAction(room, RoomMemberActions.ARCHIVE, userId))) {
throw new Meteor.Error('error-room-type-not-unarchivable', `Room type: ${room.t} can not be unarchived`);
}
if (!(await hasPermissionAsync(userId, 'unarchive-room', room._id))) {
throw new Meteor.Error('error-not-authorized', 'Not authorized');
}
if (!room.archived) {
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Channel_already_Unarchived', {
interpolation: { escapeValue: false },
channelName: channel,
lng: settings.get('Language') || 'en',
}),
});
return;
}
await unarchiveRoom(room._id, user);
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Channel_Unarchived', {
interpolation: { escapeValue: false },
channelName: channel,
lng: settings.get('Language') || 'en',
}),
});
},
options: {
description: 'Unarchive',
params: '#channel',
permission: 'unarchive-room',
},
});