Update an imported module in Typescript - node.js

I'm sorry, but I'm kinda new in this language.
I was creating a custom discord bot these days and I got stucked on this problem...
I gave this bot the possibility to load the commands dynamically from a folder with one module for each command, but now I was trying to make a command to reload them all, but each time after the commands are reloaded the output is always the same.
Here is the code:
refreshCommands = () => {
this.commands = {};
console.log("Refreshing commands");
Promise.all(fs.readdirSync("./dist/commands").map(file => {
return new Promise(async resolve => {
const tmp = (await import(`./commands/${file}`)).default;
this.commands[tmp.name] = tmp;
resolve(tmp);
});
})).then(() => {
console.log("Listing commands: ");
console.log(Object.keys(this.commands));
});
}
Of course I update the commands from the js file, and not from the ts 'cause I would have to compile it again.
I tried to make a simple "ping! Pong!" like command, and then to edit it to "ping! ping!" on runtime before using the //reload command, but it keeps writing "ping! Pong!"
Edit 1:
The modules I have to import are made like this one:
import command from "../utils/command";
import { Guild, GuildEmoji, GuildEmojiManager, Message, MessageEmbed, Role } from "discord.js";
import { games } from "../utils/games";
import app from "../app";
import ReactionListener from "../utils/reactionListener";
const roleMessage: command = {
name: "rolesMessage",
description: "",
execute: async (message, bot) => {
message.delete();
createRoles(message.guild as Guild);
const embed = new MessageEmbed()
.setColor('#F00')
.setTitle("React to set your ROLE!");
games.forEach(game => {
let emoji = message.guild?.emojis.cache.find(emoji => emoji.name === game.emoji);
console.log(emoji);
embed.fields.push({
name: game.name,
value: (emoji as GuildEmoji).toString(),
inline: false
});
});
const msg = await message.channel.send(embed);
app.reactionListeners.push(new ReactionListener(msg,
(reaction, user) => {
let tmp = games.find(game=> reaction.emoji.name === game.emoji);
if(tmp){
//msg.channel.send(tmp);
const role = (message.guild as Guild).roles.cache.find(role => role.name === tmp?.roleName) as Role;
message.guild?.members.cache.find(member => member.id === user.id)?.roles.add(role);
}else{
reaction.remove();
}
}, (reaction, user)=>{
let tmp = games.find(game=> reaction.emoji.name === game.emoji);
if(tmp){
//msg.channel.send(tmp);
const role = (message.guild as Guild).roles.cache.find(role => role.name === tmp?.roleName) as Role;
message.guild?.members.cache.find(member => member.id === user.id)?.roles.remove(role);
}
})
);
games.forEach(game => {
msg.react((message.guild?.emojis.cache.find(emoji => emoji.name === game.emoji) as GuildEmoji));
});
}
}
const createRoles = (guild: Guild) => {
games.forEach(game => {
if(!guild.roles.cache.find(role => role.name === game.roleName)){
guild.roles.create({
data: {
name: game.roleName,
color: "#9B59B6",
},
reason: 'we needed a role for Super Cool People',
})
.then(console.log)
.catch(console.error);
}
});
}
export default roleMessage;
This is a different one from the one I was talking about earlier, but the problem is the same... Once I update and reload it (from the js compiled version), the old version keeps being runned

I managed to find a solution to the problem.
As node js chaches every module once imported, I deleted it from the cache like this
refreshCommands = () => {
Promise.all(fs.readdirSync("./dist/commands").map(file => {
return new Promise(async resolve => {
delete require.cache[require.resolve('./commands/' + file)];
resolve(file);
});
})).then(() => {
this.commands = {};
console.log("Refreshing commands");
Promise.all(fs.readdirSync("./dist/commands").map(file => {
return new Promise(async resolve => {
const tmp = (await import(`./commands/${file}`)).default;
this.commands[tmp.name] = tmp;
resolve(tmp);
});
})).then(() => {
console.log("Listing commands: ");
console.log(Object.keys(this.commands));
});
});
}
The code might look like garbage, but it actually works... I'm on my way to make it better, but meanwhile I can rely on it.
Any suggestion is well accepted

Related

Discord.js maximum number of webhooks error

Have this slash command code and turned it into webhook. It worked when I used it once but it stopped working after that. I got this error DiscordAPIError: Maximum number of webhooks reached (10). Does anyone have any idea on how to fix this?
Code:
run: async (client, interaction, args) => {
if(!interaction.member.permissions.has('MANAGE_CHANNELS')) {
return interaction.followUp({content: 'You don\'t have the required permission!', ephemeral: true})
}
const [subcommand] = args;
const embedevent = new MessageEmbed()
if(subcommand === 'create'){
const eventname = args[1]
const prize = args[2]
const sponsor = args[3]
embedevent.setDescription(`__**Event**__ <a:w_right_arrow:945334672987127869> ${eventname}\n__**Prize**__ <a:w_right_arrow:945334672987127869> ${prize}\n__**Donor**__ <a:w_right_arrow:945334672987127869> ${sponsor}`)
embedevent.setFooter(`${interaction.guild.name}`, `${interaction.guild.iconURL({ format: 'png', dynamic: true })}`)
embedevent.setTimestamp()
}
await interaction.followUp({content: `Event started!`}).then(msg => {
setTimeout(() => {
msg.delete()
}, 5000)
})
interaction.channel.createWebhook(interaction.user.username, {
avatar: interaction.user.displayAvatarURL({dynamic: true})
}).then(webhook => {
webhook.send({content: `<#&821578337075200000>`, embeds: [embedevent]})
})
}
}
You cannot fix that error, discord limits webhooks per channel (10 webhooks per channel).
However, if you don't want your code to return an error you can just chock that code into a try catch or add a .catch
Here is an example of how to handle the error:
try {
interaction.channel.createWebhook(interaction.user.username, {
avatar: interaction.user.displayAvatarURL({dynamic: true})
}).then(webhook => {
webhook.send({content: `<#&821578337075200000>`, embeds: [embedevent]})
})
} catch(e) {
return // do here something if there is an error
}

ms bot framework onMembersAddedActivity does not get invoked using nodejs

i am using nodejs google cloud functions with ms bot framework. I have the invoke code looks like below:
const BotFrameworkAdapter = require('botbuilder').BotFrameworkAdapter
const { TeamsConversationBot } = require('./flashmsteamsbot');
const msadapter = new BotFrameworkAdapter({
appId: 'XXX',
appPassword: 'XXX'
});
const msteamsbot = new TeamsConversationBot()
const app = express();
app.post('/api/messages', (req:any, res:any) => {
msadapter.processActivity(req, res, async (context:any) => {
// Route to main dialog.
await msteamsbot.run(context)
});
});
the teams class looks like below:
const {
TurnContext,
TeamsActivityHandler,
CardFactory,
AttachmentLayoutTypes,
ActionTypes
} = require('botbuilder');
class TeamsConversationBot extends TeamsActivityHandler {
constructor() {
super();
this.onMessage(async (context:any, next:any) => {
TurnContext.removeRecipientMention(context.activity);
let msg = context.activity.text
const senderId = context.activity.from.aadObjectId
switch (msg) {
case 'don1':
await this.don1(context, keyword.trim(), userKey)
break;
default:
await this.help(context)
break;
}
await next();
});
this.onMembersAddedActivity(async (context:any, next:any) => {
functions.logger.log("start of onMembersAddedActivity", context)
context.activity.membersAdded.forEach(async (teamMember:any) => {
if (teamMember.id !== context.activity.recipient.id) {
await context.sendActivity(`Welcome to the team ${ teamMember.givenName } ${ teamMember.surname }`);
}
});
await next();
});
}
Whenever i send a message to the bot the this.onMessage is getting invoked. However, when i add a new member to a group where my bot is already present, the onMembersAddedActivity is not invoked. what i am missing here?
This is partially an issue in our docs and code comments, which I addressed here and here, respectively. The other issue is that you're using <method>Activity() instead of <method>Event().
The latest instructions are in the code comments, which just got merged, but basically,
Developers may handle Conversation Update activities sent from Microsoft Teams via two methods:
Overriding methods starting with on.. and not ending in ..Event() (e.g. onTeamsMembersAdded()), or instead
Passing callbacks to methods starting with on.. and ending in ...Event() (e.g. onTeamsMembersAddedEvent()),
to stay in line with older {#link ActivityHandler} implementation.
Developers should use either #1 or #2, above for all Conversation Update activities and not both #2 and #3 for the same activity. Meaning,
developers should override onTeamsMembersAdded() and not use both onTeamsMembersAdded() and onTeamsMembersAddedEvent().
Developers wanting to handle Invoke activities must override methods starting with handle...() (e.g. handleTeamsTaskModuleFetch()).
So, for you, you can either:
constructor() {
[...]
// This is passing in a callback
this.onTeamsMembersAddedEvent(async (
membersAdded: TeamsChannelAccount[],
teamInfo: TeamInfo,
context: TurnContext,
next: () => Promise<void>) => {
functions.logger.log("start of onMembersAddedActivity", context)
context.activity.membersAdded.forEach(async (teamMember:any) => {
if (teamMember.id !== context.activity.recipient.id) {
await context.sendActivity(`Welcome to the team ${ teamMember.givenName } ${ teamMember.surname }`);
}
});
await next();
});
}
or
constructor() {
[...]
}
[...]
// This is an override
async onTeamsMembersAdded(context: TurnContext): Promise<void> {
functions.logger.log("start of onMembersAddedActivity", context)
context.activity.membersAdded.forEach(async (teamMember:any) => {
if (teamMember.id !== context.activity.recipient.id) {
await context.sendActivity(`Welcome to the team ${ teamMember.givenName } ${ teamMember.surname }`);
}
});
}

Why Hook is called in all update services methods

I'm create a hook file with the following information, which is Hooks.js
Hooks.js is working to authenticate an actions with JWT when need it, I dont need it in all servies calls.
As my understanding the syntax to call a hook was app/use route/hooks and those hooks were only applied to and specific route and not globally.
module.exports = {
errorHandler: (context) => {
if (context.error) {
context.error.stack = null;
return context;
}
},
isValidToken: (context) => {
const token = context.params.headers.authorization;
const payload = Auth.validateToken(token);
console.log(payload);
if(payload !== "Invalid" && payload !== "No Token Provided"){
context.data = payload._id;
}
else {
throw new errors.NotAuthenticated('Authentication Error Token');
}
},
isValidDomain: (context) => {
if (
config.DOMAINS_WHITE_LIST.includes(
context.params.headers.origin || context.params.headers.host
)
) {
return context;
}
throw new errors.NotAuthenticated("Not Authenticated Domain");
},
normalizedId: (context) => {
context.id = context.id || context.params.route.id;
},
normalizedCode: (context) => {
context.id = context.params.route.code;
},
};
Then I create a file for services and routes, like the following:
const Hooks = require("../../Hooks/Hooks");
const userServices = require("./user.services");
module.exports = (app) => {
app
.use("/users", {
find: userServices.find,
create: userServices.createUser,
})
.hooks({
before: {
find: [Hooks.isValidDomain],
create: [Hooks.isValidDomain],
},
});
app
.use("/users/:code/validate", {
update: userServices.validateCode,
})
.hooks({
before: {
update: [Hooks.isValidDomain, Hooks.normalizedCode],
},
});
app
.use("/users/personal", {
update: userServices.personalInfo,
})
.hooks({
before: {
update: [Hooks.isValidDomain, Hooks.isValidToken],
},
});
};
Why Hooks.isValidToken applies to all my update methods? Even if I'm not calling it?
Please help.
app.hooks registers an application level hook which runs for all services. If you only want it for a specific service and method it needs to be app.service('users').hooks().

How to test function in class using jest

I wasn't able to make unit testing worked using jest
I'm trying to test a specific function that's calling or expecting result from other function but I'm not sure why it is not working. I'm pretty new to unit testing and really have no idea how could I make it work. currently this is what I've tried
export class OrganizationService {
constructor() {
this.OrganizationRepo = new OrganizationRepository()
}
async getOrganizations(req) {
if (req.permission !== 'internal' && req.isAuthInternal === false) {
throw new Error('Unauthenticated')
}
const opt = { deleted: true }
return this.OrganizationRepo.listAll(opt)
}
}
This is my OrganizationRepository that extends the MongoDbRepo
import { MongoDbRepo } from './mongodb_repository'
export class OrganizationRepository extends MongoDbRepo {
constructor(collection = 'organizations') {
super(collection)
}
}
and this is the MongoDbRepo
const mongoClient = require('../config/mongo_db_connection')
const mongoDb = require('mongodb')
export class MongoDbRepo {
constructor(collectionName) {
this.collection = mongoClient.get().collection(collectionName)
}
listAll(opt) {
return new Promise((resolve, reject) => {
this.collection.find(opt).toArray((err, data) => {
if (err) {
reject(err)
}
resolve(data)
})
})
}
}
and this is the test that I've made
import { OrganizationService } from '../../../src/services/organization_service'
describe('getOrganizations', () => {
it('should return the list of organizations', () => {
// const OrgRepo = new OrganizationRepository()
const orgService = new OrganizationService()
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]")
// orgService.getOrganizations = jest.fn().mockReturnValue('')
const result = orgService.getOrganizations()
expect(result).toBe(OrgRepo)
})
})
I see two issues in the way you are testing:
1.
You are trying to test an asynchronous method, and on your test, you are not waiting for this method to be finished before your expect statement.
A good test structure should be:
it('should test your method', (done) => {
const orgService = new OrganizationService();
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]")
orgService.getOrganizations()
.then((result) => {
expect(result).toEqual(OrgRepo); // I recommend using "toEqual" when comparing arrays
done();
});
})
Don't forget to put done as a parameter for your test!
You can find more about testing asynchronous functions on the Jest official documentation.
2.
In order to test your method properly, you want to isolate it from external dependencies. Here, the actual method OrganizationRepo.listAll() is called. You want to mock this method, for instance with a spy, so that you control its result and only test the getOrganizations method. That would look like this:
it('should test your method', (done) => {
const req = {
// Whatever structure it needs to be sure that the error in your method is not thrown
};
const orgService = new OrganizationService();
const orgRepoMock = spyOn(orgService['OrganizationRepo'], 'listAll')
.and.returnValue(Promise.resolve("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]"));
const OrgRepo = jest.fn().mockReturnValue("[{_id: '123', name: 'testname'}, {_id: '456, name: 'testname2'}]");
orgService.getOrganizations(req)
.then((result) => {
expect(result).toEqual(OrgRepo); // I recommend using "toEqual" when comparing arrays
expect(orgRepoMock).toHaveBeenCalled(); // For good measure
done();
});
})
This way, we make sure that your method is isolated and its outcome cannot be altered by external methods.
For this particular method, I also recommend that you test the error throwing depending on the input of your method.
I was able to answer this, first is I mocked the repository using
jest.mock('path/to/repo')
const mockGetOne = jest.fn()
OrganizationRepository.protorype.getOne = mockGetOne
then the rest is the test

Maintaining native module dependencies for node.js and electron at the same time

I am trying to build an Electron app that requires nodegit, which is a native module. As far as I know, a native module's native library must targets the same NODE_MODULE_VERSION as the run-time engine (I mean Node.js or Electron) does.
For example, if my Electron runs with NODE_MODULE_VERSION 64, then my nodegit should be installed with a native library that targets NODE_MODULE_VERSION 64.
Current I have some tests in my project, and I would like to run them on both Electron and Node.js. Because (1) Electron is closer to the environment of the final product and (2) Node.js is much easier to debug.
To achieve this goal, the native module must be compatible with both Electron and Node.js at the same time. However, this is nearly impossible.
The funny thing is that, from the charts that list the NODE_MODULE_VERSION of Electron versions (it is called Chrome version in this chart) and Node.js versions, their NODE_MODULE_VERSION rarely match. It is hard to find a Electron version that uses a Node.js which also use the same NODE_MODULE_VERSION. As a consequence, I have to settle down with Electron and Node.js using different NODE_MODULE_VERSION. In other words, the native module can only be compatible with either Electron or Node.js, not both of them.
I am curious about if it is possible to separate the native module used by Node.js and Electron without rebuilding the module or is there a version switching functionality to let me quickly switch the version of the native module?
Or it would be event better if anyone can share a way to make the Electron and Node.js use the same NODE_MODULE_VERSION.
Don't know if there is a better solution, I came up with an extremely simple script that copy and paste module files with environment selection (attached below).
Would still greatly appreciate any good idea about how to solve this problem.
'use strict';
const fs = require('fs-extra');
const path = require('path');
let args = process.argv.slice(2);
let cachePath = path.resolve(__dirname, '../.module_cache');
let configPath = path.join(cachePath, '.config');
let modulePath = path.resolve(__dirname, '../node_modules');
wrapper(args)
.catch(err => {
console.error(err);
})
function wrapper(args) {
switch (args[0]) {
case 'save':
return saveModule(args[1], args[2]);
case 'load':
return loadModule(args[1], args[2]);
case 'drop':
if (args.length === 3) {
return dropModuleEnvironment(args[1]);
}
else {
return dropModule(args[1]);
}
case 'ls':
return listModules();
case 'which':
return printWhichModule(args[1]);
case 'versions':
return listModuleVersions(args[1]);
case 'help':
printUsage();
return Promise.resolve();
default:
printUsage();
return Promise.reject(new Error("Unexcepted arguments: " + args.join(', ')));
}
}
function printUsage() {
console.log(`
Usage:
save <module> <environment>: cache a module environment for later use
load <module> <environment>: load a previously saved module environment, and set it to be active
drop <module> [environment]: remove all cached module environments,
or when [environment] is provided, remove the specified environment
ls: list all cached modules and their current environment
which <module>: show the active environment for the module
versions <module>: list all available environments for the module. Active environment is marked by "*"
help: show this help info`);
}
function saveModule(moduleName, envName) {
let storePath = path.join(cachePath, moduleName, envName);
let sourcePath = path.join(modulePath, moduleName);
return fs.emptyDir(storePath)
.then(() => {
return fs.copy(sourcePath, storePath);
})
.then(() => {
return updateConfig(moduleName, ".system.");
});
}
function loadModule(moduleName, envName) {
let storePath = path.join(cachePath, moduleName, envName);
let targetPath = path.join(modulePath, moduleName);
return whichModuleVersion(moduleName)
.then(currentVersion => {
if (currentVersion === envName) {
console.log(`Not loading ${envName} for ${moduleName} because it is current version`);
return Promise.resolve();
}
else {
return fs.emptyDir(targetPath)
.then(() => {
return fs.copy(storePath, targetPath);
})
.then(() => {
return updateConfig(moduleName, envName);
})
}
})
}
function dropModuleEnvironment(moduleName, envName) {
let storePath = path.join(cachePath, moduleName, envName);
return fs.remove(storePath)
.then(() => {
return fs.readFile(configPath)
.then(configRaw => {
let config = JSON.parse(configRaw);
let currentEnv = config[moduleName];
if (currentEnv && currentEnv === envName) {
config[currentEnv] = '.system.';
}
return JSON.stringify(config);
})
.then(configRaw => {
return fs.writeFile(configPath, configRaw);
});
});
}
function dropModule(moduleName) {
return fs.remove(path.join(cachePath, moduleName))
.then(() => {
return fs.readFile(configPath)
.then(configRaw => {
let config = JSON.parse(configRaw);
if (config[moduleName]) {
delete config[moduleName];
}
return JSON.stringify(config);
})
.then(configRaw => {
return fs.writeFile(configPath, configRaw);
});
})
}
function listModules() {
return fs.readFile(configPath)
.then(configRaw => {
let config = JSON.parse(configRaw);
Object.keys(config).forEach(moduleName => {
printModuleVersion(moduleName, config[moduleName]);
})
})
}
function printWhichModule(moduleName) {
return whichModuleVersion(moduleName)
.then(version => {
printModuleVersion(moduleName, version);
});
}
function listModuleVersions(moduleName) {
let modulePath = path.join(cachePath, moduleName);
return fs.exists(modulePath)
.then(exists => {
if (exists) {
let currentVersion;
return whichModuleVersion(moduleName)
.then(version => currentVersion = version)
.then(() => fs.readdir(modulePath))
.then(envNames => {
envNames.forEach(envName => {
if (currentVersion === envName) {
console.log('* ' + envName);
}
else {
console.log(' ' + envName);
}
});
});
}
else {
console.log('not installed');
}
})
}
function whichModuleVersion(moduleName) {
return fs.readFile(configPath)
.then(configRaw => {
let config = JSON.parse(configRaw);
return config[moduleName];
});
}
function printModuleVersion(moduleName, moduleVersion) {
console.log(`${moduleName}: ${moduleVersion || 'not installed'}`);
}
function updateConfig(moduleName, envName) {
return fs.readFile(configPath)
.then(configRaw => {
let config = JSON.parse(configRaw);
config[moduleName] = envName;
return JSON.stringify(config);
})
.then(configRaw => {
fs.writeFile(configPath, configRaw);
})
}

Resources