Telegram Bot (Telegraf) stuck in infinite loop - node.js

My function named status sending information about products that are stored in Firebase Realtime Database. When user ask my bot for status then Firebase function get data from DB and then send back Telegram message for every of product that the user is assigned. For some users this can take some time even 2 minutes.
When 1st user asks bot for status and bot function run for the first time everything is fine.
BUT when 2nd user ask for status during first call of bot function (when function still running for 1st user) first call run start over and after first call is done finally second call are handled.
When 3rd user calls function when old calls are not finished, everything stacking on and finally everything loops until all calls of function are over. Because bot has almost 2000 active users this above problem runs into "infinite" loop because constantly some of users calls "status" for themselves.
This situation causes that users getting almost infinite numbers of messages.
I'm using JavaScript & Node 12 on Firebase.
const { Telegraf, Markup } = require('telegraf');
const { telegrafThrottler } = require('telegraf-throttler');
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const debug = true;
admin.initializeApp(); // initialize firebase-admin
const bot = new Telegraf(functions.config().telegram_token.key); // initialize Telegraf bot
const throttler = telegrafThrottler(); // Telegraf Throttler
bot.use(throttler);
/**
*
bot.command('some_commands_here', async (ctx) => {
const nothing = 'THIS IS NOT IMPORTANT';
});
bot.command(...) more commands
*/
/** action with infinite loop problem */
bot.action('status', async (ctx) => {
const uid = ctx.update.callback_query.from.id;
let userData = await getUserAccountData(admin, uid); // get some data from firebase realtime db
if (userData !== null) {
let userProducts = await getUserProducts(admin, uid); // get some data from firebase realtime db
if (userProducts !== null) {
// userProducts contain even 200 items for one user
for (const [privateProductId, privateProductData] of Object.entries(userProducts)) {
// some not important logic that create message and keyboard context
// after this bot send informations about every product (can be even 200 messages )
await bot.telegram.sendMessage(uid, `${message}`, {
parse_mode: 'html',
reply_markup: keyboard.reply_markup,
});
}
}
}
// some not important logic if userData / userProducts are null
});
/** Default response for undefinded msg/commands */
bot.on('message', (ctx) => ctx.reply("Use /help if You don't know commands!"));
/** bot catch errors here */
bot.catch((err, ctx) => {
if (debug) {functions.logger.error(`Bot Catched ERROR: ${err}`);}
bot.telegram.sendMessage(123456789, `Bot Catched ERROR: ${err}`);
});
const runtimeOpts = {
timeoutSeconds: 500,
memory: '1GB',
};
exports.bot = functions.region('europe-west3').runWith(runtimeOpts).https.onRequest((req, res) => {
// bot.handleUpdate(req.body, res);
bot.handleUpdate(req.body, res).catch((err) => {
if (debug) {functions.logger.error(err);}
});
});

OK Finally I found cause ^^
I'm missed process.once in my code, because of that function was executed more than one time.
process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));

Related

Node js repeating a get request until there is a change in response

I will start off by saying I am a complete newbie when it comes to node js. I have the code below which currently sends a get request to the URL. It parses a specific value of the response and stores it as the search variable. It then uses the instagram api to change the bio on my instagram account to that search variable. However I would like the get request to continue until it detects a change. Ex. When the program is first run it fires off a get request. The first response value we get we will call 1. However after the first response I want it to continue to do get requests say every 5 seconds. The moment the response value changes from 1 to anything else I want that new value to be sent to the instagram bio. Can anyone help?
const { IgApiClient } = require("instagram-private-api")
const ig = new IgApiClient()
const https = require('https')
const USERNAME = "MYUSERNAME"
const PASSWORD = "MYPASS"
ig.state.generateDevice(USERNAME)
const main = async () => {
let url = "https://11z.co/_w/14011/selection";
https.get(url,(res) => {
let body = "";
res.on("data", (chunk) => {
body += chunk;
});
res.on("end", async () => {
try {
search = await JSON.parse(body).value;
} catch (error) {
console.error(error.message);
};
});
}).on("error", (error) => {
console.error(error.message);
});
await ig.simulate.preLoginFlow()
await ig.account.login(USERNAME, PASSWORD)
// log out of Instagram when done
process.nextTick(async () => await ig.simulate.postLoginFlow())
// fill in whatever you want your new Instagram bio to be
await ig.account.setBiography(search)
}
main()
// code is written in main() so that I can use async/await
to be good citizen to the target endpoint:
have a look on exponential-backoff package - https://www.npmjs.com/package/exponential-backoff
A utility that allows retrying a function with an exponential delay between attempts.

How do i make a "!kick" command message for discord server?

I'm tryin' to make my own discord moderator bot!And i need help with ''!kick'' command , like if i want to kick someone i simply use !kick #user
Been starting with things like
const discord = require('discord.js');
const client = new discord.Client;
and then
client.on('ready', () => {
console.log('This bot is running.');
});
client.on('message', msg => {
and there should go my kick command!
I found some code in the discordjs documentation
// Import the discord.js module
const Discord = require('discord.js');
// Create an instance of a Discord client
const client = new Discord.Client();
/**
* The ready event is vital, it means that only _after_ this will your bot start reacting to information
* received from Discord
*/
client.on('ready', () => {
console.log('I am ready!');
});
client.on('message', message => {
// Ignore messages that aren't from a guild
if (!message.guild) return;
// If the message content starts with "!kick"
if (message.content.startsWith('!kick')) {
// Assuming we mention someone in the message, this will return the user
// Read more about mentions over at https://discord.js.org/#/docs/main/master/class/MessageMentions
const user = message.mentions.users.first();
// If we have a user mentioned
if (user) {
// Now we get the member from the user
const member = message.guild.member(user);
// If the member is in the guild
if (member) {
/**
* Kick the member
* Make sure you run this on a member, not a user!
* There are big differences between a user and a member
*/
member
.kick('Optional reason that will display in the audit logs')
.then(() => {
// We let the message author know we were able to kick the person
message.reply(`Successfully kicked ${user.tag}`);
})
.catch(err => {
// An error happened
// This is generally due to the bot not being able to kick the member,
// either due to missing permissions or role hierarchy
message.reply('I was unable to kick the member');
// Log the error
console.error(err);
});
} else {
// The mentioned user isn't in this guild
message.reply("That user isn't in this guild!");
}
// Otherwise, if no user was mentioned
} else {
message.reply("You didn't mention the user to kick!");
}
}
});
// Log our bot in using the token from https://discordapp.com/developers/applications/me
client.login('your token here');
The main code for this is member.kick('Optional reason that will display in the audit logs'), but you are going to want to nest it inside of certain conditional statements that check if the member exists and if the person writing the command is an administrator.

Using Mocha for integration testing of a callback that should trigger when on an AMQP message received event

I have a Feathers application that is using RabbitMQ and a custom amqplib wrapper to communicate with some other code running elsewhere and I'm struggling to write a good integration test to show that the callback that runs when a message is received runs correctly. The actual callback just takes the body of the received message and calls an internal service to put the data in the database.
I have a RabbitMQ server running in the test environment, and the idea was to write a test that publishes some dummy data to the correct exchange, and then check that the data ends up in the database. The problem is that I can't work out how to tell that the callback has finished before I check the database.
Right now, I just publish the message and then use a timeout to wait a few seconds before checking the database, but I don't like this since there is no guarantee that the callback will have completed.
The code I'm testing looks something like this (not the actual code just an example):
const app = require('./app');
// handleAMQP is passed as a callback to the consumer
// it creates a new record in the myService database
const handleAMQP = async(message) => {
await app.service('users').create(message.content);
};
// Subscribe takes an amqp connection, opens a channel, and connects a callback
const subscribe = (conn) => {
let queue = 'myQueue';
let exchange = 'myExchange';
return conn.createChannel().then(function (ch) {
var ok = ch.assertExchange(exchange, 'topic', { durable: true });
ok = ok.then(function () {
return ch.assertQueue(queue, { exclusive: true });
});
ok = ok.then(function (qok) {
var queue = qok.queue;
ch.bindQueue(queue, exchange, topic);
});
ok = ok.then(function (queue) {
return ch.consume(queue, handleAMQP);
});
});
};
module.exports = {subscribe};
And my test looks something like this:
const assert = require('assert');
const amqp = require('amqplib');
describe('AMQP Pub/Sub Tests', async () => {
let exchange = 'myExchange';
let topic = 'myTopic';
let dummyData = {
email: 'example#example.com',
name: 'Example User'
}
it('creates a new db enry when amqp message recieved', async () => {
// Publish some dummy data
await amqp.connect('amqp://localhost').then((conn) => {
conn.createChannel().then((ch) => {
ch.assertExchange(exchange, 'topic', {durable: true}).then(() => {
ch.publish(exchange, topic, dummyData).then(() => {
ch.close();
})
});
});
});
await setTimeout(() => { // Wait three seconds
let result = app.service('users').find({email : 'example#example.com'}); // Attempt to find the newly created user
assert.deepEqual(result.email, dummyData.email);
assert.deepEqual(result.name, dummyData.name);
}, 3000);
});
});
Instead of just waiting an arbitrary time limit before I check if the record exists, is there a better way to structure this test?
Or is waiting a certain time a totally valid for event-driven functionality?

Populate unpopulated reaction.users

On a messageReactionAdd event, one of the parameters is a MessageReaction. From that reaction, I do message.reaction to find the message that the reaction is from. I want to use that message to find all the reactions that the parameters user has reacted to on that message.
However, one obstacle is that when the bot restarts, it seems as though the message.reactions is not fully populated, and the data needs to be fetched. I need the users of a looped reaction, but I'm struggling on that part.
I have tried:
await message.reactions.reduce((p, c) => c.fetchUsers(), 0);
// I had thought this would of cached the users of every reaction on the message, but that did not work.
// next
message.reactions.forEach((x, y) => {
x.fetchUsers();
});
// this was also the same train of thought, but still to no avail.
What I mean by that users are not in the message.reaction.users object, I mean this:
// the bot restarts - and a reaction is added
console.log(reaction.users);
// outputs: Collection [Map] {}
// this is supposed to be populated with many users, but it's empty
// however, if I add ANY reaction again, not doing anything else
// it outputs a Collection with many users in it, which is expected
I have no idea how to do this.
Edit: relevant code
// raw.js (the raw event)
const events = {
MESSAGE_REACTION_ADD: 'messageReactionAdd',
};
const Discord = require('discord.js');
module.exports = async (client, event) => {
if (!events.hasOwnProperty(event.t)) return;
const { d: data } = event;
const user = client.users.get(data.user_id);
if (!user) return;
const channel = client.channels.get(data.channel_id);
if (!channel) return;
if (channel.messages.has(data.message_id)) return;
const message = await channel.fetchMessage(data.message_id);
const emojiKey = (data.emoji.id) ? `${data.emoji.name}:${data.emoji.id}` : data.emoji.name;
let reaction = message.reactions.get(emojiKey);
if (!reaction) {
const emoji = new Discord.Emoji(client.guilds.get(data.guild_id), data.emoji);
reaction = new Discord.MessageReaction(message, emoji, 1, data.user_id === client.user.id);
}
client.emit(events[event.t], reaction, user);
};
// messageReactionAdd.js (the messageReactionAdd event)
module.exports = async (client, reaction, user) => {
const message = reaction.message;
if (!message)
return;
//await message.reactions.reduce((p, c) => c.fetchUsers(), 0);
message.reactions.forEach((x,y) => {
x.fetchUsers();
});
reactions = await message.reactions.filter(r => r.users.has(`${user.id}`));
console.log(reactions);
};
This code is what fixed this problem for me.
// raw.js event file
await reaction.message.reactions.forEach(r => {
r.fetchUsers({before: `${reaction.message.author.id}`});
});
// emit the messageReactionAdd event
// messageReactionAdd.js event file
// the message.reactions.users should be populated, which I can use
reactions = await reaction.message.reactions.filter(r => r.users.has(`${reaction.message.author.id}`));
I had used this code in the wrong events which made no logical sense which made the results that I had thought was invalid.

Can you make Supertest wait for an Express handler to finish executing?

I use Supertest to test my Express apps, but I'm running into a challenge when I want my handlers to do asynchronous processing after a request is sent. Take this code, for example:
const request = require('supertest');
const express = require('express');
const app = express();
app.get('/user', async (req, res) => {
res.status(200).json({ success: true });
await someAsyncTaskThatHappensAfterTheResponse();
});
describe('A Simple Test', () => {
it('should get a valid response', () => {
return request(app)
.get('/user')
.expect(200)
.then(response => {
// Test stuff here.
});
});
});
If the someAsyncTaskThatHappensAfterTheResponse() call throws an error, then the test here is subject to a race condition where it may or may not failed based on that error. Even aside from error handling, it's also difficult to check for side effects if they happen after the response is set. Imagine that you wanted to trigger database updates after sending a response. You wouldn't be able to tell from your test when you should expect that the updates have completely. Is there any way to use Supertest to wait until the handler function has finished executing?
This can not be done easily because supertest acts like a client and you do not have access to the actual req/res objects in express (see https://stackoverflow.com/a/26811414/387094).
As a complete hacky workaround, here is what worked for me.
Create a file which house a callback/promise. For instance, my file test-hack.js looks like so:
let callback = null
export const callbackPromise = () => new Promise((resolve) => {
callback = resolve
})
export default function callWhenComplete () {
if (callback) callback('hack complete')
}
When all processing is complete, call the callback callWhenComplete function. For instance, my middleware looks like so.
import callWhenComplete from './test-hack'
export default function middlewareIpnMyo () {
return async function route (req, res, next) {
res.status(200)
res.send()
// async logic logic
callWhenComplete()
}
}
And finally in your test, await for the callbackPromise like so:
import { callbackPromise } from 'test-hack'
describe('POST /someHack', () => {
it.only('should handle a post request', async () => {
const response = await request
.post('/someHack')
.send({soMuch: 'hackery'})
.expect(200)
const result = await callbackPromise()
// anything below this is executed after callWhenComplete() is
// executed from the route
})
})
Inspired by #travis-stevens, here is a slightly different solution that uses setInterval so you can be sure the promise is set up before you make your supertest call. This also allows tracking requests by id in case you want to use the library for many tests without collisions.
const backgroundResult = {};
export function backgroundListener(id, ms = 1000) {
backgroundResult[id] = false;
return new Promise(resolve => {
// set up interval
const interval = setInterval(isComplete, ms);
// completion logic
function isComplete() {
if (false !== backgroundResult[id]) {
resolve(backgroundResult[id]);
delete backgroundResult[id];
clearInterval(interval);
}
}
});
}
export function backgroundComplete(id, result = true) {
if (id in backgroundResult) {
backgroundResult[id] = result;
}
}
Make a call to get the listener promise BEFORE your supertest.request() call (in this case, using agent).
it('should respond with a 200 but background error for failed async', async function() {
const agent = supertest.agent(app);
const trackingId = 'jds934894d34kdkd';
const bgListener = background.backgroundListener(trackingId);
// post something but include tracking id
await agent
.post('/v1/user')
.field('testTrackingId', trackingId)
.field('name', 'Bob Smith')
.expect(200);
// execute the promise which waits for the completion function to run
const backgroundError = await bgListener;
// should have received an error
assert.equal(backgroundError instanceof Error, true);
});
Your controller should expect the tracking id and pass it to the complete function at the end of controller backgrounded processing. Passing an error as the second value is one way to check the result later, but you can just pass false or whatever you like.
// if background task(s) were successful, promise in test will return true
backgroundComplete(testTrackingId);
// if not successful, promise in test will return this error object
backgroundComplete(testTrackingId, new Error('Failed'));
If anyone has any comments or improvements, that would be appreciated :)

Resources