Merge pull request '#10-RSS-feed-manager' (#14) from #10-RSS-feed-manager into main
Reviewed-on: logan/DRB-Server#14
This commit is contained in:
56
.gitea/workflows/DRBv3_server_build.yaml
Normal file
56
.gitea/workflows/DRBv3_server_build.yaml
Normal file
@@ -0,0 +1,56 @@
|
||||
name: release-tag
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
release-image:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
env:
|
||||
DOCKER_ORG: teacup
|
||||
DOCKER_LATEST: nightly
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker BuildX
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with: # replace it with your local IP
|
||||
config-inline: |
|
||||
[registry."${{ secrets.LOCAL_GITEA_IP}}:3000"]
|
||||
http = true
|
||||
insecure = true
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ secrets.LOCAL_GITEA_IP}}:3000 # replace it with your local IP
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Get Meta
|
||||
id: meta
|
||||
run: |
|
||||
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
|
||||
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
push: true
|
||||
tags: | # replace it with your local IP and tags
|
||||
${{ secrets.LOCAL_GITEA_IP}}:3000/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
|
||||
${{ secrets.LOCAL_GITEA_IP}}:3000/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
|
||||
@@ -1,8 +1,10 @@
|
||||
// addons/addon1/index.js
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "server");
|
||||
|
||||
// Function called by the main application to initialize the addon
|
||||
export function initialize(nodeIo, config) {
|
||||
console.log(`Initializing ${config.name}`);
|
||||
log.INFO(`Initializing ${config.name}`);
|
||||
|
||||
// Call other functions within the addon module
|
||||
registerSocketEvents(nodeIo, config);
|
||||
@@ -12,6 +14,6 @@ export function initialize(nodeIo, config) {
|
||||
// Function to register Socket.IO event handlers
|
||||
function registerSocketEvents(nodeIo, config) {
|
||||
nodeIo.on(config.options.eventName, (data) => {
|
||||
console.log(`Received event "${config.options.eventName}" from client:`, data);
|
||||
log.DEBUG(`Received event "${config.options.eventName}" from client:`, data);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.join");
|
||||
import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
|
||||
import { requestNodeJoinSystem, checkIfNodeIsConnectedToVC, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem } from '../../modules/socketServerWrappers.mjs';
|
||||
import { getSystemsByNuid, getAllSystems, getSystemByName } from '../../modules/mongo-wrappers/mongoSystemsWrappers.mjs';
|
||||
@@ -27,7 +29,7 @@ export async function autocomplete(nodeIo, interaction) {
|
||||
const choices = await getAllSystems();
|
||||
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
|
||||
|
||||
console.log(focusedValue, choices, filtered);
|
||||
log.DEBUG(focusedValue, choices, filtered);
|
||||
|
||||
await interaction.respond(
|
||||
filtered.map(choice => ({ name: choice.name, value: choice.name })),
|
||||
@@ -44,7 +46,7 @@ export async function execute(nodeIo, interaction) {
|
||||
if (!interaction.member.voice.channel) { return await interaction.editReply({ content: `<@${interaction.member.id}>, you need to enter a voice channel before you use this command`, ephemeral: true }) }
|
||||
// Grab the channel if the user is connected to VC
|
||||
const channelToJoin = interaction.member.voice.channel;
|
||||
console.log(`The user '${interaction.member.id}' is in the voice channel '${channelToJoin}'`);
|
||||
log.INFO(`The user '${interaction.member.id}' is in the voice channel '${channelToJoin}'`);
|
||||
|
||||
// Get the selected system option from the command interaction
|
||||
const selectedSystem = interaction.options.getString('system');
|
||||
@@ -58,11 +60,11 @@ export async function execute(nodeIo, interaction) {
|
||||
const openSocket = await nodeIo.sockets.sockets.get(selectedNodeSocketId);
|
||||
// Get the open ID for this connection\
|
||||
const discordTokens = await getAvailableTokensInGuild(nodeIo, interaction.guild.id);
|
||||
console.log("Available discord tokens: ", discordTokens);
|
||||
log.DEBUG("Available discord tokens: ", discordTokens);
|
||||
|
||||
if (discordTokens.length >= 1) {
|
||||
// TODO - Implement a method to have preferred tokens (bot users) for specific systems
|
||||
console.log("Joining selected open socket:", selectedNodeSocketId, system.name, channelToJoin.id, openSocket.node.name, discordTokens[0].token);
|
||||
log.INFO("Joining selected open socket:", selectedNodeSocketId, system.name, channelToJoin.id, openSocket.node.name, discordTokens[0].token);
|
||||
|
||||
// Ask the node to join the selected channel and system
|
||||
await requestNodeJoinSystem(openSocket, system.name, channelToJoin.id, discordTokens[0].token);
|
||||
@@ -74,7 +76,7 @@ export async function execute(nodeIo, interaction) {
|
||||
|
||||
// Get all open socket nodes
|
||||
const openSockets = [...await nodeIo.allSockets()]; // TODO - Filter the returned nodes to only nodes that have the radio capability
|
||||
console.log("All open sockets: ", openSockets);
|
||||
log.DEBUG("All open sockets: ", openSockets);
|
||||
|
||||
var availableNodes = [];
|
||||
// Check each open socket to see if the node has the requested system
|
||||
@@ -85,14 +87,14 @@ export async function execute(nodeIo, interaction) {
|
||||
if (hasOpenClient) {
|
||||
let currentSystem = await getNodeCurrentListeningSystem(openSocket);
|
||||
if (currentSystem != system.name) {
|
||||
console.log("Node is listening to a different system than requested", openSocket.node.name);
|
||||
log.INFO("Node is listening to a different system than requested", openSocket.node.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the bot has an open voice connection in the requested server already
|
||||
const connected = await checkIfNodeIsConnectedToVC(nodeIo, interaction.guild.id, openSocket.node.nuid);
|
||||
console.log("Connected:", connected);
|
||||
log.INFO("Connected:", connected);
|
||||
if (!connected) {
|
||||
// Check if this node has the requested system, if so add it to the availble array
|
||||
if (system.nodes.includes(openSocket.node.nuid)) {
|
||||
@@ -102,7 +104,7 @@ export async function execute(nodeIo, interaction) {
|
||||
|
||||
}));
|
||||
|
||||
console.log("Availble nodes:", availableNodes.map(socket => socket.node.name));
|
||||
log.DEBUG("Availble nodes:", availableNodes.map(socket => socket.node.name));
|
||||
|
||||
// If there are no available nodes, let the user know there are none available
|
||||
if (availableNodes.length == 0) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.leave");
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { requestBotLeaveServer, getSocketIdByNuid } from '../../modules/socketServerWrappers.mjs';
|
||||
import { checkOnlineBotsInGuild } from '../modules/wrappers.mjs'
|
||||
@@ -25,11 +27,11 @@ export async function autocomplete(nodeIo, interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const choices = (await checkOnlineBotsInGuild(nodeIo, interaction.guild.id));
|
||||
|
||||
console.log(choices);
|
||||
log.DEBUG(choices);
|
||||
|
||||
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue)).map(choice => choice = {name: choice.name, value: choice.nuid});
|
||||
|
||||
console.log(focusedValue, choices, filtered);
|
||||
log.DEBUG(focusedValue, choices, filtered);
|
||||
|
||||
await interaction.respond(filtered);
|
||||
}
|
||||
@@ -44,7 +46,7 @@ export async function execute(nodeIo, interaction) {
|
||||
// Get the requested bot
|
||||
const selectedNode = interaction.options.getString('bot');
|
||||
const socket = await getSocketIdByNuid(nodeIo, selectedNode);
|
||||
console.log("All open sockets:", socket, selectedNode);
|
||||
log.DEBUG("All open sockets:", socket, selectedNode);
|
||||
await requestBotLeaveServer(socket, interaction.guild.id);
|
||||
//await interaction.reply(`**Online Sockets: '${sockets}'**`);
|
||||
await interaction.editReply(`Ok <@${interaction.member.id}>, the bot is leaving shortly`);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.ping");
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
@@ -17,10 +19,10 @@ export const deferInitialReply = false; // If we the initial reply in discord sh
|
||||
/*
|
||||
export async function autocomplete(nodeIo, interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const choices = [];
|
||||
const choices = []; // The array to be filled with the autocorrect values
|
||||
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
|
||||
console.log(focusedValue, choices, filtered);
|
||||
await interaction.respond(filtered);
|
||||
log.INFO(focusedValue, choices, filtered);
|
||||
await interaction.respond(filtered.map(choice => ({name: choice.name, value: choice.name})));
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -32,7 +34,7 @@ export async function autocomplete(nodeIo, interaction) {
|
||||
export const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
const sockets = await nodeIo.allSockets();
|
||||
console.log("All open sockets: ",sockets);
|
||||
log.DEBUG("All open sockets: ",sockets);
|
||||
//await interaction.reply(`**Online Sockets: '${sockets}'**`);
|
||||
await interaction.reply('**Pong.**');
|
||||
//await interaction.channel.send('**Pong.**');
|
||||
|
||||
71
discordBot/commands/rssAdd.mjs
Normal file
71
discordBot/commands/rssAdd.mjs
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { addSource } from '../../rss-manager/sourceManager.mjs'
|
||||
const log = new DebugBuilder("server", "discordBot.command.rssAdd");
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('rss-add')
|
||||
.setDescription('Add RSS Source')
|
||||
.addStringOption(option =>
|
||||
option.setName('title')
|
||||
.setDescription('The title of the RSS feed')
|
||||
.setRequired(true))
|
||||
.addStringOption(option =>
|
||||
option.setName('link')
|
||||
.setDescription('The link to the RSS feed')
|
||||
.setRequired(true))
|
||||
.addStringOption(option =>
|
||||
option.setName('category')
|
||||
.setDescription('The category for the RSS feed *("ALL" by default")*')
|
||||
.setRequired(false))
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/rss-add [title] [https://domain.com/feed.xml] [category]"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
export const deferInitialReply = false; // If we the initial reply in discord should be deferred. This gives extra time to respond, however the method of replying is different.
|
||||
|
||||
/**
|
||||
* Function to give the user auto-reply suggestions
|
||||
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||
* @param {any} interaction The interaction object
|
||||
*/
|
||||
|
||||
// TODO - Setup autocorrect for the category
|
||||
/*
|
||||
export async function autocomplete(nodeIo, interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const choices = [];
|
||||
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
|
||||
log.DEBUG(focusedValue, choices, filtered);
|
||||
await interaction.respond(filtered);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The function to run when the command is called by a discord user
|
||||
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||
* @param {any} interaction The interaction object
|
||||
*/
|
||||
export const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
var title = interaction.options.getString('title');
|
||||
var link = interaction.options.getString('link');
|
||||
var category = interaction.options.getString('category');
|
||||
|
||||
if (!category) category = "ALL";
|
||||
|
||||
await addSource(title, link, category, interaction.guildId, interaction.channelId, (err, result) => {
|
||||
log.DEBUG("Result from adding entry", result);
|
||||
|
||||
if (result) {
|
||||
interaction.reply(`Successfully added ${title} to the list of RSS sources`);
|
||||
} else {
|
||||
interaction.reply(`${title} already exists in the list of RSS sources`);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
log.ERROR(err)
|
||||
await interaction.reply(err.toString());
|
||||
}
|
||||
}
|
||||
58
discordBot/commands/rssRemove.mjs
Normal file
58
discordBot/commands/rssRemove.mjs
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { removeSource } from '../../rss-manager/sourceManager.mjs'
|
||||
import { getAllFeeds, deleteFeedByTitle } from '../../modules/mongo-wrappers/mongoFeedsWrappers.mjs'
|
||||
const log = new DebugBuilder("server", "discordBot.command.rssRemove");
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('rss-remove')
|
||||
.setDescription('Add RSS Source')
|
||||
.addStringOption(option =>
|
||||
option.setName('title')
|
||||
.setDescription('The title of the RSS feed')
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true))
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/rss-remove [title]"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
export const deferInitialReply = false; // If we the initial reply in discord should be deferred. This gives extra time to respond, however the method of replying is different.
|
||||
|
||||
/**
|
||||
* Function to give the user auto-reply suggestions
|
||||
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||
* @param {any} interaction The interaction object
|
||||
*/
|
||||
|
||||
export async function autocomplete(nodeIo, interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const choices = await getAllFeeds() ?? [];
|
||||
log.INFO("RSS Remove Choices:", choices);
|
||||
const filtered = choices.filter(choice => choice.title.startsWith(focusedValue));
|
||||
log.DEBUG(focusedValue, choices, filtered);
|
||||
await interaction.respond(filtered.map(choice => ({ name: choice.title, value: choice.title })));
|
||||
}
|
||||
|
||||
/**
|
||||
* The function to run when the command is called by a discord user
|
||||
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||
* @param {any} interaction The interaction object
|
||||
*/
|
||||
export const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
var title = interaction.options.getString('title');
|
||||
interaction.reply(`Removing ${title} from the list of RSS sources, please wait...`);
|
||||
|
||||
const results = await deleteFeedByTitle(title);
|
||||
if (!results) {
|
||||
log.WARN(`Failed to remove source: ${title}`);
|
||||
interaction.editReply(`Failed to remove source: '${title}'`);
|
||||
return;
|
||||
}
|
||||
interaction.editReply(`${title} was successfully removed from the RSS sources.`)
|
||||
} catch (err) {
|
||||
log.ERROR(err)
|
||||
interaction.editReply(err.toString());
|
||||
}
|
||||
}
|
||||
46
discordBot/commands/rssTrigger.mjs
Normal file
46
discordBot/commands/rssTrigger.mjs
Normal file
@@ -0,0 +1,46 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.rssTrigger");
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { updateFeeds } from '../../rss-manager/feedHandler.mjs'
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('rss-trigger')
|
||||
.setDescription('Manually triggers an RSS feed update');
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/rss-trigger"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
export const deferInitialReply = false; // If we the initial reply in discord should be deferred. This gives extra time to respond, however the method of replying is different.
|
||||
|
||||
/**
|
||||
* Function to give the user auto-reply suggestions
|
||||
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||
* @param {any} interaction The interaction object
|
||||
*/
|
||||
/*
|
||||
export async function autocomplete(nodeIo, interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const choices = [];
|
||||
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
|
||||
log.INFO(focusedValue, choices, filtered);
|
||||
await interaction.respond(filtered);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The function to run when the command is called by a discord user
|
||||
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||
* @param {any} interaction The interaction object
|
||||
*/
|
||||
export const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
//const sockets = await nodeIo.allSockets();
|
||||
//await interaction.reply(`**Online Sockets: '${sockets}'**`);
|
||||
await interaction.reply('Triggering RSS update');
|
||||
await updateFeeds(interaction.client);
|
||||
//await interaction.channel.send('**Pong.**');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
// await interaction.reply(err.toString());
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.update");
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { requestNodeUpdate } from '../../modules/socketServerWrappers.mjs';
|
||||
|
||||
@@ -18,7 +20,7 @@ export const deferInitialReply = false; // If we the initial reply in discord sh
|
||||
export const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
const openSockets = [...await nodeIo.allSockets()]; // TODO - Filter the returned nodes to only nodes that have the radio capability
|
||||
console.log("All open sockets: ", openSockets);
|
||||
log.DEBUG("All open sockets: ", openSockets);
|
||||
|
||||
// Check each open socket to see if the node has the requested system
|
||||
await Promise.all(openSockets.map(openSocket => {
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
import { Client, GatewayIntentBits, Collection } from 'discord.js';
|
||||
import { registerActiveCommands, unregisterAllCommands } from './modules/registerCommands.mjs'
|
||||
import { RSSController } from '../rss-manager/rssController.mjs'
|
||||
import { join, dirname } from 'path';
|
||||
import { readdirSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
const log = new DebugBuilder("server", "discordBot");
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
/**
|
||||
* Add the enabled commands to the bot to be used by users in discord
|
||||
@@ -25,18 +30,18 @@ export const addEnabledCommands = async (serverClient, _commandsPath = "./comman
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const filePath = await join(commandsPath, file);
|
||||
console.log(`Adding enabled command: ${filePath}`);
|
||||
log.INFO(`Adding enabled command: ${filePath}`);
|
||||
await import(`file://${filePath}`).then(command => {
|
||||
if (command.data instanceof Promise) {
|
||||
command.data.then(async (builder) => {
|
||||
command.data = builder;
|
||||
console.log("Importing command: ", command.data.name, command);
|
||||
log.DEBUG("Importing command: ", command.data.name, command);
|
||||
// Set a new item in the Collection
|
||||
// With the key as the command name and the value as the exported module
|
||||
serverClient.commands.set(command.data.name, command);
|
||||
});
|
||||
} else {
|
||||
console.log("Importing command: ", command.data.name, command);
|
||||
log.DEBUG("Importing command: ", command.data.name, command);
|
||||
// Set a new item in the Collection
|
||||
// With the key as the command name and the value as the exported module
|
||||
serverClient.commands.set(command.data.name, command);
|
||||
@@ -61,9 +66,9 @@ export function addEnabledEventListeners(serverClient, _eventsPath = "./events")
|
||||
|
||||
for (const file of eventFiles) {
|
||||
const filePath = join(eventsPath, file);
|
||||
console.log(`Adding enabled event listener: ${filePath}`);
|
||||
log.INFO(`Adding enabled event listener: ${filePath}`);
|
||||
import(`file://${filePath}`).then(event => {
|
||||
console.log("Adding event: ", event);
|
||||
log.DEBUG("Adding event: ", event);
|
||||
if (event.once) {
|
||||
serverClient.once(event.name, (...args) => event.execute(serverClient.nodeIo, ...args));
|
||||
} else {
|
||||
@@ -78,15 +83,21 @@ export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds, Gat
|
||||
|
||||
// Run when the bot is ready
|
||||
serverClient.on('ready', async () => {
|
||||
console.log(`Logged in as ${serverClient.user.tag}!`);
|
||||
log.INFO(`Logged in as ${serverClient.user.tag}!`);
|
||||
|
||||
// Add and register commands
|
||||
await addEnabledCommands(serverClient);
|
||||
|
||||
// Config the discord bot with events
|
||||
await addEnabledEventListeners(serverClient);
|
||||
|
||||
// Start the RSS Controller
|
||||
serverClient.RSSController = await new RSSController(serverClient);
|
||||
serverClient.RSSController.start();
|
||||
|
||||
log.INFO("RSS Controller:", serverClient.RSSController);
|
||||
});
|
||||
|
||||
// Startup the discord bot
|
||||
console.log(`Logging into discord with ID: ${process.env.DISCORD_TOKEN}`);
|
||||
log.INFO(`Logging into discord with ID: ${process.env.DISCORD_TOKEN}`);
|
||||
serverClient.login(process.env.DISCORD_TOKEN);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.events.interactionCreate");
|
||||
import { Events } from 'discord.js';
|
||||
|
||||
export const name = Events.InteractionCreate;
|
||||
|
||||
export async function execute(nodeIo, interaction) {
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
console.log("Interaction created for command: ", command);
|
||||
log.INFO("Interaction created for command: ", command);
|
||||
|
||||
// Execute autocomplete if the user is checking autocomplete
|
||||
if (interaction.isAutocomplete()) {
|
||||
console.log("Running autocomplete for command: ", command.data.name);
|
||||
log.INFO("Running autocomplete for command: ", command.data.name);
|
||||
return await command.autocomplete(nodeIo, interaction);
|
||||
}
|
||||
|
||||
@@ -20,7 +22,7 @@ export async function execute(nodeIo, interaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${interaction.member.user} is running '${interaction.commandName}'`);
|
||||
log.INFO(`${interaction.member.user} is running '${interaction.commandName}'`);
|
||||
|
||||
// Defer the initial reply if the command has the parameter set
|
||||
if (command.deferInitialReply) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.modules.registerCommands");
|
||||
import { REST, Routes } from 'discord.js';
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
@@ -15,21 +17,21 @@ export const registerActiveCommands = async (serverClient) => {
|
||||
|
||||
// and deploy your commands!
|
||||
guildIDs.forEach(guild => {
|
||||
console.log("Deploying commands for: ", guild.id);
|
||||
console.log("Commands", commands);
|
||||
log.INFO("Deploying commands for: ", guild.id);
|
||||
log.DEBUG("Commands", commands);
|
||||
(async () => {
|
||||
try {
|
||||
console.log(`Started refreshing application (/) commands for guild ID: ${guild.id}.`);
|
||||
log.DEBUG(`Started refreshing application (/) commands for guild ID: ${guild.id}.`);
|
||||
// The put method is used to fully refresh all commands in the guild with the current set
|
||||
const data = await rest.put(
|
||||
Routes.applicationGuildCommands(clientId, guild.id),
|
||||
{ body: commands },
|
||||
);
|
||||
|
||||
console.log(`Successfully reloaded ${data.length} application (/) commands for guild ID: ${guild.id}.`);
|
||||
log.DEBUG(`Successfully reloaded ${data.length} application (/) commands for guild ID: ${guild.id}.`);
|
||||
} catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
console.log("ERROR Deploying commands: ", error, "Body from error: ", commands);
|
||||
log.ERROR("ERROR Deploying commands: ", error, "Body from error: ", commands);
|
||||
}
|
||||
})()
|
||||
})
|
||||
@@ -47,20 +49,20 @@ export const unregisterAllCommands = async (serverClient) => {
|
||||
|
||||
const rest = new REST({ version: '10' }).setToken(discordToken);
|
||||
guildIDs.forEach(guild => {
|
||||
console.log("Removing commands for: ", clientId, guild.id);
|
||||
log.INFO("Removing commands for: ", clientId, guild.id);
|
||||
(async () => {
|
||||
try {
|
||||
console.log(`Started removal of ${commands.length} application (/) commands for guild ID: ${guild.id}.`);
|
||||
log.DEBUG(`Started removal of ${commands.length} application (/) commands for guild ID: ${guild.id}.`);
|
||||
// The put method is used to fully refresh all commands in the guild with the current set
|
||||
const data = await rest.put(
|
||||
Routes.applicationGuildCommands(clientId, guild.id),
|
||||
{ body: commands },
|
||||
);
|
||||
|
||||
console.log(`Successfully removed ${data.length} application (/) commands for guild ID: ${guild.id}.`);
|
||||
log.DEBUG(`Successfully removed ${data.length} application (/) commands for guild ID: ${guild.id}.`);
|
||||
} catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
console.log("ERROR removing commands: ", error, "Body from error: ", commands);
|
||||
log.ERROR("ERROR removing commands: ", error, "Body from error: ", commands);
|
||||
}
|
||||
})()
|
||||
})
|
||||
@@ -74,10 +76,10 @@ export const unregisterAllCommands = async (serverClient) => {
|
||||
*/
|
||||
export const refreshActiveCommandsWrapper = async (serverClient) => {
|
||||
// Remove all commands
|
||||
console.log("Removing/Unregistering all commands from all connected servers/guilds");
|
||||
log.INFO("Removing/Unregistering all commands from all connected servers/guilds");
|
||||
await unregisterAllCommands(serverClient);
|
||||
// Deploy the active commands
|
||||
console.log("Adding commands to all connected servers/guilds");
|
||||
log.INFO("Adding commands to all connected servers/guilds");
|
||||
await registerActiveCommands(serverClient);
|
||||
return;
|
||||
}
|
||||
93
discordBot/modules/rssWrappers.mjs
Normal file
93
discordBot/modules/rssWrappers.mjs
Normal file
@@ -0,0 +1,93 @@
|
||||
// Import necessary modules
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { parse } from "node-html-parser";
|
||||
import { config } from 'dotenv';
|
||||
|
||||
// Load environment variables
|
||||
config();
|
||||
|
||||
const log = new DebugBuilder("server", "discordBot.modules.rssWrappers");
|
||||
|
||||
const imageRegex = /(http(s?):)([/|.|\w|\s|-])*((\.(?:jpg|gif|png|webm))|(\/gallery\/(?:[/|.|\w|\s|-])*))/g;
|
||||
const youtubeVideoRegex = /((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)/g;
|
||||
|
||||
export class DRBEmbedBuilder extends EmbedBuilder {
|
||||
constructor() {
|
||||
super();
|
||||
this.setTimestamp();
|
||||
this.setFooter({ text: 'Brought to you by Emmelia.' });
|
||||
}
|
||||
}
|
||||
|
||||
export const sendPost = (post, source, channel) => {
|
||||
log.DEBUG("Sending post from source: ", post, source);
|
||||
|
||||
const postTitle = String(post.title).substring(0, 150);
|
||||
const postLink = post.link;
|
||||
let postContent = `*This post has no content* [Direct Link](${post.link})`;
|
||||
|
||||
if (post.content || post['content:encoded']) {
|
||||
const content = post['content:encoded'] ?? post.content;
|
||||
const parsedContent = parse(content);
|
||||
let postText = parsedContent.text.trim();
|
||||
|
||||
if (postText.length >= 3800) {
|
||||
postText = `${postText.slice(0, 3800).substring(0, postText.lastIndexOf(" "))} [...](${post.link})`;
|
||||
} else if (postText.length === 0) {
|
||||
postText = `*This post has no content* [Direct Link](${post.link})`;
|
||||
}
|
||||
postContent = postText;
|
||||
|
||||
// Check for embedded YouTube videos and add the first four as links
|
||||
const ytVideos = content.match(youtubeVideoRegex);
|
||||
if (ytVideos) {
|
||||
ytVideos.slice(0, 4).forEach((ytVideo) => {
|
||||
if (ytVideo.includes("embed")) ytVideo = ytVideo.replace("embed/", "watch?v=");
|
||||
postContent += `\nEmbedded Video from Post: [YouTube](${ytVideo})`;
|
||||
});
|
||||
}
|
||||
|
||||
// Extract the first image link if available
|
||||
const imageLinks = parsedContent.querySelectorAll("a")
|
||||
.map(link => link.getAttribute("href"))
|
||||
.filter(href => href && href.match(imageRegex));
|
||||
|
||||
if (imageLinks.length > 0) {
|
||||
post.image = imageLinks[0];
|
||||
}
|
||||
}
|
||||
|
||||
const postId = post.postId;
|
||||
const postPubDate = new Date(post.pubDate || Date.now()).toISOString();
|
||||
const postSourceLink = source.title;
|
||||
const postImage = post.image;
|
||||
|
||||
log.DEBUG("Post content: ", postContent);
|
||||
|
||||
try {
|
||||
const rssMessage = new DRBEmbedBuilder()
|
||||
.setColor(0x0099FF)
|
||||
.setTitle(postTitle)
|
||||
.setURL(postLink)
|
||||
.addFields({ name: 'Source', value: postSourceLink, inline: true })
|
||||
.addFields({ name: 'Published', value: postPubDate, inline: true });
|
||||
|
||||
if (postImage) {
|
||||
log.DEBUG("Image from post:", postImage);
|
||||
rssMessage.setImage(postImage);
|
||||
}
|
||||
|
||||
postContent = postContent.slice(0, 4090).trim();
|
||||
if (postContent) rssMessage.setDescription(postContent);
|
||||
|
||||
const channelResponse = channel.send({ embeds: [rssMessage] });
|
||||
|
||||
log.DEBUG("Channel send response", channelResponse);
|
||||
|
||||
return channelResponse;
|
||||
} catch (err) {
|
||||
log.ERROR("Error sending message: ", postTitle, postId, postContent, postPubDate, err);
|
||||
return err;
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.modules.wrappers");
|
||||
import { checkIfNodeIsConnectedToVC, getNodeDiscordID, getNodeDiscordUsername } from '../../modules/socketServerWrappers.mjs';
|
||||
import { getAllDiscordIDs } from '../../modules/mongo-wrappers/mongoDiscordIDWrappers.mjs'
|
||||
|
||||
@@ -8,7 +10,7 @@ export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
|
||||
await Promise.all(openSockets.map(async openSocket => {
|
||||
openSocket = await nodeIo.sockets.sockets.get(openSocket);
|
||||
const connected = await checkIfNodeIsConnectedToVC(nodeIo, guildId, openSocket.node.nuid);
|
||||
console.log("Connected:", connected);
|
||||
log.INFO("Connected:", connected);
|
||||
if (connected) {
|
||||
const username = await getNodeDiscordUsername(openSocket, guildId);
|
||||
const discordID = await getNodeDiscordID(openSocket);
|
||||
@@ -33,8 +35,8 @@ export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
|
||||
]);
|
||||
|
||||
// Use the results of both promises here
|
||||
console.log("Available Discord IDs:", discordIDs);
|
||||
console.log("Online bots in the guild:", onlineBots);
|
||||
log.INFO("Available Discord IDs:", discordIDs);
|
||||
log.INFO("Online bots in the guild:", onlineBots);
|
||||
|
||||
// Filter any discordIDs that are not active
|
||||
const availableDiscordIDs = discordIDs.filter(discordID => discordID.active == true).filter(discordID => !onlineBots.some(bot => Number(bot.discord_id) == discordID.discord_id));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "addonManager");
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
@@ -20,9 +22,9 @@ export const loadAddons = async (nodeIo) => {
|
||||
if (addonConfig.enabled) {
|
||||
const addonIndexPath = path.join(addonsDir, addonDir.name, 'index.js');
|
||||
import(`file://${addonIndexPath}`).then(addonModule => {
|
||||
console.log("Loading addon: ", addonModule);
|
||||
log.DEBUG("Loading addon: ", addonModule);
|
||||
addonModule.initialize(nodeIo, addonConfig);
|
||||
console.log(`Addon ${addonConfig.name} loaded.`);
|
||||
log.DEBUG(`Addon ${addonConfig.name} loaded.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
71
modules/debugger.mjs
Normal file
71
modules/debugger.mjs
Normal file
@@ -0,0 +1,71 @@
|
||||
// Import necessary modules
|
||||
import debug from 'debug';
|
||||
import { config } from 'dotenv';
|
||||
config();
|
||||
import { promises as fs } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { inspect } from 'util';
|
||||
|
||||
/**
|
||||
* Write a given message to the log file
|
||||
* @param {any} logMessage The message to write to the log file
|
||||
* @param {string} appName The app name that created the log entry
|
||||
*/
|
||||
const writeToLog = async (logMessage, appName) => {
|
||||
const logLocation = join(process.env.LOG_LOCATION ?? `./logs/${appName}.log`);
|
||||
|
||||
// Ensure the log directory exists
|
||||
try {
|
||||
await fs.mkdir(dirname(logLocation), { recursive: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
// Ensure the message is a string
|
||||
logMessage = `${String(logMessage)}\n`;
|
||||
|
||||
// Write to the file
|
||||
try {
|
||||
await fs.writeFile(logLocation, logMessage, { encoding: 'utf-8', flag: 'a+' });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the different logging methods for a function
|
||||
* Namespace template = ("[app]:[fileName]:['INFO', 'WARNING', 'DEBUG', 'ERROR']")
|
||||
* @param {string} appName The name of the app to be used in the 'app' portion of the namespace
|
||||
* @param {string} fileName The name of the file calling the builder to be used in the 'fileName' portion of the namespace
|
||||
*/
|
||||
export class DebugBuilder {
|
||||
constructor(appName, fileName) {
|
||||
const buildLogger = (level) => (...messageParts) => {
|
||||
const logger = debug(`${appName}:${fileName}:${level}`);
|
||||
logger(messageParts);
|
||||
|
||||
const timeStamp = new Date().toLocaleString('en-US', { timeZone: 'America/New_York' });
|
||||
const message = `${timeStamp} - ${appName}:${fileName}:${level}\t-\t${messageParts.map(part => inspect(part)).join(' ')}`;
|
||||
|
||||
// Write to console
|
||||
console.log(message);
|
||||
|
||||
// Write to logfile
|
||||
writeToLog(message, appName);
|
||||
};
|
||||
|
||||
this.INFO = buildLogger('INFO');
|
||||
this.DEBUG = buildLogger('DEBUG');
|
||||
this.VERBOSE = buildLogger('VERBOSE');
|
||||
this.WARN = buildLogger('WARNING');
|
||||
this.ERROR = (...messageParts) => {
|
||||
buildLogger('ERROR')(...messageParts);
|
||||
|
||||
if (process.env.EXIT_ON_ERROR && process.env.EXIT_ON_ERROR > 0) {
|
||||
writeToLog("!--- EXITING ---!", appName);
|
||||
const exitDelay = parseInt(process.env.EXIT_ON_ERROR_DELAY, 10) || 0;
|
||||
setTimeout(() => process.exit(1), exitDelay);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "mongoDiscordIDWrappers");
|
||||
import { insertDocument, getDocuments, connectToDatabase } from "./mongoHandler.mjs";
|
||||
|
||||
const collectionName = 'discord-ids';
|
||||
@@ -8,7 +10,7 @@ export const createDiscordID = async (discordID) => {
|
||||
const insertedId = await insertDocument(collectionName, discordID);
|
||||
return insertedId;
|
||||
} catch (error) {
|
||||
console.error('Error creating Discord ID:', error);
|
||||
log.ERROR('Error creating Discord ID:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -19,7 +21,7 @@ export const getAllDiscordIDs = async () => {
|
||||
const discordIDs = await getDocuments(collectionName);
|
||||
return discordIDs;
|
||||
} catch (error) {
|
||||
console.error('Error getting all Discord IDs:', error);
|
||||
log.ERROR('Error getting all Discord IDs:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -37,7 +39,7 @@ export const getDiscordID = async (identifier) => {
|
||||
});
|
||||
return discordID;
|
||||
} catch (error) {
|
||||
console.error('Error getting Discord ID:', error);
|
||||
log.ERROR('Error getting Discord ID:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -56,10 +58,10 @@ export const updateDiscordID = async (identifier, updatedFields) => {
|
||||
{ discord_id: identifier }
|
||||
]
|
||||
}, { $set: updatedFields });
|
||||
console.log('Discord ID updated:', result.modifiedCount);
|
||||
log.INFO('Discord ID updated:', result.modifiedCount);
|
||||
return result.modifiedCount;
|
||||
} catch (error) {
|
||||
console.error('Error updating Discord ID:', error);
|
||||
log.ERROR('Error updating Discord ID:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -78,10 +80,10 @@ export const deleteDiscordID = async (identifier) => {
|
||||
{ discord_id: identifier }
|
||||
]
|
||||
});
|
||||
console.log('Discord ID deleted:', result.deletedCount);
|
||||
log.INFO('Discord ID deleted:', result.deletedCount);
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
console.error('Error deleting Discord ID:', error);
|
||||
log.ERROR('Error deleting Discord ID:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
|
||||
112
modules/mongo-wrappers/mongoFeedsWrappers.mjs
Normal file
112
modules/mongo-wrappers/mongoFeedsWrappers.mjs
Normal file
@@ -0,0 +1,112 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "mongoFeedsWrappers");
|
||||
import {
|
||||
insertDocument,
|
||||
getDocuments,
|
||||
getDocumentByField,
|
||||
updateDocumentByField,
|
||||
deleteDocumentByField,
|
||||
} from "./mongoHandler.mjs";
|
||||
|
||||
const feedCollectionName = 'feeds';
|
||||
const postCollectionName = 'posts';
|
||||
|
||||
// Wrapper for inserting a feed
|
||||
export const createFeed = async (feed) => {
|
||||
try {
|
||||
const insertedId = await insertDocument(feedCollectionName, feed);
|
||||
return insertedId;
|
||||
} catch (error) {
|
||||
log.ERROR('Error creating feed:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for retrieving all feeds
|
||||
export const getAllFeeds = async () => {
|
||||
try {
|
||||
const feeds = await getDocuments(feedCollectionName);
|
||||
return feeds;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting all feeds:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for retrieving a feed by link
|
||||
export const getFeedByLink = async (link) => {
|
||||
try {
|
||||
const feed = await getDocumentByField(feedCollectionName, 'link', link);
|
||||
return feed;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting feed by link:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for retrieving a feed by the title
|
||||
export const getFeedByTitle = async (title) => {
|
||||
try {
|
||||
const feed = await getDocumentByField(feedCollectionName, 'title', title);
|
||||
return feed;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting feed by link:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for updating a feed by link
|
||||
export const updateFeedByLink = async (link, updatedFields) => {
|
||||
try {
|
||||
const modifiedCount = await updateDocumentByField(feedCollectionName, 'link', link, updatedFields);
|
||||
return modifiedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error updating feed by link:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for deleting a feed by link
|
||||
export const deleteFeedByLink = async (link) => {
|
||||
try {
|
||||
const deletedCount = await deleteDocumentByField(feedCollectionName, 'link', link);
|
||||
return deletedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error deleting feed by link:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for deleting a feed by title
|
||||
export const deleteFeedByTitle = async (title) => {
|
||||
try {
|
||||
const deletedCount = await deleteDocumentByField(feedCollectionName, 'title', title);
|
||||
return deletedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error deleting feed by link:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for inserting a post
|
||||
export const createPost = async (post) => {
|
||||
try {
|
||||
const insertedId = await insertDocument(postCollectionName, post);
|
||||
return insertedId;
|
||||
} catch (error) {
|
||||
log.ERROR('Error creating post:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper for retrieving a post by postId
|
||||
export const getPostByPostId = async (postId) => {
|
||||
try {
|
||||
const post = await getDocumentByField(postCollectionName, 'postId', postId);
|
||||
return post;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting post by postId:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Import necessary modules
|
||||
import { MongoClient } from 'mongodb';
|
||||
import { DebugBuilder } from '../debugger.mjs';
|
||||
const log = new DebugBuilder("server", 'mongoHandler');
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
@@ -21,10 +23,11 @@ export const connectToDatabase = async () => {
|
||||
// Function to insert a document into the collection
|
||||
export const insertDocument = async (collectionName, document) => {
|
||||
const db = await connectToDatabase();
|
||||
log.DEBUG("Inserting document:", collectionName, document);
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.insertOne(document);
|
||||
console.log('Document inserted:', result.insertedId);
|
||||
log.DEBUG('Document inserted:', result.insertedId);
|
||||
return result.insertedId;
|
||||
} catch (error) {
|
||||
console.error('Error inserting document:', error);
|
||||
@@ -37,11 +40,12 @@ export const insertDocument = async (collectionName, document) => {
|
||||
|
||||
// Function to retrieve documents from the collection
|
||||
export const getDocuments = async (collectionName) => {
|
||||
log.DEBUG("Getting all documents:", collectionName);
|
||||
const db = await connectToDatabase();
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const documents = await collection.find({}).toArray();
|
||||
console.log('Documents retrieved:', documents);
|
||||
log.DEBUG('Documents retrieved:', documents);
|
||||
return documents;
|
||||
} catch (error) {
|
||||
console.error('Error retrieving documents:', error);
|
||||
@@ -54,6 +58,7 @@ export const getDocuments = async (collectionName) => {
|
||||
|
||||
// Function to retrieve a document by a specific field
|
||||
export const getDocumentByField = async (collectionName, field, value) => {
|
||||
log.DEBUG("Getting document by field:", collectionName, field, value);
|
||||
const db = await connectToDatabase();
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
@@ -69,11 +74,12 @@ export const getDocumentByField = async (collectionName, field, value) => {
|
||||
|
||||
// Function to update a document by a specific field
|
||||
export const updateDocumentByField = async (collectionName, field, value, updatedFields) => {
|
||||
log.DEBUG("Update document by field:", collectionName, field, value, updatedFields);
|
||||
const db = await connectToDatabase();
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.updateOne({ [field]: value }, { $set: updatedFields });
|
||||
console.log('Document updated:', result.modifiedCount);
|
||||
log.DEBUG('Document updated:', result.modifiedCount);
|
||||
return result.modifiedCount;
|
||||
} catch (error) {
|
||||
console.error('Error updating document:', error);
|
||||
@@ -85,11 +91,12 @@ export const updateDocumentByField = async (collectionName, field, value, update
|
||||
|
||||
// Function to delete a document by a specific field
|
||||
export const deleteDocumentByField = async (collectionName, field, value) => {
|
||||
log.DEBUG("Delete document by field:", collectionName, field, value);
|
||||
const db = await connectToDatabase();
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.deleteOne({ [field]: value });
|
||||
console.log('Document deleted:', result.deletedCount);
|
||||
log.DEBUG('Document deleted:', result.deletedCount);
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
console.error('Error deleting document:', error);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "mongoNodesWrappers");
|
||||
import { insertDocument, getDocuments, connectToDatabase } from "./mongoHandler.mjs";
|
||||
|
||||
const collectionName = 'nodes';
|
||||
@@ -8,7 +10,7 @@ export const createNode = async (node) => {
|
||||
const insertedId = await insertDocument(collectionName, node);
|
||||
return insertedId;
|
||||
} catch (error) {
|
||||
console.error('Error creating node:', error);
|
||||
log.ERROR('Error creating node:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -19,7 +21,7 @@ export const getAllNodes = async () => {
|
||||
const nodes = await getDocuments(collectionName);
|
||||
return nodes;
|
||||
} catch (error) {
|
||||
console.error('Error getting all nodes:', error);
|
||||
log.ERROR('Error getting all nodes:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -32,7 +34,7 @@ export const getNodeByNuid = async (nuid) => {
|
||||
const node = await collection.findOne({ nuid });
|
||||
return node;
|
||||
} catch (error) {
|
||||
console.error('Error getting node by NUID:', error);
|
||||
log.ERROR('Error getting node by NUID:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -46,10 +48,10 @@ export const updateNodeByNuid = async (nuid, updatedFields) => {
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.updateOne({ nuid }, { $set: updatedFields });
|
||||
console.log('Node updated:', result.modifiedCount);
|
||||
log.INFO('Node updated:', result.modifiedCount);
|
||||
return result.modifiedCount;
|
||||
} catch (error) {
|
||||
console.error('Error updating node by NUID:', error);
|
||||
log.ERROR('Error updating node by NUID:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -63,10 +65,10 @@ export const deleteNodeByNuid = async (nuid) => {
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.deleteOne({ nuid });
|
||||
console.log('Node deleted:', result.deletedCount);
|
||||
log.INFO('Node deleted:', result.deletedCount);
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
console.error('Error deleting node by NUID:', error);
|
||||
log.ERROR('Error deleting node by NUID:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "mongoSystemsWrappers");
|
||||
import { insertDocument, getDocuments, connectToDatabase } from "./mongoHandler.mjs";
|
||||
|
||||
const collectionName = 'radio-systems';
|
||||
@@ -21,7 +23,7 @@ export const createSystem = async (name, system, nuid) => {
|
||||
const insertedId = await insertDocument(collectionName, system);
|
||||
return insertedId;
|
||||
} catch (error) {
|
||||
console.error('Error creating system:', error);
|
||||
log.ERROR('Error creating system:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -32,7 +34,7 @@ export const getAllSystems = async () => {
|
||||
const systems = await getDocuments(collectionName);
|
||||
return systems;
|
||||
} catch (error) {
|
||||
console.error('Error getting all systems:', error);
|
||||
log.ERROR('Error getting all systems:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -45,7 +47,7 @@ export const getSystemByName = async (name) => {
|
||||
const system = await collection.findOne({ name });
|
||||
return system;
|
||||
} catch (error) {
|
||||
console.error('Error getting system by name:', error);
|
||||
log.ERROR('Error getting system by name:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -65,7 +67,7 @@ export const getSystemsByNuid = async (nuid) => {
|
||||
|
||||
return systems;
|
||||
} catch (error) {
|
||||
console.error('Error finding entries:', error);
|
||||
log.ERROR('Error finding entries:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -82,10 +84,10 @@ export const updateSystemByName = async (name, updatedSystem) => {
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.updateOne({ name }, { $set: updatedSystem });
|
||||
console.log('System updated:', result.modifiedCount);
|
||||
log.INFO('System updated:', result.modifiedCount);
|
||||
return result.modifiedCount;
|
||||
} catch (error) {
|
||||
console.error('Error updating system by name:', error);
|
||||
log.ERROR('Error updating system by name:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -99,10 +101,10 @@ export const deleteSystemByName = async (name) => {
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.deleteOne({ name });
|
||||
console.log('System deleted:', result.deletedCount);
|
||||
log.INFO('System deleted:', result.deletedCount);
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
console.error('Error deleting system by name:', error);
|
||||
log.ERROR('Error deleting system by name:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "socketServer");
|
||||
import express from 'express';
|
||||
import { createServer } from 'node:http';
|
||||
import { Server } from 'socket.io';
|
||||
@@ -15,7 +17,7 @@ app.get('/', (req, res) => {
|
||||
});
|
||||
|
||||
nodeIo.on('connection', (socket) => {
|
||||
console.log('a user connected', socket.id);
|
||||
log.INFO('a user connected', socket.id);
|
||||
|
||||
socket.on('node-login', async (data) => {
|
||||
await nodeLoginWrapper(data, socket);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "socketServerWrappers");
|
||||
import { createNode, getNodeByNuid, updateNodeByNuid } from "./mongo-wrappers/mongoNodesWrappers.mjs"
|
||||
import { createSystem, getSystemByName, updateSystemByName, getSystemsByNuid, deleteSystemByName } from "./mongo-wrappers/mongoSystemsWrappers.mjs"
|
||||
|
||||
@@ -22,17 +24,16 @@ const sendNodeCommand = async (socket, command, data) => {
|
||||
* @returns {any}
|
||||
*/
|
||||
export const nodeLoginWrapper = async (data, socket) => {
|
||||
console.log(`Login requested from node: ${data.nuid}`, data);
|
||||
log.INFO(`Login requested from node: ${data.nuid}`, data);
|
||||
// Check to see if node exists
|
||||
var node = await getNodeByNuid(data.nuid);
|
||||
console.log("After grabbing", node);
|
||||
var node = await getNodeByNuid(data.nuid);
|
||||
if (!node) {
|
||||
const insertedId = await createNode(data);
|
||||
console.log("Added new node to the database:", insertedId);
|
||||
log.DEBUG("Added new node to the database:", insertedId);
|
||||
} else {
|
||||
// Check for updates
|
||||
const updatedNode = await updateNodeByNuid(data.nuid, data)
|
||||
console.log("Updated node:", updatedNode);
|
||||
log.DEBUG("Updated node:", updatedNode);
|
||||
}
|
||||
|
||||
node = await getNodeByNuid(data.nuid);
|
||||
@@ -59,7 +60,7 @@ export const nodeDisconnectWrapper = async (socketId) => {
|
||||
* @returns {any}
|
||||
*/
|
||||
export const nodeUpdateWrapper = async (nodeData) => {
|
||||
console.log("Data update sent by node: ", nodeData);
|
||||
log.DEBUG("Data update sent by node: ", nodeData);
|
||||
const updateResults = await updateNodeByNuid(nodeData.nuid, nodeData);
|
||||
return;
|
||||
}
|
||||
@@ -70,10 +71,10 @@ export const nodeUpdateWrapper = async (nodeData) => {
|
||||
* @param {object} nearbySystems The nearby systems object passed from the node to be updated
|
||||
*/
|
||||
export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
|
||||
console.log("System updates sent by node: ", nuid, nearbySystems);
|
||||
log.DEBUG("System updates sent by node: ", nuid, nearbySystems);
|
||||
// Check to see if the node removed any systems
|
||||
const existingSystems = await getSystemsByNuid(nuid);
|
||||
console.log("Existing systems:", existingSystems);
|
||||
log.DEBUG("Existing systems:", existingSystems);
|
||||
if (existingSystems !== nearbySystems) {
|
||||
for (const existingSystem of existingSystems) {
|
||||
if (existingSystem.name in nearbySystems) {
|
||||
@@ -81,17 +82,17 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log("System exists that was not given by node", existingSystem);
|
||||
log.DEBUG("System exists that was not given by node", existingSystem);
|
||||
// Check if this node was the only node on this system
|
||||
if (existingSystem.nodes.filter(node => node !== nuid).length === 0) {
|
||||
// Remove the system if so
|
||||
console.log("Given node was the only node on this system, removing the system...");
|
||||
log.INFO("Given node was the only node on this system, removing the system...");
|
||||
await deleteSystemByName(existingSystem.name);
|
||||
} else {
|
||||
// Remove the node from the array if there are other nodes with this system
|
||||
console.log("Other nodes found on this system, removing the given NUID");
|
||||
log.INFO("Other nodes found on this system, removing the given NUID");
|
||||
existingSystem.nodes = existingSystem.nodes.filter(node => node !== nuid);
|
||||
console.log(existingSystem);
|
||||
log.DEBUG(existingSystem);
|
||||
await updateSystemByName(existingSystem.name, existingSystem);
|
||||
}
|
||||
}
|
||||
@@ -111,7 +112,7 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
|
||||
existingSystem.nodes.push(nuid);
|
||||
// Update the system with the added node
|
||||
const updateResults = await updateSystemByName(nearbySystem, existingSystem);
|
||||
if (updateResults) console.log("System updated", nearbySystem);
|
||||
if (updateResults) log.INFO("System updated", nearbySystem);
|
||||
}
|
||||
} else {
|
||||
// The systems are not the same
|
||||
@@ -125,13 +126,13 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
|
||||
|
||||
// Update the system with the added node
|
||||
const updateResults = await updateSystemByName(nearbySystem, nearbySystems[nearbySystem]);
|
||||
if (updateResults) console.log("System updated", nearbySystem);
|
||||
if (updateResults) log.INFO("System updated", nearbySystem);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Create a new system
|
||||
const newSystem = await createSystem(nearbySystem, nearbySystems[nearbySystem], nuid);
|
||||
console.log("New system created", nearbySystem, newSystem);
|
||||
log.INFO("New system created", nearbySystem, newSystem);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -146,7 +147,7 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
|
||||
export const getSocketIdByNuid = async (nodeIo, nuid) => {
|
||||
const openSockets = await nodeIo.allSockets();
|
||||
for (const openSocketId of openSockets) {
|
||||
console.log(openSockets)
|
||||
log.DEBUG(openSockets)
|
||||
const openSocket = await nodeIo.sockets.sockets.get(openSocketId);
|
||||
if (openSocket.node.nuid == nuid)
|
||||
return openSocket;
|
||||
@@ -171,10 +172,10 @@ export const getAllSocketsConnectedToVC = async (nodeIo, guildId) => {
|
||||
await new Promise((res) => {
|
||||
openSocket.emit('node-check-connected-status', guildId, (status) => {
|
||||
if (status) {
|
||||
console.log("Socket is connected to VC:", openSocket.node.name, status);
|
||||
log.INFO("Socket is connected to VC:", openSocket.node.name, status);
|
||||
socketsConnectedToVC.push(openSocket);
|
||||
} else {
|
||||
console.log("Socket is NOT connected to VC:", openSocket.node.name);
|
||||
log.INFO("Socket is NOT connected to VC:", openSocket.node.name);
|
||||
}
|
||||
res();
|
||||
})
|
||||
@@ -196,10 +197,10 @@ export const checkIfNodeHasOpenDiscordClient = async (openSocket) => {
|
||||
await new Promise((res) => {
|
||||
openSocket.emit('node-check-discord-open-client', (status) => {
|
||||
if (status) {
|
||||
console.log("Socket has an open discord client:", openSocket.node.name, status);
|
||||
log.INFO("Socket has an open discord client:", openSocket.node.name, status);
|
||||
hasOpenDiscordClient = true;
|
||||
} else {
|
||||
console.log("Socket does NOT have an open discord client:", openSocket.node.name);
|
||||
log.INFO("Socket does NOT have an open discord client:", openSocket.node.name);
|
||||
}
|
||||
res();
|
||||
})
|
||||
@@ -217,10 +218,10 @@ export const getNodeCurrentListeningSystem = async (openSocket) => {
|
||||
await new Promise((res) => {
|
||||
openSocket.emit('node-check-current-system', (system) => {
|
||||
if (system) {
|
||||
console.log("Socket is listening to system:", openSocket.node.name, system);
|
||||
log.INFO("Socket is listening to system:", openSocket.node.name, system);
|
||||
currentSystem = system;
|
||||
} else {
|
||||
console.log("Socket is not currently listening to a system:", openSocket.node.name);
|
||||
log.INFO("Socket is not currently listening to a system:", openSocket.node.name);
|
||||
}
|
||||
res();
|
||||
})
|
||||
@@ -307,9 +308,9 @@ export const requestBotLeaveServer = async (socket, guildId) => {
|
||||
export const requestNodeUpdate = async (socket) => {
|
||||
await sendNodeCommand(socket, 'node-update', (status) => {
|
||||
if (status) {
|
||||
console.log("Node is out of date, updating now", socket.node.name);
|
||||
log.INFO("Node is out of date, updating now", socket.node.name);
|
||||
} else {
|
||||
console.log("Node is up to date", socket.node.name);
|
||||
log.INFO("Node is up to date", socket.node.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
174
package-lock.json
generated
174
package-lock.json
generated
@@ -14,7 +14,10 @@
|
||||
"express": "^4.18.2",
|
||||
"mongodb": "^6.3.0",
|
||||
"morgan": "^1.10.0",
|
||||
"socket.io": "^4.7.2"
|
||||
"node-html-parser": "^6.1.13",
|
||||
"rss-parser": "^3.13.0",
|
||||
"socket.io": "^4.7.2",
|
||||
"user-agents": "^1.1.208"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^5.1.0",
|
||||
@@ -385,6 +388,11 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
@@ -606,6 +614,32 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"domutils": "^3.0.1",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@@ -723,6 +757,57 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
|
||||
@@ -846,6 +931,17 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
@@ -1155,7 +1251,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
@@ -1313,6 +1408,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||
},
|
||||
"node_modules/lodash.snakecase": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
|
||||
@@ -1573,6 +1673,15 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-parser": {
|
||||
"version": "6.1.13",
|
||||
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz",
|
||||
"integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==",
|
||||
"dependencies": {
|
||||
"css-select": "^5.1.0",
|
||||
"he": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
@@ -1582,6 +1691,17 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -1785,6 +1905,23 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rss-parser": {
|
||||
"version": "3.13.0",
|
||||
"resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.13.0.tgz",
|
||||
"integrity": "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==",
|
||||
"dependencies": {
|
||||
"entities": "^2.0.3",
|
||||
"xml2js": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rss-parser/node_modules/entities": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@@ -1809,6 +1946,11 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
||||
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
@@ -2155,6 +2297,14 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/user-agents": {
|
||||
"version": "1.1.208",
|
||||
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.1.208.tgz",
|
||||
"integrity": "sha512-OMDd2qJF3g9HVEzMGv9Zi8Fp9hF2YqfhDZRacbn/x4YknW2YGdhqrLW8MCiWgUEDB3eDLf7IvuzXGoOwWhnaBw==",
|
||||
"dependencies": {
|
||||
"lodash.clonedeep": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
@@ -2240,6 +2390,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
|
||||
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
"express": "^4.18.2",
|
||||
"mongodb": "^6.3.0",
|
||||
"morgan": "^1.10.0",
|
||||
"socket.io": "^4.7.2"
|
||||
"node-html-parser": "^6.1.13",
|
||||
"rss-parser": "^3.13.0",
|
||||
"socket.io": "^4.7.2",
|
||||
"user-agents": "^1.1.208"
|
||||
}
|
||||
}
|
||||
|
||||
93
rss-manager/feedHandler.mjs
Normal file
93
rss-manager/feedHandler.mjs
Normal file
@@ -0,0 +1,93 @@
|
||||
import { getAllFeeds, deleteFeedByLink, createPost, getPostByPostId } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
||||
import crypto from 'crypto';
|
||||
import { sendPost } from '../discordBot/modules/rssWrappers.mjs';
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
import { removeSource } from './sourceManager.mjs'
|
||||
import UserAgent from "user-agents";
|
||||
import Parser from 'rss-parser';
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
// Initialize the User-Agent string
|
||||
process.env.USER_AGENT_STRING = new UserAgent({ platform: 'Win32' }).toString();
|
||||
|
||||
const parser = new Parser({
|
||||
headers: {
|
||||
'User-Agent': process.env.USER_AGENT_STRING,
|
||||
"Accept": "application/rss+xml,application/xhtml+xml,application/xml"
|
||||
}
|
||||
});
|
||||
|
||||
const log = new DebugBuilder("server", "feedHandler");
|
||||
|
||||
export const returnHash = (...stringsIncluded) => {
|
||||
return crypto.createHash('sha1').update(stringsIncluded.join("-<<??//\\\\??>>-")).digest("base64");
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the active RSS feeds and send any new posts to their discord channels
|
||||
* @param {any} client The discord client to send posts with
|
||||
* @returns {any}
|
||||
*/
|
||||
export const updateFeeds = async (client) => {
|
||||
if (!client) throw new Error("Client object not passed");
|
||||
|
||||
try {
|
||||
const records = await getAllFeeds();
|
||||
|
||||
const sourcePromiseArray = records.map(async (source) => {
|
||||
log.DEBUG('Processing source:', source.title);
|
||||
|
||||
try {
|
||||
const parsedFeed = await parser.parseURL(source.link);
|
||||
|
||||
if (parsedFeed?.items) {
|
||||
await Promise.all(parsedFeed.items.reverse().map(async (post) => {
|
||||
log.DEBUG("Processing post:", post.title);
|
||||
|
||||
if (!post.title || !post.link) throw new Error("Missing title or link in the post");
|
||||
if (!post.content && !post['content:encoded']) log.WARN("No content for post:", post.title);
|
||||
|
||||
post.postId = post.postId ?? post.guid ?? post.id ?? returnHash(post.title, post.link, post.pubDate ?? Date.now());
|
||||
|
||||
const existingRecord = await getPostByPostId(post.postId);
|
||||
if (!existingRecord) {
|
||||
const channel = client.channels.cache.get(source.channel_id);
|
||||
const sendResults = await sendPost(post, source, channel);
|
||||
if (!sendResults) throw new Error("Failed to send post");
|
||||
|
||||
log.DEBUG("Saving post to database:", post.title, source.channel_id);
|
||||
|
||||
const postToSave = {
|
||||
title: post.title,
|
||||
link: post.link,
|
||||
pubDate: post.pubDate,
|
||||
author: post.author,
|
||||
contentSnippet: post.contentSnippet,
|
||||
id: post.id,
|
||||
isoDate: post.isoDate,
|
||||
postId: post.postId
|
||||
};
|
||||
|
||||
await createPost(postToSave);
|
||||
log.DEBUG("Post saved:", postToSave);
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
await deleteFeedByLink(source.link);
|
||||
}
|
||||
} catch (err) {
|
||||
log.ERROR("Error processing source:", source.title, err);
|
||||
await removeSource(source.link);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(sourcePromiseArray);
|
||||
log.DEBUG("All sources processed");
|
||||
} catch (error) {
|
||||
log.ERROR("Error updating feeds:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
49
rss-manager/rssController.mjs
Normal file
49
rss-manager/rssController.mjs
Normal file
@@ -0,0 +1,49 @@
|
||||
// Will handle updating feeds in all channels
|
||||
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
import { updateFeeds } from "./feedHandler.mjs";
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
const log = new DebugBuilder("server", "rssController");
|
||||
|
||||
const refreshInterval = parseInt(process.env.RSS_REFRESH_INTERVAL) || 300000;
|
||||
|
||||
export class RSSController {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
this.intervalId = null;
|
||||
}
|
||||
|
||||
async start() {
|
||||
try {
|
||||
log.INFO("Starting RSS Controller");
|
||||
// Get initial feeds before starting the interval loop
|
||||
await this.collectLatestPosts();
|
||||
|
||||
// Start the interval loop for updating feeds
|
||||
this.intervalId = setInterval(async () => {
|
||||
await this.collectLatestPosts();
|
||||
}, refreshInterval);
|
||||
|
||||
} catch (error) {
|
||||
log.ERROR(`Failed to start RSS Controller: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
log.INFO("RSS Controller stopped");
|
||||
}
|
||||
}
|
||||
|
||||
async collectLatestPosts() {
|
||||
try {
|
||||
log.INFO("Updating sources");
|
||||
await updateFeeds(this.client);
|
||||
} catch (error) {
|
||||
log.ERROR(`Error updating feeds: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
rss-manager/sourceManager.mjs
Normal file
75
rss-manager/sourceManager.mjs
Normal file
@@ -0,0 +1,75 @@
|
||||
import { createFeed, getFeedByLink, deleteFeedByLink } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
||||
|
||||
class SourceManager {
|
||||
constructor(sourceFailureLimit) {
|
||||
this.sourceFailureLimit = sourceFailureLimit;
|
||||
this.runningSourcesToRemove = {};
|
||||
}
|
||||
|
||||
async removeSource(sourceURL) {
|
||||
log.INFO(`Removing source: ${sourceURL}`);
|
||||
|
||||
const currentTime = Date.now();
|
||||
const sourceData = this.runningSourcesToRemove[sourceURL];
|
||||
|
||||
if (!sourceData) {
|
||||
this.runningSourcesToRemove[sourceURL] = { count: 1, timestamp: currentTime, ignoredAttempts: 0 };
|
||||
return;
|
||||
}
|
||||
|
||||
const elapsedTimeSinceLastAttempt = currentTime - sourceData.timestamp;
|
||||
const waitTime = sourceData.count * 30000;
|
||||
|
||||
if (elapsedTimeSinceLastAttempt <= waitTime) {
|
||||
sourceData.ignoredAttempts += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sourceData.count < this.sourceFailureLimit) {
|
||||
sourceData.count += 1;
|
||||
sourceData.timestamp = currentTime;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const record = await getFeedByLink(sourceURL);
|
||||
if (!record) {
|
||||
log.ERROR(`Source not found in storage: ${sourceURL}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const results = await deleteFeedByLink(sourceURL);
|
||||
if (!results) {
|
||||
log.WARN(`Failed to remove source: ${sourceURL}`);
|
||||
return;
|
||||
}
|
||||
|
||||
log.DEBUG(`Source removed after exceeding failure limit: ${sourceURL}`);
|
||||
// Optionally, clean up the entry from runningSourcesToRemove
|
||||
delete this.runningSourcesToRemove[sourceURL];
|
||||
} catch (err) {
|
||||
log.ERROR(`Error removing source from storage: ${sourceURL}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
async addSource(title, link, category, guildId, channelId, callback) {
|
||||
try {
|
||||
const feed = { title, link, category, guild_id: guildId, channel_id: channelId };
|
||||
const record = await createFeed(feed);
|
||||
log.DEBUG("Source added:", record);
|
||||
if (callback) callback(null, record);
|
||||
} catch (err) {
|
||||
log.ERROR("Error adding source:", err);
|
||||
if (callback) callback(err, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create a default instance of SourceManager
|
||||
const defaultSourceManager = new SourceManager();
|
||||
|
||||
// Export the class and default instance methods
|
||||
export { SourceManager };
|
||||
export const addSource = defaultSourceManager.addSource.bind(defaultSourceManager);
|
||||
export const removeSource = defaultSourceManager.removeSource.bind(defaultSourceManager);
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DebugBuilder } from "./modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "server");
|
||||
import { nodeIo, app, server } from './modules/socketServer.mjs';
|
||||
import { loadAddons } from './modules/addonManager.mjs';
|
||||
import { serverClient, addEnabledEventListeners } from './discordBot/discordBot.mjs';
|
||||
@@ -7,7 +9,7 @@ dotenv.config()
|
||||
|
||||
// Startup the node server
|
||||
server.listen(process.env.SERVER_PORT || 3000, () => {
|
||||
console.log(`server running at http://localhost:${process.env.SERVER_PORT}`);
|
||||
log.INFO(`server running at http://localhost:${process.env.SERVER_PORT}`);
|
||||
});
|
||||
|
||||
// Add objects to the others
|
||||
|
||||
Reference in New Issue
Block a user