DHX / index.js
maylinejix's picture
Create index.js
a80e01e verified
const express = require('express');
const { WebSocketServer } = require('ws');
const { Octokit } = require('@octokit/rest');
const http = require('http');
const CONFIG = {
GITHUB_TOKEN: process.env.GITHUB_TOKEN || 'ghp_your_github_token_here',
REPOS_TO_WATCH: [
{ owner: process.env.REPO_OWNER || 'herzonly', repo: process.env.REPO_NAME || 'DHX' },
],
POLL_INTERVAL: 30000,
BUFFER_TIME: 5 * 60 * 1000,
PORT: process.env.PORT || 7860
};
const octokit = new Octokit({
auth: CONFIG.GITHUB_TOKEN
});
const app = express();
const server = http.createServer(app);
const wss = new WebSocketServer({ server });
app.use((req, res, next) => {
const allowedMethods = ['GET', 'OPTIONS'];
if (!allowedMethods.includes(req.method)) {
console.log(`[BLOCKED] ${req.method} request from ${req.ip} to ${req.path}`);
return res.status(405).json({
error: 'Method Not Allowed',
message: 'Only GET requests are allowed',
allowed_methods: allowedMethods
});
}
next();
});
const changedFilesBuffer = [];
const cleanupTimers = new Map();
const lastCommits = new Map();
const clients = new Set();
wss.on('connection', (ws) => {
console.log('New WebSocket client connected');
clients.add(ws);
ws.send(JSON.stringify({
type: 'initial',
count: changedFilesBuffer.length,
data: changedFilesBuffer
}));
ws.on('close', () => {
console.log('Client disconnected');
clients.delete(ws);
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
clients.delete(ws);
});
});
function broadcast(data) {
const message = JSON.stringify(data);
clients.forEach((client) => {
if (client.readyState === 1) {
client.send(message);
}
});
}
async function getFileContent(owner, repo, path, ref) {
try {
const { data } = await octokit.repos.getContent({
owner,
repo,
path,
ref
});
if (data.content) {
return Buffer.from(data.content, 'base64').toString('utf-8');
}
return null;
} catch (error) {
console.error(`Error getting file content for ${path}:`, error.message);
return null;
}
}
function addChangedFile(fileData) {
const id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const item = {
id,
...fileData,
created_at: new Date().toISOString()
};
changedFilesBuffer.push(item);
console.log(`[BUFFER] Added file: ${fileData.changed_file.name} (Total: ${changedFilesBuffer.length})`);
const timer = setTimeout(() => {
const index = changedFilesBuffer.findIndex(f => f.id === id);
if (index !== -1) {
const removed = changedFilesBuffer.splice(index, 1)[0];
console.log(`[CLEANUP] Removed: ${removed.changed_file.name} after 5 minutes (Remaining: ${changedFilesBuffer.length})`);
broadcast({
type: 'cleanup',
id: id,
filename: removed.changed_file.name,
remaining_count: changedFilesBuffer.length
});
}
cleanupTimers.delete(id);
}, CONFIG.BUFFER_TIME);
cleanupTimers.set(id, timer);
return item;
}
async function checkRepoChanges(owner, repo) {
try {
const { data: commits } = await octokit.repos.listCommits({
owner,
repo,
per_page: 10
});
if (commits.length === 0) return;
const repoKey = `${owner}/${repo}`;
const lastCommitSha = lastCommits.get(repoKey);
if (!lastCommitSha) {
lastCommits.set(repoKey, commits[0].sha);
console.log(`[INIT] Tracking ${repoKey} from commit: ${commits[0].sha.substring(0, 7)}`);
return;
}
const newCommits = [];
for (const commit of commits) {
if (commit.sha === lastCommitSha) break;
newCommits.push(commit);
}
if (newCommits.length === 0) return;
console.log(`[DETECTED] ${newCommits.length} new commit(s) in ${repoKey}`);
for (const commit of newCommits.reverse()) {
try {
const { data: commitDetail } = await octokit.repos.getCommit({
owner,
repo,
ref: commit.sha
});
console.log(`[COMMIT] ${commit.sha.substring(0, 7)}: "${commit.commit.message}" - ${commitDetail.files.length} file(s)`);
for (const file of commitDetail.files) {
if (file.status === 'removed') {
console.log(` [SKIP] ${file.filename} (removed)`);
continue;
}
console.log(` [PROCESS] ${file.filename} (${file.status})`);
const content = await getFileContent(owner, repo, file.filename, commit.sha);
const fileChangeData = {
repository: repoKey,
commit: {
sha: commit.sha,
short_sha: commit.sha.substring(0, 7),
message: commit.commit.message,
author: commit.commit.author.name,
email: commit.commit.author.email,
date: commit.commit.author.date,
url: commit.html_url
},
changed_file: {
name: file.filename,
status: file.status,
additions: file.additions,
deletions: file.deletions,
changes: file.changes,
isinya: content || 'Unable to fetch content',
patch: file.patch || null
}
};
const bufferedItem = addChangedFile(fileChangeData);
broadcast({
type: 'new_change',
data: bufferedItem,
total_count: changedFilesBuffer.length
});
}
} catch (error) {
console.error(`[ERROR] Processing commit ${commit.sha.substring(0, 7)}:`, error.message);
}
}
lastCommits.set(repoKey, commits[0].sha);
} catch (error) {
console.error(`[ERROR] Checking repo ${owner}/${repo}:`, error.message);
}
}
app.get('/', (req, res) => {
res.json({
name: 'GitHub Repository Watcher',
version: '1.0.0',
endpoints: {
data: '/data',
health: '/health'
},
websocket: 'Connect to same URL with ws:// or wss://'
});
});
app.get('/data', (req, res) => {
res.json({
success: true,
count: changedFilesBuffer.length,
data: changedFilesBuffer,
timestamp: new Date().toISOString()
});
});
app.get('/health', (req, res) => {
res.json({
status: 'running',
buffered_files: changedFilesBuffer.length,
ws_clients: clients.size,
watching_repos: CONFIG.REPOS_TO_WATCH.length,
poll_interval_sec: CONFIG.POLL_INTERVAL / 1000
});
});
server.listen(CONFIG.PORT, () => {
console.log(`\n${'='.repeat(60)}`);
console.log(` GitHub Repository Watcher - Running`);
console.log(`${'='.repeat(60)}`);
console.log(` Server: http://localhost:${CONFIG.PORT}`);
console.log(` WebSocket: ws://localhost:${CONFIG.PORT}`);
console.log(` Data Endpoint: http://localhost:${CONFIG.PORT}/data`);
console.log(`${'='.repeat(60)}`);
console.log(` Watching ${CONFIG.REPOS_TO_WATCH.length} repo(s):`);
CONFIG.REPOS_TO_WATCH.forEach(r => {
console.log(` - ${r.owner}/${r.repo}`);
});
console.log(` Poll Interval: ${CONFIG.POLL_INTERVAL / 1000}s`);
console.log(` Buffer Time: ${CONFIG.BUFFER_TIME / 1000 / 60}min`);
console.log(`${'='.repeat(60)}\n`);
});
async function startWatching() {
console.log('[START] Initializing repository watcher...\n');
for (const { owner, repo } of CONFIG.REPOS_TO_WATCH) {
await checkRepoChanges(owner, repo);
}
setInterval(async () => {
for (const { owner, repo } of CONFIG.REPOS_TO_WATCH) {
await checkRepoChanges(owner, repo);
}
}, CONFIG.POLL_INTERVAL);
}
startWatching();
process.on('SIGINT', () => {
console.log('\n[SHUTDOWN] Cleaning up...');
cleanupTimers.forEach(timer => clearTimeout(timer));
cleanupTimers.clear();
wss.close();
server.close();
console.log('[SHUTDOWN] Server stopped');
process.exit(0);
});