1
0
Fork 0
tg-proxy/skill/client.js
2026-05-04 23:12:53 +03:00

302 lines
9.4 KiB
JavaScript

#!/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 <chat_id> [--limit 50] [--offset 0]
* node tg-client.js delta <chat_id> <since> [--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 <chat_id> [--limit N] [--offset N]');
console.log(' node tg-client.js delta <chat_id> <since> [--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
};