Skip to content

Commit af0ea57

Browse files
Enhances chat message list. Closes #7169
1 parent e5906b0 commit af0ea57

3 files changed

Lines changed: 243 additions & 9 deletions

File tree

docs/docs/cmd/teams/chat/chat-message-list.mdx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,17 @@ m365 teams chat message list [options]
1818
`-i, --chatId <chatId>`
1919
: The ID of the chat conversation.
2020

21-
`--endDateTime [endDateTime]`
21+
`--createdEndDateTime [createdEndDateTime]`
2222
: Time indicating the exclusive end of a time range when the message was created.
23+
24+
`--endDateTime [endDateTime]`
25+
: (deprecated. Use `createdEndDateTime` instead) Time indicating the exclusive end of a time range when the message was created.
26+
27+
`--modifiedStartDateTime [modifiedStartDateTime]`
28+
: Time indicating the inclusive start of a time range when the message was last modified. Cannot be combined with `createdEndDateTime`.
29+
30+
`--modifiedEndDateTime [modifiedEndDateTime]`
31+
: Time indicating the exclusive end of a time range when the message was last modified. Cannot be combined with `createdEndDateTime`.
2332
```
2433

2534
<Global />
@@ -54,7 +63,19 @@ m365 teams chat message list --chatId 19:2da4c29f6d7041eca70b638b43d45437@thread
5463
List messages from a Microsoft Teams chat conversation created before November 1, 2022
5564

5665
```sh
57-
m365 teams chat message list --chatId 19:2da4c29f6d7041eca70b638b43d45437@thread.v2 --endDateTime 2022-11-01T00:00:00Z
66+
m365 teams chat message list --chatId 19:2da4c29f6d7041eca70b638b43d45437@thread.v2 --createdEndDateTime 2022-11-01T00:00:00Z
67+
```
68+
69+
List messages from a Microsoft Teams chat conversation modified after October 1, 2025
70+
71+
```sh
72+
m365 teams chat message list --chatId 19:2da4c29f6d7041eca70b638b43d45437@thread.v2 --modifiedStartDateTime 2025-10-01T00:00:00Z
73+
```
74+
75+
List messages from a Microsoft Teams chat conversation modified between October 1, 2025 and November 1, 2025
76+
77+
```sh
78+
m365 teams chat message list --chatId 19:2da4c29f6d7041eca70b638b43d45437@thread.v2 --modifiedStartDateTime 2025-10-01T00:00:00Z --modifiedEndDateTime 2025-11-01T00:00:00Z
5879
```
5980

6081
## Response

src/m365/teams/commands/chat/chat-message-list.spec.ts

Lines changed: 171 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { sinonUtil } from '../../../../utils/sinonUtil.js';
1313
import commands from '../../commands.js';
1414
import command, { options } from './chat-message-list.js';
1515
import { settingsNames } from '../../../../settingsNames.js';
16+
import { z } from 'zod';
1617

1718
describe(commands.CHAT_MESSAGE_LIST, () => {
1819

@@ -134,8 +135,10 @@ describe(commands.CHAT_MESSAGE_LIST, () => {
134135
let log: string[];
135136
let logger: Logger;
136137
let loggerLogSpy: sinon.SinonSpy;
138+
let loggerLogToStderrSpy: sinon.SinonSpy;
137139
let commandInfo: CommandInfo;
138140
let commandOptionsSchema: typeof options;
141+
let refinedSchema: z.ZodTypeAny;
139142

140143
before(() => {
141144
sinon.stub(auth, 'restoreAuth').resolves();
@@ -146,6 +149,7 @@ describe(commands.CHAT_MESSAGE_LIST, () => {
146149

147150
commandInfo = cli.getCommandInfo(command);
148151
commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options;
152+
refinedSchema = commandInfo.command.getRefinedSchema!(commandOptionsSchema as any)!;
149153
sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => settingName === settingsNames.prompt ? false : defaultValue);
150154
});
151155

@@ -163,6 +167,7 @@ describe(commands.CHAT_MESSAGE_LIST, () => {
163167
}
164168
};
165169
loggerLogSpy = sinon.spy(logger, 'log');
170+
loggerLogToStderrSpy = sinon.spy(logger, 'logToStderr');
166171
});
167172

168173
afterEach(() => {
@@ -213,20 +218,97 @@ describe(commands.CHAT_MESSAGE_LIST, () => {
213218

214219
it('fails validation if the endDateTime is not valid', async () => {
215220
const actual = commandOptionsSchema.safeParse({
216-
chatId: '19:2da4c29f6d7041eca70b638b43d45437',
221+
chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2',
217222
endDateTime: 'invalid date time'
218223
});
219224
assert.notStrictEqual(actual.success, true);
220225
});
221226

222-
it('validates for a correct input', async () => {
227+
it('fails validation if the createdEndDateTime is not valid', async () => {
228+
const actual = commandOptionsSchema.safeParse({
229+
chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2',
230+
createdEndDateTime: 'invalid date time'
231+
});
232+
assert.notStrictEqual(actual.success, true);
233+
});
234+
235+
it('fails validation if the modifiedStartDateTime is not valid', async () => {
236+
const actual = commandOptionsSchema.safeParse({
237+
chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2',
238+
modifiedStartDateTime: 'not a date'
239+
});
240+
assert.notStrictEqual(actual.success, true);
241+
});
242+
243+
it('fails validation if the modifiedEndDateTime is not valid', async () => {
244+
const actual = commandOptionsSchema.safeParse({
245+
chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2',
246+
modifiedEndDateTime: 'not a date'
247+
});
248+
assert.notStrictEqual(actual.success, true);
249+
});
250+
251+
it('fails validation if both endDateTime and createdEndDateTime are specified', async () => {
252+
const actual = refinedSchema.safeParse({
253+
chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2',
254+
endDateTime: '2025-11-01T00:00:00Z',
255+
createdEndDateTime: '2025-11-01T00:00:00Z'
256+
});
257+
assert.notStrictEqual(actual.success, true);
258+
});
259+
260+
it('fails validation if createdEndDateTime is combined with modifiedStartDateTime', async () => {
261+
const actual = refinedSchema.safeParse({
262+
chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2',
263+
createdEndDateTime: '2025-11-01T00:00:00Z',
264+
modifiedStartDateTime: '2025-10-01T00:00:00Z'
265+
});
266+
assert.notStrictEqual(actual.success, true);
267+
});
268+
269+
it('fails validation if createdEndDateTime is combined with modifiedEndDateTime', async () => {
270+
const actual = refinedSchema.safeParse({
271+
chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2',
272+
createdEndDateTime: '2025-11-01T00:00:00Z',
273+
modifiedEndDateTime: '2025-12-01T00:00:00Z'
274+
});
275+
assert.notStrictEqual(actual.success, true);
276+
});
277+
278+
it('fails validation if endDateTime is combined with modifiedStartDateTime', async () => {
279+
const actual = refinedSchema.safeParse({
280+
chatId: '19:2da4c29f6d7041eca70b638b43d45437@thread.v2',
281+
endDateTime: '2025-11-01T00:00:00Z',
282+
modifiedStartDateTime: '2025-10-01T00:00:00Z'
283+
});
284+
assert.notStrictEqual(actual.success, true);
285+
});
286+
287+
it('validates for a correct input with endDateTime', async () => {
223288
const actual = commandOptionsSchema.safeParse({
224289
chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2",
225290
endDateTime: "2022-11-01T00:00:00Z"
226291
});
227292
assert.strictEqual(actual.success, true);
228293
});
229294

295+
it('validates for a correct input with createdEndDateTime', async () => {
296+
const actual = commandOptionsSchema.safeParse({
297+
chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2",
298+
createdEndDateTime: "2022-11-01T00:00:00Z"
299+
});
300+
assert.strictEqual(actual.success, true);
301+
});
302+
303+
it('validates for a correct input with modifiedStartDateTime and modifiedEndDateTime', async () => {
304+
const actual = commandOptionsSchema.safeParse({
305+
chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2",
306+
modifiedStartDateTime: "2025-10-01T00:00:00Z",
307+
modifiedEndDateTime: "2025-11-01T00:00:00Z"
308+
});
309+
assert.strictEqual(actual.success, true);
310+
});
311+
230312
it('lists chat messages (verbose)', async () => {
231313
sinon.stub(request, 'get').callsFake(async (opts) => {
232314
if (opts.url === `https://graph.microsoft.com/v1.0/chats/19:2da4c29f6d7041eca70b638b43d45437@thread.v2/messages`) {
@@ -248,7 +330,7 @@ describe(commands.CHAT_MESSAGE_LIST, () => {
248330
assert(loggerLogSpy.calledWith(apiResponse));
249331
});
250332

251-
it('lists chat messages using endDateTime', async () => {
333+
it('lists chat messages using deprecated endDateTime and shows deprecation warning', async () => {
252334
sinon.stub(request, 'get').callsFake(async (opts) => {
253335
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`) {
254336
return {
@@ -266,6 +348,92 @@ describe(commands.CHAT_MESSAGE_LIST, () => {
266348
}
267349
});
268350

351+
assert(loggerLogSpy.calledWith(apiResponse));
352+
assert(loggerLogToStderrSpy.calledWith(sinon.match(`Option 'endDateTime' is deprecated. Please use 'createdEndDateTime' instead.`)));
353+
});
354+
355+
it('lists chat messages using createdEndDateTime', async () => {
356+
sinon.stub(request, 'get').callsFake(async (opts) => {
357+
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`) {
358+
return {
359+
value: [...apiResponse]
360+
};
361+
}
362+
363+
throw 'Invalid request';
364+
});
365+
366+
await command.action(logger, {
367+
options: {
368+
chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2",
369+
createdEndDateTime: "2025-11-01T00:00:00Z"
370+
}
371+
});
372+
373+
assert(loggerLogSpy.calledWith(apiResponse));
374+
});
375+
376+
it('lists chat messages using modifiedStartDateTime', async () => {
377+
sinon.stub(request, 'get').callsFake(async (opts) => {
378+
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`) {
379+
return {
380+
value: [...apiResponse]
381+
};
382+
}
383+
384+
throw 'Invalid request';
385+
});
386+
387+
await command.action(logger, {
388+
options: {
389+
chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2",
390+
modifiedStartDateTime: "2025-10-01T00:00:00Z"
391+
}
392+
});
393+
394+
assert(loggerLogSpy.calledWith(apiResponse));
395+
});
396+
397+
it('lists chat messages using modifiedEndDateTime', async () => {
398+
sinon.stub(request, 'get').callsFake(async (opts) => {
399+
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`) {
400+
return {
401+
value: [...apiResponse]
402+
};
403+
}
404+
405+
throw 'Invalid request';
406+
});
407+
408+
await command.action(logger, {
409+
options: {
410+
chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2",
411+
modifiedEndDateTime: "2025-12-01T00:00:00Z"
412+
}
413+
});
414+
415+
assert(loggerLogSpy.calledWith(apiResponse));
416+
});
417+
418+
it('lists chat messages using both modifiedStartDateTime and modifiedEndDateTime', async () => {
419+
sinon.stub(request, 'get').callsFake(async (opts) => {
420+
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`) {
421+
return {
422+
value: [...apiResponse]
423+
};
424+
}
425+
426+
throw 'Invalid request';
427+
});
428+
429+
await command.action(logger, {
430+
options: {
431+
chatId: "19:2da4c29f6d7041eca70b638b43d45437@thread.v2",
432+
modifiedStartDateTime: "2025-10-01T00:00:00Z",
433+
modifiedEndDateTime: "2025-12-01T00:00:00Z"
434+
}
435+
});
436+
269437
assert(loggerLogSpy.calledWith(apiResponse));
270438
});
271439

src/m365/teams/commands/chat/chat-message-list.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,25 @@ export const options = z.strictObject({
1818
error: e => `'${e.input}' is not a valid value for option chatId.`
1919
})
2020
.alias('i'),
21+
createdEndDateTime: z.string()
22+
.refine(time => validation.isValidISODateTime(time), {
23+
error: e => `'${e.input}' is not a valid ISO date-time string for option createdEndDateTime.`
24+
})
25+
.optional(),
2126
endDateTime: z.string()
2227
.refine(time => validation.isValidISODateTime(time), {
2328
error: e => `'${e.input}' is not a valid ISO date-time string for option endDateTime.`
2429
})
30+
.optional(),
31+
modifiedStartDateTime: z.string()
32+
.refine(time => validation.isValidISODateTime(time), {
33+
error: e => `'${e.input}' is not a valid ISO date-time string for option modifiedStartDateTime.`
34+
})
35+
.optional(),
36+
modifiedEndDateTime: z.string()
37+
.refine(time => validation.isValidISODateTime(time), {
38+
error: e => `'${e.input}' is not a valid ISO date-time string for option modifiedEndDateTime.`
39+
})
2540
.optional()
2641
});
2742

@@ -47,14 +62,44 @@ class TeamsChatMessageListCommand extends GraphCommand {
4762
return options;
4863
}
4964

65+
public getRefinedSchema(schema: typeof options): z.ZodObject<any> | undefined {
66+
return schema
67+
.refine(options => !(options.endDateTime && options.createdEndDateTime), {
68+
error: 'Specify either endDateTime or createdEndDateTime, but not both.'
69+
})
70+
.refine(options => !(options.createdEndDateTime && (options.modifiedStartDateTime || options.modifiedEndDateTime)), {
71+
error: 'You cannot combine createdEndDateTime with modifiedStartDateTime or modifiedEndDateTime. These filters operate on different properties.'
72+
})
73+
.refine(options => !(options.endDateTime && (options.modifiedStartDateTime || options.modifiedEndDateTime)), {
74+
error: 'You cannot combine endDateTime with modifiedStartDateTime or modifiedEndDateTime. These filters operate on different properties.'
75+
});
76+
}
77+
5078
public async commandAction(logger: Logger, args: CommandArgs): Promise<void> {
79+
if (args.options.endDateTime) {
80+
await this.warn(logger, `Option 'endDateTime' is deprecated. Please use 'createdEndDateTime' instead.`);
81+
82+
args.options.createdEndDateTime = args.options.endDateTime;
83+
}
84+
5185
try {
5286
let apiUrl = `${this.resource}/v1.0/chats/${args.options.chatId}/messages`;
5387

54-
if (args.options.endDateTime) {
55-
// You can only filter results if the request URL contains the $orderby and $filter query parameters configured for the same property;
56-
// otherwise, the $filter query option is ignored.
57-
apiUrl += `?$filter=createdDateTime lt ${args.options.endDateTime}&$orderby=createdDateTime desc`;
88+
if (args.options.createdEndDateTime) {
89+
apiUrl += `?$filter=createdDateTime lt ${args.options.createdEndDateTime}&$orderby=createdDateTime desc`;
90+
}
91+
else if (args.options.modifiedStartDateTime || args.options.modifiedEndDateTime) {
92+
const filters: string[] = [];
93+
94+
if (args.options.modifiedStartDateTime) {
95+
filters.push(`lastModifiedDateTime gt ${args.options.modifiedStartDateTime}`);
96+
}
97+
98+
if (args.options.modifiedEndDateTime) {
99+
filters.push(`lastModifiedDateTime lt ${args.options.modifiedEndDateTime}`);
100+
}
101+
102+
apiUrl += `?$filter=${filters.join(' and ')}&$orderby=lastModifiedDateTime desc`;
58103
}
59104

60105
const items = await odata.getAllItems<ExtendedMessage>(apiUrl);

0 commit comments

Comments
 (0)