Compare commits

...
Author SHA1 Message Date
0c45055e0f
Use SQL on owner custom command subcommand
All checks were successful
CI / CI (push) Successful in 21s
Lint Codebase / eslint (push) Successful in 16s
2024-12-19 20:19:38 +01:00
345a03a3b0
Use sqlite database for creating new custom commands
All checks were successful
CI / CI (push) Successful in 21s
Lint Codebase / eslint (push) Successful in 16s
2024-12-19 19:22:42 +01:00
7702f10587
Use set for listing all custom commands
All checks were successful
CI / CI (push) Successful in 25s
Lint Codebase / eslint (push) Successful in 16s
2024-12-19 18:57:48 +01:00
eae1e9130e
Change onmessage to use new custom commands set instead of json file
All checks were successful
CI / CI (push) Successful in 20s
Lint Codebase / eslint (push) Successful in 16s
2024-12-19 18:26:59 +01:00
570a8e49b7
Add conversion of custom commands to SQL
All checks were successful
CI / CI (push) Successful in 20s
Lint Codebase / eslint (push) Successful in 15s
2024-12-17 11:10:45 +01:00
904fa25a6d
Add error if prepareBot fails 2024-11-06 09:12:14 +01:00
f28055e024
Refactor server.js
All checks were successful
CI / CI (push) Successful in 16s
Lint Codebase / eslint (push) Successful in 17s
2024-11-06 04:39:19 +01:00
5a4cfd0a5f
Rename database table functions to be clearer
All checks were successful
CI / CI (push) Successful in 19s
Lint Codebase / eslint (push) Successful in 12s
2024-11-06 01:44:16 +01:00
4b53d875e4
Remove njsscan workflow
All checks were successful
CI / CI (push) Successful in 17s
Lint Codebase / eslint (push) Successful in 12s
2024-11-05 06:55:47 +01:00
aaa10cb57a
update actions/checkout and actions/setup-node to v4
All checks were successful
CI / CI (push) Successful in 17s
Lint Codebase / eslint (push) Successful in 11s
2024-11-05 06:45:08 +01:00
b89b926f9a
Update actions/checkout to v4
All checks were successful
CI / CI (push) Successful in 17s
Lint Codebase / eslint (push) Successful in 13s
2024-11-05 06:37:03 +01:00
34c5f08806
Use scopes on switch cases
All checks were successful
CI / CI (push) Successful in 17s
Lint Codebase / eslint (push) Successful in 12s
2024-11-05 06:10:56 +01:00
a3e9e1f90c
Add count subcommand to custom command
Some checks failed
CI / CI (push) Successful in 17s
Lint Codebase / eslint (push) Failing after 12s
Use scopes in each case in the switch statement
2024-11-05 06:07:51 +01:00
bab508e4b9
Add eslint as linter and add linter workflow
Some checks failed
CI / CI (push) Successful in 18s
Lint Codebase / eslint (push) Failing after 12s
2024-11-05 06:03:14 +01:00
85821f5fa3
Update README
All checks were successful
CI / CI (push) Successful in 17s
2024-11-02 22:48:21 +01:00
28 changed files with 1483 additions and 189 deletions

22
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: Lint Codebase
on: [push]
jobs:
eslint:
runs-on: node
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 'latest'
- name: Install dependencies
run: npm install
- name: Run ESLint
run: npm run lint

View file

@ -1,42 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow integrates njsscan with GitHub's Code Scanning feature
# nodejsscan is a static security code scanner that finds insecure code patterns in your Node.js applications
name: njsscan sarif
on:
push:
branches: [ "master", "devbot" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master", "devbot" ]
schedule:
- cron: '43 12 * * 6'
permissions:
contents: read
jobs:
njsscan:
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
runs-on: ubuntu-latest
name: njsscan code scanning
steps:
- name: Checkout the code
uses: actions/checkout@v3
- name: nodejsscan scan
id: njsscan
uses: ajinabraham/njsscan-action@v7
with:
args: '. --sarif --output results.sarif || true'
- name: Upload njsscan report
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif

View file

@ -27,7 +27,7 @@ You can also change the global prefix.
You should enter you discord user id, so you can use the admin commands.
Every time you want to change something in this file, you have to restart the bot.
If the config is ever changed, you need to either delete it and repeat the steps above or take a look at the util/createInitialConfig.js file.
If the config is ever changed, you need to either delete it and repeat the steps above or take a look at the [util/createInitialConfig.js](./util/createInitialConfig.js) file.
## Known issues

View file

@ -1,4 +1,3 @@
const fs = require('fs');
const {EmbedBuilder} = require('discord.js');
const getSubdirFiles = require('../../util/getSubdirFiles');
@ -17,7 +16,6 @@ module.exports = {
let fieldName = `Page [[page]]/${Math.round(client.commands.size / 10)}`;
let iteration = 0;
let num_in_args = false;
let added_commands = 0;
let page = -1;
@ -53,7 +51,6 @@ module.exports = {
if (!command.admin){
if(start < iteration){
}else{
added_commands++
commands = commands + `${prefix}${command.name} | ${command.description}\n`;
iteration++
}

View file

@ -17,13 +17,10 @@ module.exports = {
"<prefix>custom remove - Delete your custom commands.",
"<prefix>custom owner - check owner of custom command",
"<prefix>custom list - list all custom commands",
"<prefix>custom variables - list all variables you can use"
"<prefix>custom variables - list all variables you can use",
"<prefix>custom count - display the amount of custom commands"
],
execute({message, args, client, prefix, owners}) {
const customPath = 'data/customCommands.json';
if(!fs.existsSync(customPath)){
fs.writeFileSync(customPath,"[]")
}
async execute({message, args, client, prefix, owners}) {
let sendText;
let isEmbed = false;
if (args){
@ -31,19 +28,21 @@ module.exports = {
let customMessage = args.slice(2, args.length).join(" ");
switch (args[0].toLowerCase()) {
case "add":
case "add":{
if(!customMessage) {
message.channel.send("Message can't be empty");
return;
}
sendText = addCustomCommand(customName, customMessage, message.author.id);
sendText = await addCustomCommand(client, customName, customMessage, message.author.id);
break;
}
case "remove":
case "delete":
case "delete":{
sendText = deleteCustomCommand(customName, message.author.id, owners);
break;
case "owner":
let author = getOwnerOfCustomCommand(customName);
}
case "owner":{
let author = await getOwnerOfCustomCommand(customName);
let user;
if(!author)
sendText = `${customName} does not exist`
@ -55,10 +54,11 @@ module.exports = {
}
break;
case "list":
}
case "list": {
const embed = new EmbedBuilder();
sendText = getAllCustomCommands();
if(sendText != ""){
sendText = getAllCustomCommands(client);
if(sendText !== ""){
embed.setColor(15780145)
embed.addFields(
{ name: "Custom commands", value: sendText },
@ -67,13 +67,16 @@ module.exports = {
isEmbed = true;
}else sendText = "NO CUSTOM COMMANDS"
break;
case "variables":
}
case "variables": {
sendText = "The variables you can use are:\n<prefix>\n<globalPrefix>\n<username>\n<nickname>\n<user_id>\n<guild_name>\n<guild_id>"
break;
case "edit":
}
case "edit": {
sendText = editCustomCommand(customName, message.author.id, customMessage)
break;
case "show":
}
case "show": {
let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json)
sendText = "Command not found."
@ -83,14 +86,24 @@ module.exports = {
}
});
break;
case "rename":
}
case "rename": {
sendText = renameCustomCommand(customName, args[2], message.author.id);
break;
default:
}
case "count": {
const customPath = './data/customCommands.json';
let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json)
sendText = `There are ${customCommands.length} custom commands in total`;
break;
}
default: {
sendText = `Argument not recognized.\n"${prefix}help custom" to see all arguments you can use.`
break;
}
}
}
if(isEmbed) message.channel.send({embeds :[sendText]})
else message.channel.send(sendText);
}

View file

@ -19,7 +19,8 @@ module.exports = {
try {
num = num.slice(0, -1);
} catch (e) {
message.channel.send("There was an error.");
console.error(e)
message.channel.send("There was an error. Check the logs");
return;
}
message.channel.send("https://cdn.discordapp.com/emojis/" + num + extension);

View file

@ -17,21 +17,25 @@ module.exports = {
let sendText = "This should never happen.";
switch (args[0]) {
case "add":
case "create":
case "create": {
sendText = await createTimer(message, args, false);
break;
case "edit":
}
case "edit": {
sendText = "not implemented yet"
break;
}
case "delete":
case "remove":
case "remove": {
let timerID = args[1];
sendText = await deleteTimer(message.author.id, timerID);
break;
case "show":
}
case "show": {
sendText = await showTimer(message.author.id, args[1]);
break;
default:
}
default: {
sendText = "not sure what you mean";
if(args.length < 2){
sendText = "Please specify a time, and a message to send after the timer has finished";
@ -41,6 +45,7 @@ module.exports = {
sendText = await createTimer(message, args, true);
break;
}
}
message.channel.send(sendText);
}
};

32
eslint.config.mjs Normal file
View file

@ -0,0 +1,32 @@
import jest from "eslint-plugin-jest";
import globals from "globals";
export default [{
plugins: {
jest,
},
languageOptions: {
globals: {
...globals.jest,
...jest.environments.globals.globals,
},
},
rules: {
"no-fallthrough": "error",
"no-case-declarations": "error",
"no-unused-vars": "warn"
},
},
];
// /** @type {import('eslint').Linter.Config[]} */
// export default [
// {files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}},
// {languageOptions: { globals: globals.node }},
// pluginJs.configs.recommended,
// ];

1135
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,8 @@
"sqlite3": "^5.1.6"
},
"scripts": {
"test": "jest"
"test": "jest",
"lint": "npx eslint ."
},
"repository": {
"type": "git",
@ -20,7 +21,11 @@
"author": "SileNce5k",
"license": "UNLICENSE",
"devDependencies": {
"@eslint/js": "^9.14.0",
"@types/jest": "^29.5.14",
"eslint": "^9.14.0",
"eslint-plugin-jest": "^28.8.3",
"globals": "^15.12.0",
"jest": "^29.7.0"
}
}

101
server.js
View file

@ -1,25 +1,15 @@
const fs = require('fs');
const createInitialConfig = require("./util/createInitialConfig")
const convertJSONToSQL = require('./util/timer/convertJSONToSQL');
const convertTimerJSONToSQL = require('./util/timer/convertTimerJSONToSQL.js');
const createTimersTable = require('./server/createDatabaseTables/createTimersTable');
const createCustomCommandsTable = require('./server/createDatabaseTables/createCustomCommandsTable.js');
const convertCustomCommandsJSONToSQL = require('./server/convertCustomCommandsJSONToSQL.js');
const sqlite3 = require('sqlite3').verbose();
if(!fs.existsSync("./data/config.json")) {
createInitialConfig();
}
async function checkAndConvertJSONToSQL(){
process.stdout.write("Checking if timers.json exists... ")
if(fs.existsSync("./data/timers.json")){
process.stdout.write(true + "\n")
await createDatabaseTables();
await convertJSONToSQL();
fs.renameSync('data/timers.json', 'data/timers.json.old');
console.log("Renamed timers.json to timers.json.old");
}else{
process.stdout.write(false + "\n")
}
}
const createDatabaseTables = require('./server/createDatabaseTables');
const createLastfmTable = require('./server/createLastfmTable');
createLastfmTable();
checkAndConvertJSONToSQL();
const { Collection, Client, GatewayIntentBits, Partials } = require('discord.js');
const client = new Client({ intents: [GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
@ -27,6 +17,49 @@ const client = new Client({ intents: [GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildPresences
], partials: [Partials.Channel] });
async function checkAndConvertJSONToSQL(){
process.stdout.write("Checking if timers.json exists... ")
if(fs.existsSync("./data/timers.json")){
process.stdout.write(true + "\n")
await convertTimerJSONToSQL();
fs.renameSync('data/timers.json', 'data/timers.json.old');
console.log("Renamed timers.json to timers.json.old");
}else{
process.stdout.write(false + "\n")
}
process.stdout.write("Checking if customCommands.json exists... ")
if(fs.existsSync('./data/customCommands.json')){
process.stdout.write(true + "\n")
await convertCustomCommandsJSONToSQL();
fs.renameSync('data/customCommands.json', 'data/customCommands.json.old');
}else{
process.stdout.write(false + "\n")
}
}
async function loadcustomCommands() {
const db = new sqlite3.Database('data/database.db');
client.customCommands = new Collection();
db.all("SELECT * FROM customCommands", (error, commands) => {
if(error){
console.error("Error while loading custom commands:\n",error.message)
}else {
commands.forEach(command => {
client.customCommands.set(command.customName, command.customMessage);
})
}
})
}
function startBot(){
const {
globalPrefix,
token,
@ -70,3 +103,37 @@ client.on('messageCreate', async message => {
client.login(token);
}
async function prepareBot(){
const createLastfmTable = require('./server/createDatabaseTables/createLastfmTable');
const taskGroups = [
[
createTimersTable,
createLastfmTable,
createCustomCommandsTable
],
[
checkAndConvertJSONToSQL
]
];
(async () => {
for (const taskGroup of taskGroups) {
await Promise.all(taskGroup.map(task => task()));
}
loadcustomCommands()
})();
}
prepareBot().then( () => {
startBot();
}).catch(error => {
console.error("Error preparing the bot:\n", error)
})

View file

@ -0,0 +1,28 @@
const sqlite3 = require('sqlite3').verbose();
module.exports = async function () {
const customCommands = require('../data/customCommands.json')
const db = new sqlite3.Database('data/database.db');
return new Promise((resolve, reject) => {
customCommands.forEach(command => {
const isDeleted = false;
db.run(`INSERT INTO customCommands (
customName,
customMessage,
author,
isDeleted
) VALUES (?, ?, ?, ?)`, [command.customName, command.customMessage, command.author, isDeleted], function (error) {
if (error) {
console.error(`Error while converting customCommands.json to SQL: ${error}`)
reject(error);
}
})
});
db.close();
console.log("Converted customCommands.json to SQL successfully.");
resolve();
})
}

View file

@ -0,0 +1,24 @@
const sqlite3 = require('sqlite3').verbose();
module.exports = async function () {
const db = new sqlite3.Database('data/database.db');
return new Promise ((resolve, reject)=>{
db.run(
`CREATE TABLE IF NOT EXISTS customCommands (
customName TEXT PRIMARY KEY,
customMessage TEXT,
author TEXT,
isDeleted INTEGER)`,
(err) => {
if (err) {
console.error(`Error while creating table 'customCommands': ${err}`);
reject(err);
} else {
console.log("Table 'customCommands' created successfully.");
resolve();
}
db.close();
}
);
})
}

View file

@ -23,16 +23,10 @@ module.exports = function(client, owners, message, globalPrefix){
const commandName = args.shift().toLowerCase();
const command = client.commands.get(commandName);
if (!command){
const customPath = './data/customCommands.json';
if(fs.existsSync(customPath)){
let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json)
customCommands.forEach(function (customCommand) {
if (customCommand.customName === commandName) {
let customMessage = customReplaceWithVariables(customCommand.customMessage, message, prefix, globalPrefix)
message.channel.send(customMessage)
}
});
const command = client.customCommands.get(commandName);
if(command){
let customMessage = customReplaceWithVariables(command, message, prefix, globalPrefix);
message.channel.send(customMessage);
}
return;
}

View file

@ -9,7 +9,8 @@ module.exports = function(client, enableLoginMessage, loginChannel, loginMessage
try{
client.channels.cache.get(loginChannel).send(loginMessage)
}catch(err){
console.log("Failed trying to send a message on login.\n")
console.error("Failed trying to send a message on login.\n")
console.error(err)
}
loadServerPrefixes(client)
checkTimer(client);

View file

@ -1,25 +1,33 @@
const fs = require('fs');
module.exports = function(customName, customMessage, author){
let sendText;
const customPath = './data/customCommands.json';
const sqlite3 = require('sqlite3').verbose();
module.exports = async function(client, customName, customMessage, author){
let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json)
let sendText = "";
let isExists = client.customCommands.get(customName);
let isExists = false;
customCommands.forEach(function (customCommand) {
if (customCommand.customName === customName) {
sendText = "This custom command already exists";
isExists = true;
}
});
if (!isExists) {
let newCustomCommand = {
"customName": customName, "customMessage": customMessage, "author": author
const db = new sqlite3.Database("data/database.db");
sendText = await new Promise((resolve, reject)=>{
db.run(`INSERT INTO customCommands (
customName,
customMessage,
author,
isDeleted
) VALUES (?, ?, ?, ?)`, [customName, customMessage, author, false],
function(error){
if(error){
console.error(error)
let sendText = "Error while inserting new custom command.";
reject(sendText);
}else{
client.customCommands.set(customName, customMessage)
let sendText = `New custom command with the name "${customName}" added`
resolve(sendText)
}
customCommands.push(newCustomCommand)
sendText = `New custom command with the name "${customName}" added`
})
})
db.close();
}
fs.writeFileSync(customPath, JSON.stringify(customCommands, null, 4))
return sendText;
}

View file

@ -1,13 +1,7 @@
const fs = require('fs');
module.exports = function(){
const customPath = './data/customCommands.json';
module.exports = function(client){
let list = "";
let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json)
customCommands.forEach(function (customCommand) {
list = list + customCommand.customName+"\n";
});
client.customCommands.keys().forEach(commandName => list += `${commandName}\n`)
return list;
}

View file

@ -1,10 +1,24 @@
const fs = require('fs');
module.exports = function(customName){
const customPath = './data/customCommands.json';
let json = fs.readFileSync(customPath, 'utf8');
let customCommands = JSON.parse(json)
let author = customCommands.filter(customCommands => customCommands.customName === customName);
if(author.length === 0) return false;
return author[0].author;
const sqlite3 = require('sqlite3').verbose();
module.exports = async function(customName){
const db = new sqlite3.Database("data/database.db");
let author = await new Promise((resolve, reject)=>{
db.get("SELECT * FROM customCommands WHERE customName = ? AND isDeleted = 0", [customName],
function(error, command){
if(error){
console.error(error)
let author = "Error when getting the owner of this custom command. Check console.";
reject(author)
}else {
let author;
if(command){
author = command.author;
}
resolve(author)
}
})
})
return author;
}

View file

@ -25,6 +25,7 @@ module.exports = async function(userID, guild) {
try {
track = data.recenttracks.track[0];
} catch (error) {
console.error(error);
scrobble.error = true;
switch(data.error){
case 6:

View file

@ -39,6 +39,7 @@ module.exports = async function (userID, guild) {
}
resolve(tracks);
} catch (error) {
console.error(error)
let scrobble = {};
scrobble.error = true;
if (data.error === 6) {

View file

@ -1,35 +1,41 @@
module.exports = function (user) {
let details;
switch (user.presence.activities[0].name) {
case "foobar2000":
case "foobar2000": {
let artist = user.presence.activities[0].state.split(":")[0];
let album = user.presence.activities[0].state.split(":")[1].slice(1);
details = `Artist: ${artist}\nAlbum: ${album}\nSong: ${user.presence.activities[0].details}\n`
break;
case "Apple Music":
}
case "Apple Music": {
details = `Artist/Song: ${user.presence.activities[0].details} \nAlbum: ${user.presence.activities[0].state}\n`
break;
case "Spotify":
}
case "Spotify": {
details = `Artist: ${user.presence.activities[0].state}\nAlbum: ${user.presence.activities[0].details}\nSong: ${user.presence.activities[0].assets.largeText}\n`
break;
case "Custom Status":
}
case "Custom Status": {
if(user.presence.activities[0].state !== null)
details = `"${user.presence.activities[0].state}"\n`
else
details = "";
break;
case "Code":
}
case "Code": {
if(user.presence.activities[0].details != null)
if(user.presence.activities[0].details.slice(0, 7) === "Editing")
details = `Workspace: ${user.presence.activities[0].state}\nEditing: ${user.presence.activities[0].details.slice(8)}\n`
else
details = `Workspace: ${user.presence.activities[0].state}\n${user.presence.activities[0].details}\n`
break;
default:
}
default: {
details = 0;
break;
}
}
return details
}

View file

@ -1,4 +1,3 @@
const fs = require('fs');
const getSubdirFiles = require('./getSubdirFiles');
const commandPath = 'commands/'
const utilPath = 'util/'

View file

@ -1,5 +1,4 @@
const sendTimerReminder = require('./sendTimerReminder')
const fs = require('fs')
const sqlite3 = require('sqlite3').verbose();
module.exports = async function (client) {
const checkTimer = require('./checkTimer')

View file

@ -1,4 +1,3 @@
const fs = require('fs');
const sqlite3 = require('sqlite3').verbose();
module.exports = async function () {
const timers = require('../../data/timers.json')

View file

@ -1,4 +1,3 @@
const fs = require('fs');
const parseTime = require('./parseTime');
const sqlite3 = require('sqlite3').verbose();
module.exports = async function (message, args, compatibility) {

View file

@ -30,9 +30,9 @@ module.exports = function(time, currentUnixTime){
function getTime(time, currentUnixTime) {
// function getTime(time, currentUnixTime) {
return timeInSeconds;
}
// return timeInSeconds;
// }