···99# User Access Control (comma-separated list of Telegram user IDs)
1010AUTHORIZED_USERS=123456789,987654321
11111212+# Bot Owner ID (for admin-only commands like manual updates)
1313+OWNER_ID=123456789
1414+1215# Posting Schedule Configuration
1316DEFAULT_CRON_SCHEDULE=0 */1 * * *
1417IMAGES_PER_INTERVAL=1
+65-2
bot/telegramBot.js
···11const TelegramBot = require('node-telegram-bot-api');
22const axios = require('axios');
33const fs = require('fs');
44+const { exec } = require('child_process');
55+const { promisify } = require('util');
66+const execAsync = promisify(exec);
47const config = require('../config');
58const queueManager = require('../queue/queueManager');
69const scraperManager = require('../utils/scraperManager');
···3841/setcount [number] - Set number of images per scheduled post (default: 1)
3942/clear - Clear the queue
4043/cleancache - Clean expired items from media cache
4444+/update - Update bot from GitHub repository (owner only)
41454246Send any link to a supported site to add it to the queue.
4347Supported sites: e621, FurAffinity, SoFurry, Weasyl, Bluesky
···234238 this.bot.sendMessage(chatId, `Queue cleared (${queueLength} items removed).`);
235239 });
236240241241+ // Command to manually trigger an update from GitHub
242242+ this.bot.onText(/\/update/, async (msg) => {
243243+ const chatId = msg.chat.id;
244244+245245+ // Only the bot owner can run updates
246246+ if (!this.isOwner(msg.from.id)) {
247247+ this.bot.sendMessage(chatId, 'Only the bot owner can trigger updates.');
248248+ return;
249249+ }
250250+251251+ this.bot.sendMessage(chatId, 'Checking for updates...');
252252+253253+ try {
254254+ const updater = require('../utils/updater');
255255+ const isUpdateAvailable = await updater.isUpdateAvailable();
256256+257257+ if (!isUpdateAvailable) {
258258+ this.bot.sendMessage(chatId, 'No updates available. Bot is already running the latest version.');
259259+ return;
260260+ }
261261+262262+ const statusMessage = await this.bot.sendMessage(chatId, 'Updates found! Downloading and applying updates...');
263263+264264+ const updateResult = await updater.manualUpdate();
265265+266266+ if (updateResult) {
267267+ await this.bot.editMessageText('Update successful! Bot will restart to apply changes.', {
268268+ chat_id: chatId,
269269+ message_id: statusMessage.message_id
270270+ });
271271+272272+ // Give a moment for the message to be delivered before restarting
273273+ setTimeout(async () => {
274274+ try {
275275+ // Restart the bot using PM2
276276+ await execAsync('pm2 restart --update-env stagehand');
277277+ } catch (restartError) {
278278+ console.error('Error restarting bot:', restartError);
279279+ this.bot.sendMessage(chatId, `Error during restart: ${restartError.message}`);
280280+ }
281281+ }, 2000);
282282+ } else {
283283+ this.bot.sendMessage(chatId, 'Update process completed, but no changes were applied.');
284284+ }
285285+ } catch (error) {
286286+ console.error('Error during manual update:', error);
287287+ this.bot.sendMessage(chatId, `Error during update: ${error.message}`);
288288+ }
289289+ });
290290+237291 // Handle URL links
238292 this.bot.on('message', async (msg) => {
239293 if (msg.text && msg.text.startsWith('http')) {
···436490 // Show posting status for each service
437491 let statusIcons = '';
438492 if (item.postedTo) {
439439- if (item.postedTo.telegram) statusIcons += '✓TG ';
493493+ if (item.postedTo.telegram) statusIcons += '✅TG ';
440494 else statusIcons += '❌TG ';
441495442496 if (queueManager.postServices.includes('discord')) {
443443- if (item.postedTo.discord) statusIcons += '✓DS';
497497+ if (item.postedTo.discord) statusIcons += '✅DS';
444498 else statusIcons += '❌DS';
445499 }
446500 }
···528582 }
529583530584 return config.authorizedUsers.includes(userId.toString());
585585+ }
586586+587587+ /**
588588+ * Check if the user is the owner of the bot
589589+ * @param {number} userId - The Telegram user ID to check
590590+ * @returns {boolean} - Whether the user is the owner
591591+ */
592592+ isOwner(userId) {
593593+ return config.ownerId && userId.toString() === config.ownerId.toString();
531594 }
532595533596 /**
+6
config.js
···14141515 // Access control - can be expanded with more user IDs
1616 authorizedUsers: process.env.AUTHORIZED_USERS ? process.env.AUTHORIZED_USERS.split(',') : [],
1717+ ownerId: process.env.OWNER_ID,
17181819 // Queue configuration
1920 defaultCronSchedule: process.env.DEFAULT_CRON_SCHEDULE || '0 */1 * * *', // Default: every hour
···4041 name: 'Bluesky',
4142 domain: 'deer.social',
4243 pattern: /^https:\/\/(?:www\.)?deer\.social\//
4444+ },
4545+ {
4646+ name: 'Bluesky',
4747+ domain: 'sky.thebull.app',
4848+ pattern: /^https:\/\/(?:www\.)?sky\.thebull\.app\//
4349 },
4450 {
4551 name: 'e621',
+2-2
scrapers/blueskyScraper.js
···1111 super();
1212 // Initialize with service endpoint only - no authentication needed for public posts
1313 this.serviceEndpoint = config.bluesky.service || 'https://bsky.social';
1414- // Update regex to support both bsky.app and deer.social
1515- this.matcher = new RegExp('(?:https?://)?(?:bsky\\.app|deer\\.social)/profile/(?<repo>\\S+)/post/(?<rkey>\\S+)');
1414+ // Update regex to support bsky.app, deer.social, and sky.thebull.app
1515+ this.matcher = new RegExp('(?:https?://)?(?:bsky\\.app|deer\\.social|sky\\.thebull\\.app)/profile/(?<repo>\\S+)/post/(?<rkey>\\S+)');
16161717 // Initialize the agent
1818 this.agent = new BskyAgent({ service: this.serviceEndpoint });