This commit is contained in:
sebseb7
2025-08-04 10:19:29 +02:00
commit 9a8086acea
6 changed files with 1288 additions and 0 deletions

368
x.js Normal file
View File

@@ -0,0 +1,368 @@
// server.js
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
const app = express();
const { exec, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
// Create logs directory if it doesn't exist
const logsDir = path.join(__dirname, 'logs');
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir);
}
// Logger function
function logMessage(message, type = 'info') {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] [${type.toUpperCase()}] ${message}`;
console[type === 'error' ? 'error' : 'log'](logEntry);
// Also log to file
const logFile = path.join(logsDir, `webhook_${new Date().toISOString().split('T')[0]}.log`);
fs.appendFileSync(logFile, logEntry + '\n');
}
// Telegram bot function
async function sendTelegramMessage(message) {
const botToken = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
if (!botToken || !chatId) {
logMessage('Telegram bot token or chat ID not configured', 'warn');
return;
}
try {
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
const response = await axios.post(url, {
chat_id: chatId,
text: message,
parse_mode: 'MarkdownV2',
disable_web_page_preview: false
});
logMessage('Telegram message sent successfully');
return response.data;
} catch (error) {
logMessage(`Failed to send Telegram message: ${error.message}`, 'error');
if (error.response) {
logMessage(`Telegram API error: ${JSON.stringify(error.response.data)}`, 'error');
}
}
}
// Format commit message for Telegram
function formatCommitMessage(payload) {
const repo = payload.repository?.name || 'Unknown Repository';
const commits = payload.commits || [];
const pusher = payload.pusher?.username || payload.pusher?.login || 'Unknown';
if (!commits || commits.length === 0) {
return `🔄 *${repo}* - Push event received but no commit details available`;
}
// Aggregate all files across commits
const allAdded = new Set();
const allModified = new Set();
const allRemoved = new Set();
commits.forEach(commit => {
(commit.added || []).forEach(file => allAdded.add(file));
(commit.modified || []).forEach(file => allModified.add(file));
(commit.removed || []).forEach(file => allRemoved.add(file));
});
// Format commits list
let commitsText = '';
// MarkdownV2 full escaper for arbitrary text (not code)
const escapeMdV2 = (s) => {
if (s == null) return '';
return String(s)
// Order matters: escape backslash first
.replace(/\\/g, '\\\\')
// Telegram MarkdownV2 reserved characters (in text contexts)
.replace(/([_*[\]()~`>#+\-=|{}.!])/g, '\\$1');
};
// Escaper for inline code content inside backticks: only escape backticks and backslash
const escapeInlineCode = (s) => {
if (s == null) return '';
return String(s)
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`');
};
commits.forEach((commit) => {
const commitMessage = commit.message?.trim() || 'No commit message';
const escapedMessage = escapeMdV2(commitMessage);
const shortHash = commit.id?.substring(0, 7) || 'unknown';
const author = commit.author?.name || 'Unknown';
// backticked hash must use code-escaper rules
const hashInline = escapeInlineCode(shortHash);
commitsText += `\n🔧 \`${hashInline}\` - ${escapedMessage}`;
if (commits.length > 1) {
commitsText += ` (${escapeMdV2(author)})`;
}
});
// Format aggregated file lists
let filesList = '';
if (allAdded.size > 0) {
filesList += `\n *Added (${allAdded.size}):*\n`;
Array.from(allAdded).slice(0, 10).forEach(file => {
filesList += `\`${escapeInlineCode(file)}\`\n`;
});
if (allAdded.size > 10) {
filesList += ` • ... and ${allAdded.size - 10} more\n`;
}
}
if (allModified.size > 0) {
filesList += `\n📝 *Modified (${allModified.size}):*\n`;
Array.from(allModified).slice(0, 10).forEach(file => {
filesList += `\`${escapeInlineCode(file)}\`\n`;
});
if (allModified.size > 10) {
filesList += ` • ... and ${allModified.size - 10} more\n`;
}
}
if (allRemoved.size > 0) {
filesList += `\n❌ *Removed (${allRemoved.size}):*\n`;
Array.from(allRemoved).slice(0, 10).forEach(file => {
filesList += `\`${escapeInlineCode(file)}\`\n`;
});
if (allRemoved.size > 10) {
filesList += ` • ... and ${allRemoved.size - 10} more\n`;
}
}
if (!filesList) {
filesList = '\n📂 No files changed';
}
const totalCommits = commits.length;
const commitWord = totalCommits === 1 ? 'commit' : 'commits';
// Escape heading line content and link text/url appropriately
const repoEsc = escapeMdV2(repo);
const heading = `🚀 *${repoEsc}* - ${totalCommits} ${commitWord} pushed`;
let compare = '';
if (payload.compare_url) {
const linkText = escapeMdV2('Compare Changes');
const linkUrl = escapeMdV2(payload.compare_url);
compare = `[${linkText}](${linkUrl})`;
}
return `${heading}
${commitsText}
${filesList}
${compare}`;
}
app.use(bodyParser.json());
app.post('/releasehook_kjfhdkf987987', (req, res) => {
try {
const payload = req.body;
logMessage(`Webhook received for repository: ${payload?.repository?.name || 'unknown'}`);
// Log the complete payload for analysis
logMessage(`Complete payload received: ${JSON.stringify(payload, null, 2)}`);
// Send Telegram notification
const telegramMessage = formatCommitMessage(payload);
sendTelegramMessage(telegramMessage).catch(error => {
logMessage(`Error sending Telegram message: ${error.message}`, 'error');
});
// Set a flag to track if we've sent a response
let responseSent = false;
if(payload && payload.repository && payload.repository.name == 'reactShop'){
// cd /home/seb/src/reactShop ; git pull ; npm run build
const repoPath = '/home/seb/src/growheads_de';
const command = `cd ${repoPath} && pkill -f "node.*prerender\\.cjs" || true && git pull && npm i . && npm run build:prerender`;
logMessage(`Executing command for reactShop: ${command}`);
// Use spawn to stream output line by line
// @note Try with explicit options to prevent early termination
const child = spawn('bash', ['-c', command], {
detached: false,
stdio: ['pipe', 'pipe', 'pipe']
});
logMessage(`Started process for reactShop with PID: ${child.pid}`);
// Stream stdout line by line
child.stdout.on('data', (data) => {
const lines = data.toString().split('\n');
lines.forEach(line => {
if (line.trim()) {
logMessage(`[reactShop stdout] ${line.trim()}`);
}
});
});
// Stream stderr line by line
child.stderr.on('data', (data) => {
const lines = data.toString().split('\n');
lines.forEach(line => {
if (line.trim()) {
logMessage(`[reactShop stderr] ${line.trim()}`, 'warn');
}
});
});
// Handle process completion
child.on('close', (code, signal) => {
if (signal) {
logMessage(`Command for reactShop was terminated by signal: ${signal}`, 'error');
if (!responseSent) {
responseSent = true;
return res.status(500).json({
error: 'Command execution failed',
details: `Process terminated by signal: ${signal}`
});
}
} else if (code !== 0) {
logMessage(`Command for reactShop exited with code ${code}`, 'error');
if (!responseSent) {
responseSent = true;
return res.status(500).json({
error: 'Command execution failed',
details: `Process exited with code ${code}`
});
}
} else {
logMessage(`Command for reactShop completed successfully (exit code: ${code})`);
if (!responseSent) {
responseSent = true;
res.status(200).json({ success: true, repository: 'reactShop' });
}
}
});
// Handle process errors
child.on('error', (error) => {
logMessage(`Error executing command for reactShop: ${error.message}`, 'error');
if (!responseSent) {
responseSent = true;
return res.status(500).json({
error: 'Command execution failed',
details: error.message
});
}
});
// Handle process exit (fired before close)
child.on('exit', (code, signal) => {
if (signal) {
logMessage(`Process for reactShop exited due to signal: ${signal}`, 'warn');
} else {
logMessage(`Process for reactShop exited with code: ${code}`, code === 0 ? 'info' : 'warn');
}
});
return; // Exit early to avoid double response
}
if(payload && payload.repository && payload.repository.name == 'shopApi'){
// cd /home/seb/src/shopApi ; git pull
const repoPath = '/home/seb/src/shopApi';
const command = `cd ${repoPath} && git pull`;
logMessage(`Executing command for shopApi: ${command}`);
exec(command, (error, stdout, stderr) => {
if (error) {
logMessage(`Error executing command for shopApi: ${error.message}`, 'error');
if (stderr) {
logMessage(`Command stderr: ${stderr}`, 'error');
}
if (!responseSent) {
responseSent = true;
return res.status(500).json({
error: 'Command execution failed',
details: error.message
});
}
}
if (stdout) {
logMessage(`Command output for shopApi: ${stdout.trim()}`);
}
if (stderr && !error) {
logMessage(`Command stderr (non-fatal) for shopApi: ${stderr}`, 'warn');
}
if (!responseSent) {
responseSent = true;
res.status(200).json({ success: true, repository: 'shopApi' });
}
});
return; // Exit early to avoid double response
}
if(payload && payload.repository && payload.repository.name == 'quickdhl'){
// cd /home/seb/src/shopApi ; git pull
const repoPath = '/home/seb/src/quickdhl';
const command = `cd ${repoPath} && git pull`;
logMessage(`Executing command for quickdhl: ${command}`);
exec(command, (error, stdout, stderr) => {
if (error) {
logMessage(`Error executing command for quickdhl: ${error.message}`, 'error');
if (stderr) {
logMessage(`Command stderr: ${stderr}`, 'error');
}
if (!responseSent) {
responseSent = true;
return res.status(500).json({
error: 'Command execution failed',
details: error.message
});
}
}
if (stdout) {
logMessage(`Command output for quickdhl: ${stdout.trim()}`);
}
if (stderr && !error) {
logMessage(`Command stderr (non-fatal) for quickdhl: ${stderr}`, 'warn');
}
if (!responseSent) {
responseSent = true;
res.status(200).json({ success: true, repository: 'shopApi' });
}
});
return; // Exit early to avoid double response
}
// If we get here, no repository matched
logMessage(`No handler found for repository: ${payload?.repository?.name || 'unknown'}`, 'warn');
res.status(200).json({ success: true, message: 'No action taken' });
} catch (err) {
logMessage(`Unhandled exception in webhook handler: ${err.message}`, 'error');
logMessage(err.stack, 'error');
res.status(500).json({ error: 'Internal server error', message: err.message });
}
});
// Add a simple health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});
app.listen(9304, () => logMessage('Webhook server listening on port 9304'));