Discord.js | interaction.reply is not a function [SlashCommand][Typescript] - node.js

I am making a /ping command in my bot, following the discordjs guide, but when I use the command, I get an error:
TypeError: interaction.reply is not a function
at Object.<anonymous> (C:\Users\timda\code\discord bot\bot-data\commands\ping.ts:8:15)
at Generator.next (<anonymous>)
at C:\Users\timda\code\discord bot\bot-data\commands\ping.ts:8:71
at new Promise (<anonymous>)
at __awaiter (C:\Users\timda\code\discord bot\bot-data\commands\ping.ts:4:12)
at Object.execute (C:\Users\timda\code\discord bot\bot-data\commands\ping.ts:18:16)
at C:\Users\timda\code\discord bot\main.ts:43:18
at Generator.next (<anonymous>)
at C:\Users\timda\code\discord bot\main.ts:8:71
at new Promise (<anonymous>)
This is the /ping commands code:
import { SlashCommandBuilder } from 'discord.js';
module.exports = {
data: new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with Pong!'),
async execute(interaction: { reply: (arg0: string) => any }) {
console.log(interaction);
interaction.reply('Pong!');
},
};
And here is the code in my main file that loads in the SlashCommand files:
import fs from 'node:fs';
import path from 'node:path';
import { Client, Collection, GatewayIntentBits, Partials, REST, Routes } from 'discord.js';
import { clientId, token } from './config.json';
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.DirectMessages, GatewayIntentBits.MessageContent],
partials: [Partials.Message, Partials.Channel],
presence: { status: 'online', activities: [{ name: 'you 👀', type: 3 }] }
});
(client as any).commands = new Collection();
const commands: any[] = [];
for (const file of (fs.readdirSync(path.join('./bot-data/commands')).filter(file => file.endsWith('.ts')))) {
const command = require(`./bot-data/commands/${file}`);
(client as any).commands.set(command.data.name, command);
commands.push(command.data.toJSON());
};
client.on('ready', async (client: { user: { tag: any; }; }) => {
const rest = new REST({ version: '10' }).setToken(token);
try {
await rest.put(Routes.applicationCommands(clientId), { body: commands});
} catch (error) {
console.error(error);
}
console.log(`Succesfully logged in as ${client.user.tag}!`);
});
client.on('interactionCreate', async interaction => {
if(interaction.isCommand()) {
const command = (client as any).commands.get(interaction.commandName);
if (!command) {
await interaction.reply({content: `Something went wrong while executing ${interaction.commandName}.`, ephemeral: true});
return;
}
try {
await command.execute(client, interaction);
} catch (error) {
await interaction.reply({content: `Something went wrong while executing ${interaction.commandName}., ephemeral: true});
console.error(error);
}
}
});
The command does get loaded when /ping is used, because it does log the interaction in the console.
I am writing the bot in Typescript, that's why I did interaction: { reply: (arg0: string) => any }
and I'm using node to compile and run it.
NVM: version 1.1.10
NPM: version 8.19.2
I've tried writing interaction: { reply: (arg0: string) => any } in different ways and I've also already done a lot of googling, but I can't find the problem I'm facing here.
If someone could help me I would appreciate it.

Inside of main.ts, you're calling the execute function with these arguments:
await command.execute(client, interaction);
But inside of ping.ts, you're only accepting these arguments:
async execute(interaction: { reply: (arg0: string) => any }) { // etc
You need to include both client and interaction as arguments in ping.ts to prevent the values being passed into the wrong variables.
// I'm fairly certain that these are the right imports
import { BaseInteraction, SlashCommandBuilder, Client} from 'discord.js';
...
async execute(client: Client, interaction: BaseInteraction) { // etc

Related

Why am I only getting Mailgun.js error in Cloud Run?

I'm trying to send an email using Mailgun's npm client - Mailgun.js.
When sending in development mode, everything works correctly. But when I upload the Node server to Cloud Run, something breaks.
Here is the code in the sendEmail helper file:
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const sendEmail = async ({ to, subject, text, attachment, scheduledDate }) => {
const mailgun = new Mailgun(formData);
const mg = mailgun.client({
username: 'api',
key: process.env.MAILGUN_KEY,
url: 'https://api.eu.mailgun.net'
});
const data = {
from: `<myemail#mydomain.com>`,
to,
subject,
text
};
if (attachment) {
data.attachment = attachment;
}
if (scheduledDate) {
data['o:deliverytime'] = new Date(scheduledDate).toUTCString();
}
try {
const result = await mg.messages.create(process.env.MAILGUN_DOMAIN, data);
if (result.status && result.status !== 200) {
throw ({ code: result.status, message: result.message });
}
return true;
} catch(err) {
console.log(err);
return { error: err };
}
};
export default sendEmail;
And then in another file:
import { Router } from 'express';
import generateInvoicePDF from '../helpers/generateInvoicePDF.js';
import sendEmail from '../helpers/sendEmail.js';
const router = Router();
router.post('/email', async (req, res, next) => {
try {
const file = await generateInvoicePDF(invoice);
if (file?.error) {
throw ({ code: pdf.error.code, message: pdf.error.message });
}
const email = await sendEmail({
to: 'testemail#example.com',
subject: 'Invoice',
text: 'Test',
attachment: { filename: 'Invoice', data: file }
});
if (email?.error) {
throw ({ code: email.error.code, message: email.error.message });
}
res.status(200).json({ success: true });
} catch(err) {
next(err);
}
});
export default router;
The error I get when in production mode in Cloud Run's logs is:
TypeError: fetch failed
at Object.processResponse (node:internal/deps/undici/undici:5575:34)
at node:internal/deps/undici/undici:5901:42
at node:internal/process/task_queues:140:7
at AsyncResource.runInAsyncScope (node:async_hooks:202:9)
at AsyncResource.runMicrotask (node:internal/process/task_queues:137:8) {
cause: TypeError: object2 is not iterable
at action (node:internal/deps/undici/undici:1661:39)
at action.next (<anonymous>)
at Object.pull (node:internal/deps/undici/undici:1709:52)
at ensureIsPromise (node:internal/webstreams/util:172:19)
at readableStreamDefaultControllerCallPullIfNeeded (node:internal/webstreams/readablestream:1884:5)
at node:internal/webstreams/readablestream:1974:7
}
Why the hell does it work in development mode on my local machine, but not when uploaded to Cloud Run?
For anyone struggling with something similar - I eventually figured out the problem.
On my local machine, where everything was working as expected, I'm using Node v16.15.0, whereas in the Dockerfile, I had specified
FROM node:latest
and therefore Cloud Run was using a newer version, which led to the problems...
I've now deployed using version 16.15.0 and everything works fine

How to make kuzzle-device-manager plugin API actions works?

I successfully installed and loaded kuzzle-device-manager in the backend file:
import { Backend } from 'kuzzle';
import { DeviceManagerPlugin } from 'kuzzle-device-manager';
const app = new Backend('playground');
console.log(app.config);
const deviceManager = new DeviceManagerPlugin();
const mappings = {
updatedAt: { type: 'date' },
payloadUuid: { type: 'keyword' },
value: { type: 'float' }
}
deviceManager.devices.registerMeasure('humidity', mappings)
app.plugin.use(deviceManager)
app.start()
.then(async () => {
// Interact with Kuzzle API to create a new index if it does not already exist
console.log(' started!');
})
.catch(console.error);
But when i try to use controllers from that plugin for example device-manager/device with create action i get an error output.
Here is my "client" code in js:
const { Kuzzle, WebSocket } = require("kuzzle-sdk")
const kuzzle = new Kuzzle(
new WebSocket('KUZZLE_IP')
)
kuzzle.on('networkError', error => {
console.error('Network Error: ', error);
})
const run = async () => {
try {
// Connects to the Kuzzle server
await kuzzle.connect();
// Creates an index
const result = await kuzzle.query({
index: "nyc-open-data",
controller: "device-manager/device",
action: "create",
body: {
model: "model-1234",
reference: "reference-1234"
}
}, {
queuable: false
})
console.log(result)
} catch (error) {
console.error(error.message);
} finally {
kuzzle.disconnect();
}
};
run();
And the result log:
API action "device-manager/device":"create" not found
Note: The nyc-open-data index exists and is empty.
We apologize for this mistake in the documentation, the device-manager/device:create method is not available because the plugin is using auto-provisioning until the v2.
You should send a payload to your decoder, the plugin will automatically provision the device if it does not exists https://docs.kuzzle.io/official-plugins/device-manager/1/guides/decoders/#receive-payloads

Jest Test suite failed to run - AssertionError [ERR_ASSERTION]: opts.entryPoint required

We had a test running successfully for our code that uses the notifications-node-client to send emails, eg
import { NotifyClient } from 'notifications-node-client';
import notify from './notify';
import config from '../../../config';
jest.mock('fs');
jest.mock('notifications-node-client');
describe('notify', () => {
const EMAIL = 'user#test.com';
const REGION_GB = 'gb';
const REGION_NI = 'ni';
const feedbackOptions = {
personalisation: {
satisfactionLevel: 'satisfied',
improvements: 'improvements',
},
};
test('for original GB submission sendEmail should be called and true returned', async () => {
NotifyClient.prototype.sendEmail.mockReturnValue({
body: {
id: 1,
},
});
expect(await notify(EMAIL, 'submissionSuccess', REGION_GB)).toBeTruthy();
expect(NotifyClient.prototype.sendEmail).toHaveBeenCalledWith(
config.notify.templates.submissionSuccess.gb,
EMAIL,
undefined,
);
});
...
Having swapped out our Winston logging solution to use #dwp/node-logger, the 'notify' tests now do not run, failing with
● Test suite failed to run
AssertionError [ERR_ASSERTION]: opts.entryPoint required
at new Thrift (node_modules/thriftrw/thrift.js:64:5)
at Object.<anonymous> (node_modules/jaeger-client/src/thrift.js:21:20)
at Object.<anonymous> (node_modules/jaeger-client/src/reporters/remote_reporter.js:15:1)
All of the other test suites in the project still run successfully.
Could anyone point me in the right direction about what change to make?
The code that we're testing is
import { NotifyClient } from 'notifications-node-client';
import config from '../../../config/index';
import { RegionKeys } from '../../../config/types';
import logger from '../../../xxxx/lib/logger';
type TemplateId = 'submissionSuccess' | 'supportingEvidence' | 'feedbackTemplateId';
type FeedbackOptions = {
personalisation: {
satisfactionLevel: string,
improvements: string,
},
reference?: string,
}
const { apiKey, templates, proxy } = config.notify;
export default async function notify(
email: string,
templateId: TemplateId,
region: RegionKeys,
options?: FeedbackOptions,
): Promise<boolean> {
let notifyTemplate;
let notifyApiKey;
let notifyOptions;
try {
if (templateId === 'feedbackTemplateId' && !options) {
throw new Error(`Unable to send email - mismatch between template ID (${templateId}) and options supplied`);
} else if (options && templateId !== 'feedbackTemplateId') {
notifyOptions = {};
} else {
notifyOptions = options;
}
const notifyClient = new NotifyClient(apiKey[region]);
notifyClient.setProxy(proxy);
notifyTemplate = templateId === 'feedbackTemplateId' ? templates.feedbackTemplateId : templates[templateId][region];
logger.debug(`apiKey: ${notifyApiKey}`);
logger.debug(`notify template Id: ${notifyTemplate}`);
logger.debug(`proxy: ${JSON.stringify(proxy)}`);
const response = await notifyClient.sendEmail(notifyTemplate, email, notifyOptions);
if (response.body) {
logger.info(`confirmation email sent to ${email} and relates to message id ${response.body.id}`);
}
return true;
} catch (err: any) {
logger.error(`there was an error sending the message: ${err.message}`);
return false;
}
}

When I run my bot, the `ready` event doesn't fire but the bot is online

When I run my bot, the ready event doesn't fire but the bot is online. My event handler is in the start method in the Client.ts class,
and I execute the start method in the index.ts file.
My client class: ./classes/Client.ts
import { Client as DiscordClient, ClientOptions, Collection } from 'discord.js';
import fs from 'fs';
import path from 'path';
export class Client extends DiscordClient {
commandarray: any[] = [];
commands: Collection<string, any> = new Collection();
constructor (options: ClientOptions, token: string) {
super(options);
this.login(token);
}
async start() {
//Event Handler
const eventDirectories = await fs.readdirSync('./events');
for (const dir of eventDirectories) {
const eventFiles = await fs.readdirSync(`./events/${dir}`).filter(file => file.endsWith(".ts"));
if (eventFiles.length <= 0)
return console.log("[EVENT HANDLER] - Cannot find any events!");
for (const file of eventFiles) {
const event = require(`../events/${dir}/${file}`);
if (event.once) {
this.once(event.name, (...args) => event.execute(...args, this));
} else {
this.on(event.name, (...args) => event.execute(...args));
}
}
}
// Slash Command Handler
const cmdDirectories = await fs.readdirSync('./commands');
for (const dir of cmdDirectories) {
const cmdFiles = await fs.readdirSync(`./commands/${dir}`).filter(file => file.endsWith(".ts"));
if (cmdFiles.length <= 0)
return console.log("[COMMAND HANDLER] - Cannot find any commands!");
for (const file of cmdFiles) {
const command = require(`../commands/${dir}/${file}`)
await this.commandarray.push(command);
await this.commands.set(command.name, command);
}
}
}
};
My index.ts file: ./index.ts
import { Client } from "./classes/Client";
import { config } from "dotenv";
config();
export const client: Client = new Client({ intents: 515 }, process.env.token!);
client.start();
My ready event: ./events/Client/ready.ts
import { Client } from '../../classes/Client';
export default {
name: 'ready',
once: true,
async execute(client: Client) {
await console.log(`Logged in as ${client.user?.tag}`);
await client.application?.commands.set(client.commandarray);
}
}
Edit, I fixed the first issue thanks to Rahuletto, but found a new one and have updated the question accordingly.
It's a wrong path. You should show ur path structure (file format).
I think it's ../events/Client/ready.ts.
I used export default to export the event
So, instead of using event.<property/function> in the handler, I should use event.default.<property/function>

How to mock the api call in jest for saga test

I am writing tests to test my saga. Can anyone guide me how I can change the code below so that I can mock the api call? I don`t want to test real data.
import { call, put } from 'redux-saga/effects';
import { API_BUTTON_CLICK_SUCCESS, } from './actions/consts';
import { getDataFromAPI } from './api';
it('apiSideEffect - fetches data from API and dispatches a success action', () => {
const generator = apiSideEffect();
expect(generator.next().value)
.toEqual(call(getDataFromAPI));
expect(generator.next().value)
.toEqual(put({ type: API_BUTTON_CLICK_SUCCESS }));
expect(generator.next())
.toEqual({ done: true, value: undefined });
});
The getDataFromAPI()
import axios from "axios";
export const getDataFromAPI =(
method: string,
url: string,
path: string,
data?: any
) =>{
switch (method) {
case "create": {
return axios
.post(url + path, data, {
headers: {
Accept: "application/json",
"content-type": "application/json"
}
})
.catch(error => {
throw error.response;
});
}
I have tried to use
jest.mock('../../src/Utilities/api');
const { callApi } = require('../../src/Utilities/api');
callApi.mockImplementation( () => console.log("some api call"));
I am having the error
TypeError: Cannot read property 'mockImplementation' of undefined
at Object.<anonymous> (src/Payments/PaymentSagas.spec.ts:10:17)
at new Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
I usually do
import * as apis from '../../src/Utilities/api';
jest.spyOn(api, "callApi");
api.callApi.mockImplementation(/* your mock */);
easily exportable as a per-se function
export function spyUtil(obj, name, mockFunction = undefined) {
const spy = jest.spyOn(obj, name);
let mock;
if (mockFunction) {
mock = jest.fn(mockFunction);
obj[name].mockImplementation(mock);
}
return { spy, mock };
}
and consumable, in your test
spyUtil(apis, "callApi", jest.fn())

Resources