Compare commits

37 Commits

Author SHA1 Message Date
Logan Cusano
dfb2765f39 Add a new key for feeds to set active status 2024-09-21 11:36:02 -04:00
Logan Cusano
33680209ba Added missing packages to dev
Some checks failed
release-tag / release-image (push) Has been cancelled
Update Wiki from JSDoc / update-wiki (push) Failing after 12s
Lint JavaScript/Node.js / lint-js (push) Successful in 12s
DRB Tests / drb_mocha_tests (push) Successful in 31s
2024-08-18 01:29:23 -04:00
Logan Cusano
f70ea4229a Trying node dev #25
Some checks failed
DRB Tests / drb_mocha_tests (push) Has been cancelled
Lint JavaScript/Node.js / lint-js (push) Has been cancelled
Update Wiki from JSDoc / update-wiki (push) Has been cancelled
release-tag / release-image (push) Successful in 2m4s
2024-08-18 01:27:01 -04:00
Logan Cusano
acadc9faee Another attempt #25
Some checks failed
release-tag / release-image (push) Successful in 2m1s
Update Wiki from JSDoc / update-wiki (push) Failing after 11s
Lint JavaScript/Node.js / lint-js (push) Successful in 11s
DRB Tests / drb_mocha_tests (push) Successful in 29s
2024-08-18 01:23:14 -04:00
Logan Cusano
f725ec88f9 Sepcify dir for #25
All checks were successful
release-tag / release-image (push) Successful in 2m1s
Update Wiki from JSDoc / update-wiki (push) Successful in 12s
Lint JavaScript/Node.js / lint-js (push) Successful in 11s
DRB Tests / drb_mocha_tests (push) Successful in 27s
2024-08-18 01:05:05 -04:00
Logan Cusano
8d34b93527 Hopefully fix for action issue #25
All checks were successful
release-tag / release-image (push) Successful in 2m1s
Update Wiki from JSDoc / update-wiki (push) Successful in 12s
Lint JavaScript/Node.js / lint-js (push) Successful in 11s
DRB Tests / drb_mocha_tests (push) Successful in 28s
2024-08-18 01:00:38 -04:00
Logan Cusano
918cfe06e4 More debug for #25
All checks were successful
release-tag / release-image (push) Successful in 2m4s
Update Wiki from JSDoc / update-wiki (push) Successful in 13s
Lint JavaScript/Node.js / lint-js (push) Successful in 11s
DRB Tests / drb_mocha_tests (push) Successful in 26s
2024-08-18 00:43:44 -04:00
Logan Cusano
e2f276e65b #25
All checks were successful
release-tag / release-image (push) Successful in 2m4s
Update Wiki from JSDoc / update-wiki (push) Successful in 14s
Lint JavaScript/Node.js / lint-js (push) Successful in 11s
DRB Tests / drb_mocha_tests (push) Successful in 27s
2024-08-18 00:36:02 -04:00
52353ec1fb Merge pull request '#22 - Add Automated Docs' (#24) from automated-docs-#22 into main
All checks were successful
release-tag / release-image (push) Successful in 2m2s
Update Wiki from JSDoc / update-wiki (push) Successful in 13s
Lint JavaScript/Node.js / lint-js (push) Successful in 11s
DRB Tests / drb_mocha_tests (push) Successful in 26s
Reviewed-on: #24
2024-08-17 19:48:46 -04:00
Logan Cusano
750877db1a Remove docs on PRs
All checks were successful
Lint JavaScript/Node.js / lint-js (pull_request) Successful in 11s
DRB Tests / drb_mocha_tests (pull_request) Successful in 28s
- Update wiki step
2024-08-17 19:46:15 -04:00
Logan Cusano
a18337d0f8 Updated 'home' -> 'Home'
All checks were successful
Update Wiki from JSDoc / update-wiki (pull_request) Successful in 12s
Lint JavaScript/Node.js / lint-js (pull_request) Successful in 11s
DRB Tests / drb_mocha_tests (pull_request) Successful in 29s
2024-08-17 19:40:18 -04:00
Logan Cusano
6f45a60030 Update action to force copy
Some checks failed
Update Wiki from JSDoc / update-wiki (pull_request) Failing after 11s
Lint JavaScript/Node.js / lint-js (pull_request) Successful in 10s
DRB Tests / drb_mocha_tests (pull_request) Successful in 26s
2024-08-17 19:33:06 -04:00
Logan Cusano
1fb4728b0a Revert incomplete changes to GPT handler
Some checks failed
Update Wiki from JSDoc / update-wiki (pull_request) Failing after 11s
Lint JavaScript/Node.js / lint-js (pull_request) Successful in 11s
DRB Tests / drb_mocha_tests (pull_request) Successful in 28s
2024-08-17 19:28:44 -04:00
Logan Cusano
ebf48c7618 Linting 2024-08-17 19:27:48 -04:00
Logan Cusano
11b3504f28 Update docs for test
Some checks failed
Update Wiki from JSDoc / update-wiki (pull_request) Successful in 12s
Lint JavaScript/Node.js / lint-js (pull_request) Failing after 10s
DRB Tests / drb_mocha_tests (pull_request) Successful in 29s
2024-08-17 19:15:02 -04:00
Logan Cusano
14171a9c13 Fix homepage name 2024-08-17 19:13:44 -04:00
Logan Cusano
47a03898cc Update action and npm script
Some checks failed
Update Wiki from JSDoc / update-wiki (pull_request) Successful in 12s
Lint JavaScript/Node.js / lint-js (pull_request) Failing after 10s
DRB Tests / drb_mocha_tests (pull_request) Successful in 26s
2024-08-17 19:10:56 -04:00
Logan Cusano
2551498d2e update npx -> npm
Some checks failed
Update Wiki from JSDoc / update-wiki (pull_request) Failing after 10s
Lint JavaScript/Node.js / lint-js (pull_request) Failing after 11s
DRB Tests / drb_mocha_tests (pull_request) Successful in 29s
2024-08-17 19:07:23 -04:00
Logan Cusano
277f7d176a Update docs to work
Some checks failed
Update Wiki from JSDoc / update-wiki (pull_request) Has been cancelled
Lint JavaScript/Node.js / lint-js (pull_request) Has been cancelled
DRB Tests / drb_mocha_tests (pull_request) Has been cancelled
2024-08-17 19:05:38 -04:00
Logan Cusano
066404dd10 Updated dir structure to put the actual source code in the general /src dir
Some checks failed
Update Wiki from JSDoc / update-wiki (pull_request) Has been cancelled
Lint JavaScript/Node.js / lint-js (pull_request) Failing after 11s
DRB Tests / drb_mocha_tests (pull_request) Has been cancelled
2024-08-17 18:44:18 -04:00
Logan Cusano
8f2891f5d8 Update docs to use npm script for consistency 2024-08-17 18:43:39 -04:00
Logan Cusano
9f2ed48caf Added better docs
Some checks failed
Lint JavaScript/Node.js / lint-js (pull_request) Failing after 14s
DRB Tests / drb_mocha_tests (pull_request) Successful in 34s
Update Wiki from JSDoc / update-wiki (pull_request) Successful in 15s
2024-08-17 18:11:38 -04:00
Logan Cusano
3f42d60efc Add docs to ignored file for this repo 2024-08-17 18:11:14 -04:00
Logan Cusano
cf9f48dfa7 Fix jsdoc bug
Some checks failed
Update Wiki from JSDoc / update-wiki (pull_request) Failing after 10s
Lint JavaScript/Node.js / lint-js (pull_request) Failing after 10s
DRB Tests / drb_mocha_tests (pull_request) Successful in 25s
- Was importing class within the jsdoc
2024-08-17 17:56:25 -04:00
Logan Cusano
bde7dbce45 add pr to docs for testing
Some checks failed
Update Wiki from JSDoc / update-wiki (pull_request) Failing after 10s
Lint JavaScript/Node.js / lint-js (pull_request) Successful in 11s
DRB Tests / drb_mocha_tests (pull_request) Successful in 25s
2024-08-17 17:53:32 -04:00
Logan Cusano
a8e96ab5dc Update JSDoc config
Some checks failed
release-tag / release-image (push) Successful in 1m57s
Update Wiki from JSDoc / update-wiki (push) Failing after 10s
Lint JavaScript/Node.js / lint-js (push) Successful in 10s
DRB Tests / drb_mocha_tests (push) Successful in 25s
2024-08-17 17:51:18 -04:00
Logan Cusano
628fd80710 Update lint and tests to run on PRs 2024-08-17 17:49:12 -04:00
Logan Cusano
edb7ec41b1 Add draft doc generator
Some checks failed
release-tag / release-image (push) Successful in 2m0s
Update Wiki from JSDoc / update-wiki (push) Failing after 10s
Lint JavaScript/Node.js / lint-js (push) Successful in 10s
DRB Tests / drb_mocha_tests (push) Successful in 25s
2024-08-17 17:46:34 -04:00
Logan Cusano
0be5b059da #19 fix guild specific configs
All checks were successful
release-tag / release-image (push) Successful in 2m24s
Lint JavaScript/Node.js / lint-js (push) Successful in 12s
DRB Tests / drb_mocha_tests (push) Successful in 34s
- Fixed the guild key
- forced guild ID to be a number
2024-08-17 17:35:20 -04:00
Logan Cusano
46989942d8 Updated linting config #21
- Now works
- Ran linting on the repo
2024-08-17 17:02:05 -04:00
Logan Cusano
ab929489b0 Attempt to improve linting
All checks were successful
release-tag / release-image (push) Successful in 2m16s
Lint JavaScript/Node.js / lint-js (push) Successful in 10s
DRB Tests / drb_mocha_tests (push) Successful in 28s
- Trying to get auto linting to work; Doesn't seem to work
2024-08-11 20:31:44 -04:00
Logan Cusano
cf49ac414a Linting 2024-08-11 20:14:36 -04:00
Logan Cusano
94374b4d45 Implement Dynamic Presence #19
## Added Dynamic Presence to Functions
- Added default to startup
- Added to RSS manager
- Added to interaction create event
- Added to message create function

## Related Work #15
### LinkCop
- Updated with new regex string and logic approved and restricted channels
- Implemented new config storage
### Guild Member Add (event)
- Implemented new config storage for welcome channel
### Message Create (event)
- Implemented new config storage for ignored channel IDs
- Improved the logic for gpt interactions to reset presence
### Mongo Config Wrappers
- Updated logic in order to handle different data types the same way
- Updated set functions to wrap the value in the key
- Updated get functions to return the keyyed value ie `config[key]`
2024-08-11 20:13:57 -04:00
Logan Cusano
d18ffd4c11 Update Presence Manager #15
- Added convert functions to convert strings to activities and statuses
- Updated status and activity to use the discord.js consts from convert functions
- Reset default will get and use the default presence from the DB
- Reset default will set default presence in DB if not set
- Reset default will now use the same `this.setPresence()` function to limit variation
2024-08-11 18:41:42 -04:00
Logan Cusano
a56c19a466 Added mongo wrapper for configs #19
- Can handle discord guid specific configs and global configs
2024-08-11 18:38:21 -04:00
Logan Cusano
f4886f9fc5 Improve Mongo Handler
- Added delete/update/get with multiple fields
- Updated single field handlers to use multi field handlers to limit variation
- Added upsert function to wrap the update function with `upsert: true`
2024-08-11 18:34:55 -04:00
Logan Cusano
e324ee1738 Update build action versions 2024-08-11 16:04:25 -04:00
50 changed files with 2907 additions and 135 deletions

View File

@@ -16,13 +16,13 @@ jobs:
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
with: # replace it with your local IP
config-inline: |
[registry."git.vpn.cusano.net"]
@@ -30,7 +30,7 @@ jobs:
insecure = false
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: git.vpn.cusano.net # replace it with your local IP
username: ${{ secrets.DOCKER_USERNAME }}

View File

@@ -0,0 +1,56 @@
name: Update Wiki from JSDoc
on:
push:
branches:
- main
# schedule:
# - cron: '0 0 * * 1' # Every Monday at midnight (UTC)
env:
NODE_ENV: development
jobs:
update-wiki:
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
run: npm install
- name: Generate JSDoc
run: npm run docs
- name: Checkout the wiki repository
uses: actions/checkout@v4
with:
repository: logan/drb-server.wiki # Replace with your wiki repository
path: wiki
- name: Output Generated Documentation
run: |
cat Home.md
ls
- name: Update wiki
run: |
cp -rf Home.md wiki/Home.md
cd wiki
git config user.name "gitea-actions"
git config user.email "gitea-actions@cusano.net"
git add .
# Check if there are any changes to commit
if git diff --cached --quiet; then
echo "No changes to commit."
else
git commit -m "Update wiki from JSDoc"
git push
fi

View File

@@ -5,6 +5,8 @@ on:
branches:
- main
pull_request:
branches:
- "*"
jobs:
lint-js:

View File

@@ -3,7 +3,7 @@ name: DRB Tests
on:
pull_request:
branches:
- main
- "*"
push:
branches:
- main

3
.gitignore vendored
View File

@@ -302,3 +302,6 @@ op25/
# Ignore any local run scripts for development
*.bat
# Ignore the auto-generated docs folder
/docs

View File

@@ -12,7 +12,7 @@ RUN npm install -g node-gyp
RUN npm install
# Copy the rest of the application code to the working directory
COPY . .
COPY ./src ./src
# Expose the port on which your Node.js application will run
EXPOSE 3420

View File

@@ -1,29 +0,0 @@
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "discordBot.events.messageCreate");
import dotenv from "dotenv";
dotenv.config();
import { Events } from "discord.js";
import { gptInteraction } from "../addons/gptInteraction.mjs";
import { linkCop } from "../addons/linkCop.mjs";
const IGNORED_CHANNELS = process.env.IGNORED_CHANNEL_IDS.split(",");
export const name = Events.MessageCreate;
export async function execute(nodeIo, message) {
// Ignore ignored channels
if (IGNORED_CHANNELS.includes(message.channel.id)) return;
// Ignore messages from a bot
if (message.author.bot) return;
log.INFO("Message create", message);
// Check if the message mentions the bot
if (message.mentions.users.has(nodeIo.serverClient.user.id)) {
return await gptInteraction(nodeIo, message);
}
// Check if the message contains a link in a channel it shouldn't
if (await linkCop(nodeIo, message)) return;
}

View File

@@ -1,51 +0,0 @@
import { getConfig } from "./configHandler.mjs";
class PresenceManager {
/**
* Creates an instance of PresenceManager.
* @param {import('discord.js').Client} client - The Discord client instance.
*/
constructor(client) {
this.client = client;
this.defaultStatus = "online";
this.defaultActivityType = "LISTENING";
this.defaultActivityName = "for your commands";
this.defaultUrl = null;
}
/**
* Set the bot's presence.
* @param {"online"|"idle"|"dnd"} status - The status of the bot (online, idle, dnd).
* @param {"PLAYING"|"STREAMING"|"LISTENING"|"WATCHING"|"COMPETING"} activityType - The type of activity.
* @param {string} activityName - The name of the activity.
* @param {string} [url=null] - The URL for STREAMING activity type (optional).
*/
setPresence(status, activityType, activityName, url = null) {
const activityOptions = {
type: activityType.toUpperCase(),
name: activityName,
};
if (activityType.toUpperCase() === "STREAMING" && url) {
activityOptions.url = url;
}
this.client.user.setPresence({
status: status,
activities: [activityOptions],
});
}
/**
* Reset the bot's presence to the default state.
*/
resetToDefault() {
const defaultPresence = getConfig("presence");
console.log("Default Presence:", defaultPresence);
// Update your bot's presence using this configuration
this.client.user.setPresence(defaultPresence);
}
}
export default PresenceManager;

View File

@@ -4,6 +4,7 @@ import { FlatCompat } from "@eslint/eslintrc";
import mjs from "@eslint/js";
import prettierConfig from "eslint-config-prettier";
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
import unusedImports from "eslint-plugin-unused-imports";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -13,17 +14,34 @@ const compat = new FlatCompat({
});
export default [
// Apply ESLint recommended settings first
...compat.extends().map((config) => ({
...config,
files: ["**/*.mjs", "**/*.js", "**/*.cjs"],
rules: {
...config.rules,
// ...other your custom rules
"no-console": "warn",
"no-unused-vars": "warn",
"unused-imports/no-unused-imports": "error",
},
files: ["src/**/*.mjs", "src/**/*.js", "src/**/*.cjs"],
})),
// Custom rules and plugin configuration
{
plugins: {
"unused-imports": unusedImports,
},
rules: {
// Custom rules here
"no-console": "warn",
"no-unused-vars": "off", // or "@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
"prettier/prettier": "warn", // Integrate prettier
},
},
prettierConfig, // Turns off all ESLint rules that have the potential to interfere with Prettier rules.
eslintPluginPrettierRecommended,
];

10
jsdoc.conf Normal file
View File

@@ -0,0 +1,10 @@
{
"source": {
"includePattern": ".+([mc]?js(doc|x)?)$"
},
"plugins": ["node_modules/jsdoc-babel"],
"babel": {
"presets": [ "es2015" ],
"plugins": [ "transform-async-to-generator" ]
}
}

View File

@@ -21,10 +21,6 @@ run:
-e MONGO_URL=${MONGO_URL} \
-e DISCORD_TOKEN=${DISCORD_TOKEN} \
-e RSS_REFRESH_INTERVAL=${RSS_REFRESH_INTERVAL} \
-e WELCOME_CHANNEL_ID=${WELCOME_CHANNEL_ID} \
-e IGNORED_CHANNEL_IDS=${IGNORED_CHANNEL_IDS} \
-e LINKCOP_RESTRICTED_CHANNEL_IDS=${LINKCOP_RESTRICTED_CHANNEL_IDS} \
-e DRB_SERVER_INITIAL_PROMPT=${DRB_SERVER_INITIAL_PROMPT} \
-e OPENAI_API_KEY=${OPENAI_API_KEY} \
-e LOG_LOCATION="./logs/server.log" \
-p ${SERVER_PORT}:${SERVER_PORT} \

2329
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,22 +2,27 @@
"name": "drb-server",
"version": "3.0.0",
"description": "",
"main": "server.js",
"main": "src/server.js",
"scripts": {
"docs": "jsdoc2md -c jsdoc.conf src/*js > Home.md",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"test": "mocha --timeout 5000",
"start": "node server.js"
"start": "node src/server.js"
},
"author": "Logan Cusano",
"license": "ISC",
"type": "module",
"devDependencies": {
"babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-preset-es2015": "^6.24.1",
"chai": "^5.1.1",
"eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-unused-imports": "^4.1.3",
"jsdoc-babel": "^0.5.0",
"jsdoc-to-markdown": "^8.0.3",
"mocha": "^10.4.0",
"prettier": "^3.3.3",
"socket.io-client": "^4.7.5"

View File

@@ -2,25 +2,37 @@ import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "discordBot.addons.linkCop");
import { gptHandler } from "../modules/gptHandler.mjs";
import dotenv from "dotenv";
import { getGuildConfig } from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
dotenv.config();
const approvedLinksChannel = "767303243285790721";
const restrictedChannelIds =
process.env.LINKCOP_RESTRICTED_CHANNEL_IDS.split(",");
const linkRegExp =
/(?:http[s]?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/g;
const linkRegExp = /http[s]?:\/\/\S+/g;
export const linkCop = async (nodeIo, message) => {
// Set the channel IDs based on the guild the message was sent in
const approvedLinksChannel =
(await getGuildConfig(message.guild.id, "approvedLinksChannel")) ||
"767303243285790721";
const restrictedChannelIds = await getGuildConfig(
message.guild.id,
"restrictedChannelIds",
);
// Check if the message was sent in an restricted channel
if (
message.channel.id == approvedLinksChannel ||
!Array.isArray(restrictedChannelIds) ||
Array.isArray(restrictedChannelIds) ||
!restrictedChannelIds.includes(message.channel.id)
)
) {
return false;
}
// Check if there are URLs in the sent message
const urls = [...message.content.matchAll(linkRegExp)];
log.DEBUG("Parsed URLs from message:", urls);
const urls = String(message.content).matchAll(linkRegExp);
if (!urls || urls.length === 0) return false;
log.DEBUG("Found URLs: ", urls);
log.INFO("Found URLs: ", urls);
let conversation = [];

View File

@@ -1,6 +1,5 @@
import { SlashCommandBuilder } from "discord.js";
import { DebugBuilder } from "../../modules/debugger.mjs";
import { removeSource } from "../../rss-manager/sourceManager.mjs";
import {
getAllFeeds,
deleteFeedByTitle,

View File

@@ -1,13 +1,11 @@
import { DebugBuilder } from "../modules/debugger.mjs";
import { Client, GatewayIntentBits, Collection } from "discord.js";
import {
registerActiveCommands,
unregisterAllCommands,
} from "./modules/registerCommands.mjs";
import { registerActiveCommands } 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 PresenceManager from "./modules/presenceManager.mjs";
import dotenv from "dotenv";
dotenv.config();
@@ -110,6 +108,10 @@ export const serverClient = new Client({
serverClient.on("ready", async () => {
log.INFO(`Logged in as ${serverClient.user.tag}!`);
// Set the presence to default
const pm = new PresenceManager(serverClient);
await pm.resetToDefault();
// Add and register commands
await addEnabledCommands(serverClient);

View File

@@ -4,12 +4,15 @@ import dotenv from "dotenv";
dotenv.config();
import { Events } from "discord.js";
import { gptHandler } from "../modules/gptHandler.mjs";
const welcomeChannel = process.env.WELCOME_CHANNEL_ID; // TODO - Need to add a DB section for server configs so it's not static to one server
import { getGuildConfig } from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
export const name = Events.GuildMemberAdd;
export async function execute(nodeIo, member) {
const welcomeChannel = await getGuildConfig(
message.guild.id,
"welcomeChannelId",
);
log.INFO("New user joined the server", member);
let conversation = [];
conversation.push({

View File

@@ -1,6 +1,7 @@
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "discordBot.events.interactionCreate");
import { Events } from "discord.js";
import PresenceManager from "../modules/presenceManager.mjs";
export const name = Events.InteractionCreate;
@@ -8,6 +9,10 @@ export async function execute(nodeIo, interaction) {
const command = interaction.client.commands.get(interaction.commandName);
log.INFO("Interaction created for command: ", command);
// Set the presence for handling interaction
const interactionPm = new PresenceManager(interaction.client);
await interactionPm.setPresence("online", "PLAYING", "handling interaction");
// Execute autocomplete if the user is checking autocomplete
if (interaction.isAutocomplete()) {
log.INFO("Running autocomplete for command: ", command.data.name);
@@ -33,4 +38,7 @@ export async function execute(nodeIo, interaction) {
// Execute the command
command.execute(nodeIo, interaction);
// Reset the presence
await interactionPm.resetToDefault();
}

View File

@@ -0,0 +1,55 @@
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "discordBot.events.messageCreate");
import dotenv from "dotenv";
dotenv.config();
import { Events } from "discord.js";
import { gptInteraction } from "../addons/gptInteraction.mjs";
import { linkCop } from "../addons/linkCop.mjs";
import PresenceManager from "../modules/presenceManager.mjs";
import { getGuildConfig } from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
export const name = Events.MessageCreate;
export async function execute(nodeIo, message) {
// Get the ignored channels from the server config
const IGNORED_CHANNELS = await getGuildConfig(
message.guild.id,
"ignoredChannels",
);
// Ignore ignored channels
if (
!Array.isArray(IGNORED_CHANNELS) ||
(Array.isArray(IGNORED_CHANNELS) &&
IGNORED_CHANNELS.includes(message.channel.id))
) {
return;
}
// Ignore messages from a bot
if (message.author.bot) {
return;
}
log.INFO("Message create", message);
// Set presence for reading message
const messagePm = new PresenceManager(message.client);
await messagePm.setPresence("online", "WATCHING", "latest messages");
// Check if the message mentions the bot
if (message.mentions.users.has(nodeIo.serverClient.user.id)) {
const interaction = await gptInteraction(nodeIo, message);
// Reset the presence
await messagePm.resetToDefault();
return interaction;
}
// Check if the message contains a link in a channel it shouldn't
await linkCop(nodeIo, message);
// Reset the presence
await messagePm.resetToDefault();
}

View File

@@ -0,0 +1,115 @@
import {
getConfig,
setConfig,
} from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
import { ActivityType, PresenceUpdateStatus } from "discord.js";
/**
* Control the presence or activity of the discord bot.
*/
class PresenceManager {
/**
* Creates an instance of PresenceManager.
* @param {Client} client - The Discord client instance.
*/
constructor(client) {
this.client = client;
}
/**
* Set the bot's presence.
* @param {"online"|"idle"|"dnd"} status - The status of the bot (online, idle, dnd).
* @param {"PLAYING"|"STREAMING"|"LISTENING"|"WATCHING"|"COMPETING"} activityType - The type of activity.
* @param {string} activityName - The name of the activity.
* @param {string} [url=null] - The URL for STREAMING activity type (optional).
*/
async setPresence(status, activityType, activityName, url = null) {
const activityOptions = {
type: this.convertActivityType(activityType),
name: activityName,
};
if (activityType.toUpperCase() === "STREAMING" && url) {
activityOptions.url = url;
}
await this.client.user.setPresence({
status: this.convertStatus(status),
activities: [activityOptions],
});
}
/**
* Reset the bot's presence to the default state.
*/
async resetToDefault() {
let defaultPresence = await getConfig("presence");
if (!defaultPresence) {
defaultPresence = {
status: "idle",
activities: [
{
name: "your commands",
type: "LISTENING",
},
],
};
await setConfig("presence", defaultPresence);
}
console.log("Default Presence:", defaultPresence);
// Update your bot's presence using this configuration
await this.setPresence(
defaultPresence.status,
defaultPresence.activities[0].type,
defaultPresence.activities[0].name,
);
}
/**
* Convert a string activity type to the corresponding ActivityType enum.
* @param {string} activityType - The activity type string.
* @returns {ActivityType} - The corresponding ActivityType enum.
*/
convertActivityType(activityType) {
switch (activityType.toUpperCase()) {
case "PLAYING":
return ActivityType.Playing;
case "STREAMING":
return ActivityType.Streaming;
case "LISTENING":
return ActivityType.Listening;
case "WATCHING":
return ActivityType.Watching;
case "COMPETING":
return ActivityType.Competing;
default:
throw new Error("Invalid activity type");
}
}
/**
* Convert a string status to the corresponding PresenceUpdateStatus enum.
* @param {string} status - The status string.
* @returns {PresenceUpdateStatus} - The corresponding PresenceUpdateStatus enum.
*/
convertStatus(status) {
switch (status.toLowerCase()) {
case "online":
return PresenceUpdateStatus.Online;
case "idle":
return PresenceUpdateStatus.Idle;
case "dnd":
return PresenceUpdateStatus.DoNotDisturb;
case "invisible":
return PresenceUpdateStatus.Invisible;
default:
throw new Error("Invalid status");
}
}
}
export default PresenceManager;

View File

@@ -4,7 +4,6 @@ import {
checkIfNodeIsConnectedToVC,
getNodeDiscordID,
getNodeDiscordUsername,
checkIfNodeHasOpenDiscordClient,
getNodeCurrentListeningSystem,
requestNodeJoinSystem,
} from "../../modules/socketServerWrappers.mjs";
@@ -86,9 +85,8 @@ export const getAvailableNodes = async (nodeIo, guildId, system) => {
openSockets.map(async (openSocket) => {
openSocket = await nodeIo.sockets.sockets.get(openSocket);
// Check if the node has an existing open client (meaning the radio is already being listened to)
const hasOpenClient = await checkIfNodeHasOpenDiscordClient(openSocket);
if (hasOpenClient) {
let currentSystem = await getNodeCurrentListeningSystem(openSocket);
if (currentSystem) {
if (currentSystem != system.name) {
log.INFO(
"Node is listening to a different system than requested",

View File

@@ -0,0 +1,113 @@
import {
getDocumentByField,
deleteDocumentByField,
getDocumentByFields,
upsertDocumentByField,
deleteDocumentByFields,
upsertDocumentByFields,
} from "./mongoHandler.mjs"; // Import your MongoDB handlers
import { DebugBuilder } from "../debugger.mjs";
const log = new DebugBuilder("server", "mongoConfigWrappers");
const collectionName = "configurations";
// Function to get a configuration by key
export const getConfig = async (key) => {
try {
const config = await getDocumentByField(collectionName, "key", key);
log.DEBUG(`Configuration for key "${key}" retrieved:`, config);
return config ? config[key] : null; // Return null if no configuration is found
} catch (error) {
log.ERROR("Error retrieving configuration:", error);
throw error;
}
};
// Function to set a configuration by key
export const setConfig = async (key, value) => {
// Set the config object
value = { key: value };
try {
const result = await upsertDocumentByField(
collectionName,
"key",
key,
value,
);
log.DEBUG(`Configuration for key "${key}" set:`, value, result);
return result > 0 ? key : null; // Return key if updated successfully, otherwise null
} catch (error) {
log.ERROR("Error setting configuration:", error);
throw error;
}
};
// Function to delete a configuration by key (optional)
export const deleteConfig = async (key) => {
try {
const result = await deleteDocumentByField(collectionName, "key", key);
log.DEBUG(`Configuration for key "${key}" deleted:`, result);
return result; // Return the count of deleted documents
} catch (error) {
log.ERROR("Error deleting configuration:", error);
throw error;
}
};
// Function to get a configuration by key for a specific guild
export const getGuildConfig = async (guildId, key) => {
try {
const config = await getDocumentByFields(
collectionName,
["guild", Number(guildId)],
["key", key],
);
log.DEBUG(
`Guild ${guildId} configuration for key "${key}" retrieved:`,
config,
);
return config ? config[key] : null; // Return null if no configuration is found
} catch (error) {
log.ERROR("Error retrieving guild configuration:", error);
throw error;
}
};
// Function to set a configuration by key for a specific guild
export const setGuildConfig = async (guildId, key, value) => {
// Set the config object
value = { key: value };
try {
const result = await upsertDocumentByFields(
collectionName,
value,
["guild", Number(guildId)],
["key", key],
);
log.DEBUG(`Guild ${guildId} configuration for key "${key}" set:`, value);
return result > 0 ? key : null; // Return key if updated successfully, otherwise null
} catch (error) {
log.ERROR("Error setting guild configuration:", error);
throw error;
}
};
// Function to delete a configuration by key for a specific guild (optional)
export const deleteGuildConfig = async (guildId, key) => {
try {
const result = await deleteDocumentByFields(
collectionName,
["guild", Number(guildId)],
["key", key],
);
log.DEBUG(
`Guild ${guildId} configuration for key "${key}" deleted:`,
result,
);
return result; // Return the count of deleted documents
} catch (error) {
log.ERROR("Error deleting guild configuration:", error);
throw error;
}
};

View File

@@ -71,6 +71,22 @@ export const updateFeedByLink = async (link, updatedFields) => {
}
};
// Wrapper for deactivating a feed by link
export const deactivateFeedByLink = async (link) => {
try {
const updatedCount = await updateDocumentByField(
feedCollectionName,
"link",
link,
[{'active':false}]
);
return updatedCount;
} catch (error) {
log.ERROR("Error deleting feed by link:", error);
throw error;
}
};
// Wrapper for deleting a feed by link
export const deleteFeedByLink = async (link) => {
try {

View File

@@ -59,10 +59,26 @@ 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);
return await getDocumentByFields(collectionName, [field, value]);
};
// Function to retrieve a document by multiple fields
export const getDocumentByFields = async (
collectionName,
...fieldValuePairs
) => {
log.DEBUG("Getting document by fields:", collectionName, fieldValuePairs);
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const document = await collection.findOne({ [field]: value });
// Convert the fieldValuePairs array into an object
const query = fieldValuePairs.reduce((acc, [field, value]) => {
acc[field] = value;
return acc;
}, {});
const document = await collection.findOne(query);
return document;
} catch (error) {
console.error("Error retrieving document:", error);
@@ -72,12 +88,55 @@ export const getDocumentByField = async (collectionName, field, value) => {
}
};
// Function to update a document by a specific field
export const upsertDocumentByField = async (
collectionName,
field,
value,
updatedFields,
) => {
log.DEBUG(
"Upsert document by field:",
collectionName,
field,
value,
updatedFields,
);
return await updateDocumentByFields(
collectionName,
updatedFields,
{ upsert: true },
[field, value],
);
};
// Function to update a document by a specific field
export const upsertDocumentByFields = async (
collectionName,
updatedFields,
...fieldValuePairs
) => {
log.DEBUG(
"Upsert document by fields:",
collectionName,
updatedFields,
fieldValuePairs,
);
return await updateDocumentByFields(
collectionName,
updatedFields,
{ upsert: true },
fieldValuePairs,
);
};
// Function to update a document by a specific field
export const updateDocumentByField = async (
collectionName,
field,
value,
updatedFields,
options = null,
) => {
log.DEBUG(
"Update document by field:",
@@ -85,13 +144,42 @@ export const updateDocumentByField = async (
field,
value,
updatedFields,
options,
);
return await updateDocumentByFields(collectionName, updatedFields, options, [
field,
value,
]);
};
// Function to update a document by multiple fields
export const updateDocumentByFields = async (
collectionName,
updatedFields,
options,
...fieldValuePairs
) => {
log.DEBUG(
"Update document by fields:",
collectionName,
updatedFields,
options,
fieldValuePairs,
);
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
// Convert the fieldValuePairs array into an object
const query = fieldValuePairs.reduce((acc, [field, value]) => {
acc[field] = value;
return acc;
}, {});
const result = await collection.updateOne(
{ [field]: value },
query,
{ $set: updatedFields },
options,
);
log.DEBUG("Document updated:", result.modifiedCount);
return result.modifiedCount;
@@ -106,10 +194,26 @@ export const updateDocumentByField = async (
// 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);
return await deleteDocumentByFields(collectionName, [field, value]);
};
// Function to delete a document by multiple fields
export const deleteDocumentByFields = async (
collectionName,
...fieldValuePairs
) => {
log.DEBUG("Delete document by fields:", collectionName, fieldValuePairs);
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const result = await collection.deleteOne({ [field]: value });
// Convert the fieldValuePairs array into an object
const query = fieldValuePairs.reduce((acc, [field, value]) => {
acc[field] = value;
return acc;
}, {});
const result = await collection.deleteOne(query);
log.DEBUG("Document deleted:", result.deletedCount);
return result.deletedCount;
} catch (error) {

View File

@@ -10,6 +10,7 @@ import { DebugBuilder } from "../modules/debugger.mjs";
import { removeSource } from "./sourceManager.mjs";
import UserAgent from "user-agents";
import Parser from "rss-parser";
import PresenceManager from "../discordBot/modules/presenceManager.mjs";
import dotenv from "dotenv";
dotenv.config();
@@ -41,11 +42,19 @@ export const returnHash = (...stringsIncluded) => {
export const updateFeeds = async (client) => {
if (!client) throw new Error("Client object not passed");
// Setup presence manager
const feedPm = new PresenceManager(client);
await feedPm.setPresence("online", "WATCHING", "for RSS feed updates");
try {
const records = await getAllFeeds();
const sourcePromiseArray = records.map(async (source) => {
log.DEBUG("Processing source:", source.title);
// Check if the feed is active
if (!source.active) {
return
}
try {
const parsedFeed = await parser.parseURL(source.link);
@@ -106,6 +115,7 @@ export const updateFeeds = async (client) => {
await Promise.all(sourcePromiseArray);
log.DEBUG("All sources processed");
await feedPm.resetToDefault();
} catch (error) {
log.ERROR("Error updating feeds:", error);
throw error;

View File

@@ -3,7 +3,7 @@ const log = new DebugBuilder("server", "sourceManager");
import {
createFeed,
getFeedByLink,
deleteFeedByLink,
deactivateFeedByLink,
} from "../modules/mongo-wrappers/mongoFeedsWrappers.mjs";
class SourceManager {
@@ -48,7 +48,7 @@ class SourceManager {
return;
}
const results = await deleteFeedByLink(sourceURL);
const results = await deactivateFeedByLink(sourceURL);
if (!results) {
log.WARN(`Failed to remove source: ${sourceURL}`);
return;
@@ -70,6 +70,7 @@ class SourceManager {
category,
guild_id: guildId,
channel_id: channelId,
active: true
};
const record = await createFeed(feed);
log.DEBUG("Source added:", record);

View File

@@ -1,11 +1,8 @@
import { DebugBuilder } from "./modules/debugger.mjs";
const log = new DebugBuilder("server", "server");
import { nodeIo, app, server } from "./modules/socketServer.mjs";
import { nodeIo, server } from "./modules/socketServer.mjs";
import { loadAddons } from "./modules/addonManager.mjs";
import {
serverClient,
addEnabledEventListeners,
} from "./discordBot/discordBot.mjs";
import { serverClient } from "./discordBot/discordBot.mjs";
import dotenv from "dotenv";
dotenv.config();

View File

@@ -4,11 +4,11 @@ import ioClient from "socket.io-client";
import {
deleteNodeByNuid,
getNodeByNuid,
} from "../modules/mongo-wrappers/mongoNodesWrappers.mjs";
} from "../src/modules/mongo-wrappers/mongoNodesWrappers.mjs";
import {
deleteSystemByName,
getSystemByName,
} from "../modules/mongo-wrappers/mongoSystemsWrappers.mjs";
} from "../src/modules/mongo-wrappers/mongoSystemsWrappers.mjs";
import {
nodeDisconnectWrapper,
checkIfNodeHasOpenDiscordClient,
@@ -19,8 +19,8 @@ import {
requestBotLeaveServer,
requestNodeJoinSystem,
requestNodeUpdate,
} from "../modules/socketServerWrappers.mjs";
import { nodeIo } from "../modules/socketServer.mjs";
} from "../src/modules/socketServerWrappers.mjs";
import { nodeIo } from "../src/modules/socketServer.mjs";
import dotenv from "dotenv";
dotenv.config();