-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathhistory.js
More file actions
73 lines (65 loc) · 2.53 KB
/
Copy pathhistory.js
File metadata and controls
73 lines (65 loc) · 2.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const MAX_HISTORY_ENTRIES = Number(process.env.HISTORY_MAX_ENTRIES || 5_000);
const DEFAULT_HISTORY_FILE = process.env.HISTORY_FILE
? path.resolve(process.env.HISTORY_FILE)
: path.join(__dirname, 'data', 'history.json');
class HistoryStore {
constructor(filePath = DEFAULT_HISTORY_FILE) {
this.filePath = filePath;
this.queue = Promise.resolve();
}
enqueue(operation) {
const result = this.queue.then(operation, operation);
this.queue = result.catch(() => {});
return result;
}
async read() {
try {
const parsed = JSON.parse(await fs.promises.readFile(this.filePath, 'utf8'));
return Array.isArray(parsed.entries) ? parsed : { version: 1, entries: [] };
} catch (error) {
if (error.code === 'ENOENT') return { version: 1, entries: [] };
throw error;
}
}
async write(data) {
await fs.promises.mkdir(path.dirname(this.filePath), { recursive: true });
const temporaryPath = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;
await fs.promises.writeFile(temporaryPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
await fs.promises.rename(temporaryPath, this.filePath);
}
add(entry) {
return this.enqueue(async () => {
const data = await this.read();
const normalized = {
id: crypto.randomUUID(),
guildId: String(entry.guildId),
station: {
name: String(entry.station?.name || 'Unknown station').slice(0, 100),
url: String(entry.station?.url || '').slice(0, 2_000),
},
botInstance: Number(entry.botInstance || 1),
voiceChannelId: entry.voiceChannelId ? String(entry.voiceChannelId) : null,
voiceChannelName: entry.voiceChannelName ? String(entry.voiceChannelName).slice(0, 100) : null,
requestedBy: entry.requestedBy ? String(entry.requestedBy) : null,
source: entry.source === 'web' ? 'web' : 'discord',
playedAt: entry.playedAt || new Date().toISOString(),
};
data.entries.unshift(normalized);
data.entries = data.entries.slice(0, Math.max(100, MAX_HISTORY_ENTRIES));
await this.write(data);
return normalized;
});
}
list(guildId, limit = 50) {
return this.enqueue(async () => {
const data = await this.read();
return data.entries
.filter((entry) => entry.guildId === String(guildId))
.slice(0, Math.min(100, Math.max(1, Number(limit) || 50)));
});
}
}
module.exports = { HistoryStore, MAX_HISTORY_ENTRIES };