|
@@ -0,0 +1,435 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * ShadowMan Telegram Bot Plugin v3.1.0
|
|
|
|
|
+ * Telegram Bot API integration with long polling via background worker.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Features:
|
|
|
|
|
+ * - Background worker polling (non-blocking)
|
|
|
|
|
+ * - Automatic LLM response forwarding to Telegram
|
|
|
|
|
+ * - Typing indicator while LLM processes
|
|
|
|
|
+ * - Photo/document receiving and sending
|
|
|
|
|
+ * - File download from Telegram servers
|
|
|
|
|
+ *
|
|
|
|
|
+ * Requires: bot_token (secret), authorized_user_id (config)
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+var config = shadowman.config.get();
|
|
|
|
|
+
|
|
|
|
|
+var BOT_TOKEN = config.bot_token || shadowman.config.value('bot_token');
|
|
|
|
|
+if (!BOT_TOKEN) {
|
|
|
|
|
+ shadowman.log.error('bot_token not configured — plugin cannot start');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+var AUTHORIZED_USER_ID = config.authorized_user_id || shadowman.config.value('authorized_user_id') || '';
|
|
|
|
|
+if (!AUTHORIZED_USER_ID) {
|
|
|
|
|
+ shadowman.log.warn('authorized_user_id not set — bot will reject all messages');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+var POLLING_ENABLED = config.polling_enabled !== false;
|
|
|
|
|
+var POLLING_INTERVAL = parseInt(config.polling_interval) || 2000;
|
|
|
|
|
+var POLLING_TIMEOUT = parseInt(config.polling_timeout) || 30;
|
|
|
|
|
+var API_BASE = 'https://api.telegram.org/bot' + BOT_TOKEN + '/';
|
|
|
|
|
+var FILE_BASE = 'https://api.telegram.org/file/bot' + BOT_TOKEN + '/';
|
|
|
|
|
+
|
|
|
|
|
+var lastUpdateId = shadowman.storage.get('last_update_id') || 0;
|
|
|
|
|
+
|
|
|
|
|
+// ── Helpers ────────────────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+function apiCall(method, params) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ var response = shadowman.http.post(API_BASE + method,
|
|
|
|
|
+ JSON.stringify(params || {}),
|
|
|
|
|
+ { headers: { 'Content-Type': 'application/json' }, timeout: 60 });
|
|
|
|
|
+ var data = response.json();
|
|
|
|
|
+ if (!data.ok) {
|
|
|
|
|
+ return { success: false, error: data.description || 'Unknown error', error_code: data.error_code };
|
|
|
|
|
+ }
|
|
|
|
|
+ return { success: true, result: data.result };
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ return { success: false, error: e.toString() };
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function sanitizeResponse(response) {
|
|
|
|
|
+ if (!BOT_TOKEN) return response;
|
|
|
|
|
+ if (typeof response === 'string') {
|
|
|
|
|
+ return response.replace(new RegExp(BOT_TOKEN, 'g'), '[REDACTED]');
|
|
|
|
|
+ }
|
|
|
|
|
+ if (typeof response === 'object' && response !== null) {
|
|
|
|
|
+ var s = {};
|
|
|
|
|
+ for (var key in response) {
|
|
|
|
|
+ if (response.hasOwnProperty(key)) s[key] = sanitizeResponse(response[key]);
|
|
|
|
|
+ }
|
|
|
|
|
+ return s;
|
|
|
|
|
+ }
|
|
|
|
|
+ return response;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isAuthorizedUser(userId) {
|
|
|
|
|
+ return String(userId) === String(AUTHORIZED_USER_ID);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getOrCreateConversationId(chatId) {
|
|
|
|
|
+ var key = 'conv_' + String(chatId);
|
|
|
|
|
+ var existing = shadowman.storage.get(key);
|
|
|
|
|
+ if (existing) return existing;
|
|
|
|
|
+ var id = shadowman.utils.uuid();
|
|
|
|
|
+ shadowman.storage.set(key, id);
|
|
|
|
|
+ return id;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Download a file from Telegram servers by file_id.
|
|
|
|
|
+ * Returns { success, file_path, size, data } where data is base64 content.
|
|
|
|
|
+ */
|
|
|
|
|
+function downloadTelegramFile(fileId) {
|
|
|
|
|
+ var fileInfo = apiCall('getFile', { file_id: fileId });
|
|
|
|
|
+ if (!fileInfo.success || !fileInfo.result || !fileInfo.result.file_path) {
|
|
|
|
|
+ return { success: false, error: 'Failed to get file info' };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var filePath = fileInfo.result.file_path;
|
|
|
|
|
+ var url = FILE_BASE + filePath;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ var response = shadowman.http.get(url, { timeout: 60 });
|
|
|
|
|
+ if (response.status === 200) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ file_path: filePath,
|
|
|
|
|
+ size: fileInfo.result.file_size || 0,
|
|
|
|
|
+ data: shadowman.utils.base64Encode(response.body)
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ return { success: false, error: 'HTTP ' + response.status };
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ return { success: false, error: e.toString() };
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Get the largest photo from a Telegram photo array.
|
|
|
|
|
+ */
|
|
|
|
|
+function getBestPhoto(photoArray) {
|
|
|
|
|
+ if (!photoArray || photoArray.length === 0) return null;
|
|
|
|
|
+ var best = photoArray[0];
|
|
|
|
|
+ for (var i = 1; i < photoArray.length; i++) {
|
|
|
|
|
+ if ((photoArray[i].file_size || 0) > (best.file_size || 0)) {
|
|
|
|
|
+ best = photoArray[i];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return best;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ── Message processing ─────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+function processTelegramUpdate(update) {
|
|
|
|
|
+ var message = update.message;
|
|
|
|
|
+ if (!message || !message.from) return;
|
|
|
|
|
+
|
|
|
|
|
+ var userId = message.from.id;
|
|
|
|
|
+ var chatId = message.chat.id;
|
|
|
|
|
+ var username = message.from.username || message.from.first_name || 'Unknown';
|
|
|
|
|
+
|
|
|
|
|
+ if (!isAuthorizedUser(userId)) {
|
|
|
|
|
+ shadowman.log.debug('Unauthorized message from ' + userId);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var conversationId = getOrCreateConversationId(chatId);
|
|
|
|
|
+ var text = message.text || message.caption || '';
|
|
|
|
|
+ var hasPhoto = message.photo && message.photo.length > 0;
|
|
|
|
|
+ var hasDocument = message.document;
|
|
|
|
|
+
|
|
|
|
|
+ // Handle photo messages
|
|
|
|
|
+ if (hasPhoto) {
|
|
|
|
|
+ var photo = getBestPhoto(message.photo);
|
|
|
|
|
+ if (photo) {
|
|
|
|
|
+ shadowman.log.info('Photo from @' + username + (text ? ': ' + text.substring(0, 40) : ''));
|
|
|
|
|
+ var downloaded = downloadTelegramFile(photo.file_id);
|
|
|
|
|
+ if (downloaded.success) {
|
|
|
|
|
+ var filename = 'telegram_photo_' + message.message_id + '.jpg';
|
|
|
|
|
+ var saved = shadowman.files.save(filename, downloaded.data, 'image/jpeg');
|
|
|
|
|
+ if (saved && saved.url) {
|
|
|
|
|
+ // Use markdown image syntax so WebUI renders it
|
|
|
|
|
+ text = (text || '') + '\n\n';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ text = text || 'Sent a photo (failed to save)';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Handle document messages
|
|
|
|
|
+ if (hasDocument) {
|
|
|
|
|
+ var doc = message.document;
|
|
|
|
|
+ var docFilename = doc.file_name || ('telegram_file_' + message.message_id);
|
|
|
|
|
+ var mime = doc.mime_type || 'application/octet-stream';
|
|
|
|
|
+ shadowman.log.info('Document from @' + username + ': ' + docFilename);
|
|
|
|
|
+ var downloaded = downloadTelegramFile(doc.file_id);
|
|
|
|
|
+ if (downloaded.success) {
|
|
|
|
|
+ var saved = shadowman.files.save(docFilename, downloaded.data, mime);
|
|
|
|
|
+ if (saved && saved.url) {
|
|
|
|
|
+ // Images as markdown, other files as links
|
|
|
|
|
+ var isImage = mime.indexOf('image/') === 0;
|
|
|
|
|
+ if (isImage) {
|
|
|
|
|
+ text = (text || '') + '\n\n';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ text = (text || '') + '\n\n[' + docFilename + '](' + saved.url + ')';
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ text = text || 'Sent a file: ' + docFilename + ' (failed to save)';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!text && !hasPhoto && !hasDocument) return;
|
|
|
|
|
+
|
|
|
|
|
+ shadowman.log.info('Message from @' + username + ': ' + text.substring(0, 80));
|
|
|
|
|
+
|
|
|
|
|
+ // Start typing indicator
|
|
|
|
|
+ shadowman.state.set('typing_chat_id', chatId);
|
|
|
|
|
+
|
|
|
|
|
+ shadowman.events.emit('message', {
|
|
|
|
|
+ text: text,
|
|
|
|
|
+ conversationId: conversationId,
|
|
|
|
|
+ platform: 'Telegram',
|
|
|
|
|
+ sender: message.from.first_name || username,
|
|
|
|
|
+ metadata: {
|
|
|
|
|
+ source: 'telegram',
|
|
|
|
|
+ user_id: userId,
|
|
|
|
|
+ username: username,
|
|
|
|
|
+ chat_id: chatId,
|
|
|
|
|
+ message_id: message.message_id
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ── Event handlers (main thread) ───────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+shadowman.events.on('llm_response', function(data) {
|
|
|
|
|
+ if (!data.metadata || data.metadata.source !== 'telegram') return;
|
|
|
|
|
+
|
|
|
|
|
+ var chatId = data.metadata.chat_id;
|
|
|
|
|
+ var text = data.text || '';
|
|
|
|
|
+
|
|
|
|
|
+ // Stop typing indicator
|
|
|
|
|
+ shadowman.state.set('typing_chat_id', 0);
|
|
|
|
|
+
|
|
|
|
|
+ if (!chatId || !text) return;
|
|
|
|
|
+
|
|
|
|
|
+ // Detect image references and send as multipart photo uploads
|
|
|
|
|
+ var imageRegex = /!\[([^\]]*)\]\((\/files\/[^\s\)]+\.(png|jpg|jpeg|gif|webp))\)/gi;
|
|
|
|
|
+ var imgMatch;
|
|
|
|
|
+ var sentImages = {};
|
|
|
|
|
+
|
|
|
|
|
+ while ((imgMatch = imageRegex.exec(text)) !== null) {
|
|
|
|
|
+ var imgCaption = imgMatch[1] || '';
|
|
|
|
|
+ var imgUrl = imgMatch[2];
|
|
|
|
|
+
|
|
|
|
|
+ if (!sentImages[imgUrl]) {
|
|
|
|
|
+ sentImages[imgUrl] = true;
|
|
|
|
|
+ // Extract filename from URL path
|
|
|
|
|
+ var parts = imgUrl.split('/');
|
|
|
|
|
+ var imgFilename = parts[parts.length - 1];
|
|
|
|
|
+ try {
|
|
|
|
|
+ var fileData = shadowman.files.read(imgFilename);
|
|
|
|
|
+ if (fileData && fileData.content) {
|
|
|
|
|
+ shadowman.log.info('Uploading photo to Telegram: ' + imgFilename);
|
|
|
|
|
+ shadowman.http.post(API_BASE + 'sendPhoto', null, {
|
|
|
|
|
+ multipart: [
|
|
|
|
|
+ { name: 'chat_id', value: String(chatId) },
|
|
|
|
|
+ { name: 'photo', filename: imgFilename, data: fileData.content, contentType: 'image/' + (imgFilename.endsWith('.png') ? 'png' : 'jpeg') },
|
|
|
|
|
+ { name: 'caption', value: imgCaption || '' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ timeout: 60
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ shadowman.log.debug('Could not send image: ' + e.toString());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Send text — strip image markdown for cleaner message
|
|
|
|
|
+ var cleanText = text.replace(/!\[[^\]]*\]\(\/files\/[^\)]+\)/g, '').trim();
|
|
|
|
|
+ if (cleanText) {
|
|
|
|
|
+ // Telegram 4096 char limit — split long messages
|
|
|
|
|
+ while (cleanText.length > 0) {
|
|
|
|
|
+ var chunk = cleanText.substring(0, 4096);
|
|
|
|
|
+ cleanText = cleanText.substring(4096);
|
|
|
|
|
+ apiCall('sendMessage', {
|
|
|
|
|
+ chat_id: chatId,
|
|
|
|
|
+ text: chunk,
|
|
|
|
|
+ parse_mode: 'Markdown'
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// Handle updates from background worker
|
|
|
|
|
+shadowman.events.on('telegram_update', function(update) {
|
|
|
|
|
+ processTelegramUpdate(update);
|
|
|
|
|
+ var lastId = shadowman.state.get('last_update_id');
|
|
|
|
|
+ if (lastId > 0) {
|
|
|
|
|
+ shadowman.storage.set('last_update_id', lastId);
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// ── Tools ──────────────────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+shadowman.tools.register('telegram_send_message', function(args) {
|
|
|
|
|
+ if (!args.chat_id) return { success: false, error: 'chat_id is required' };
|
|
|
|
|
+ if (!args.text) return { success: false, error: 'text is required' };
|
|
|
|
|
+
|
|
|
|
|
+ var params = { chat_id: args.chat_id, text: args.text };
|
|
|
|
|
+ if (args.parse_mode) params.parse_mode = args.parse_mode;
|
|
|
|
|
+ if (args.disable_notification) params.disable_notification = true;
|
|
|
|
|
+ if (args.reply_to_message_id) params.reply_to_message_id = args.reply_to_message_id;
|
|
|
|
|
+
|
|
|
|
|
+ return sanitizeResponse(apiCall('sendMessage', params));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+shadowman.tools.register('telegram_send_to_owner', function(args) {
|
|
|
|
|
+ if (!args.text) return { success: false, error: 'text is required' };
|
|
|
|
|
+ if (!AUTHORIZED_USER_ID) return { success: false, error: 'authorized_user_id not configured' };
|
|
|
|
|
+
|
|
|
|
|
+ var params = { chat_id: AUTHORIZED_USER_ID, text: args.text };
|
|
|
|
|
+ if (args.parse_mode) params.parse_mode = args.parse_mode;
|
|
|
|
|
+
|
|
|
|
|
+ return sanitizeResponse(apiCall('sendMessage', params));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+shadowman.tools.register('telegram_send_photo', function(args) {
|
|
|
|
|
+ if (!args.chat_id) return { success: false, error: 'chat_id is required' };
|
|
|
|
|
+ if (!args.photo) return { success: false, error: 'photo is required' };
|
|
|
|
|
+
|
|
|
|
|
+ var params = { chat_id: args.chat_id, photo: args.photo };
|
|
|
|
|
+ if (args.caption) params.caption = args.caption;
|
|
|
|
|
+ if (args.parse_mode) params.parse_mode = args.parse_mode;
|
|
|
|
|
+
|
|
|
|
|
+ return sanitizeResponse(apiCall('sendPhoto', params));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+shadowman.tools.register('telegram_send_document', function(args) {
|
|
|
|
|
+ if (!args.chat_id) return { success: false, error: 'chat_id is required' };
|
|
|
|
|
+ if (!args.document) return { success: false, error: 'document is required' };
|
|
|
|
|
+
|
|
|
|
|
+ var params = { chat_id: args.chat_id, document: args.document };
|
|
|
|
|
+ if (args.caption) params.caption = args.caption;
|
|
|
|
|
+ if (args.parse_mode) params.parse_mode = args.parse_mode;
|
|
|
|
|
+
|
|
|
|
|
+ return sanitizeResponse(apiCall('sendDocument', params));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+shadowman.tools.register('telegram_forward_message', function(args) {
|
|
|
|
|
+ if (!args.chat_id) return { success: false, error: 'chat_id is required' };
|
|
|
|
|
+ if (!args.from_chat_id) return { success: false, error: 'from_chat_id is required' };
|
|
|
|
|
+ if (!args.message_id) return { success: false, error: 'message_id is required' };
|
|
|
|
|
+
|
|
|
|
|
+ return sanitizeResponse(apiCall('forwardMessage', {
|
|
|
|
|
+ chat_id: args.chat_id,
|
|
|
|
|
+ from_chat_id: args.from_chat_id,
|
|
|
|
|
+ message_id: args.message_id
|
|
|
|
|
+ }));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+shadowman.tools.register('telegram_get_me', function() {
|
|
|
|
|
+ return sanitizeResponse(apiCall('getMe', {}));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+shadowman.tools.register('telegram_get_chat', function(args) {
|
|
|
|
|
+ if (!args.chat_id) return { success: false, error: 'chat_id is required' };
|
|
|
|
|
+ return sanitizeResponse(apiCall('getChat', { chat_id: args.chat_id }));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+shadowman.tools.register('telegram_set_webhook', function(args) {
|
|
|
|
|
+ if (!args.webhook_url) return { success: false, error: 'webhook_url is required' };
|
|
|
|
|
+ var params = { url: args.webhook_url, allowed_updates: ['message', 'edited_message', 'callback_query'] };
|
|
|
|
|
+ if (args.secret_token) params.secret_token = args.secret_token;
|
|
|
|
|
+ return sanitizeResponse(apiCall('setWebhook', params));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+shadowman.tools.register('telegram_delete_webhook', function() {
|
|
|
|
|
+ return sanitizeResponse(apiCall('deleteWebhook', {}));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+shadowman.tools.register('telegram_get_webhook_info', function() {
|
|
|
|
|
+ return sanitizeResponse(apiCall('getWebhookInfo', {}));
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// ── Shared state and background workers ────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+shadowman.state.set('api_base', API_BASE);
|
|
|
|
|
+shadowman.state.set('last_update_id', lastUpdateId);
|
|
|
|
|
+shadowman.state.set('polling', POLLING_ENABLED);
|
|
|
|
|
+shadowman.state.set('polling_timeout', POLLING_TIMEOUT);
|
|
|
|
|
+shadowman.state.set('polling_interval', POLLING_INTERVAL);
|
|
|
|
|
+shadowman.state.set('typing_chat_id', 0);
|
|
|
|
|
+
|
|
|
|
|
+// Typing indicator worker — sends "typing..." every 4s while LLM processes
|
|
|
|
|
+shadowman.background.run(function() {
|
|
|
|
|
+ while (shadowman.state.get('polling') !== false) {
|
|
|
|
|
+ var chatId = shadowman.state.get('typing_chat_id');
|
|
|
|
|
+ if (chatId && chatId !== 0) {
|
|
|
|
|
+ var base = shadowman.state.get('api_base');
|
|
|
|
|
+ shadowman.http.post(base + 'sendChatAction', JSON.stringify({
|
|
|
|
|
+ chat_id: chatId,
|
|
|
|
|
+ action: 'typing'
|
|
|
|
|
+ }), { headers: { 'Content-Type': 'application/json' }, timeout: 10 });
|
|
|
|
|
+ }
|
|
|
|
|
+ shadowman.utils.sleep(4000);
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// Polling worker
|
|
|
|
|
+if (POLLING_ENABLED) {
|
|
|
|
|
+ shadowman.background.run(function() {
|
|
|
|
|
+ var base = shadowman.state.get('api_base');
|
|
|
|
|
+ var timeout = shadowman.state.get('polling_timeout');
|
|
|
|
|
+ var interval = shadowman.state.get('polling_interval');
|
|
|
|
|
+
|
|
|
|
|
+ shadowman.http.post(base + 'deleteWebhook', '{}',
|
|
|
|
|
+ { headers: { 'Content-Type': 'application/json' } });
|
|
|
|
|
+ shadowman.utils.sleep(2000);
|
|
|
|
|
+ shadowman.log.info('Polling worker started');
|
|
|
|
|
+
|
|
|
|
|
+ while (shadowman.state.get('polling')) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ var offset = shadowman.state.get('last_update_id');
|
|
|
|
|
+ var result = shadowman.http.post(base + 'getUpdates', JSON.stringify({
|
|
|
|
|
+ offset: offset + 1,
|
|
|
|
|
+ timeout: timeout,
|
|
|
|
|
+ allowed_updates: ['message', 'edited_message', 'callback_query']
|
|
|
|
|
+ }), {
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
+ timeout: timeout + 10
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (result.status === 200) {
|
|
|
|
|
+ var data = result.json();
|
|
|
|
|
+ if (data.ok && data.result && data.result.length > 0) {
|
|
|
|
|
+ for (var i = 0; i < data.result.length; i++) {
|
|
|
|
|
+ var update = data.result[i];
|
|
|
|
|
+ if (update.update_id > offset) {
|
|
|
|
|
+ shadowman.state.set('last_update_id', update.update_id);
|
|
|
|
|
+ }
|
|
|
|
|
+ shadowman.events.emit('telegram_update', update);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ shadowman.log.warn('Polling error: HTTP ' + result.status);
|
|
|
|
|
+ shadowman.utils.sleep(5000);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ shadowman.log.error('Polling exception: ' + e.toString());
|
|
|
|
|
+ shadowman.utils.sleep(5000);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ shadowman.utils.sleep(interval);
|
|
|
|
|
+ }
|
|
|
|
|
+ shadowman.log.info('Polling worker stopped');
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+shadowman.log.info('Telegram plugin v3.1.0 ready (' +
|
|
|
|
|
+ (POLLING_ENABLED ? 'polling' : 'webhook') + ' mode)');
|