#!/usr/bin/env node /** * Telegram Reader Client — удобный JS интерфейс к tg-proxy.wzray.com * * Usage: * node tg-client.js get-folders * node tg-client.js get-chats [--limit 50] [--offset 0] [--archived true|false] [--chat-type direct|bot|group|channel] [--folder-id N] * node tg-client.js get-messages [--limit 50] [--offset 0] * node tg-client.js delta [--limit 50] [--offset 0] * * Examples: * node tg-client.js get-folders * node tg-client.js get-chats --limit 100 --chat-type group --folder-id 2 * node tg-client.js get-messages 5880803391 --limit 10 * node tg-client.js delta 5880803391 "2026-02-16T09:00:00Z" */ const https = require('https'); const BASE_URL = 'tg-proxy.wzray.com'; function request(path, query = {}) { const queryString = new URLSearchParams(query).toString(); const url = `${path}${queryString ? '?' + queryString : ''}`; return new Promise((resolve, reject) => { const req = https.request( { hostname: BASE_URL, path: url, method: 'GET', headers: { 'User-Agent': 'openclaw-telegram-reader/1.0' } }, (res) => { let data = ''; res.on('data', (chunk) => data += chunk); res.on('end', () => { try { resolve(JSON.parse(data)); } catch (e) { reject(new Error(`JSON parse error: ${e.message}`)); } }); } ); req.on('error', reject); req.end(); }); } function getFlagValue(args, flag, fallback) { const idx = args.indexOf(flag); if (idx === -1 || idx + 1 >= args.length) return fallback; const parsed = parseInt(args[idx + 1], 10); return Number.isNaN(parsed) ? fallback : parsed; } function getStringFlagValue(args, flag, fallback = undefined) { const idx = args.indexOf(flag); if (idx === -1 || idx + 1 >= args.length) return fallback; return args[idx + 1]; } function getBooleanFlagValue(args, flag, fallback = undefined) { const value = getStringFlagValue(args, flag, undefined); if (value === undefined) return fallback; if (value === 'true') return true; if (value === 'false') return false; return fallback; } /** * Get list of chats */ async function getChats(limit = 50, offset = 0, options = {}) { const query = { limit, offset }; if (options.archived !== undefined) query.archived = String(options.archived); if (options.chatType) query.chat_type = options.chatType; if (options.folderId !== undefined) query.folder_id = String(options.folderId); const result = await request('/chats', query); return result; } /** * Get list of folders */ async function getFolders() { const result = await request('/folders'); return result; } /** * Get messages in a specific chat */ async function getChatMessages(chatId, limit = 50, offset = 0) { const result = await request(`/chats/${chatId}/messages`, { limit, offset }); return result; } /** * Get messages delta since timestamp for one chat */ async function getChatDelta(chatId, since, limit = 50, offset = 0) { const result = await request(`/chats/${chatId}/delta`, { since, limit, offset }); return result; } /** * Filter chats by criteria */ function filterChats(chats, criteria) { const now = new Date(); const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); return chats.filter(chat => { // Not muted if (criteria.notMuted && chat.is_muted) return false; // Pinned if (criteria.pinned && !chat.is_pinned) return false; // Active last week if (criteria.activeLastWeek) { if (!chat.last_message_date) return false; const lastMsg = new Date(chat.last_message_date); if (lastMsg < weekAgo) return false; } // Inactive (no messages in last week) if (criteria.inactiveLastWeek) { if (!chat.last_message_date) return true; // no messages = inactive const lastMsg = new Date(chat.last_message_date); if (lastMsg >= weekAgo) return false; // has recent messages, skip } // Type filter if (criteria.type) { if (criteria.type === 'direct' && chat.type !== 'private') return false; else if (criteria.type === 'bot' && chat.type !== 'bot') return false; else if (criteria.type === 'group' && !['group', 'supergroup', 'forum'].includes(chat.type)) return false; else if (criteria.type === 'channel' && chat.type !== 'channel') return false; } // Archived filter if (criteria.archived !== undefined && chat.archived !== criteria.archived) return false; // Folder filter if (criteria.folderId !== undefined && !(chat.folder_ids || []).includes(criteria.folderId)) return false; // Has unread if (criteria.hasUnread && chat.unread_count === 0) return false; return true; }); } /** * Filter messages by importance */ function filterImportantMessages(messages) { const keywords = ['важно', 'срочно', 'deadline', 'сегодня', '@wzray', 'визирей']; const lowerKeywords = keywords.map(k => k.toLowerCase()); return messages.filter(msg => { if (!msg.text) return false; const text = msg.text.toLowerCase(); // Check for keywords if (lowerKeywords.some(kw => text.includes(kw))) return true; // Mentions (basic check) if (text.includes('@')) return true; return false; }); } /** * CLI */ async function main() { const args = process.argv.slice(2); if (args.length === 0) { console.log('Usage:'); console.log(' node tg-client.js get-folders'); console.log(' node tg-client.js get-chats [--limit N] [--offset N] [--archived true|false] [--chat-type direct|bot|group|channel] [--folder-id N]'); console.log(' node tg-client.js get-messages [--limit N] [--offset N]'); console.log(' node tg-client.js delta [--limit N] [--offset N]'); console.log(' node tg-client.js filter-chats [--pinned] [--not-muted] [--active-last-week]'); console.log(''); console.log('Examples:'); console.log(' node tg-client.js get-folders'); console.log(' node tg-client.js get-chats --limit 100 --chat-type group --folder-id 2'); console.log(' node tg-client.js get-messages 5880803391 --limit 10'); console.log(' node tg-client.js delta 5880803391 "2026-02-16T09:00:00Z"'); console.log(' node tg-client.js filter-chats --pinned --not-muted --type=direct'); process.exit(0); } try { const [command] = args; switch (command) { case 'get-folders': { const result = await getFolders(); console.log(JSON.stringify(result, null, 2)); break; } case 'get-chats': { const limit = getFlagValue(args, '--limit', 50); const offset = getFlagValue(args, '--offset', 0); const archived = getBooleanFlagValue(args, '--archived', undefined); const chatType = getStringFlagValue(args, '--chat-type', undefined); const folderId = getFlagValue(args, '--folder-id', undefined); const result = await getChats(limit, offset, { archived, chatType, folderId }); console.log(JSON.stringify(result, null, 2)); break; } case 'get-messages': { const chatId = args[1]; if (!chatId) throw new Error('chat_id is required'); const limit = getFlagValue(args, '--limit', 50); const offset = getFlagValue(args, '--offset', 0); const result = await getChatMessages(chatId, limit, offset); console.log(JSON.stringify(result, null, 2)); break; } case 'delta': { const chatId = args[1]; const since = args[2]; if (!chatId || !since) throw new Error('chat_id and since are required'); const limit = getFlagValue(args, '--limit', 50); const offset = getFlagValue(args, '--offset', 0); const result = await getChatDelta(chatId, since, limit, offset); console.log(JSON.stringify(result, null, 2)); break; } case 'filter-chats': { const limit = getFlagValue(args, '--limit', 100); const result = await getChats(limit); if (!result || !result.items) { throw new Error('Invalid response from API'); } const criteria = { pinned: args.includes('--pinned'), notMuted: args.includes('--not-muted'), activeLastWeek: args.includes('--active-last-week'), inactiveLastWeek: args.includes('--inactive-last-week'), hasUnread: args.includes('--has-unread'), type: args.find(a => a.startsWith('--type='))?.split('=')[1], folderId: args.find(a => a.startsWith('--folder-id=')) ? parseInt(args.find(a => a.startsWith('--folder-id='))?.split('=')[1], 10) : undefined, archived: args.find(a => a.startsWith('--archived='))?.split('=')[1] === 'true' ? true : args.find(a => a.startsWith('--archived='))?.split('=')[1] === 'false' ? false : undefined }; const filtered = filterChats(result.items, criteria); console.log(JSON.stringify(filtered, null, 2)); break; } default: throw new Error(`Unknown command: ${command}`); } } catch (error) { console.error(`Error: ${error.message}`); process.exit(1); } } // Run CLI if called directly if (require.main === module) { main(); } // Export for use as a module module.exports = { getFolders, getChats, getChatMessages, getChatDelta, filterChats, filterImportantMessages };