Update v9.4.0: Advanced Telegram Integration & Custom UI
- Incremented version to 9.4.0 (versionCode: 94000) - Added custom blocked dialog layout with improved UX - Implemented Telegram commands: /allow, /deny, /pending - Enhanced Telegram polling for real-time device management - Custom token display with click-to-copy functionality - Improved device verification workflow via Telegram - Better error handling and user feedback - Enhanced documentation for Telegram integration - Improved token validation and management system 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,9 @@ const CONFIG_PATH = path.join(__dirname, 'config.json');
|
||||
|
||||
const config = loadConfig();
|
||||
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || config.telegramBotToken || '';
|
||||
const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID || config.telegramChatId || '';
|
||||
const TELEGRAM_CHAT_ID = (process.env.TELEGRAM_CHAT_ID || config.telegramChatId || '').toString();
|
||||
let telegramOffset = 0;
|
||||
let telegramPollingStarted = false;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
@@ -109,22 +111,30 @@ function generateTokenParts() {
|
||||
};
|
||||
}
|
||||
|
||||
async function sendTelegramNotification(message) {
|
||||
async function sendTelegramMessage(message, options = {}) {
|
||||
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
||||
return;
|
||||
}
|
||||
const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`;
|
||||
try {
|
||||
await axios.post(url, {
|
||||
const payload = {
|
||||
chat_id: TELEGRAM_CHAT_ID,
|
||||
text: message,
|
||||
parse_mode: 'Markdown'
|
||||
});
|
||||
disable_web_page_preview: true
|
||||
};
|
||||
if (options.parseMode) {
|
||||
payload.parse_mode = options.parseMode;
|
||||
}
|
||||
await axios.post(url, payload);
|
||||
} catch (error) {
|
||||
console.warn('Failed to send Telegram notification', error.response ? error.response.data : error.message);
|
||||
console.warn('Failed to send Telegram message', error.response ? error.response.data : error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendTelegramNotification(message) {
|
||||
await sendTelegramMessage(message, { parseMode: 'Markdown' });
|
||||
}
|
||||
|
||||
function formatTelegramMessage(device, verificationRequired) {
|
||||
const lines = [
|
||||
'*Nuevo registro de dispositivo*',
|
||||
@@ -137,11 +147,143 @@ function formatTelegramMessage(device, verificationRequired) {
|
||||
];
|
||||
if (verificationRequired && device.verification) {
|
||||
lines.push('`Token Admin` (guárdalo): `' + device.verification.adminPart + '`');
|
||||
lines.push('Comparte el token del cliente y este admin para autorizar.');
|
||||
lines.push('Autorizar: `/allow ' + device.deviceId + ' TOKEN_CLIENTE`');
|
||||
lines.push('Rechazar: `/deny ' + device.deviceId + ' TOKEN_CLIENTE [motivo]`');
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
async function initializeTelegramOffset() {
|
||||
if (!TELEGRAM_BOT_TOKEN) {
|
||||
return;
|
||||
}
|
||||
const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates`;
|
||||
try {
|
||||
const { data } = await axios.get(url, { params: { timeout: 1 } });
|
||||
if (data.ok && Array.isArray(data.result) && data.result.length > 0) {
|
||||
telegramOffset = data.result[data.result.length - 1].update_id + 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to initialize Telegram offset', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function pollTelegramUpdates() {
|
||||
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
||||
return;
|
||||
}
|
||||
const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates`;
|
||||
const params = {
|
||||
timeout: 25
|
||||
};
|
||||
if (telegramOffset) {
|
||||
params.offset = telegramOffset;
|
||||
}
|
||||
try {
|
||||
const { data } = await axios.get(url, { params });
|
||||
if (data.ok && Array.isArray(data.result)) {
|
||||
for (const update of data.result) {
|
||||
telegramOffset = update.update_id + 1;
|
||||
if (update.message) {
|
||||
processTelegramMessage(update.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Telegram polling error', error.message);
|
||||
} finally {
|
||||
setTimeout(pollTelegramUpdates, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
async function startTelegramPolling() {
|
||||
if (telegramPollingStarted || !TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
||||
return;
|
||||
}
|
||||
telegramPollingStarted = true;
|
||||
await initializeTelegramOffset();
|
||||
pollTelegramUpdates();
|
||||
}
|
||||
|
||||
function processTelegramMessage(message) {
|
||||
const chatId = message.chat && message.chat.id ? message.chat.id.toString() : '';
|
||||
if (chatId && TELEGRAM_CHAT_ID && chatId !== TELEGRAM_CHAT_ID.toString()) {
|
||||
return;
|
||||
}
|
||||
const text = (message.text || '').trim();
|
||||
if (!text.startsWith('/')) {
|
||||
return;
|
||||
}
|
||||
const parts = text.split(/\s+/);
|
||||
const command = parts[0].toLowerCase();
|
||||
if (command === '/allow' && parts.length >= 3) {
|
||||
handleTelegramAllow(parts[1], parts[2]);
|
||||
} else if (command === '/deny' && parts.length >= 3) {
|
||||
const reason = parts.slice(3).join(' ') || 'Bloqueado desde Telegram';
|
||||
handleTelegramDeny(parts[1], parts[2], reason);
|
||||
} else if (command === '/pending') {
|
||||
handleTelegramPending();
|
||||
} else {
|
||||
sendTelegramMessage('Comandos disponibles:\n/allow <deviceId> <token_cliente>\n/deny <deviceId> <token_cliente> [motivo]\n/pending');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTelegramAllow(deviceId, clientToken) {
|
||||
const devices = readDevices();
|
||||
const device = devices.find(d => d.deviceId === deviceId);
|
||||
if (!device || !device.verification) {
|
||||
await sendTelegramMessage(`❌ Dispositivo ${deviceId} no encontrado.`);
|
||||
return;
|
||||
}
|
||||
if (device.verification.status === 'verified') {
|
||||
await sendTelegramMessage(`ℹ️ El dispositivo ${deviceId} ya está verificado.`);
|
||||
return;
|
||||
}
|
||||
if (device.verification.clientPart !== clientToken.trim()) {
|
||||
await sendTelegramMessage('❌ Token del cliente inválido.');
|
||||
return;
|
||||
}
|
||||
device.verification.status = 'verified';
|
||||
device.verification.verifiedAt = new Date().toISOString();
|
||||
device.blocked = false;
|
||||
device.notes = '';
|
||||
writeDevices(devices);
|
||||
await sendTelegramMessage(`✅ Dispositivo ${deviceId} autorizado correctamente.`);
|
||||
}
|
||||
|
||||
async function handleTelegramDeny(deviceId, clientToken, reason) {
|
||||
const devices = readDevices();
|
||||
const device = devices.find(d => d.deviceId === deviceId);
|
||||
if (!device || !device.verification) {
|
||||
await sendTelegramMessage(`❌ Dispositivo ${deviceId} no encontrado.`);
|
||||
return;
|
||||
}
|
||||
if (device.verification.clientPart !== clientToken.trim()) {
|
||||
await sendTelegramMessage('❌ Token del cliente inválido.');
|
||||
return;
|
||||
}
|
||||
device.verification.status = 'denied';
|
||||
device.verification.deniedAt = new Date().toISOString();
|
||||
device.blocked = true;
|
||||
device.notes = reason;
|
||||
writeDevices(devices);
|
||||
await sendTelegramMessage(`🚫 Dispositivo ${deviceId} bloqueado. Motivo: ${reason}`);
|
||||
}
|
||||
|
||||
async function handleTelegramPending() {
|
||||
const devices = readDevices();
|
||||
const pending = devices.filter(d => !d.verification || d.verification.status !== 'verified');
|
||||
if (!pending.length) {
|
||||
await sendTelegramMessage('No hay dispositivos pendientes.');
|
||||
return;
|
||||
}
|
||||
const lines = pending.slice(0, 10).map(device => {
|
||||
const token = device.verification ? device.verification.clientPart : 'N/A';
|
||||
return `${device.deviceId} - Token cliente: ${token}`;
|
||||
});
|
||||
await sendTelegramMessage('Pendientes:\n' + lines.join('\n'));
|
||||
}
|
||||
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
@@ -318,4 +460,5 @@ app.delete('/api/devices/:deviceId', (req, res) => {
|
||||
app.listen(PORT, () => {
|
||||
ensureDataFile();
|
||||
console.log(`StreamPlayer dashboard server listening on port ${PORT}`);
|
||||
startTelegramPolling();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user