feat: modernize and harden bot deployment
This commit is contained in:
+62
-15
@@ -1,21 +1,68 @@
|
||||
import fs from 'fs';
|
||||
import { createEntry, Entry, Type } from "./entry.ts";
|
||||
|
||||
import { Entry } from './entry.ts';
|
||||
const configPath = Deno.env.get("CONFIG_PATH") ?? "./config.json";
|
||||
let pendingWrite = Promise.resolve();
|
||||
|
||||
export async function writeConfig(entries: Entry[]) {
|
||||
const data = JSON.stringify(entries, null, 2);
|
||||
fs.writeFile('./config.json', data, (err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
});
|
||||
export function writeConfig(entries: Entry[]): Promise<void> {
|
||||
const data = `${JSON.stringify(entries, null, 2)}\n`;
|
||||
pendingWrite = pendingWrite
|
||||
.catch(() => undefined)
|
||||
.then(async () => {
|
||||
const temporaryPath = `${configPath}.tmp`;
|
||||
await Deno.writeTextFile(temporaryPath, data);
|
||||
await Deno.chmod(temporaryPath, 0o600);
|
||||
await Deno.rename(temporaryPath, configPath);
|
||||
});
|
||||
return pendingWrite;
|
||||
}
|
||||
|
||||
export function readConfig(): Entry[] {
|
||||
if (!fs.existsSync('./config.json')) {
|
||||
return [];
|
||||
let data: string;
|
||||
try {
|
||||
data = Deno.readTextFileSync(configPath);
|
||||
} catch (error) {
|
||||
if (error instanceof Deno.errors.NotFound) return [];
|
||||
throw error;
|
||||
}
|
||||
const data = fs.readFileSync('./config.json', 'utf8');
|
||||
return JSON.parse(data);
|
||||
}
|
||||
|
||||
const parsed: unknown = JSON.parse(data);
|
||||
if (!Array.isArray(parsed)) throw new Error("config must contain an array");
|
||||
return parsed.map((value, index) => parseEntry(value, index));
|
||||
}
|
||||
|
||||
function parseEntry(value: unknown, index: number): Entry {
|
||||
if (!value || typeof value !== "object") {
|
||||
throw new Error(`config entry ${index} is invalid`);
|
||||
}
|
||||
|
||||
const raw = value as Record<string, unknown>;
|
||||
if (typeof raw.server !== "string" || raw.server.length === 0) {
|
||||
throw new Error(`config entry ${index} has no server ID`);
|
||||
}
|
||||
|
||||
const entry = createEntry(raw.server);
|
||||
if (typeof raw.output_channel === "string") entry.output_channel = raw.output_channel;
|
||||
if (raw.channel_type === Type.INCLUDE || raw.channel_type === Type.EXCLUDE) {
|
||||
entry.channel_type = raw.channel_type;
|
||||
}
|
||||
if (Array.isArray(raw.channels)) entry.channels = uniqueStrings(raw.channels);
|
||||
if (raw.emoji_type === Type.INCLUDE || raw.emoji_type === Type.EXCLUDE) {
|
||||
entry.emoji_type = raw.emoji_type;
|
||||
}
|
||||
if (Array.isArray(raw.emojis)) entry.emojis = uniqueStrings(raw.emojis);
|
||||
if (typeof raw.score === "number" && Number.isFinite(raw.score) && raw.score > 0) {
|
||||
entry.score = raw.score;
|
||||
}
|
||||
if (raw.awards && typeof raw.awards === "object" && !Array.isArray(raw.awards)) {
|
||||
entry.awards = Object.fromEntries(
|
||||
Object.entries(raw.awards).filter(
|
||||
(item): item is [string, string] => typeof item[1] === "string",
|
||||
),
|
||||
);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
function uniqueStrings(values: unknown[]): string[] {
|
||||
return [...new Set(values.filter((item): item is string => typeof item === "string"))];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user