maylinejix commited on
Commit
a80e01e
·
verified ·
1 Parent(s): fb09f5d

Create index.js

Browse files
Files changed (1) hide show
  1. index.js +293 -0
index.js ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { WebSocketServer } = require('ws');
3
+ const { Octokit } = require('@octokit/rest');
4
+ const http = require('http');
5
+
6
+ const CONFIG = {
7
+ GITHUB_TOKEN: process.env.GITHUB_TOKEN || 'ghp_your_github_token_here',
8
+ REPOS_TO_WATCH: [
9
+ { owner: process.env.REPO_OWNER || 'herzonly', repo: process.env.REPO_NAME || 'DHX' },
10
+ ],
11
+ POLL_INTERVAL: 30000,
12
+ BUFFER_TIME: 5 * 60 * 1000,
13
+ PORT: process.env.PORT || 7860
14
+ };
15
+
16
+ const octokit = new Octokit({
17
+ auth: CONFIG.GITHUB_TOKEN
18
+ });
19
+
20
+ const app = express();
21
+ const server = http.createServer(app);
22
+ const wss = new WebSocketServer({ server });
23
+
24
+ app.use((req, res, next) => {
25
+ const allowedMethods = ['GET', 'OPTIONS'];
26
+
27
+ if (!allowedMethods.includes(req.method)) {
28
+ console.log(`[BLOCKED] ${req.method} request from ${req.ip} to ${req.path}`);
29
+ return res.status(405).json({
30
+ error: 'Method Not Allowed',
31
+ message: 'Only GET requests are allowed',
32
+ allowed_methods: allowedMethods
33
+ });
34
+ }
35
+
36
+ next();
37
+ });
38
+
39
+ const changedFilesBuffer = [];
40
+ const cleanupTimers = new Map();
41
+ const lastCommits = new Map();
42
+ const clients = new Set();
43
+
44
+ wss.on('connection', (ws) => {
45
+ console.log('New WebSocket client connected');
46
+ clients.add(ws);
47
+
48
+ ws.send(JSON.stringify({
49
+ type: 'initial',
50
+ count: changedFilesBuffer.length,
51
+ data: changedFilesBuffer
52
+ }));
53
+
54
+ ws.on('close', () => {
55
+ console.log('Client disconnected');
56
+ clients.delete(ws);
57
+ });
58
+
59
+ ws.on('error', (error) => {
60
+ console.error('WebSocket error:', error);
61
+ clients.delete(ws);
62
+ });
63
+ });
64
+
65
+ function broadcast(data) {
66
+ const message = JSON.stringify(data);
67
+ clients.forEach((client) => {
68
+ if (client.readyState === 1) {
69
+ client.send(message);
70
+ }
71
+ });
72
+ }
73
+
74
+ async function getFileContent(owner, repo, path, ref) {
75
+ try {
76
+ const { data } = await octokit.repos.getContent({
77
+ owner,
78
+ repo,
79
+ path,
80
+ ref
81
+ });
82
+
83
+ if (data.content) {
84
+ return Buffer.from(data.content, 'base64').toString('utf-8');
85
+ }
86
+ return null;
87
+ } catch (error) {
88
+ console.error(`Error getting file content for ${path}:`, error.message);
89
+ return null;
90
+ }
91
+ }
92
+
93
+ function addChangedFile(fileData) {
94
+ const id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
95
+
96
+ const item = {
97
+ id,
98
+ ...fileData,
99
+ created_at: new Date().toISOString()
100
+ };
101
+
102
+ changedFilesBuffer.push(item);
103
+
104
+ console.log(`[BUFFER] Added file: ${fileData.changed_file.name} (Total: ${changedFilesBuffer.length})`);
105
+
106
+ const timer = setTimeout(() => {
107
+ const index = changedFilesBuffer.findIndex(f => f.id === id);
108
+ if (index !== -1) {
109
+ const removed = changedFilesBuffer.splice(index, 1)[0];
110
+ console.log(`[CLEANUP] Removed: ${removed.changed_file.name} after 5 minutes (Remaining: ${changedFilesBuffer.length})`);
111
+
112
+ broadcast({
113
+ type: 'cleanup',
114
+ id: id,
115
+ filename: removed.changed_file.name,
116
+ remaining_count: changedFilesBuffer.length
117
+ });
118
+ }
119
+ cleanupTimers.delete(id);
120
+ }, CONFIG.BUFFER_TIME);
121
+
122
+ cleanupTimers.set(id, timer);
123
+
124
+ return item;
125
+ }
126
+
127
+ async function checkRepoChanges(owner, repo) {
128
+ try {
129
+ const { data: commits } = await octokit.repos.listCommits({
130
+ owner,
131
+ repo,
132
+ per_page: 10
133
+ });
134
+
135
+ if (commits.length === 0) return;
136
+
137
+ const repoKey = `${owner}/${repo}`;
138
+ const lastCommitSha = lastCommits.get(repoKey);
139
+
140
+ if (!lastCommitSha) {
141
+ lastCommits.set(repoKey, commits[0].sha);
142
+ console.log(`[INIT] Tracking ${repoKey} from commit: ${commits[0].sha.substring(0, 7)}`);
143
+ return;
144
+ }
145
+
146
+ const newCommits = [];
147
+ for (const commit of commits) {
148
+ if (commit.sha === lastCommitSha) break;
149
+ newCommits.push(commit);
150
+ }
151
+
152
+ if (newCommits.length === 0) return;
153
+
154
+ console.log(`[DETECTED] ${newCommits.length} new commit(s) in ${repoKey}`);
155
+
156
+ for (const commit of newCommits.reverse()) {
157
+ try {
158
+ const { data: commitDetail } = await octokit.repos.getCommit({
159
+ owner,
160
+ repo,
161
+ ref: commit.sha
162
+ });
163
+
164
+ console.log(`[COMMIT] ${commit.sha.substring(0, 7)}: "${commit.commit.message}" - ${commitDetail.files.length} file(s)`);
165
+
166
+ for (const file of commitDetail.files) {
167
+ if (file.status === 'removed') {
168
+ console.log(` [SKIP] ${file.filename} (removed)`);
169
+ continue;
170
+ }
171
+
172
+ console.log(` [PROCESS] ${file.filename} (${file.status})`);
173
+
174
+ const content = await getFileContent(owner, repo, file.filename, commit.sha);
175
+
176
+ const fileChangeData = {
177
+ repository: repoKey,
178
+ commit: {
179
+ sha: commit.sha,
180
+ short_sha: commit.sha.substring(0, 7),
181
+ message: commit.commit.message,
182
+ author: commit.commit.author.name,
183
+ email: commit.commit.author.email,
184
+ date: commit.commit.author.date,
185
+ url: commit.html_url
186
+ },
187
+ changed_file: {
188
+ name: file.filename,
189
+ status: file.status,
190
+ additions: file.additions,
191
+ deletions: file.deletions,
192
+ changes: file.changes,
193
+ isinya: content || 'Unable to fetch content',
194
+ patch: file.patch || null
195
+ }
196
+ };
197
+
198
+ const bufferedItem = addChangedFile(fileChangeData);
199
+
200
+ broadcast({
201
+ type: 'new_change',
202
+ data: bufferedItem,
203
+ total_count: changedFilesBuffer.length
204
+ });
205
+ }
206
+
207
+ } catch (error) {
208
+ console.error(`[ERROR] Processing commit ${commit.sha.substring(0, 7)}:`, error.message);
209
+ }
210
+ }
211
+
212
+ lastCommits.set(repoKey, commits[0].sha);
213
+
214
+ } catch (error) {
215
+ console.error(`[ERROR] Checking repo ${owner}/${repo}:`, error.message);
216
+ }
217
+ }
218
+
219
+ app.get('/', (req, res) => {
220
+ res.json({
221
+ name: 'GitHub Repository Watcher',
222
+ version: '1.0.0',
223
+ endpoints: {
224
+ data: '/data',
225
+ health: '/health'
226
+ },
227
+ websocket: 'Connect to same URL with ws:// or wss://'
228
+ });
229
+ });
230
+
231
+ app.get('/data', (req, res) => {
232
+ res.json({
233
+ success: true,
234
+ count: changedFilesBuffer.length,
235
+ data: changedFilesBuffer,
236
+ timestamp: new Date().toISOString()
237
+ });
238
+ });
239
+
240
+ app.get('/health', (req, res) => {
241
+ res.json({
242
+ status: 'running',
243
+ buffered_files: changedFilesBuffer.length,
244
+ ws_clients: clients.size,
245
+ watching_repos: CONFIG.REPOS_TO_WATCH.length,
246
+ poll_interval_sec: CONFIG.POLL_INTERVAL / 1000
247
+ });
248
+ });
249
+
250
+ server.listen(CONFIG.PORT, () => {
251
+ console.log(`\n${'='.repeat(60)}`);
252
+ console.log(` GitHub Repository Watcher - Running`);
253
+ console.log(`${'='.repeat(60)}`);
254
+ console.log(` Server: http://localhost:${CONFIG.PORT}`);
255
+ console.log(` WebSocket: ws://localhost:${CONFIG.PORT}`);
256
+ console.log(` Data Endpoint: http://localhost:${CONFIG.PORT}/data`);
257
+ console.log(`${'='.repeat(60)}`);
258
+ console.log(` Watching ${CONFIG.REPOS_TO_WATCH.length} repo(s):`);
259
+ CONFIG.REPOS_TO_WATCH.forEach(r => {
260
+ console.log(` - ${r.owner}/${r.repo}`);
261
+ });
262
+ console.log(` Poll Interval: ${CONFIG.POLL_INTERVAL / 1000}s`);
263
+ console.log(` Buffer Time: ${CONFIG.BUFFER_TIME / 1000 / 60}min`);
264
+ console.log(`${'='.repeat(60)}\n`);
265
+ });
266
+
267
+ async function startWatching() {
268
+ console.log('[START] Initializing repository watcher...\n');
269
+
270
+ for (const { owner, repo } of CONFIG.REPOS_TO_WATCH) {
271
+ await checkRepoChanges(owner, repo);
272
+ }
273
+
274
+ setInterval(async () => {
275
+ for (const { owner, repo } of CONFIG.REPOS_TO_WATCH) {
276
+ await checkRepoChanges(owner, repo);
277
+ }
278
+ }, CONFIG.POLL_INTERVAL);
279
+ }
280
+
281
+ startWatching();
282
+
283
+ process.on('SIGINT', () => {
284
+ console.log('\n[SHUTDOWN] Cleaning up...');
285
+
286
+ cleanupTimers.forEach(timer => clearTimeout(timer));
287
+ cleanupTimers.clear();
288
+
289
+ wss.close();
290
+ server.close();
291
+ console.log('[SHUTDOWN] Server stopped');
292
+ process.exit(0);
293
+ });