mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
feat(Microsoft Teams Node): Add the chat member get many operation (#37745)
This commit is contained in:
parent
6f64dc6ae3
commit
42dcc709f7
33
packages/nodes-base/nodes/Microsoft/Teams/__schema__/v2.0.0/chatMember/getAll.json
vendored
Normal file
33
packages/nodes-base/nodes/Microsoft/Teams/__schema__/v2.0.0/chatMember/getAll.json
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"@odata.type": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"tenantId": {
|
||||
"type": "string"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string"
|
||||
},
|
||||
"visibleHistoryStartDateTime": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
{
|
||||
"name": "Teams chatMember getAll returnAll",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "6666-9999-77777",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [880, 380]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "chatMember",
|
||||
"operation": "getAll",
|
||||
"chatId": {
|
||||
"__rl": true,
|
||||
"value": "19:ff1e2d3c4b5a69788796a5b4c3d2e1f0@thread.v2",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Project team (group)"
|
||||
},
|
||||
"returnAll": true
|
||||
},
|
||||
"id": "6666-5555-77777",
|
||||
"name": "Microsoft Teams",
|
||||
"type": "n8n-nodes-base.microsoftTeams",
|
||||
"typeVersion": 2,
|
||||
"position": [1100, 380],
|
||||
"credentials": {
|
||||
"microsoftTeamsOAuth2Api": {
|
||||
"id": "6isd5ytvA0qV78eK",
|
||||
"name": "Microsoft Teams account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "9d1a2e59-c71c-486c-b3ac-dec6adbc26b3",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1400, 380]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"id": "MCMjMCMjMjM3ODZjYTYtN2ZmMi00NjcyLTg3ZDAtNWM2NDllZTBhMzM3IyMxOTplYmVkOWFkNDJjOTA0ZDZjODNhZGYwZGIzNjAwNTNlY0B0aHJlYWQudjIjI2U3NmY0NTZmLTVjM2YtNGYxZS05ZDVlLTRkOGYwZjZhYjExMQ==",
|
||||
"@odata.type": "#microsoft.graph.aadUserConversationMember",
|
||||
"roles": ["owner"],
|
||||
"displayName": "Ann Smith",
|
||||
"userId": "e76f456f-5c3f-4f1e-9d5e-4d8f0f6ab111",
|
||||
"email": "ann@contoso.com",
|
||||
"tenantId": "23786ca6-7ff2-4672-87d0-5c649ee0a337",
|
||||
"visibleHistoryStartDateTime": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "MCMjMCMjMjM3ODZjYTYtN2ZmMi00NjcyLTg3ZDAtNWM2NDllZTBhMzM3IyMxOTplYmVkOWFkNDJjOTA0ZDZjODNhZGYwZGIzNjAwNTNlY0B0aHJlYWQudjIjI2FhMTFiYjIyLTVjM2YtNGYxZS05ZDVlLTRkOGYwZjZhYjIyMg==",
|
||||
"@odata.type": "#microsoft.graph.aadUserConversationMember",
|
||||
"roles": ["owner"],
|
||||
"displayName": "Bob Jones",
|
||||
"userId": "aa11bb22-5c3f-4f1e-9d5e-4d8f0f6ab222",
|
||||
"email": "bob@contoso.com",
|
||||
"tenantId": "23786ca6-7ff2-4672-87d0-5c649ee0a337",
|
||||
"visibleHistoryStartDateTime": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Teams",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Microsoft Teams": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "8a1b2c3d-0004-4000-8000-000000000004",
|
||||
"id": "i3NYGF0LXV4qDFV9",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
// Membership ids are opaque base64 of `0##0##<tenantId>##<chatId>##<userId>`,
|
||||
// derived here from the tenant, chat and user ids below so the fixture is self-consistent.
|
||||
const member1 = {
|
||||
id: 'MCMjMCMjMjM3ODZjYTYtN2ZmMi00NjcyLTg3ZDAtNWM2NDllZTBhMzM3IyMxOTplYmVkOWFkNDJjOTA0ZDZjODNhZGYwZGIzNjAwNTNlY0B0aHJlYWQudjIjI2U3NmY0NTZmLTVjM2YtNGYxZS05ZDVlLTRkOGYwZjZhYjExMQ==',
|
||||
'@odata.type': '#microsoft.graph.aadUserConversationMember',
|
||||
roles: ['owner'],
|
||||
displayName: 'Ann Smith',
|
||||
userId: 'e76f456f-5c3f-4f1e-9d5e-4d8f0f6ab111',
|
||||
email: 'ann@contoso.com',
|
||||
tenantId: '23786ca6-7ff2-4672-87d0-5c649ee0a337',
|
||||
visibleHistoryStartDateTime: '0001-01-01T00:00:00Z',
|
||||
};
|
||||
const member2 = {
|
||||
id: 'MCMjMCMjMjM3ODZjYTYtN2ZmMi00NjcyLTg3ZDAtNWM2NDllZTBhMzM3IyMxOTplYmVkOWFkNDJjOTA0ZDZjODNhZGYwZGIzNjAwNTNlY0B0aHJlYWQudjIjI2FhMTFiYjIyLTVjM2YtNGYxZS05ZDVlLTRkOGYwZjZhYjIyMg==',
|
||||
'@odata.type': '#microsoft.graph.aadUserConversationMember',
|
||||
roles: ['owner'],
|
||||
displayName: 'Bob Jones',
|
||||
userId: 'aa11bb22-5c3f-4f1e-9d5e-4d8f0f6ab222',
|
||||
email: 'bob@contoso.com',
|
||||
tenantId: '23786ca6-7ff2-4672-87d0-5c649ee0a337',
|
||||
visibleHistoryStartDateTime: null,
|
||||
};
|
||||
|
||||
describe('Test MicrosoftTeamsV2, chatMember => getAll', () => {
|
||||
// Registered WITHOUT `.query(...)`: this endpoint supports no OData parameters, so
|
||||
// the test fails if `$top` is ever added.
|
||||
// The workflow passes this chat id By ID and percent-encoded, the way the RLC hint
|
||||
// tells users to copy it out of a Teams URL. The decoded form below is what
|
||||
// `validateMicrosoftGraphId` must interpolate into the path.
|
||||
nock('https://graph.microsoft.com')
|
||||
.get('/v1.0/chats/19:ebed9ad42c904d6c83adf0db360053ec@thread.v2/members')
|
||||
.reply(200, { value: [member1, member2] });
|
||||
|
||||
// Two pages for the returnAll workflow, on its own chat id so the interceptors
|
||||
// cannot be matched in the wrong order. The nextLink must stay same-origin or the
|
||||
// transport refuses it.
|
||||
nock('https://graph.microsoft.com')
|
||||
.get('/v1.0/chats/19:ff1e2d3c4b5a69788796a5b4c3d2e1f0@thread.v2/members')
|
||||
.reply(200, {
|
||||
value: [member1],
|
||||
'@odata.nextLink':
|
||||
'https://graph.microsoft.com/v1.0/chats/19:ff1e2d3c4b5a69788796a5b4c3d2e1f0@thread.v2/members?$skiptoken=page2',
|
||||
})
|
||||
.get('/v1.0/chats/19:ff1e2d3c4b5a69788796a5b4c3d2e1f0@thread.v2/members')
|
||||
.query({ $skiptoken: 'page2' })
|
||||
.reply(200, { value: [member2] });
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['getAll.workflow.json', 'getAll.returnAll.workflow.json'],
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"name": "Teams chatMember getAll",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "6666-9999-77777",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [880, 380]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "chatMember",
|
||||
"operation": "getAll",
|
||||
"chatId": {
|
||||
"__rl": true,
|
||||
"value": "19%3Aebed9ad42c904d6c83adf0db360053ec%40thread.v2",
|
||||
"mode": "id"
|
||||
},
|
||||
"limit": 1
|
||||
},
|
||||
"id": "6666-5555-77777",
|
||||
"name": "Microsoft Teams",
|
||||
"type": "n8n-nodes-base.microsoftTeams",
|
||||
"typeVersion": 2,
|
||||
"position": [1100, 380],
|
||||
"credentials": {
|
||||
"microsoftTeamsOAuth2Api": {
|
||||
"id": "6isd5ytvA0qV78eK",
|
||||
"name": "Microsoft Teams account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "9d1a2e59-c71c-486c-b3ac-dec6adbc26b3",
|
||||
"name": "No Operation, do nothing",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1400, 380]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"id": "MCMjMCMjMjM3ODZjYTYtN2ZmMi00NjcyLTg3ZDAtNWM2NDllZTBhMzM3IyMxOTplYmVkOWFkNDJjOTA0ZDZjODNhZGYwZGIzNjAwNTNlY0B0aHJlYWQudjIjI2U3NmY0NTZmLTVjM2YtNGYxZS05ZDVlLTRkOGYwZjZhYjExMQ==",
|
||||
"@odata.type": "#microsoft.graph.aadUserConversationMember",
|
||||
"roles": ["owner"],
|
||||
"displayName": "Ann Smith",
|
||||
"userId": "e76f456f-5c3f-4f1e-9d5e-4d8f0f6ab111",
|
||||
"email": "ann@contoso.com",
|
||||
"tenantId": "23786ca6-7ff2-4672-87d0-5c649ee0a337",
|
||||
"visibleHistoryStartDateTime": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Teams",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Microsoft Teams": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "8a1b2c3d-0003-4000-8000-000000000003",
|
||||
"id": "i3NYGF0LXV4qDFV9",
|
||||
"meta": {
|
||||
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
|
|
@ -72,6 +72,26 @@ describe('Microsoft Teams V2 — Service Principal runtime guards', () => {
|
|||
expect(transport.microsoftApiRequestAllItems).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(['getAll'])(
|
||||
'chatMember:%s throws a static error and issues no request under SP',
|
||||
async (operation) => {
|
||||
selectSp({
|
||||
resource: 'chatMember',
|
||||
operation,
|
||||
chatId: 'chatID',
|
||||
returnAll: true,
|
||||
});
|
||||
|
||||
await expect(node.execute.call(ctx)).rejects.toThrow(
|
||||
'Chat members are not available with the Service Principal credential',
|
||||
);
|
||||
expect(transport.microsoftApiRequest).not.toHaveBeenCalled();
|
||||
expect(transport.microsoftApiRequestAllItems).not.toHaveBeenCalled();
|
||||
// the guard reads only the `authentication` parameter, never the credential itself
|
||||
expect(ctx.getCredentials).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
['create', { subject: 'Sync', startDateTime: '2026-09-01T10:00:00Z' }],
|
||||
['get', { meetingId: { __rl: true, mode: 'id', value: 'meeting-id' } }],
|
||||
|
|
|
|||
|
|
@ -32,76 +32,40 @@ describe('Microsoft Teams Service Principal displayOptions contract', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('chatMessage — hidden under SP via the slash-prefixed field-level key', () => {
|
||||
it('operation selector carries hide["/authentication"] = [SP]', () => {
|
||||
const op = actionProps.find(
|
||||
(p) => p.name === 'operation' && p.displayOptions?.show?.resource?.includes('chatMessage'),
|
||||
);
|
||||
expect(isSpHidden(op)).toBe(true);
|
||||
// distinct from the un-prefixed credential gate
|
||||
expect(op?.displayOptions?.hide?.authentication).toBeUndefined();
|
||||
});
|
||||
describe.each(['chatMessage', 'chatMember', 'onlineMeeting'])(
|
||||
'%s - hidden under SP via the slash-prefixed field-level key',
|
||||
(resource) => {
|
||||
it('operation selector carries hide["/authentication"] = [SP]', () => {
|
||||
const op = actionProps.find(
|
||||
(p) => p.name === 'operation' && p.displayOptions?.show?.resource?.includes(resource),
|
||||
);
|
||||
expect(isSpHidden(op)).toBe(true);
|
||||
// distinct from the un-prefixed credential gate
|
||||
expect(op?.displayOptions?.hide?.authentication).toBeUndefined();
|
||||
});
|
||||
|
||||
it('every chatMessage field copy is hidden under SP', () => {
|
||||
const chatFields = actionProps.filter((p) =>
|
||||
p.displayOptions?.show?.resource?.includes('chatMessage'),
|
||||
);
|
||||
// the chatId RLC, message, contentType, options, messageId, send-and-wait props…
|
||||
const gatedFields = chatFields.filter((p) => p.type !== 'notice' && p.name !== 'operation');
|
||||
expect(gatedFields.length).toBeGreaterThan(0);
|
||||
for (const field of gatedFields) {
|
||||
expect(isSpHidden(field)).toBe(true);
|
||||
}
|
||||
});
|
||||
it('every field copy is hidden under SP', () => {
|
||||
const fields = actionProps.filter((p) =>
|
||||
p.displayOptions?.show?.resource?.includes(resource),
|
||||
);
|
||||
const gatedFields = fields.filter((p) => p.type !== 'notice' && p.name !== 'operation');
|
||||
expect(gatedFields.length).toBeGreaterThan(0);
|
||||
for (const field of gatedFields) {
|
||||
expect(isSpHidden(field)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('shows an SP notice for the chatMessage resource', () => {
|
||||
const notice = actionProps.find(
|
||||
(p) =>
|
||||
p.type === 'notice' &&
|
||||
p.displayOptions?.show?.resource?.includes('chatMessage') &&
|
||||
p.displayOptions?.show?.authentication?.includes(SERVICE_PRINCIPAL_AUTH),
|
||||
);
|
||||
expect(notice).toBeDefined();
|
||||
expect(notice?.displayOptions?.show?.authentication).toEqual([SERVICE_PRINCIPAL_AUTH]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onlineMeeting — hidden under SP via the slash-prefixed field-level key', () => {
|
||||
it('operation selector carries hide["/authentication"] = [SP]', () => {
|
||||
const op = actionProps.find(
|
||||
(p) =>
|
||||
p.name === 'operation' && p.displayOptions?.show?.resource?.includes('onlineMeeting'),
|
||||
);
|
||||
expect(isSpHidden(op)).toBe(true);
|
||||
// distinct from the un-prefixed credential gate
|
||||
expect(op?.displayOptions?.hide?.authentication).toBeUndefined();
|
||||
});
|
||||
|
||||
it('every onlineMeeting field copy is hidden under SP', () => {
|
||||
const meetingFields = actionProps.filter((p) =>
|
||||
p.displayOptions?.show?.resource?.includes('onlineMeeting'),
|
||||
);
|
||||
// subject, start/end times, options, meetingId…
|
||||
const gatedFields = meetingFields.filter(
|
||||
(p) => p.type !== 'notice' && p.name !== 'operation',
|
||||
);
|
||||
expect(gatedFields.length).toBeGreaterThan(0);
|
||||
for (const field of gatedFields) {
|
||||
expect(isSpHidden(field)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('shows an SP notice for the onlineMeeting resource', () => {
|
||||
const notice = actionProps.find(
|
||||
(p) =>
|
||||
p.type === 'notice' &&
|
||||
p.displayOptions?.show?.resource?.includes('onlineMeeting') &&
|
||||
p.displayOptions?.show?.authentication?.includes(SERVICE_PRINCIPAL_AUTH),
|
||||
);
|
||||
expect(notice).toBeDefined();
|
||||
expect(notice?.displayOptions?.show?.authentication).toEqual([SERVICE_PRINCIPAL_AUTH]);
|
||||
});
|
||||
});
|
||||
it('shows an SP notice for the resource', () => {
|
||||
const notice = actionProps.find(
|
||||
(p) =>
|
||||
p.type === 'notice' &&
|
||||
p.displayOptions?.show?.resource?.includes(resource) &&
|
||||
p.displayOptions?.show?.authentication?.includes(SERVICE_PRINCIPAL_AUTH),
|
||||
);
|
||||
expect(notice?.displayOptions?.show?.authentication).toEqual([SERVICE_PRINCIPAL_AUTH]);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe('channelMessage — only the sending operations are hidden under SP', () => {
|
||||
const fieldsFor = (operation: string) =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
import type { INodeProperties, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import { returnAllOrLimit } from '@utils/descriptions';
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { chatRLC } from '../../descriptions';
|
||||
import { buildTeamsPath, microsoftApiRequestAllItems, SP_HIDE } from '../../transport';
|
||||
import { throwIfChatMemberUnsupported } from './sharedGuard';
|
||||
|
||||
const properties: INodeProperties[] = [chatRLC, ...returnAllOrLimit];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['chatMember'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
hide: {
|
||||
...SP_HIDE,
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, i: number) {
|
||||
// https://learn.microsoft.com/en-us/graph/api/chat-list-members?view=graph-rest-1.0
|
||||
|
||||
// The chat picker cannot list chats app-only; fail before any request.
|
||||
throwIfChatMemberUnsupported.call(this);
|
||||
|
||||
const chatId = this.getNodeParameter('chatId', i, '', { extractValue: true }) as string;
|
||||
const endpoint = buildTeamsPath.call(this, ['/v1.0/chats/', { id: chatId }, '/members']);
|
||||
|
||||
// No `$top`: this endpoint supports no OData query parameters, so the limit is
|
||||
// applied client-side while paging through @odata.nextLink.
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
const limit = returnAll ? undefined : this.getNodeParameter('limit', i);
|
||||
return await microsoftApiRequestAllItems.call(this, 'value', 'GET', endpoint, {}, {}, limit);
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as getAll from './getAll.operation';
|
||||
import { SERVICE_PRINCIPAL_AUTH, SP_HIDE } from '../../transport';
|
||||
|
||||
export { getAll };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName:
|
||||
'Chat members are not available with the Service Principal credential. The chat picker cannot list chats app-only; use an OAuth2 credential for chat actions.',
|
||||
name: 'chatMemberServicePrincipalNotice',
|
||||
type: 'notice',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['chatMember'],
|
||||
authentication: [SERVICE_PRINCIPAL_AUTH],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['chatMember'],
|
||||
},
|
||||
hide: {
|
||||
...SP_HIDE,
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many members of a chat',
|
||||
action: 'Get many chat members',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
|
||||
...getAll.description,
|
||||
];
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { getTeamsCredentialType, SERVICE_PRINCIPAL_AUTH } from '../../transport';
|
||||
|
||||
/**
|
||||
* Throws a static `NodeOperationError` when the node is configured with the
|
||||
* app-only (Service Principal) credential. The endpoints themselves have
|
||||
* application permissions, but the chat picker does not: `GET /chats` is
|
||||
* delegated-only (app-only must go through `GET /users/{id}/chats`), so every
|
||||
* chatMember operation guards on this BEFORE any request - covering
|
||||
* hand-edited workflows that bypass the hidden UI.
|
||||
*/
|
||||
export function throwIfChatMemberUnsupported(this: IExecuteFunctions): void {
|
||||
if (getTeamsCredentialType.call(this) === SERVICE_PRINCIPAL_AUTH) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Chat members are not available with the Service Principal credential',
|
||||
{
|
||||
description:
|
||||
'The chat picker cannot list chats app-only. Use an OAuth2 credential for chat actions.',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import type { AllEntities } from 'n8n-workflow';
|
|||
type NodeMap = {
|
||||
channel: 'create' | 'deleteChannel' | 'get' | 'getAll' | 'update';
|
||||
channelMessage: 'create' | 'get' | 'getAll' | 'getAllReplies' | 'reply';
|
||||
chatMember: 'getAll';
|
||||
chatMessage: 'create' | 'get' | 'getAll' | 'sendAndWait';
|
||||
onlineMeeting: 'create' | 'get';
|
||||
task: 'create' | 'deleteTask' | 'get' | 'getAll' | 'update';
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
|
||||
import * as channel from './channel';
|
||||
import * as channelMessage from './channelMessage';
|
||||
import * as chatMember from './chatMember';
|
||||
import * as chatMessage from './chatMessage';
|
||||
import type { MicrosoftTeamsType } from './node.type';
|
||||
import * as onlineMeeting from './onlineMeeting';
|
||||
|
|
@ -64,6 +65,9 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
|
|||
instanceId,
|
||||
);
|
||||
break;
|
||||
case 'chatMember':
|
||||
responseData = await chatMember[microsoftTeamsTypeData.operation].execute.call(this, i);
|
||||
break;
|
||||
case 'chatMessage':
|
||||
responseData = await chatMessage[microsoftTeamsTypeData.operation].execute.call(
|
||||
this,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
|
|||
|
||||
import * as channel from './channel';
|
||||
import * as channelMessage from './channelMessage';
|
||||
import * as chatMember from './chatMember';
|
||||
import * as chatMessage from './chatMessage';
|
||||
import * as onlineMeeting from './onlineMeeting';
|
||||
import * as task from './task';
|
||||
|
|
@ -94,6 +95,10 @@ export const versionDescription: INodeTypeDescription = {
|
|||
name: 'Channel Message',
|
||||
value: 'channelMessage',
|
||||
},
|
||||
{
|
||||
name: 'Chat Member',
|
||||
value: 'chatMember',
|
||||
},
|
||||
{
|
||||
name: 'Chat Message',
|
||||
value: 'chatMessage',
|
||||
|
|
@ -112,6 +117,7 @@ export const versionDescription: INodeTypeDescription = {
|
|||
|
||||
...channel.description,
|
||||
...channelMessage.description,
|
||||
...chatMember.description,
|
||||
...chatMessage.description,
|
||||
...onlineMeeting.description,
|
||||
...task.description,
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export function validateMicrosoftGraphId(id: string, node: INode): string {
|
|||
throw new NodeOperationError(node, 'A required ID is empty', {
|
||||
// Teams wording; make it injectable (UserTargetMessages pattern in
|
||||
// nodes/Microsoft/GenericFunctions.ts) when the second consumer (SharePoint v2) lands.
|
||||
description: 'Set the team, channel, plan, bucket, task or meeting ID and try again.',
|
||||
description: 'Set the chat, team, channel, plan, bucket, task or meeting ID and try again.',
|
||||
});
|
||||
}
|
||||
let value: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user