Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: command's endpoints #25630

Merged
merged 5 commits into from
Jun 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
}
],
"typescript.tsdk": "./node_modules/typescript/lib",
"cSpell.words": ["photoswipe"]
"cSpell.words": ["photoswipe", "tmid"]
}
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ type ActionThis<TMethod extends Method, TPathPattern extends PathPattern, TOptio
readonly request: Request;
/* @deprecated */
requestParams(): OperationParams<TMethod, TPathPattern>;
getLoggedInUser(): IUser | undefined;
getLoggedInUser(): TOptions extends { authRequired: true } ? IUser : IUser | undefined;
getPaginationItems(): {
readonly offset: number;
readonly count: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,22 @@ API.v1.addRoute(
},
);

// TODO: replace with something like client/lib/minimongo
const processQueryOptionsOnResult = (result, options = {}) => {
/* @deprecated */
const processQueryOptionsOnResult = <T extends { _id?: string } & Record<string, any>, F extends keyof T>(
result: T[],
options: {
fields?: {
[key in F]?: 1 | 0;
};
sort?: {
[key: string]: 1 | -1;
};
limit?: number;
skip?: number;
} = {},
): Pick<T, F>[] => {
if (result === undefined || result === null) {
return undefined;
return [];
}

if (Array.isArray(result)) {
Expand Down Expand Up @@ -74,66 +86,50 @@ const processQueryOptionsOnResult = (result, options = {}) => {
}
}

if (!options.fields) {
options.fields = {};
}

const fieldsToRemove = [];
const fieldsToGet = [];
const fieldsToRemove: F[] = [];
const fieldsToGet: F[] = [];

for (const field in options.fields) {
if (options.fields.hasOwnProperty(field)) {
if (options.fields[field] === 0) {
fieldsToRemove.push(field);
} else if (options.fields[field] === 1) {
fieldsToGet.push(field);
if (options.fields) {
for (const field in Object.keys(options.fields)) {
if (options.fields.hasOwnProperty(field as F)) {
if (options.fields[field as F] === 0) {
fieldsToRemove.push(field as F);
} else if (options.fields[field as F] === 1) {
fieldsToGet.push(field as F);
}
}
}
}

if (fieldsToRemove.length > 0 && fieldsToGet.length > 0) {
console.warn("Can't mix remove and get fields");
fieldsToRemove.splice(0, fieldsToRemove.length);
}

if (fieldsToGet.length > 0 && fieldsToGet.indexOf('_id') === -1) {
fieldsToGet.push('_id');
if (fieldsToGet.length > 0 && fieldsToGet.indexOf('_id' as F) === -1) {
fieldsToGet.push('_id' as F);
}

const pickFields = (obj, fields) => {
const picked = {};
fields.forEach((field) => {
if (field.indexOf('.') !== -1) {
objectPath.set(picked, field, objectPath.get(obj, field));
const pickFields = <F extends keyof T>(obj: T, fields: F[]): Pick<T, F> => {
const picked: Partial<T> = {};
fields.forEach((field: F) => {
if (String(field).indexOf('.') !== -1) {
objectPath.set(picked, String(field), objectPath.get(obj, String(field)));
} else {
picked[field] = obj[field];
}
});
return picked;
return picked as Pick<T, F>;
};

if (fieldsToRemove.length > 0 || fieldsToGet.length > 0) {
if (Array.isArray(result)) {
result = result.map((record) => {
if (fieldsToRemove.length > 0) {
return Object.fromEntries(Object.entries(record).filter(([key]) => !fieldsToRemove.includes(key)));
}

if (fieldsToGet.length > 0) {
return pickFields(record, fieldsToGet);
}
if (fieldsToRemove.length > 0 && fieldsToGet.length > 0) {
console.warn("Can't mix remove and get fields");
fieldsToRemove.splice(0, fieldsToRemove.length);
}

return null;
});
} else {
if (fieldsToRemove.length > 0 || fieldsToGet.length > 0) {
return result.map((record) => {
if (fieldsToRemove.length > 0) {
return Object.fromEntries(Object.entries(result).filter(([key]) => !fieldsToRemove.includes(key)));
return Object.fromEntries(Object.entries(record).filter(([key]) => !fieldsToRemove.includes(key as F))) as Pick<T, F>;
}

if (fieldsToGet.length > 0) {
return pickFields(result, fieldsToGet);
}
}
return pickFields(record, fieldsToGet);
});
}

return result;
Expand All @@ -149,20 +145,23 @@ API.v1.addRoute(

let commands = Object.values(slashCommands.commands);

if (query && query.command) {
if (query?.command) {
commands = commands.filter((command) => command.command === query.command);
}

const totalCount = commands.length;
commands = processQueryOptionsOnResult(commands, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
fields,
});

if (fields) {
console.warn('commands.list -> fields is deprecated and will be removed in 5.0.0');
}

return API.v1.success({
commands,
commands: processQueryOptionsOnResult(commands, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
fields,
}),
offset,
count: commands.length,
total: totalCount,
Expand All @@ -178,7 +177,6 @@ API.v1.addRoute(
{
post() {
const body = this.bodyParams;
const user = this.getLoggedInUser();

if (typeof body.command !== 'string') {
return API.v1.failure('You must provide a command to run.');
Expand All @@ -201,28 +199,28 @@ API.v1.addRoute(
return API.v1.failure('The command provided does not exist (or is disabled).');
}

if (!canAccessRoomId(body.roomId, user._id)) {
if (!canAccessRoomId(body.roomId, this.userId)) {
return API.v1.unauthorized();
}

const params = body.params ? body.params : '';
const message = {
_id: Random.id(),
rid: body.roomId,
msg: `/${cmd} ${params}`,
};

if (body.tmid) {
if (typeof body.tmid === 'string') {
const thread = Messages.findOneById(body.tmid);
if (!thread || thread.rid !== body.roomId) {
return API.v1.failure('Invalid thread.');
}
message.tmid = body.tmid;
}

const message = {
_id: Random.id(),
rid: body.roomId,
msg: `/${cmd} ${params}`,
...(body.tmid && { tmid: body.tmid }),
};

const { triggerId } = body;

const result = Meteor.runAsUser(user._id, () => slashCommands.run(cmd, params, message, triggerId));
const result = slashCommands.run(cmd, params, message, triggerId);

return API.v1.success({ result });
},
Expand All @@ -234,7 +232,7 @@ API.v1.addRoute(
{ authRequired: true },
{
// Expects these query params: command: 'giphy', params: 'mine', roomId: 'value'
get() {
async get() {
const query = this.queryParams;
const user = this.getLoggedInUser();

Expand All @@ -261,21 +259,18 @@ API.v1.addRoute(

const params = query.params ? query.params : '';

let preview;
Meteor.runAsUser(user._id, () => {
preview = Meteor.call('getSlashCommandPreviews', {
cmd,
params,
msg: { rid: query.roomId },
});
const preview = Meteor.call('getSlashCommandPreviews', {
cmd,
params,
msg: { rid: query.roomId },
});

return API.v1.success({ preview });
},

// Expects a body format of: { command: 'giphy', params: 'mine', roomId: 'value', tmid: 'value', triggerId: 'value', previewItem: { id: 'sadf8' type: 'image', value: 'https://dev.null/gif' } }
post() {
const body = this.bodyParams;
const user = this.getLoggedInUser();

if (typeof body.command !== 'string') {
return API.v1.failure('You must provide a command to run the preview item on.');
Expand Down Expand Up @@ -310,35 +305,33 @@ API.v1.addRoute(
return API.v1.failure('The command provided does not exist (or is disabled).');
}

if (!canAccessRoomId(body.roomId, user._id)) {
if (!canAccessRoomId(body.roomId, this.userId)) {
return API.v1.unauthorized();
}

const params = body.params ? body.params : '';
const message = {
rid: body.roomId,
};

const { params = '' } = body;
if (body.tmid) {
const thread = Messages.findOneById(body.tmid);
if (!thread || thread.rid !== body.roomId) {
return API.v1.failure('Invalid thread.');
}
message.tmid = body.tmid;
}

Meteor.runAsUser(user._id, () => {
Meteor.call(
'executeSlashCommandPreview',
{
cmd,
params,
msg: { rid: body.roomId, tmid: body.tmid },
},
body.previewItem,
body.triggerId,
);
});
const msg = {
rid: body.roomId,
...(body.tmid && { tmid: body.tmid }),
};

Meteor.call(
'executeSlashCommandPreview',
{
cmd,
params,
msg,
},
body.previewItem,
body.triggerId,
);

return API.v1.success();
},
Expand Down
13 changes: 9 additions & 4 deletions apps/meteor/app/apps/server/bridges/commands.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { SlashCommandContext, ISlashCommand, ISlashCommandPreviewItem } from '@rocket.chat/apps-engine/definition/slashcommands';
import { CommandBridge } from '@rocket.chat/apps-engine/server/bridges/CommandBridge';
import type { IMessage } from '@rocket.chat/core-typings';
import type { IMessage, RequiredField, SlashCommand } from '@rocket.chat/core-typings';

import { slashCommands } from '../../../utils/server';
import { Utilities } from '../../lib/misc/Utilities';
Expand Down Expand Up @@ -114,7 +114,7 @@ export class AppCommandsBridge extends CommandBridge {
previewCallback: (!command.executePreviewItem ? undefined : this._appCommandPreviewExecutor.bind(this)) as
| typeof slashCommands.commands[string]['previewCallback']
| undefined,
};
} as SlashCommand;

slashCommands.commands[command.command.toLowerCase()] = item;
this.orch.getNotifier().commandAdded(command.command.toLowerCase());
Expand Down Expand Up @@ -160,7 +160,12 @@ export class AppCommandsBridge extends CommandBridge {
}
}

private _appCommandExecutor(command: string, parameters: any, message: IMessage, triggerId: string): void {
private _appCommandExecutor(
command: string,
parameters: any,
message: RequiredField<Partial<IMessage>, 'rid'>,
triggerId?: string,
): void {
const user = this.orch.getConverters()?.get('users').convertById(Meteor.userId());
const room = this.orch.getConverters()?.get('rooms').convertById(message.rid);
const threadId = message.tmid;
Expand Down Expand Up @@ -195,6 +200,6 @@ export class AppCommandsBridge extends CommandBridge {

const context = new SlashCommandContext(Object.freeze(user), Object.freeze(room), Object.freeze(params), threadId, triggerId);

Promise.await(this.orch.getManager()?.getCommandManager().executePreview(command, preview, context));
await this.orch.getManager()?.getCommandManager().executePreview(command, preview, context);
}
}
4 changes: 2 additions & 2 deletions apps/meteor/app/slashcommand-asciiarts/lib/gimme.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Meteor } from 'meteor/meteor';
import type { IMessage } from '@rocket.chat/core-typings';
import type { IMessage, RequiredField } from '@rocket.chat/core-typings';

import { slashCommands } from '../../utils/lib/slashCommand';
/*
* Gimme is a named function that will replace /gimme commands
* @param {Object} message - The message object
*/

function Gimme(_command: 'gimme', params: string, item: IMessage): void {
function Gimme(_command: 'gimme', params: string, item: RequiredField<Partial<IMessage>, 'rid'>): void {
const msg = item;
msg.msg = `༼ つ ◕_◕ ༽つ ${params}`;
Meteor.call('sendMessage', msg);
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/slashcommand-asciiarts/lib/lenny.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IMessage } from '@rocket.chat/core-typings';
import type { IMessage, RequiredField } from '@rocket.chat/core-typings';
import { Meteor } from 'meteor/meteor';

import { slashCommands } from '../../utils/lib/slashCommand';
Expand All @@ -7,7 +7,7 @@ import { slashCommands } from '../../utils/lib/slashCommand';
* @param {Object} message - The message object
*/

function LennyFace(_command: 'lennyface', params: string, item: IMessage): void {
function LennyFace(_command: 'lennyface', params: string, item: RequiredField<Partial<IMessage>, 'rid'>): void {
const msg = item;
msg.msg = `${params} ( ͡° ͜ʖ ͡°)`;
Meteor.call('sendMessage', msg);
Expand Down
Loading