GramJs `telegram` event handler takes too long to establish - javascript

I've noticed a weird behaviour lately with GramJs, according to the documentation here, it should be quite straightforward but as I have come to find out that my event handler takes about 5-10 seconds approximately to be established, and I have no idea why this is the case.
here's my code
webSocketServer.on("connection", async (wsConnection, user) => {
console.log(`User Connected`);
wsConnection.isAlive = true;
const me = {
userId: user.entityId,
username: user.entityFields.username._value,
firstName: user.entityFields.firstName._value,
phoneNumber: user.entityFields.phoneNumber._value,
lastName: user.entityFields.lastName._value,
};
const telegramSelf = JSON.parse(
await redisHelper.getKey(`${me.userId}:telegram`)
);
if (
telegramSelf?.telegramSession &&
(await telegramService.checkSession(telegramSelf.telegramSession))
) {
const telegramClient = await quickConnect(telegramSelf.telegramSession);
console.log(`${me.username} Connected To Telegram`);
telegramClient.addEventHandler(
(event) => telegramService.eventHandler(event, wsConnection),
new NewMessage({})
);
}
wsConnection.on("pong", function () {
this.isAlive = true;
});
wsConnection.on("message", async (raw) => {
const stringMessage = Buffer.from(raw).toString("utf8");
const message = JSON.parse(stringMessage);
if (message.event === "one-to-one-message") {
if (
!(await redisHelper.getKey(`${me.userId}:telegram`)) &&
message.to === "Telegram"
) {
wsConnection.send(
JSON.stringify({
event: "client-error",
status: 403,
message: "You are not linked to Telegram yet",
})
);
return;
}
if (
!(await redisHelper.getKey(`${me.username}:whatsApp`)) &&
message.to === "WhatsApp"
) {
wsConnection.send(
JSON.stringify({
event: "client-error",
status: 403,
message: "You are not linked to WhatsApp yet",
})
);
return;
}
}
events.eventHandler(message.event)(message, wsConnection, me);
});
wsConnection.on("close", async () => {
// await redisHelper.deleteKey(`${me.username}:telegram`);
console.log(`${me.username} Disconnected`);
});
});
so as it appears I'm triggering the message handler when a connection to my WebSocket server is made, and when that happens I check my redis key to see if the user has any sessions, if so I check the validity of the session and if it's valid then I'm triggering the quickConnect function which simply initialises the client and connects it to Telegram, after that I establish the Event handler.
I'd appreciate the help as I've been suffering from this problem for weeks
Note: I've debugged the code multiple times and the async requests never seem to be holding the process at all, it takes less that one second for the code executer to be at the line where the eventHandler function is being triggered.

Related

Discord.js message user on react for applications

I would like to convert this so that people just need to react and the application gets sent to them, I have no clue on how to do message on react stuff so if anyone could help me out it will be greatly appreciated.
Two very helpful people have helped me
#Skulaurun Mrusal
and
#PerplexingParadox
Thank you! 🙂
client.on("message", async (message) => {
// Don't reply to bots
if (message.author.bot) return;
if (message.content == "#req") {
if(!message.member.hasPermission("MANAGE_MESSAGES"))
{
message.reply("You do not have permission to do that!");
return;
}
// Perform raw API request and send a message with a button,
// since it isn't supported natively in discord.js v12
client.api.channels(message.channel.id).messages.post({
data: {
embeds: [reqEmbed],
components: [
{
type: 1,
components: [
{
type: 2,
style: 4,
label: "Apply",
// Our button id, we can use that later to identify,
// that the user has clicked this specific button
custom_id: "send_application"
}
]
}
]
}
});
}
});
// Channel id where the application will be sent
const applicationChannelId = "652099170835890177";
// Our questions the bot will ask the user
const questions = [
"What is your In-Game Username?",
"How long have you been drifting on FiveM?",
"Do you use Controller or Keyboard?",
"How much do you play weekly?",
"Have you been in any other teams? If so, why did you leave?",
"Short description about yourself and why you would like to be apart of Blind Spot? (Age, Country)"
];
// Function that will ask a GuildMember a question and returns a reply
async function askQuestion(member, question) {
const message = await member.send(question);
const reply = await message.channel.awaitMessages((m) => {
return m.author.id === member.id;
}, { time: 5 * 60000, max: 1 });
return reply.first();
}
client.ws.on("INTERACTION_CREATE", async (interaction) => {
// If component type is a button
if (interaction.data.component_type === 2) {
const guildId = interaction.guild_id;
const userId = interaction.member.user.id;
const buttonId = interaction.data.custom_id;
const member = client.guilds.resolve(guildId).member(userId);
if (buttonId == "send_application") {
// Reply to an interaction, so we don't get "This interaction failed" error
client.api.interactions(interaction.id, interaction.token).callback.post({
data: {
type: 4,
data: {
content: "I have started the application process in your DM's.",
flags: 64 // make the message ephemeral
}
}
});
try {
// Create our application, we will fill it later
const application = new MessageEmbed()
.setTitle("New Application")
.setDescription(`This application was submitted by ${member.user.tag}`)
.setColor("#ED4245");
const cancel = () => member.send("Your application has been canceled.\n**If you would like to start your application again Click on the Apply button in** <#657393981851697153>");
// Ask the user if he wants to continue
const reply = await askQuestion(member,
"Please fill in this form so we can proceed with your tryout.\n" +
"**Type `yes` to continue or type `cancel` to cancel.**"
);
// If not cancel the process
if (reply.content.toLowerCase() != "yes") {
cancel(); return;
}
// Ask the user questions one by one and add them to application
for (const question of questions) {
const reply = await askQuestion(member, question);
// The user can cancel the process anytime he wants
if (reply.content.toLowerCase() == "cancel") {
cancel(); return;
}
application.addField(question, reply);
}
await askQuestion(member,
"Do you want your application to be submitted?\n" +
"**Type `yes` to get your Application submitted and reviewed by Staff Members**"
);
// If not cancel the process
if (reply.content.toLowerCase() != "yes") {
cancel(); return;
}
// Send the filled application to the application channel
client.channels.cache.get(applicationChannelId).send(application);
} catch {
// If the user took too long to respond an error will be thrown,
// we can handle that case here.
member.send(
"You took too long to respond or Something went wrong, Please contact a Staff member\n" +
"The process was canceled."
);
}
}
}
});
You could use Client's messageReactionAdd event.
client.on("messageReactionAdd", (reaction, user) => {
if (!reaction.emoji.name === "👌") return;
// Check if the message is the right message we want users to react to
// Obviously you need to enable partials for this to work
if (reaction.message.id != "...") return;
const member = reaction.message.guild.member(user);
member.send("Here's your form!");
// ... Rest of your code ...
});
Note that this won't work for reactions cast on messages sent before the bot was started. The solution is to enable Partial Structures. (If you are dealing with partial data, don't forget to fetch.)
Or create a ReactionCollector using Message.createReactionCollector().
// ... The variable message defined somewhere ...
const collector = message.createReactionCollector((reaction, user) => {
return reaction.emoji.name === "👌";
});
collector.on("collect", (reaction, user) => {
const member = message.guild.member(user);
member.send("Here's your form!");
// ... Rest of your code ...
});
Maybe it would be better to use buttons in this case instead of reactions. To create a message with a button you could perform a raw API request or use third party library like discord-buttons. The solution below is for discord.js v12.
client.on("message", async (message) => {
// Don't reply to bots
if (message.author.bot) return;
if (message.content == "#createButton") {
// Perform raw API request and send a message with a button,
// since it isn't supported natively in discord.js v12
client.api.channels(message.channel.id).messages.post({
data: {
content: "If you want to apply, click the button below.",
components: [
{
type: 1,
components: [
{
type: 2,
style: 1,
label: "Apply",
// Our button id, we can use that later to identify,
// that the user has clicked this specific button
custom_id: "send_application"
}
]
}
]
}
});
}
});
And then we need to listen for an event INTERACTION_CREATE indicating that the user has clicked our button. (Or some other interaction triggered the event, for example a slash command.)
// Channel id where the application will be sent
const applicationChannelId = "871527842180132895";
// Our questions the bot will ask the user
const questions = [
"What is your In-Game Username?",
"How long have you been drifting on FiveM?",
"Do you use Controller or Keyboard?",
"How much do you play weekly?",
"Have you been in any other teams? If so, why did you leave?",
"Short description about yourself and why you would like to be apart of Blind Spot? (Age, Country)"
];
// Function that will ask a GuildMember a question and returns a reply
async function askQuestion(member, question) {
const message = await member.send(question);
const reply = await message.channel.awaitMessages((m) => {
return m.author.id === member.id;
}, { time: 5 * 60000, max: 1 });
return reply.first();
}
client.ws.on("INTERACTION_CREATE", async (interaction) => {
// If component type is a button
if (interaction.data.component_type === 2) {
const guildId = interaction.guild_id;
const userId = interaction.member.user.id;
const buttonId = interaction.data.custom_id;
const member = client.guilds.resolve(guildId).member(userId);
if (buttonId == "send_application") {
// Reply to an interaction, so we don't get "This interaction failed" error
client.api.interactions(interaction.id, interaction.token).callback.post({
data: {
type: 4,
data: {
content: "I have started the application process in your DM's.",
flags: 64 // make the message ephemeral
}
}
});
try {
// Create our application, we will fill it later
const application = new Discord.MessageEmbed()
.setTitle("New Application")
.setDescription(`This application was submitted by ${member.user.tag}`)
.setColor("#ED4245");
const cancel = () => member.send("Ok, I have cancelled this process.");
// Ask the user if he wants to continue
const reply = await askQuestion(member,
"Please fill in this form so we can proceed with your tryout.\n" +
"**Type `yes` to continue or type `cancel` to cancel.**"
);
// If not cancel the process
if (reply.content.toLowerCase() != "yes") {
cancel(); return;
}
// Ask the user questions one by one and add them to application
for (const question of questions) {
const reply = await askQuestion(member, question);
// The user can cancel the process anytime he wants
if (reply.content.toLowerCase() == "cancel") {
cancel(); return;
}
application.addField(question, reply);
}
// Send the filled application to the application channel
client.channels.cache.get(applicationChannelId).send(application);
} catch {
// If the user took too long to respond an error will be thrown,
// we can handle that case here.
member.send(
"Something went wrong or you took too long to respond.\n" +
"The process was cancelled."
);
}
}
}
});

React socket.io-client not re-rendering component after response from server to a particular room (Python backend)

I have a server backend written in Python with Flask-SocketIO. I'm utilizing it's room feature to make private conversations. Upon a join room event the server fires the following function to let the frontend know where to send messages to specific user:
socketio.emit('room name response', {'roomName': room_name, 'recipient': recipient}, to=sid)
where sid is the private room created only for the user when connecting to a socket. Then I want to keep this information in React state in a map, like this:
function ChatWindow({ username, token }) {
const [responses, setResponses] = useState([]);
const [roomsMap, setRoomsMap] = useState(new Map());
const [currentRoom, setCurrentRoom] = useState("");
const [messageValue, setMessageValue] = useState("");
var socket = null;
useEffect(() => {
socket = socketIOClient(ENDPOINT);
});
useEffect(() => {
socket.on("global response", (data) => {
setResponses((responses) => [...responses, data]);
});
socket.on("room name response", (data) => {
console.log(`joined ${data.roomName} with ${data.recipient}`);
setCurrentRoom((currentRoom) => data.roomName);
setRoomsMap((roomsMap) => roomsMap.set(data.recipient, data.roomName));
});
return () => socket.close();
}, []);
const sendMessage = () => {
if (messageValue.length < 1) {
return;
}
socket.emit("global message", {
user_name: username,
message: messageValue,
timestamp: Date.now(),
});
setMessageValue("");
};
const joinRoom = (recipient) => {
socket.emit("join", {
token: token,
username: username,
recipient: recipient,
});
// setCurrentRoom(() => roomsMap.get(recipient));
};
const leaveRoom = (recipient) => {
socket.emit("leave", {
token: token,
username: username,
recipient: recipient,
});
const newRooms = roomsMap;
newRooms.delete(recipient);
console.log(`left room with ${recipient}`);
newRooms.forEach((val, key) => console.log(`${val}:${key}`));
setRoomsMap(newRooms);
};
const checkUser = (userToCheck) => {
if (userToCheck === username) {
return styles.chatFromUser;
} else {
return styles.chatToUser;
}
};
return (...);
}
export default ChatWindow;
Sadly, React doesnt react to the socket emitting message, even though it can be seen in network tab in developer tools. The global response works fine.
When I alter the backend function to:
socketio.emit('room name response', {'roomName': room_name, 'recipient': recipient})
React suddenly works as expected. I'm trying to understand why it happens, especially when the browser seems to see the incoming messages as stated above, so it's most likely my bad coding or some React/Javascript thing.
Thank You for any help in advance.
The problem was that socket sometimes was created multiple times, therefore, the socket that useEffect was currently listening wasn't necessarily the one in the room. So I made one, global socket to fix this and whole thing now works.

How to receive SocketIO events in React while using states

This is part of my code, what I want to do is this component at any time can receive a message on any of the conversations. Sending a message triggers a Socket event which triggers this code below, but I can't seem to get the "latest" conversations, as the useEffect only triggers when the component mounts (at that point my conversations array has zero length).
What I was thinking is that I should include "conversations" on the useEffect's dependency but that would create multiple websocket connection, one each time a Socket.io event is triggered because it does change the state. Is this the best solution? Thanks in advance!
const [conversations, setConversations] = useState<Array<Conversations>>([]);
useEffect(() => {
async function getConversations() {
try {
const { data } = await axios.get("/api/conversations/");
if (data.success) {
setConversations(data.details);
}
} catch (err) {}
}
getConversations();
socketInstance.on("connect", () => {
console.log("Connecting to Sockets...");
socketInstance.emit("authenticate", Cookies.get("token") || "");
});
socketInstance.on("ackAuth", ({ success }) => {
console.log(
success
? "Successfully connected to Sockets"
: "There has been an error connecting to Sockets"
);
});
socketInstance.on("newMessage", (data) => {
const modifiedConversation: Conversations = conversations.find(
(conv: Conversations) => {
return conv.conversationId === data.conversationId;
}
);
modifiedConversation.messages.push({
from: {
firstName: data.firstName,
lastName: data.lastName,
profilePhoto: data.profilePhoto,
userId: data.userId,
},
content: data.content,
timeStamp: data.timeStamp,
});
const updatedConversations = [
...conversations.filter(
(conv) => conv.conversationId !== data.conversationId
),
modifiedConversation,
];
setConversations(updatedConversations);
});
}, []);
While attaching and removing the socket listeners every time conversations changes is a possibility, a better option would be to use the callback form of the setters. The only time you reference the state, you proceed to update the state, luckily. You can change
socketInstance.on("newMessage", (data) => {
const modifiedConversation: Conversations = conversations.find(
// lots of code
setConversations(updatedConversations);
to
socketInstance.on("newMessage", (data) => {
setConversations(conversations => {
const modifiedConversation: Conversations = conversations.find(
// lots of code
setConversations(updatedConversations);
You should also not mutate the state, since this is React. Instead of
modifiedConversation.messages.push({
do
const modifiedConversationWithNewMessage = {
...modifiedConversation,
messages: [
...modifiedConversation.messages,
{
from: {
// rest of the object to add

Javascript SlackBot postMessage function is spamming messages

I am trying to set up a slack bot using javascript and a few helpful libraries.
All it does is run a postMessageToChannel Method when a user of the channel mentions a certain keyword " help"
My issue is when the runHelp() function is called it doesn't just post one message to the slack #channel but many. Maybe i am missing something here that someone can help me figure out.
Thanks,
Here's the js:
const SlackBot = require('slackbots');
const axios = require('axios')
const dotenv = require('dotenv')
dotenv.config()
const bot = new SlackBot({
token: `${process.env.BOT_TOKEN}`,
name: 'helpit-bot'
});
// Start Handler
bot.on('start', () => {
const params = {
icon_emoji: ':nerd_face:'
}
bot.postMessageToChannel('slack-bot', 'HELP IS ON THE WAY', params);
})
// Error Handler
bot.on('error', (err) => {
console.log(err);
});
// Message Handler
bot.on('message', (data) => {
if(data.type !== 'message') {
return;
}
handleMessage(data.text);
return;
})
// Response Handler
function handleMessage(message) {
if(message.includes(' help')) {
runHelp();
} else {
// Run something else
}
}
// Show Help
function runHelp() {
const params = {
icon_emoji: ':question:'
}
bot.postMessageToChannel('slack-bot', 'This is an automated help message', params);
}
Result:
Created an infinite loop because "This is an automated help message" includes the text "help" which triggers the bot. -.-

Getting error: `Every document read in a transaction must also be written` in Firebase

guys!
i am developing an app similar to https://airtasker.com where users outsource tasks.
the taskers would bid to the tasks, and wait for the user to approve their bids.
these are the involved collections:
tasks
transactions
bids
basically, this function should:
check if a transaction exists with the given taskId.
a transaction is added if the user starts to approve bids. i allow multiple taskers to complete the task.
if a transaction doesn't exist, it should
add a new one, mark the status ongoing if it reaches the required manpower (otherwise pending), and update the bids collection to mark the bid accepted.
if a transaction exists, it should
check if the current approved list from the transactions collection is equal to the manpower
if it hasn't reached the quota manpower yet, push a new tasker and access the bids collection to mark the bid accepted.
if after the last condition, the approved list already reached the quota manpower, mark the task close, and change the status of the transaction as ongoing
but i keep getting this error:
Uncaught (in promise) Error: Every document read in a transaction must also be written.
at Transaction.commit (transaction.js:128)
at eval (sync_engine.js:244)
here's my code:
const acceptOffer = async (taskerId, taskId, bidId, offer) => {
let bulk
try {
const taskRef = db.collection('tasks').doc(taskId)
const transRef = db.collection('transactions').doc(taskId)
const bidRef = db.collection('bids').doc(bidId)
const fees = solveFees(offer)
bulk = await db
.runTransaction(async t => {
const transdoc = await t.get(transRef)
const taskdoc = await t.get(taskRef)
const manpower = await taskdoc.get('manpower')
let status = 'pending'
if (manpower === 1) {
status = 'ongoing'
}
if (!transdoc.exists) {
t.set(transRef, {
taskId,
status, // pending, ongoing, completed
approved: [
{ taskerId, ...fees }
]
})
t.update(bidRef, {
accepted: true
})
} else {
const approved = await transdoc.get('approved')
if (manpower < approved.length) {
approved.push({ taskerId, ...fees })
t.update(transRef, { approved })
t.update(bidRef, { accepted: true })
if (manpower === approved.length) {
t.update(taskRef, { open: false })
t.update(transRef, { status: 'ongoing' })
}
}
}
})
} catch (e) {
bulk = e
console.log('nag error', e)
throw e
}
if (bulk.success) {
swal('Offer accepted!', '', 'success')
} else {
swal('Oh, no!',
'This task might already be approved',
'error'
)
}
}
i have been stuck here since i don't understand where the transaction failed. any help is very much appreciated.
thank you!
to those who are having the same problem, here is my (hackish) solution:
for every condition,
add a document write (could be a set() update() or delete()) that corresponds to each of the document reads which in my code: the use of get()s.
and return a Promise
here's the updated code:
// a transaction is added if the user starts to approve offers
// this function allows multiple taskers
const acceptOffer = async (taskerId, taskId, bidId, offer) => {
let bulk
try {
const taskRef = db.collection('tasks').doc(taskId)
const transRef = db.collection('transactions').doc(taskId)
const bidRef = db.collection('bids').doc(bidId)
const fees = solveFees(offer)
bulk = await db
.runTransaction(async t => {
const transdoc = await t.get(transRef)
const taskdoc = await t.get(taskRef)
const manpower = await taskdoc.get('manpower')
// check if a transaction exists with the given taskId
// if it doesn't, then the task doesn't have
// any approved bidders yet
if (!transdoc.exists) {
// check if there is only one manpower required for the task
// mark the status of the transaction 'ongoing' if so
const status = manpower === 1
? 'ongoing' : 'pending'
// add a transaction with the approved tasker
t.set(transRef, {
taskId,
status, // pending, ongoing, completed
approved: [
{ taskerId, ...fees }
]
})
// mark the bid 'accepted'
t.update(bidRef, {
accepted: true
})
// hackish (to prevent firestore transaction errors)
t.update(taskRef, {})
return Promise.resolve(true)
} else { // if a transaction exists with the given taskId
const approved = await transdoc.get('approved')
// check if the current approved list from
// the transactions collection hasn't
// reached the manpower quota yet
if (approved.length < manpower) {
// push new approved bid of the tasker
approved.push({ taskerId, ...fees })
t.update(transRef, { approved })
t.update(bidRef, { accepted: true }) // mark the bid 'accepted'
t.update(taskRef, {}) // hackish
// if, after pushing a new transaction,
// the approved list reached the manpower quota
if (approved.length === manpower) {
t.update(taskRef, { open: false }) // mark the task 'close'
t.update(transRef, { status: 'ongoing' }) // mark the transaction 'ongoing'
t.update(bidRef, {}) // hackish
}
return Promise.resolve(true)
}
return Promise.reject(new Error('Task closed!'))
}
})
} catch (e) {
swal('Oh, no!',
'This task might already be closed',
'error'
)
throw e
}
if (bulk) {
swal('Offer accepted!', '', 'success')
}
}
I ran into the same issue. As long as google will not be able to sent validation errors with better errors than just that the client was not allowed to write the data (security rules). I prefer to handle it on client site. So I use transactions for example to validate that a referenced doc is still available when I write data. (for example I have write an order document that references to a customer and want be sure that the customer still exists.) So I have to read it but actually there is no need to write it.
I came up with something close to nrions solution but tried to have a more general approach for it so I wrote a wrapper for runTransaction. Of cause it is not the cleanest way to do it but maybe it is useful for others.
// Transaction neads to write all docs read be transaction.get().
// To work around this we we call an update with {} for each document requested by transaction.get() before writing any data
export function runTransaction(updateFunction) {
return db.runTransaction(transaction => {
const docRefsRequested = [];
let didSetRequestedDocs = false;
function setEachRequestedDoc() {
if (didSetRequestedDocs) {
return;
}
didSetRequestedDocs = true;
docRefsRequested.forEach(({ exists, ref }) => {
if (exists) {
transaction.update(ref, {});
} else {
transaction.delete(ref);
}
});
}
const transactionWrapper = {
get: function(documentRef) {
return transaction.get(ref).then(snapshot => {
const { exists } = snapshot;
docRefsRequested.push({ ref, exists });
return Promise.resolve(snapshot);
});
},
set: function(documentRef, data) {
setEachRequestedDoc();
return transaction.set(documentRef, data);
},
update: function(documentRef, data) {
setEachRequestedDoc();
return transaction.update(documentRef, data);
},
delete: function(documentRef) {
setEachRequestedDoc();
return transaction.delete(documentRef);
},
};
return updateFunction(transactionWrapper).then(resolveValue => {
setEachRequestedDoc();
return Promise.resolve(resolveValue);
});
});
}

Categories