mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
allowing multiple chat id's for telegram
This commit is contained in:
@@ -161,6 +161,11 @@ export const send = ({ serviceName, newListings = [], notificationConfig, jobKey
|
||||
throw new Error("Telegram 'token' and 'chatId' must be provided in notification config");
|
||||
}
|
||||
|
||||
const chatIds = String(chatId)
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
// Optional Telegram topic/thread support (supergroups)
|
||||
let message_thread_id;
|
||||
if (messageThreadId !== undefined && messageThreadId !== null && `${messageThreadId}`.trim() !== '') {
|
||||
@@ -177,7 +182,10 @@ export const send = ({ serviceName, newListings = [], notificationConfig, jobKey
|
||||
const job = getJob(jobKey);
|
||||
const jobName = job == null ? jobKey : job.name;
|
||||
|
||||
const throttledCall = getThrottled(chatId, async function (endpoint, body) {
|
||||
if (!Array.isArray(newListings) || newListings.length === 0) return Promise.resolve([]);
|
||||
|
||||
const allPromises = chatIds.flatMap((id) => {
|
||||
const throttledCall = getThrottled(id, async function (endpoint, body) {
|
||||
// FormData (multipart) vs JSON. node-fetch sets its own multipart boundary
|
||||
// header, so we must NOT supply Content-Type ourselves in that case.
|
||||
const isFormData = body instanceof FormData;
|
||||
@@ -193,13 +201,13 @@ export const send = ({ serviceName, newListings = [], notificationConfig, jobKey
|
||||
return res;
|
||||
});
|
||||
|
||||
if (!Array.isArray(newListings) || newListings.length === 0) return Promise.resolve([]);
|
||||
|
||||
const promises = newListings.map(async (o) => {
|
||||
return newListings.map(async (o) => {
|
||||
const img = normalizeImageUrl(o.image);
|
||||
const textPayload = {
|
||||
chat_id: chatId,
|
||||
text: plainText ? buildTextPlain(jobName, serviceName, o, baseUrl) : buildText(jobName, serviceName, o, baseUrl),
|
||||
chat_id: id,
|
||||
text: plainText
|
||||
? buildTextPlain(jobName, serviceName, o, baseUrl)
|
||||
: buildText(jobName, serviceName, o, baseUrl),
|
||||
...(plainText ? {} : { parse_mode: 'HTML' }),
|
||||
disable_web_page_preview: true,
|
||||
...(message_thread_id ? { message_thread_id } : {}),
|
||||
@@ -220,11 +228,15 @@ export const send = ({ serviceName, newListings = [], notificationConfig, jobKey
|
||||
// "failed to get HTTP URL content". Upload the bytes via multipart instead;
|
||||
// the rendered chat message is identical.
|
||||
const photoCall = shouldUseMultipart(img)
|
||||
? buildPhotoFormData({ chatId, imageUrl: img, caption, parseMode, messageThreadId: message_thread_id }).then(
|
||||
(fd) => throttledCall('sendPhoto', fd),
|
||||
)
|
||||
? buildPhotoFormData({
|
||||
chatId: id,
|
||||
imageUrl: img,
|
||||
caption,
|
||||
parseMode,
|
||||
messageThreadId: message_thread_id,
|
||||
}).then((fd) => throttledCall('sendPhoto', fd))
|
||||
: throttledCall('sendPhoto', {
|
||||
chat_id: chatId,
|
||||
chat_id: id,
|
||||
photo: img,
|
||||
caption,
|
||||
...(parseMode ? { parse_mode: parseMode } : {}),
|
||||
@@ -239,8 +251,9 @@ export const send = ({ serviceName, newListings = [], notificationConfig, jobKey
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
return Promise.all(allPromises);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -261,7 +274,8 @@ export const config = {
|
||||
chatId: {
|
||||
type: 'chatId',
|
||||
label: 'Chat Id',
|
||||
description: 'The chat id to send messages to you.',
|
||||
description:
|
||||
'The chat ID to send messages to. Separate multiple IDs with commas to notify several recipients (e.g. 123456789, 987654321).',
|
||||
},
|
||||
messageThreadId: {
|
||||
type: 'text',
|
||||
|
||||
@@ -21,6 +21,8 @@ Steps:
|
||||
- Private chats: `chat.id` is a positive number
|
||||
- Groups/supergroups: `chat.id` is a negative number
|
||||
|
||||
**Multiple recipients:** To notify several users individually, enter a comma-separated list of chat IDs in the Chat Id field, e.g. `123456789, 987654321`. Each recipient receives the same messages and gets its own independent rate-limit window. This avoids having to create a group and add the bot to it.
|
||||
|
||||
Keep your bot token secret. If `getUpdates` returns an empty list, send a new message and try again, or make sure your bot’s privacy settings allow it to see group messages when used in groups.
|
||||
|
||||
#### Getting the thread ID (this is optional to be used for forum topics)
|
||||
|
||||
@@ -335,6 +335,61 @@ describe('telegram send() - mixed batch (regression-safety)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('telegram send() - multiple chat IDs', () => {
|
||||
const listing = {
|
||||
id: '1',
|
||||
title: 'Flat',
|
||||
link: 'https://ex.com',
|
||||
address: 'Berlin',
|
||||
price: '800',
|
||||
size: '50',
|
||||
image: 'https://ex.com/img.jpg',
|
||||
};
|
||||
|
||||
it('sends to every chat ID in a comma-separated list', async () => {
|
||||
mockNodeFetch.mockResolvedValue(jsonOk());
|
||||
|
||||
await send({
|
||||
serviceName: 'immoscout',
|
||||
newListings: [listing],
|
||||
notificationConfig: [{ id: 'telegram', fields: { token: 'TKN', chatId: '111, 222' } }],
|
||||
jobKey: 'Berlin',
|
||||
});
|
||||
|
||||
expect(mockNodeFetch).toHaveBeenCalledTimes(2);
|
||||
const bodies = mockNodeFetch.mock.calls.map((c) => JSON.parse(c[1].body));
|
||||
expect(bodies.map((b) => b.chat_id)).toEqual(expect.arrayContaining(['111', '222']));
|
||||
});
|
||||
|
||||
it('trims whitespace around each chat ID', async () => {
|
||||
mockNodeFetch.mockResolvedValue(jsonOk());
|
||||
|
||||
await send({
|
||||
serviceName: 'immoscout',
|
||||
newListings: [listing],
|
||||
notificationConfig: [{ id: 'telegram', fields: { token: 'TKN', chatId: ' 333 , 444 ' } }],
|
||||
jobKey: 'Berlin',
|
||||
});
|
||||
|
||||
expect(mockNodeFetch).toHaveBeenCalledTimes(2);
|
||||
const bodies = mockNodeFetch.mock.calls.map((c) => JSON.parse(c[1].body));
|
||||
expect(bodies.map((b) => b.chat_id)).toEqual(expect.arrayContaining(['333', '444']));
|
||||
});
|
||||
|
||||
it('sends each listing to each chat ID (N listings × M chats)', async () => {
|
||||
mockNodeFetch.mockResolvedValue(jsonOk());
|
||||
|
||||
await send({
|
||||
serviceName: 'immoscout',
|
||||
newListings: [listing, { ...listing, id: '2' }],
|
||||
notificationConfig: [{ id: 'telegram', fields: { token: 'TKN', chatId: '555, 666' } }],
|
||||
jobKey: 'Berlin',
|
||||
});
|
||||
|
||||
expect(mockNodeFetch).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('telegram send() - config validation', () => {
|
||||
it('throws when telegram adapter config is missing', () => {
|
||||
expect(() =>
|
||||
|
||||
Reference in New Issue
Block a user