From 951ccfc2a5b3126bf54ff359af2229800255dbd2 Mon Sep 17 00:00:00 2001 From: Mathijs Verbeeck Date: Thu, 2 Apr 2026 17:58:02 +0200 Subject: [PATCH 1/2] Enhances chat message list --- .../docs/cmd/teams/chat/chat-message-list.mdx | 25 ++- .../commands/chat/chat-message-list.spec.ts | 174 +++++++++++++++++- .../teams/commands/chat/chat-message-list.ts | 55 +++++- 3 files changed, 245 insertions(+), 9 deletions(-) diff --git a/docs/docs/cmd/teams/chat/chat-message-list.mdx b/docs/docs/cmd/teams/chat/chat-message-list.mdx index e41718d6b61..2d1e60c3a64 100644 --- a/docs/docs/cmd/teams/chat/chat-message-list.mdx +++ b/docs/docs/cmd/teams/chat/chat-message-list.mdx @@ -18,8 +18,17 @@ m365 teams chat message list [options] `-i, --chatId ` : The ID of the chat conversation. -`--endDateTime [endDateTime]` +`--createdEndDateTime [createdEndDateTime]` : Time indicating the exclusive end of a time range when the message was created. + +`--endDateTime [endDateTime]` +: (deprecated. Use `createdEndDateTime` instead) Time indicating the exclusive end of a time range when the message was created. + +`--modifiedStartDateTime [modifiedStartDateTime]` +: Time indicating the inclusive start of a time range when the message was last modified. Cannot be combined with `createdEndDateTime`. + +`--modifiedEndDateTime [modifiedEndDateTime]` +: Time indicating the exclusive end of a time range when the message was last modified. Cannot be combined with `createdEndDateTime`. ``` @@ -54,7 +63,19 @@ m365 teams chat message list --chatId 19:2da4c29f6d7041eca70b638b43d45437@thread List messages from a Microsoft Teams chat conversation created before November 1, 2022 ```sh -m365 teams chat message list --chatId 19:2da4c29f6d7041eca70b638b43d45437@thread.v2 --endDateTime 2022-11-01T00:00:00Z +m365 teams chat message list --chatId 19:2da4c29f6d7041eca70b638b43d45437@thread.v2 --createdEndDateTime 2022-11-01T00:00:00Z +``` + +List messages from a Microsoft Teams chat conversation modified after October 1, 2025 + +```sh +m365 teams chat message list --chatId 19:2da4c29f6d7041eca70b638b43d45437@thread.v2 --modifiedStartDateTime 2025-10-01T00:00:00Z +``` + +List messages from a Microsoft Teams chat conversation modified between October 1, 2025 and November 1, 2025 + +```sh +m365 teams chat message list --chatId 19:2da4c29f6d7041eca70b638b43d45437@thread.v2 --modifiedStartDateTime 2025-10-01T00:00:00Z --modifiedEndDateTime 2025-11-01T00:00:00Z ``` ## Response diff --git a/src/m365/teams/commands/chat/chat-message-list.spec.ts b/src/m365/teams/commands/chat/chat-message-list.spec.ts index bac9bf3d54e..0f68f8a46a4 100644 --- a/src/m365/teams/commands/chat/chat-message-list.spec.ts +++ b/src/m365/teams/commands/chat/chat-message-list.spec.ts @@ -13,6 +13,7 @@ import { sinonUtil } from '../../../../utils/sinonUtil.js'; import commands from '../../commands.js'; import command, { options } from './chat-message-list.js'; import { settingsNames } from '../../../../settingsNames.js'; +import { z } from 'zod'; describe(commands.CHAT_MESSAGE_LIST, () => { @@ -134,8 +135,10 @@ describe(commands.CHAT_MESSAGE_LIST, () => { let log: string[]; let logger: Logger; let loggerLogSpy: sinon.SinonSpy; + let loggerLogToStderrSpy: sinon.SinonSpy; let commandInfo: CommandInfo; let commandOptionsSchema: typeof options; + let refinedSchema: z.ZodTypeAny; before(() => { sinon.stub(auth, 'restoreAuth').resolves(); @@ -146,6 +149,7 @@ describe(commands.CHAT_MESSAGE_LIST, () => { commandInfo = cli.getCommandInfo(command); commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; + refinedSchema = commandInfo.command.getRefinedSchema!(commandOptionsSchema as any)!; sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => settingName === settingsNames.prompt ? false : defaultValue); }); @@ -163,6 +167,7 @@ describe(commands.CHAT_MESSAGE_LIST, () => { } }; loggerLogSpy = sinon.spy(logger, 'log'); + loggerLogToStderrSpy = sinon.spy(logger, 'logToStderr'); }); afterEach(() => { @@ -213,13 +218,73 @@ describe(commands.CHAT_MESSAGE_LIST, () => { it('fails validation if the endDateTime is not valid', async () => { const actual = commandOptionsSchema.safeParse({ - chatId: '19:2da4c29f6d7041eca70b638b43d45437', + chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2', endDateTime: 'invalid date time' }); assert.notStrictEqual(actual.success, true); }); - it('validates for a correct input', async () => { + it('fails validation if the createdEndDateTime is not valid', async () => { + const actual = commandOptionsSchema.safeParse({ + chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2', + createdEndDateTime: 'invalid date time' + }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation if the modifiedStartDateTime is not valid', async () => { + const actual = commandOptionsSchema.safeParse({ + chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2', + modifiedStartDateTime: 'not a date' + }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation if the modifiedEndDateTime is not valid', async () => { + const actual = commandOptionsSchema.safeParse({ + chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2', + modifiedEndDateTime: 'not a date' + }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation if both endDateTime and createdEndDateTime are specified', async () => { + const actual = refinedSchema.safeParse({ + chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2', + endDateTime: '2025-11-01T00:00:00Z', + createdEndDateTime: '2025-11-01T00:00:00Z' + }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation if createdEndDateTime is combined with modifiedStartDateTime', async () => { + const actual = refinedSchema.safeParse({ + chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2', + createdEndDateTime: '2025-11-01T00:00:00Z', + modifiedStartDateTime: '2025-10-01T00:00:00Z' + }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation if createdEndDateTime is combined with modifiedEndDateTime', async () => { + const actual = refinedSchema.safeParse({ + chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2', + createdEndDateTime: '2025-11-01T00:00:00Z', + modifiedEndDateTime: '2025-12-01T00:00:00Z' + }); + assert.notStrictEqual(actual.success, true); + }); + + it('fails validation if endDateTime is combined with modifiedStartDateTime', async () => { + const actual = refinedSchema.safeParse({ + chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2', + endDateTime: '2025-11-01T00:00:00Z', + modifiedStartDateTime: '2025-10-01T00:00:00Z' + }); + assert.notStrictEqual(actual.success, true); + }); + + it('validates for a correct input with endDateTime', async () => { const actual = commandOptionsSchema.safeParse({ chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2", endDateTime: "2022-11-01T00:00:00Z" @@ -227,6 +292,23 @@ describe(commands.CHAT_MESSAGE_LIST, () => { assert.strictEqual(actual.success, true); }); + it('validates for a correct input with createdEndDateTime', async () => { + const actual = commandOptionsSchema.safeParse({ + chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2", + createdEndDateTime: "2022-11-01T00:00:00Z" + }); + assert.strictEqual(actual.success, true); + }); + + it('validates for a correct input with modifiedStartDateTime and modifiedEndDateTime', async () => { + const actual = commandOptionsSchema.safeParse({ + chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2", + modifiedStartDateTime: "2025-10-01T00:00:00Z", + modifiedEndDateTime: "2025-11-01T00:00:00Z" + }); + assert.strictEqual(actual.success, true); + }); + it('lists chat messages (verbose)', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://graph.microsoft.com/v1.0/chats/19:2da4c29f6d7041eca70b638b43d45437@thread.v2/messages`) { @@ -248,7 +330,7 @@ describe(commands.CHAT_MESSAGE_LIST, () => { assert(loggerLogSpy.calledWith(apiResponse)); }); - it('lists chat messages using endDateTime', async () => { + it('lists chat messages using deprecated endDateTime and shows deprecation warning', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://graph.microsoft.com/v1.0/chats/19:2da4c29f6d7041eca70b638b43d45437@thread.v2/messages?$filter=createdDateTime lt 2025-11-01T00:00:00Z&$orderby=createdDateTime desc`) { return { @@ -266,6 +348,92 @@ describe(commands.CHAT_MESSAGE_LIST, () => { } }); + assert(loggerLogSpy.calledWith(apiResponse)); + assert(loggerLogToStderrSpy.calledWith(sinon.match(`Option 'endDateTime' is deprecated. Please use 'createdEndDateTime' instead.`))); + }); + + it('lists chat messages using createdEndDateTime', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/chats/19:2da4c29f6d7041eca70b638b43d45437@thread.v2/messages?$filter=createdDateTime lt 2025-11-01T00:00:00Z&$orderby=createdDateTime desc`) { + return { + value: [...apiResponse] + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2", + createdEndDateTime: "2025-11-01T00:00:00Z" + } + }); + + assert(loggerLogSpy.calledWith(apiResponse)); + }); + + it('lists chat messages using modifiedStartDateTime', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/chats/19:2da4c29f6d7041eca70b638b43d45437@thread.v2/messages?$filter=lastModifiedDateTime gt 2025-10-01T00:00:00Z&$orderby=lastModifiedDateTime desc`) { + return { + value: [...apiResponse] + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2", + modifiedStartDateTime: "2025-10-01T00:00:00Z" + } + }); + + assert(loggerLogSpy.calledWith(apiResponse)); + }); + + it('lists chat messages using modifiedEndDateTime', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/chats/19:2da4c29f6d7041eca70b638b43d45437@thread.v2/messages?$filter=lastModifiedDateTime lt 2025-12-01T00:00:00Z&$orderby=lastModifiedDateTime desc`) { + return { + value: [...apiResponse] + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2", + modifiedEndDateTime: "2025-12-01T00:00:00Z" + } + }); + + assert(loggerLogSpy.calledWith(apiResponse)); + }); + + it('lists chat messages using both modifiedStartDateTime and modifiedEndDateTime', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/chats/19:2da4c29f6d7041eca70b638b43d45437@thread.v2/messages?$filter=lastModifiedDateTime gt 2025-10-01T00:00:00Z and lastModifiedDateTime lt 2025-12-01T00:00:00Z&$orderby=lastModifiedDateTime desc`) { + return { + value: [...apiResponse] + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2", + modifiedStartDateTime: "2025-10-01T00:00:00Z", + modifiedEndDateTime: "2025-12-01T00:00:00Z" + } + }); + assert(loggerLogSpy.calledWith(apiResponse)); }); diff --git a/src/m365/teams/commands/chat/chat-message-list.ts b/src/m365/teams/commands/chat/chat-message-list.ts index 962a1f261a9..15186c03d67 100644 --- a/src/m365/teams/commands/chat/chat-message-list.ts +++ b/src/m365/teams/commands/chat/chat-message-list.ts @@ -18,10 +18,25 @@ export const options = z.strictObject({ error: e => `'${e.input}' is not a valid value for option chatId.` }) .alias('i'), + createdEndDateTime: z.string() + .refine(time => validation.isValidISODateTime(time), { + error: e => `'${e.input}' is not a valid ISO date-time string for option createdEndDateTime.` + }) + .optional(), endDateTime: z.string() .refine(time => validation.isValidISODateTime(time), { error: e => `'${e.input}' is not a valid ISO date-time string for option endDateTime.` }) + .optional(), + modifiedStartDateTime: z.string() + .refine(time => validation.isValidISODateTime(time), { + error: e => `'${e.input}' is not a valid ISO date-time string for option modifiedStartDateTime.` + }) + .optional(), + modifiedEndDateTime: z.string() + .refine(time => validation.isValidISODateTime(time), { + error: e => `'${e.input}' is not a valid ISO date-time string for option modifiedEndDateTime.` + }) .optional() }); @@ -47,14 +62,46 @@ class TeamsChatMessageListCommand extends GraphCommand { return options; } + public getRefinedSchema(schema: typeof options): z.ZodObject | undefined { + return schema + .refine(options => !(options.endDateTime && options.createdEndDateTime), { + error: 'Specify either endDateTime or createdEndDateTime, but not both.' + }) + .refine(options => !(options.createdEndDateTime && (options.modifiedStartDateTime || options.modifiedEndDateTime)), { + error: 'You cannot combine createdEndDateTime with modifiedStartDateTime or modifiedEndDateTime. These filters operate on different properties.' + }) + .refine(options => !(options.endDateTime && (options.modifiedStartDateTime || options.modifiedEndDateTime)), { + error: 'You cannot combine endDateTime with modifiedStartDateTime or modifiedEndDateTime. These filters operate on different properties.' + }); + } + public async commandAction(logger: Logger, args: CommandArgs): Promise { + if (args.options.endDateTime) { + await this.warn(logger, `Option 'endDateTime' is deprecated. Please use 'createdEndDateTime' instead.`); + + if (!args.options.createdEndDateTime) { + args.options.createdEndDateTime = args.options.endDateTime; + } + } + try { let apiUrl = `${this.resource}/v1.0/chats/${args.options.chatId}/messages`; - if (args.options.endDateTime) { - // You can only filter results if the request URL contains the $orderby and $filter query parameters configured for the same property; - // otherwise, the $filter query option is ignored. - apiUrl += `?$filter=createdDateTime lt ${args.options.endDateTime}&$orderby=createdDateTime desc`; + if (args.options.createdEndDateTime) { + apiUrl += `?$filter=createdDateTime lt ${args.options.createdEndDateTime}&$orderby=createdDateTime desc`; + } + else if (args.options.modifiedStartDateTime || args.options.modifiedEndDateTime) { + const filters: string[] = []; + + if (args.options.modifiedStartDateTime) { + filters.push(`lastModifiedDateTime gt ${args.options.modifiedStartDateTime}`); + } + + if (args.options.modifiedEndDateTime) { + filters.push(`lastModifiedDateTime lt ${args.options.modifiedEndDateTime}`); + } + + apiUrl += `?$filter=${filters.join(' and ')}&$orderby=lastModifiedDateTime desc`; } const items = await odata.getAllItems(apiUrl); From a9225738a5338c448d952a9bb0d6a351e1c22509 Mon Sep 17 00:00:00 2001 From: Mathijs Verbeeck Date: Thu, 2 Apr 2026 23:11:26 +0200 Subject: [PATCH 2/2] Fixes feedback from Martin --- src/m365/teams/commands/chat/chat-message-list.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/m365/teams/commands/chat/chat-message-list.ts b/src/m365/teams/commands/chat/chat-message-list.ts index 15186c03d67..9201f515652 100644 --- a/src/m365/teams/commands/chat/chat-message-list.ts +++ b/src/m365/teams/commands/chat/chat-message-list.ts @@ -79,9 +79,7 @@ class TeamsChatMessageListCommand extends GraphCommand { if (args.options.endDateTime) { await this.warn(logger, `Option 'endDateTime' is deprecated. Please use 'createdEndDateTime' instead.`); - if (!args.options.createdEndDateTime) { - args.options.createdEndDateTime = args.options.endDateTime; - } + args.options.createdEndDateTime = args.options.endDateTime; } try {