start telegraf scene only by command using telegraf.js - node.js

I'm trying to start some scene only by exact command /start
I'm using Telegraf.js library
that's not going well when I do middleware it starts automatically when I send some other input and not just /start
how can I solve it? thanks.
bot.use(session())
const userWizard = new WizardScene('user-wizard',
(ctx) => {
ctx.reply("What is your name?");
//Necessary for store the input
ctx.scene.session.user = {};
//Store the telegram user id
ctx.scene.session.user.userId = ctx.from.id;
return ctx.wizard.next();
},
(ctx) => {
//Validate the name
if (ctx.message.text.length < 1 || ctx.message.text.length > 12) {
return ctx.reply("Name entered has an invalid length!");
}
//Store the entered name
ctx.scene.session.user.name = ctx.message.text;
ctx.reply("What is your last name?");
return ctx.wizard.next();
},
async (ctx) => {
//Validate last name
if (ctx.message.text.length > 30) {
return ctx.reply("Last name has an invalid length");
}
ctx.scene.session.user.lastName = ctx.message.text;
//Store the user in a separate controller
// userController.StoreUser(ctx.scene.session.user);
return ctx.scene.leave(); //<- Leaving a scene will clear the session automatically
}
);
const stage = new Stage([userWizard], { default: 'user-wizard' })
bot.use(stage)
bot.command('/start', ctx => {
stage.start(ctx)
}
)
bot.launch()

Try to replace this part in your code:
const stage = new Stage([userWizard], { default: 'user-wizard' })
bot.use(stage)
bot.command('/start',ctx => {
stage.start(ctx)
}
)
With this:
const stage = new Stage([userWizard]);
bot.use(session());
bot.use(stage.middleware());
bot.command('/start',(ctx) => ctx.scene.enter('user-wizard'));
bot.launch();

Related

How to edit/replace embeds in discordjs-commando

I am making a Type racing minigame with my discord bot, the code works... but I want to change the messages it sends to embeds, im new to Commando and it wont let me use the discord.js functions im used to using
I need to change all the bots responses to embeds, and make it so when it sends a new embed it just edits the old one so it isnt spamming messages. Here is my code:
const Commando = require('discord.js-commando')
const { words } = require('../../util/fast-type-words')
const example = {
channelId: {
message: 'message object',
stage: 'string',
counter: 'number',
currentWord: 'string',
remainingWords: ['words here'],
points: {
userId: 'points',
},
},
}
const games = {}
const stages = {
STARTING: (counter) => {
return `A new "fast type" game is starting in ${counter}s!`
},
IN_GAME: (word) => {
let spacedWord = ''
for (const character of [...word]) {
spacedWord += character
spacedWord += ' '
}
return `The new word is **${spacedWord}**!`
},
ENDING: (points) => {
const sorted = Object.keys(points).sort((a, b) => {
return points[b] - points[a]
})
let results = ''
for (const key of sorted) {
const amount = points[key]
results += `<#${key}> had ${amount} point${amount === 1 ? '' : 's'}\n`
}
return `The game is now over Here's how everyone did:\n\n${results}------------------`
},
}
const selectWord = (game) => {
game.currentWord =
game.remainingWords[Math.floor(Math.random() * game.remainingWords.length)]
const index = game.remainingWords.indexOf(game.currentWord)
game.remainingWords.splice(index, 1)
}
const gameLoop = () => {
for (const key in games) {
const game = games[key]
const { message, stage } = game
if (stage === 'STARTING') {
let string = stages[stage](game.counter)
message.edit(string)
if (game.counter <= 0) {
game.stage = 'IN_GAME'
game.counter = 15
selectWord(game)
string = stages[game.stage](game.currentWord)
message.edit(string)
}
} else if (stage === 'IN_GAME') {
if (game.counter <= 0) {
game.stage = 'ENDING'
const string = stages[game.stage](game.points)
message.edit(string)
// Delete the game
delete games[key]
continue
}
}
--game.counter
}
setTimeout(gameLoop, 1000)
}
module.exports = class FastTypeGame extends Commando.Command {
constructor(client) {
super(client, {
name: 'fasttype',
group: 'games',
memberName: 'fasttype',
description: 'Starts a fast type game',
userPermissions: ['ADMINISTRATOR'],
})
client.on('message', (message) => {
const { channel, content, member } = message
const { id } = channel
const game = games[id]
if (game && game.currentWord && !member.user.bot) {
message.delete()
if (
game.stage === 'IN_GAME' &&
content.toLowerCase() === game.currentWord.toLowerCase()
) {
game.currentWord = null
const seconds = 2
const { points } = game
points[member.id] = points[member.id] || 0
message
.reply(`You won! +1 point (${++points[member.id]} total)`)
.then((newMessage) => {
newMessage.delete({
timeout: 1000 * seconds,
})
})
setTimeout(() => {
if (game.stage === 'IN_GAME') {
selectWord(game)
const string = stages[game.stage](game.currentWord)
game.message.edit(string)
}
}, 1000 * seconds)
}
}
})
gameLoop()
}
async run(message) {
const { channel } = message
message.delete()
channel.send('Preparing game...').then((message) => {
games[channel.id] = {
message,
stage: 'STARTING',
counter: 5,
remainingWords: [...words],
points: {
'719805930547445772': 4,
'723819104045105172': 1,
},
}
})
}
}
First change embeded content is not related to discord.js-commando to change the content of sended embeded message you need to get Message Object then using edit() method to pass the new embed content to it:
-Bonus: You can also edit text message into embed message.
Docs for edit method: https://discord.js.org/#/docs/main/stable/class/Message?scrollTo=edit
Example code:
let youCurrentMessage = await channel.send(embedContent);
yourCurrentMessage.edit(newEmbedContent);
yourCurrentMessage.edit(newEmbedContent2);
// If you edit message in other command , session.You need message id
let yourCurrentMessage = await msg.channel.messages.fetch(editMessageId);
yourCurrentMessage.edit(newEmbedContent);

Unhandled rejection Error: A required argument is missing

so I was using this file called revertranks.js and I tried to run it with node revertranks.js.
When I run it, there would be this error saying the following:
"Unhandled rejection Error: A required argument is missing"
I was wondering why this was happening so I was checking this website and tried other solutions, however, none worked. One which made the error go away actually just made the code stop.
Here is the code:
* About:
* Reverts all rank change actions of a group after a specified date, with the option to filter by user.
*/
// Settings
const cookie = process.env.Cookie || '' // Roblox account .ROBLOSECURITY cookie
const options = {
group: (process.env.GroupId), // Group ID
userId: (process.env.AA1), // Revert rank changes made by this specified user - NOTE: When <= 0 any rank changes will be reverted
afterDate: new Date('2021-01-01 00:00 CDT') // Date after which any rank changes will be reverted
}
// Dependencies
const rbx = require('noblox.js')
const logUpdate = require('log-update')
// Main
let scanning = true
const logItems = {
scanned: 0,
filtered: 0,
reverted: 0,
failed: 0
}
async function getAuditLogPage (getAuditLogOptions, cursor) {
getAuditLogOptions.cursor = cursor || ''
const auditLogPage = await rbx.getAuditLog(getAuditLogOptions)
return auditLogPage
}
function filterAuditLogItems (auditLogItems) {
const filteredAuditLogItems = []
for (const auditLogItem of auditLogItems) {
if (Date.parse(auditLogItem.created) > options.afterDate) {
logItems.filtered++
filteredAuditLogItems.push(auditLogItem.description)
}
}
return filteredAuditLogItems
}
async function revertAuditLogItems (auditLogItems) {
for (const auditLogItem of auditLogItems) {
const setRankOptions = {
group: options.group,
target: auditLogItem.TargetId,
roleset: auditLogItem.OldRoleSetId
}
await rbx.setRank(setRankOptions)
.then(() => {
logItems.reverted++
})
.catch((e) => {
logItems.failed++
})
}
}
rbx.setCookie(process.env.Cookie)
.then(async () => {
console.time('Time taken')
const logUpdater = setInterval(() => {
logUpdate(`Scanned: ${logItems.scanned}\nFiltered: ${logItems.filtered}\nReverted: ${logItems.reverted}\nFailed: ${logItems.failed}`)
if (!scanning && logItems.reverted + logItems.failed === logItems.filtered) {
clearInterval(logUpdater)
console.timeEnd('Time taken')
}
}, 100)
const getAuditLogOptions = {
group: options.group,
actionType: 'changeRank',
userId: options.userId > 0 ? options.userId : null,
limit: 100
}
let auditLogPage = await getAuditLogPage(getAuditLogOptions)
logItems.scanned += auditLogPage.data.length
await revertAuditLogItems(filterAuditLogItems(auditLogPage.data))
while (auditLogPage.nextPageCursor !== null && Date.parse(auditLogPage.data[auditLogPage.data.length - 1].created) > options.afterDate) {
auditLogPage = await getAuditLogPage(getAuditLogOptions, auditLogPage.nextPageCursor)
logItems.scanned += auditLogPage.data.length
await revertAuditLogItems(filterAuditLogItems(auditLogPage.data))
}
scanning = false
})
Please help me, I need this to revert an action on a website using a bot.
Here is the error message since someone asked: https://imgur.com/a/U9HJW7O
I apologize for the previous message, I put some of the code I was experimenting with.

stub never called with sinon and nodejs using chai-as-promised

i'm facing a issue with my unit test, stuck completely, the code is simple, please need to understand what's going on, my stub is never called, the set seems to be correct, here the code:
let strategy = fixtures.load('strategy')
chai.use(chaiAsPromised)
describe.only('Spawn Order Job', () => {
let getPositionsStub, createJobStub, daoStub,sandbox
beforeEach(()=>{
sandbox = sinon.createSandbox()
daoStub = sandbox.stub(dao, 'updateActiveOrders').resolves(true) //async
getPositionsStub = sandbox.stub(strategyModule, 'getPositions') //sync
createJobStub = sandbox.stub(helpers, 'createJob') //sync
createJobStub.returns(true)
getPositionsStub.resolves([{fake:'t'}, {fake:'t'}])
})
afterEach(()=>{
sandbox.restore()
})
//OK
it('Should failed with no param, type error context', ()=> {
const promise = spawnOrderJob()
expect(promise).to.be.rejectedWith(TypeError)
})
//OK
it('Should throw error timeout order', () => {
getPositionsStub.resolves([{fake:'t'}, {fake:'t'}])
strategy.lastDateOrder = new Date()
const ctx = { state: {strategy, dashboard, position:null}}
const action = {b: true, s: false}
const promise = spawnOrderJob(action, ctx)
expect(getPositionsStub.called).to.be.true
expect(daoStub.called).to.be.false
expect(createJobStub.called).to.be.false
expect(promise).to.be.rejectedWith(ORDER_ERROR, 'Timeout between order not expired.')
})
//KO stub never called
it.only('Should pass validation on buy', () => {
strategy.lastDateOrder = 0
const ctx = { state: {strategy, dashboard, position: null }}
const action = {b: true, s: false}
const promise = spawnOrderJob(action, ctx)
expect(promise).to.be.fulfilled
expect(getPositionsStub.called).to.be.true //ok
expect(createJobStub.called).to.be.true //never callled ????
expect(daoStub.called).to.be.true //never called ????
})
})
Want to understand what's going now there, the call are correct imo, running with mocha 5.2
Helpers.js : function is described as follow:
async function spawnOrderJob(action, ctx) {
try {
const { strategy, dashboard, position } = ctx.state
const {b, s} = action
//check in case strategy context
if (strategy) {
//pass validation buy contnext
if (b) {
//this stub is working
const positions = await strategyModule.getPositions(ctx)
const { maxPosition } = strategy.allocatedBTC
const { activeOrders, maxActiveOrders, timeBetweenOrder, lastDateOrder } = strategy
debug('Active orders:', strategy.activeOrders)
debug('Position:', positions.length)
if (activeOrders >= maxActiveOrders)
throw new ORDER_ERROR('Max active orders reach.')
if (positions.length + activeOrders >= maxPosition)
throw new ORDER_ERROR('Max positions reach.')
if (!timeoutExpired(lastDateOrder, timeBetweenOrder))
throw new ORDER_ERROR('Timeout between order not expired.')
//increment active orders counter
//stub fail, but not called at all
await dao.updateActiveOrders(strategy, true)
}
//Sell context
if (s) {
if (!position)
throw new ORDER_ERROR('No position to sell')
}
}
//stub fail, but called internally
return createJob(constants.DASHBOARD_CREATE_ORDER, {
orderType: b ? 'BUY' : 'SELL',
title: `Strategy create order ( ${ b ? 'BUY' : 'SELL'} )`,
strategy,
dashboard,
position
})
} catch (e) {
throw e
}
}
function createJob(name, data){
//shortcut queue.create (kue.js)
return queue.c(name,data)
}
module.exports = {
createJob,
spawnOrderJob
}
DAO
const updateActiveOrders = async (strategy, increment) => {
try {
const s = await model.findOne({_id: strategy._id})
if (!s) throw new Error('Strategy not found.')
s.activeOrders = increment ? s.activeOrders+1 :s.activeOrders-1
s.lastDateOrder = new Date()
return await s.save()
}catch(e){
throw e
}
}
module.exports = {updateActiveOrders}

Deleting all messages in discord.js text channel

Ok, so I searched for a while, but I couldn't find any information on how to delete all messages in a discord channel. And by all messages I mean every single message ever written in that channel. Any clues?
Try this
async () => {
let fetched;
do {
fetched = await channel.fetchMessages({limit: 100});
message.channel.bulkDelete(fetched);
}
while(fetched.size >= 2);
}
Discord does not allow bots to delete more than 100 messages, so you can't delete every message in a channel. You can delete less then 100 messages, using BulkDelete.
Example:
const Discord = require("discord.js");
const client = new Discord.Client();
const prefix = "!";
client.on("ready" () => {
console.log("Successfully logged into client.");
});
client.on("message", msg => {
if (msg.content.toLowerCase().startsWith(prefix + "clearchat")) {
async function clear() {
msg.delete();
const fetched = await msg.channel.fetchMessages({limit: 99});
msg.channel.bulkDelete(fetched);
}
clear();
}
});
client.login("BOT_TOKEN");
Note, it has to be in a async function for the await to work.
Here's my improved version that is quicker and lets you know when its done in the console but you'll have to run it for each username that you used in a channel (if you changed your username at some point):
// Turn on Developer Mode under User Settings > Appearance > Developer Mode (at the bottom)
// Then open the channel you wish to delete all of the messages (could be a DM) and click the three dots on the far right.
// Click "Copy ID" and paste that instead of LAST_MESSAGE_ID.
// Copy / paste the below script into the JavaScript console.
var before = 'LAST_MESSAGE_ID';
var your_username = ''; //your username
var your_discriminator = ''; //that 4 digit code e.g. username#1234
var foundMessages = false;
clearMessages = function(){
const authToken = document.body.appendChild(document.createElement`iframe`).contentWindow.localStorage.token.replace(/"/g, "");
const channel = window.location.href.split('/').pop();
const baseURL = `https://discordapp.com/api/channels/${channel}/messages`;
const headers = {"Authorization": authToken };
let clock = 0;
let interval = 500;
function delay(duration) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), duration);
});
}
fetch(baseURL + '?before=' + before + '&limit=100', {headers})
.then(resp => resp.json())
.then(messages => {
return Promise.all(messages.map((message) => {
before = message.id;
foundMessages = true;
if (
message.author.username == your_username
&& message.author.discriminator == your_discriminator
) {
return delay(clock += interval).then(() => fetch(`${baseURL}/${message.id}`, {headers, method: 'DELETE'}));
}
}));
}).then(() => {
if (foundMessages) {
foundMessages = false;
clearMessages();
} else {
console.log('DONE CHECKING CHANNEL!!!')
}
});
}
clearMessages();
The previous script I found for deleting your own messages without a bot...
// Turn on Developer Mode under User Settings > Appearance > Developer Mode (at the bottom)
// Then open the channel you wish to delete all of the messages (could be a DM) and click the three dots on the far right.
// Click "Copy ID" and paste that instead of LAST_MESSAGE_ID.
// Copy / paste the below script into the JavaScript console.
// If you're in a DM you will receive a 403 error for every message the other user sent (you don't have permission to delete their messages).
var before = 'LAST_MESSAGE_ID';
clearMessages = function(){
const authToken = document.body.appendChild(document.createElement`iframe`).contentWindow.localStorage.token.replace(/"/g, "");
const channel = window.location.href.split('/').pop();
const baseURL = `https://discordapp.com/api/channels/${channel}/messages`;
const headers = {"Authorization": authToken };
let clock = 0;
let interval = 500;
function delay(duration) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), duration);
});
}
fetch(baseURL + '?before=' + before + '&limit=100', {headers})
.then(resp => resp.json())
.then(messages => {
return Promise.all(messages.map((message) => {
before = message.id;
return delay(clock += interval).then(() => fetch(`${baseURL}/${message.id}`, {headers, method: 'DELETE'}));
}));
}).then(() => clearMessages());
}
clearMessages();
Reference: https://gist.github.com/IMcPwn/0c838a6248772c6fea1339ddad503cce
This will work on discord.js version 12.2.0
Just put this inside your client on message event
and type the command: !nuke-this-channel
Every message on channel will get wiped
then, a kim jong un meme will be posted.
if (msg.content.toLowerCase() == '!nuke-this-channel') {
async function wipe() {
var msg_size = 100;
while (msg_size == 100) {
await msg.channel.bulkDelete(100)
.then(messages => msg_size = messages.size)
.catch(console.error);
}
msg.channel.send(`<#${msg.author.id}>\n> ${msg.content}`, { files: ['http://www.quickmeme.com/img/cf/cfe8938e72eb94d41bbbe99acad77a50cb08a95e164c2b7163d50877e0f86441.jpg'] })
}
wipe()
}
This will work so long your bot has appropriate permissions.
module.exports = {
name: "clear",
description: "Clear messages from the channel.",
args: true,
usage: "<number greater than 0, less than 100>",
execute(message, args) {
const amount = parseInt(args[0]) + 1;
if (isNaN(amount)) {
return message.reply("that doesn't seem to be a valid number.");
} else if (amount <= 1 || amount > 100) {
return message.reply("you need to input a number between 1 and 99.");
}
message.channel.bulkDelete(amount, true).catch((err) => {
console.error(err);
message.channel.send(
"there was an error trying to prune messages in this channel!"
);
});
},
};
In case you didn't read the DiscordJS docs, you should have an index.js file that looks a little something like this:
const Discord = require("discord.js");
const { prefix, token } = require("./config.json");
const client = new Discord.Client();
client.commands = new Discord.Collection();
const commandFiles = fs
.readdirSync("./commands")
.filter((file) => file.endsWith(".js"));
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
client.commands.set(command.name, command);
}
//client portion:
client.once("ready", () => {
console.log("Ready!");
});
client.on("message", (message) => {
if (!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).split(/ +/);
const commandName = args.shift().toLowerCase();
if (!client.commands.has(commandName)) return;
const command = client.commands.get(commandName);
if (command.args && !args.length) {
let reply = `You didn't provide any arguments, ${message.author}!`;
if (command.usage) {
reply += `\nThe proper usage would be: \`${prefix}${command.name} ${command.usage}\``;
}
return message.channel.send(reply);
}
try {
command.execute(message, args);
} catch (error) {
console.error(error);
message.reply("there was an error trying to execute that command!");
}
});
client.login(token);
Another approach could be cloning the channel and deleting the one with the messages you want deleted:
// Clears all messages from a channel by cloning channel and deleting old channel
async function clearAllMessagesByCloning(channel) {
// Clone channel
const newChannel = await channel.clone()
console.log(newChannel.id) // Do with this new channel ID what you want
// Delete old channel
channel.delete()
}
I prefer this method rather than the ones listed on this thread because it most likely takes less time to process and (I'm guessing) puts the Discord API under less stress. Also, channel.bulkDelete() is only able to delete messages that are newer than two weeks, which means you won't be able to delete every channel message in case your channel has messages that are older than two weeks.
The possible downside is the channel changing id. In case you rely on storing ids in a database or such, don't forget to update those documents with the id of the newly cloned channel!
Here's #Kiyokodyele answer but with some changes from #user8690818 answer.
(async () => {
let deleted;
do {
deleted = await channel.bulkDelete(100);
} while (deleted.size != 0);
})();

ESOCKETTIMEDOUT Cloud Functions for Firebase

I am using Cloud Functions for Firebase together with my Firebase Realtime Database in order to do some data management for my app.
One of my functions though seems to get terminated since it takes about 100-150 seconds to complete. This happens with error : ESOCKETTIMEDOUT.
Is there a way to prevent this?
Here is my function:
function getTopCarsForUserWithPreferences(userId, genres) {
const pathToCars = admin.database().ref('cars');
pathTocars.orderByChild("IsTop").equalTo(true).once("value").then(function(snapshot) {
return writeSuggestedCars(userId, genres, snapshot);
}).catch(reason => {
console.log(reason)
})
}
function writeSuggestedCars(userId, genres, snapshot) {
const carsToWrite = {};
var snapCount = 0
snapshot.forEach(function(carSnapshot) {
snapCount += 1
const carDict = carSnapshot.val();
const carGenres = carDict.taCarGenre;
const genre_one = genres[0];
const genre_two = genres[1];
if (carGenres[genre_one] === true ||carGenres[genre_two] == true) {
carsToWrite[carSnapshot.key] = carDict
}
if (snapshot.numChildren() - 1 == snapCount) {
const pathToSuggest = admin.database().ref('carsSuggested').child(userId);
pathToSuggest.set(carsToWrite).then(snap => {
}).catch(reason => {
console.log(reason)
});
}
});
}
The getTopCarsForUserWithPreferences gets called when a user adds preferences. Also the cars table has about 50k entries.
Well you need to return everytime you use a async task.
Edit: you return 'writeSuggestedCars' but I think it never returns a value. I do not have a compiler, but I thought it was return Promise.resolved(). Can you insert it where I putted 'HERE'?
Maybe this will work:
function getTopCarsForUserWithPreferences(userId, genres) {
const pathToCars = admin.database().ref('cars');
return pathTocars.orderByChild("IsTop").equalTo(true).once("value").then(function(snapshot) {
return writeSuggestedCars(userId, genres, snapshot);
}).catch(reason => {
console.log(reason)
})
}
function writeSuggestedCars(userId, genres, snapshot) {
const carsToWrite = {};
var snapCount = 0
snapshot.forEach(function(carSnapshot) {
snapCount += 1
const carDict = carSnapshot.val();
const carGenres = carDict.taCarGenre;
const genre_one = genres[0];
const genre_two = genres[1];
if (carGenres[genre_one] === true ||carGenres[genre_two] == true) {
carsToWrite[carSnapshot.key] = carDict
}
if (snapshot.numChildren() - 1 == snapCount) {
const pathToSuggest = admin.database().ref('carsSuggested').child(userId);
return pathToSuggest.set(carsToWrite).then(snap => {
// 'HERE' I think return promise/Promise.resolve() will work
}).catch(reason => {
console.log(reason)
});
}
});
}

Resources