Skip to content

Commit 19f2bac

Browse files
committed
feat: enhance telegram commands to support range format for public and private apps/templates
1 parent 3325bd6 commit 19f2bac

2 files changed

Lines changed: 238 additions & 12 deletions

File tree

backend/src/routes/telegram.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function createTelegramRouter(db: Knex): Router {
7373
return res.status(200).json({
7474
method: 'sendMessage',
7575
chat_id: chatId,
76-
text: 'No valid app IDs provided. Format should be "public apps: 1, 2, 3"',
76+
text: 'No valid app IDs provided. Format should be "public apps: 1, 2, 3" or "public apps: 1-10"',
7777
})
7878
}
7979

@@ -112,7 +112,7 @@ export function createTelegramRouter(db: Knex): Router {
112112
return res.status(200).json({
113113
method: 'sendMessage',
114114
chat_id: chatId,
115-
text: 'No valid template IDs provided. Format should be "public templates: 1, 2, 3"',
115+
text: 'No valid template IDs provided. Format should be "public templates: 1, 2, 3" or "public templates: 1-10"',
116116
})
117117
}
118118

@@ -151,7 +151,7 @@ export function createTelegramRouter(db: Knex): Router {
151151
return res.status(200).json({
152152
method: 'sendMessage',
153153
chat_id: chatId,
154-
text: 'No valid app IDs provided. Format should be "private apps: 1, 2, 3"',
154+
text: 'No valid app IDs provided. Format should be "private apps: 1, 2, 3" or "private apps: 1-10"',
155155
})
156156
}
157157

@@ -190,7 +190,7 @@ export function createTelegramRouter(db: Knex): Router {
190190
return res.status(200).json({
191191
method: 'sendMessage',
192192
chat_id: chatId,
193-
text: 'No valid template IDs provided. Format should be "private templates: 1, 2, 3"',
193+
text: 'No valid template IDs provided. Format should be "private templates: 1, 2, 3" or "private templates: 1-10"',
194194
})
195195
}
196196

@@ -226,10 +226,10 @@ export function createTelegramRouter(db: Knex): Router {
226226
chat_id: chatId,
227227
text:
228228
'Supported commands:\n' +
229-
'- "public apps: 1, 2, 3" - Make specified apps public\n' +
230-
'- "public templates: 1, 2, 3" - Make specified templates public\n' +
231-
'- "private apps: 1, 2, 3" - Make specified apps private\n' +
232-
'- "private templates: 1, 2, 3" - Make specified templates private',
229+
'- "public apps: 1, 2, 3" or "public apps: 1-10" - Make specified apps public\n' +
230+
'- "public templates: 1, 2, 3" or "public templates: 1-10" - Make specified templates public\n' +
231+
'- "private apps: 1, 2, 3" or "private apps: 1-10" - Make specified apps private\n' +
232+
'- "private templates: 1, 2, 3" or "private templates: 1-10" - Make specified templates private',
233233
})
234234
} catch (error) {
235235
console.error('Error processing telegram webhook:', error)
@@ -242,15 +242,40 @@ export function createTelegramRouter(db: Knex): Router {
242242

243243
/**
244244
* Parse a comma-separated string of IDs into an array of numbers
245-
* @param {string} text - The comma-separated string of IDs
245+
* @param {string} text - The comma-separated string of IDs, can also include ranges like "1-5"
246246
* @returns {number[]} Array of valid numeric IDs
247247
*/
248248
function parseIds(text: string): number[] {
249249
if (!text) return []
250250

251+
// Split by commas and process each part
251252
return text
252253
.split(',')
253-
.map(id => id.trim())
254-
.filter(id => id && !isNaN(Number(id)))
255-
.map(id => Number(id))
254+
.flatMap(part => {
255+
part = part.trim()
256+
257+
// Check if it's a range (contains hyphen)
258+
if (part.includes('-')) {
259+
const [startStr, endStr] = part.split('-').map(x => x.trim())
260+
const start = Number(startStr)
261+
const end = Number(endStr)
262+
263+
// Validate that both start and end are numbers
264+
if (isNaN(start) || isNaN(end)) {
265+
return []
266+
}
267+
268+
// Validate that start is less than or equal to end
269+
if (start > end) {
270+
return []
271+
}
272+
273+
// Generate array of numbers in the range
274+
return Array.from({ length: end - start + 1 }, (_, i) => start + i)
275+
}
276+
277+
// Handle single number case
278+
return !isNaN(Number(part)) ? [Number(part)] : []
279+
})
280+
.filter(id => id > 0) // Ensure all IDs are positive numbers
256281
}

backend/src/tests/telegram.test.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,207 @@ describe('Telegram Webhook', () => {
262262
})
263263
expect(mockDb).not.toHaveBeenCalled()
264264
})
265+
266+
it('should process public apps command with range format correctly', async () => {
267+
const response = await request(app)
268+
.post('/webhook')
269+
.send({
270+
update_id: 123456789,
271+
message: {
272+
message_id: 1,
273+
from: {
274+
id: 123456789,
275+
is_bot: false,
276+
first_name: 'Test',
277+
},
278+
chat: {
279+
id: 123456789,
280+
first_name: 'Test',
281+
type: 'private',
282+
},
283+
date: 1631234567,
284+
text: 'public apps: 1-3',
285+
},
286+
})
287+
288+
expect(response.status).toBe(200)
289+
expect(response.body).toEqual({
290+
method: 'sendMessage',
291+
chat_id: 123456789,
292+
text: 'Successfully made 1 app(s) public',
293+
})
294+
295+
expect(mockDb).toHaveBeenCalledWith('apps')
296+
expect(mockQueryBuilder.whereIn).toHaveBeenCalledWith('id', [1, 2, 3])
297+
expect(mockQueryBuilder.update).toHaveBeenCalledWith({ moderated: true })
298+
})
299+
300+
it('should process public templates command with range format correctly', async () => {
301+
const response = await request(app)
302+
.post('/webhook')
303+
.send({
304+
update_id: 123456789,
305+
message: {
306+
message_id: 1,
307+
from: {
308+
id: 123456789,
309+
is_bot: false,
310+
first_name: 'Test',
311+
},
312+
chat: {
313+
id: 123456789,
314+
first_name: 'Test',
315+
type: 'private',
316+
},
317+
date: 1631234567,
318+
text: 'public templates: 4-6',
319+
},
320+
})
321+
322+
expect(response.status).toBe(200)
323+
expect(response.body).toEqual({
324+
method: 'sendMessage',
325+
chat_id: 123456789,
326+
text: 'Successfully made 1 template(s) public',
327+
})
328+
329+
expect(mockDb).toHaveBeenCalledWith('templates')
330+
expect(mockQueryBuilder.whereIn).toHaveBeenCalledWith('id', [4, 5, 6])
331+
expect(mockQueryBuilder.update).toHaveBeenCalledWith({ moderated: true })
332+
})
333+
334+
it('should process private apps command with range format correctly', async () => {
335+
const response = await request(app)
336+
.post('/webhook')
337+
.send({
338+
update_id: 123456789,
339+
message: {
340+
message_id: 1,
341+
from: {
342+
id: 123456789,
343+
is_bot: false,
344+
first_name: 'Test',
345+
},
346+
chat: {
347+
id: 123456789,
348+
first_name: 'Test',
349+
type: 'private',
350+
},
351+
date: 1631234567,
352+
text: 'private apps: 1-3',
353+
},
354+
})
355+
356+
expect(response.status).toBe(200)
357+
expect(response.body).toEqual({
358+
method: 'sendMessage',
359+
chat_id: 123456789,
360+
text: 'Successfully made 1 app(s) private',
361+
})
362+
363+
expect(mockDb).toHaveBeenCalledWith('apps')
364+
expect(mockQueryBuilder.whereIn).toHaveBeenCalledWith('id', [1, 2, 3])
365+
expect(mockQueryBuilder.update).toHaveBeenCalledWith({ moderated: false })
366+
})
367+
368+
it('should process private templates command with range format correctly', async () => {
369+
const response = await request(app)
370+
.post('/webhook')
371+
.send({
372+
update_id: 123456789,
373+
message: {
374+
message_id: 1,
375+
from: {
376+
id: 123456789,
377+
is_bot: false,
378+
first_name: 'Test',
379+
},
380+
chat: {
381+
id: 123456789,
382+
first_name: 'Test',
383+
type: 'private',
384+
},
385+
date: 1631234567,
386+
text: 'private templates: 4-6',
387+
},
388+
})
389+
390+
expect(response.status).toBe(200)
391+
expect(response.body).toEqual({
392+
method: 'sendMessage',
393+
chat_id: 123456789,
394+
text: 'Successfully made 1 template(s) private',
395+
})
396+
397+
expect(mockDb).toHaveBeenCalledWith('templates')
398+
expect(mockQueryBuilder.whereIn).toHaveBeenCalledWith('id', [4, 5, 6])
399+
expect(mockQueryBuilder.update).toHaveBeenCalledWith({ moderated: false })
400+
})
401+
402+
it('should process mixed format commands correctly', async () => {
403+
const response = await request(app)
404+
.post('/webhook')
405+
.send({
406+
update_id: 123456789,
407+
message: {
408+
message_id: 1,
409+
from: {
410+
id: 123456789,
411+
is_bot: false,
412+
first_name: 'Test',
413+
},
414+
chat: {
415+
id: 123456789,
416+
first_name: 'Test',
417+
type: 'private',
418+
},
419+
date: 1631234567,
420+
text: 'public apps: 1-3, 5, 7-9',
421+
},
422+
})
423+
424+
expect(response.status).toBe(200)
425+
expect(response.body).toEqual({
426+
method: 'sendMessage',
427+
chat_id: 123456789,
428+
text: 'Successfully made 1 app(s) public',
429+
})
430+
431+
expect(mockDb).toHaveBeenCalledWith('apps')
432+
expect(mockQueryBuilder.whereIn).toHaveBeenCalledWith('id', [1, 2, 3, 5, 7, 8, 9])
433+
expect(mockQueryBuilder.update).toHaveBeenCalledWith({ moderated: true })
434+
})
435+
436+
it('should handle invalid range format correctly', async () => {
437+
const response = await request(app)
438+
.post('/webhook')
439+
.send({
440+
update_id: 123456789,
441+
message: {
442+
message_id: 1,
443+
from: {
444+
id: 123456789,
445+
is_bot: false,
446+
first_name: 'Test',
447+
},
448+
chat: {
449+
id: 123456789,
450+
first_name: 'Test',
451+
type: 'private',
452+
},
453+
date: 1631234567,
454+
text: 'public apps: 5-2', // Invalid range (start > end)
455+
},
456+
})
457+
458+
expect(response.status).toBe(200)
459+
expect(response.body).toEqual({
460+
method: 'sendMessage',
461+
chat_id: 123456789,
462+
text: expect.stringContaining('No valid app IDs provided'),
463+
})
464+
expect(mockDb).not.toHaveBeenCalled()
465+
})
265466
})
266467
})
267468
/* eslint-enable @typescript-eslint/unbound-method */

0 commit comments

Comments
 (0)