···263263- `/send` - Post the next image in the queue immediately
264264- `/schedule [cron]` - Set posting schedule using cron syntax
265265- `/setcount [number]` - Set number of images per post interval
266266-- `/shuffle` - Toggle shuffle mode (randomizes queue after each post)
266266+- `/shuffle` - Toggle shuffle mode (randomizes queue after each post, persists between restarts)
267267- `/clear` - Clear the entire queue
268268- `/cleancache` - Clean expired items from media cache
269269- `/announce` - Create a new announcement
···274274#### Direct Links
275275Send a link from any supported website to the bot in a direct message. The bot will extract the image and add it to the queue.
276276277277-#### Forwarded Messages
278278-The bot can also process forwarded messages that contain links to supported websites. This includes:
277277+#### Multiple Links in Messages
278278+The bot can process any message containing multiple links to supported websites. This includes:
279279280280-- **Text Links**: Any URLs found in the forwarded message text will be extracted and processed
281281-- **Button Links**: URLs embedded in inline keyboard buttons (common in channel posts) are automatically detected and processed
282282-- **Multiple Links**: If a forwarded message contains multiple valid links, all will be processed simultaneously
280280+- **Multiple URLs in Text**: Messages containing several URLs will have all valid links processed
281281+- **Mixed Content**: Messages with both text and inline keyboard buttons containing URLs
282282+- **Embedded URLs**: Links that appear anywhere within message text (not just at the beginning)
283283+- **Button Links**: URLs embedded in inline keyboard buttons are automatically detected and processed
283284- **Error Handling**: Invalid links are silently discarded - the bot will only show an error if zero valid links are found
284285285285-**Usage**: Simply forward any message containing supported website links to the bot. The bot will automatically detect it's a forwarded message and extract all valid URLs for processing.
286286+#### Forwarded Messages
287287+The bot can also process forwarded messages that contain links to supported websites with the same capabilities as regular messages.
288288+289289+**Usage**: Send any message containing supported website links to the bot. The bot will automatically detect and process all valid URLs while silently discarding invalid ones.
286290287291### Interactive Queue Management
288292···293297- **Item Removal**: Remove specific items from the queue with a single click
294298- **Reordering**: Move any item to the top of the queue to be posted next
295299- **Pagination**: Easily navigate through pages of queued items
296296-- **Shuffle Mode**: Automatically randomize the queue after each post with `/shuffle` command
300300+- **Shuffle Mode**: Automatically randomize the queue after each post with `/shuffle` command (setting persists between restarts)
297301298302The interface shows important information about each queued item including:
299303- Item position in queue
+2
bot/telegrambot/commands/help.js
···2424/update - Update bot from GitHub repository (owner only)
25252626Send any link to a supported site to add it to the queue.
2727+Send messages with multiple links to process them all at once.
2728Forward messages with links or buttons to extract and queue multiple items.
2929+Invalid links are silently discarded - only valid content is queued.
2830Supported sites: e621, FurAffinity, SoFurry, Weasyl, Bluesky
2931 `;
3032 this.bot.sendMessage(chatId, helpText);
+101-21
bot/telegrambot/commands/linkHandler.js
···14141515 register() {
1616 this.bot.on('message', async (msg) => {
1717- // Check for direct URL messages
1818- if (msg.text && msg.text.startsWith('http')) {
1717+ // Skip processing if this is part of an announcement setup
1818+ const isInAnnouncementFlow = this.pendingAnnouncements && this.pendingAnnouncements[msg.from.id];
1919+ const isInButtonEditFlow = this.editingAnnouncementButton && this.editingAnnouncementButton[msg.from.id];
2020+2121+ if (isInAnnouncementFlow || isInButtonEditFlow) {
2222+ return;
2323+ }
2424+2525+ // Check authorization first for all URL processing
2626+ if (!this.authHelper.isAuthorized(msg.from.id)) {
2727+ // Only show unauthorized message if the message contains URLs
2828+ const urls = this.extractUrlsFromMessage(msg);
2929+ if (urls.length > 0) {
3030+ this.bot.sendMessage(msg.chat.id, 'You are not authorized to use this bot.');
3131+ }
3232+ return;
3333+ }
3434+3535+ // Check for direct URL messages (single URL starting with http)
3636+ if (msg.text && msg.text.startsWith('http') && !this.hasMultipleLinks(msg)) {
1937 await this.processDirectUrl(msg);
2038 return;
2139 }
···2543 await this.processForwardedMessage(msg);
2644 return;
2745 }
4646+4747+ // Check for any message with URLs (multiple links, buttons, or embedded URLs)
4848+ if (this.hasMultipleLinks(msg)) {
4949+ await this.processMultiLinkMessage(msg);
5050+ return;
5151+ }
2852 });
2953 }
3054···3458 async processDirectUrl(msg) {
3559 const chatId = msg.chat.id;
36603737- // Skip processing if this is part of an announcement setup
3838- const isInAnnouncementFlow = this.pendingAnnouncements && this.pendingAnnouncements[msg.from.id];
3939- const isInButtonEditFlow = this.editingAnnouncementButton && this.editingAnnouncementButton[msg.from.id];
4040-4141- if (isInAnnouncementFlow || isInButtonEditFlow) {
4242- // This URL is part of an announcement setup, so we should not process it as a link
4343- return;
4444- }
4545-4646- if (!this.authHelper.isAuthorized(msg.from.id)) {
4747- this.bot.sendMessage(chatId, 'You are not authorized to use this bot.');
4848- return;
4949- }
5050-5161 try {
5262 const url = msg.text.trim();
5363···90100 async processForwardedMessage(msg) {
91101 const chatId = msg.chat.id;
921029393- if (!this.authHelper.isAuthorized(msg.from.id)) {
9494- this.bot.sendMessage(chatId, 'You are not authorized to use this bot.');
9595- return;
9696- }
9797-98103 // Extract URLs from the forwarded message
99104 const urls = this.extractUrlsFromMessage(msg);
100105···191196 }
192197193198 return urls;
199199+ }
200200+201201+ /**
202202+ * Check if a message has multiple links or inline keyboard buttons
203203+ */
204204+ hasMultipleLinks(msg) {
205205+ // Count URLs in text
206206+ const textUrls = msg.text ? this.extractUrlsFromText(msg.text) : [];
207207+208208+ // Check for inline keyboard buttons
209209+ const hasButtons = msg.reply_markup && msg.reply_markup.inline_keyboard &&
210210+ msg.reply_markup.inline_keyboard.some(row =>
211211+ row.some(button => button.url)
212212+ );
213213+214214+ // Return true if we have multiple URLs in text, or any buttons, or a single URL not at the start
215215+ return textUrls.length > 1 ||
216216+ hasButtons ||
217217+ (textUrls.length === 1 && !msg.text.startsWith('http'));
218218+ }
219219+220220+ /**
221221+ * Process a message that may contain multiple links in text or buttons
222222+ */
223223+ async processMultiLinkMessage(msg) {
224224+ const chatId = msg.chat.id;
225225+226226+ // Extract URLs from the message
227227+ const urls = this.extractUrlsFromMessage(msg);
228228+229229+ if (urls.length === 0) {
230230+ // No URLs found - silently ignore
231231+ return;
232232+ }
233233+234234+ try {
235235+ this.bot.sendMessage(chatId, `Processing message with ${urls.length} link(s)...`, { reply_to_message_id: msg.message_id });
236236+237237+ const results = await scraperManager.extractFromUrls(urls);
238238+239239+ if (results.validResults.length === 0) {
240240+ this.bot.sendMessage(
241241+ chatId,
242242+ '❌ No valid links found in the message.',
243243+ { reply_to_message_id: msg.message_id }
244244+ );
245245+ return;
246246+ }
247247+248248+ // Add all valid results to queue
249249+ for (const mediaData of results.validResults) {
250250+ await queueManager.addToQueue(mediaData);
251251+ }
252252+253253+ const queueLength = await queueManager.getQueueLength();
254254+ let responseMessage = `✅ Added ${results.validResults.length} item(s) to queue`;
255255+256256+ if (results.invalidUrls.length > 0) {
257257+ responseMessage += ` (${results.invalidUrls.length} invalid link(s) silently discarded)`;
258258+ }
259259+260260+ responseMessage += `\nCurrent queue length: ${queueLength}`;
261261+262262+ this.bot.sendMessage(
263263+ chatId,
264264+ responseMessage,
265265+ { reply_to_message_id: msg.message_id }
266266+ );
267267+ } catch (error) {
268268+ this.bot.sendMessage(
269269+ chatId,
270270+ `Error processing message: ${error.message}`,
271271+ { reply_to_message_id: msg.message_id }
272272+ );
273273+ }
194274 }
195275196276 // Allow other modules to set these properties for proper URL handling during announcements
+2-2
bot/telegrambot/commands/shuffle.js
···2121 const isEnabled = queueManager.toggleShuffleMode();
22222323 if (isEnabled) {
2424- this.bot.sendMessage(chatId, '🔀 Shuffle mode enabled! Queue will be randomized after each post.');
2424+ this.bot.sendMessage(chatId, '🔀 Shuffle mode enabled! Queue will be randomized after each post.\n💾 Setting saved and will persist after restarts.');
2525 } else {
2626- this.bot.sendMessage(chatId, '📋 Shuffle mode disabled. Queue will maintain its order.');
2626+ this.bot.sendMessage(chatId, '📋 Shuffle mode disabled. Queue will maintain its order.\n💾 Setting saved and will persist after restarts.');
2727 }
2828 });
2929 }