I have been trying to convert my existing Node JS code from function callback to Async Await, because a new SDK came out and callbacks are deprecated. These are the related codes. The code is communicating and publishing to a mqtt broker.
First here I call the ToggleX method
super.controlToggleX(channel, value, (err, res) => {
if (err) {
this.log(`Toggle Response: err: ${err}`);
}
this.log(`Toggle Response: res: ${JSON.stringify(res)}`);
});
After that in the controlToggleX method I set the payload for the message and make the publlish to the broker.
controlToggleX(channel, onoff, callback) {
const payload = { togglex: { channel, onoff: onoff ? 1 : 0 } };
return this.publishMessage('SET', 'Appliance.Control.ToggleX', payload, callback);
}
In the publishMessage I compile the message to the broker and publish it. After publishing I waiting for the response and to keep track of the ongoing messages I create the waitingMessageIds array.
publishMessage(method, namespace, payload, callback) {
this.clientResponseTopic = `/app/${this.userId}-${appId}/subscribe`;
const messageId = crypto.createHash('md5').update(generateRandomString(16)).digest('hex');
const timestamp = Math.round(new Date().getTime() / 1000); // int(round(time.time()))
const signature = crypto.createHash('md5').update(messageId + this.key + timestamp).digest('hex');
const data = {
header: {
from: this.clientResponseTopic,
messageId,
method,
namespace,
payloadVersion: 1,
sign: signature,
timestamp,
},
payload,
};
this.client.publish(`/appliance/${this.uuid}/subscribe`, JSON.stringify(data));
if (callback) {
this.waitingMessageIds[messageId] = {};
this.waitingMessageIds[messageId].callback = callback;
this.waitingMessageIds[messageId].timeout = setTimeout(() => {
// this.log('TIMEOUT');
if (this.waitingMessageIds[messageId].callback) {
this.waitingMessageIds[messageId].callback(new Error('Timeout'));
}
delete this.waitingMessageIds[messageId];
}, 20000);
}
this.emit('rawSendData', data);
return messageId;
}
When a new message comes from the broker I check the waitingMessageIds array, the messageId is in the array? If yes I delete the Timer and process the message with the callback coming from the publishing.
this.client.on('message', (topic, message) => {
if (!message) return;
// message is Buffer
try {
message = JSON.parse(message.toString());
} catch (err) {
this.emit('error', `JSON parse error: ${err}`);
return;
}
if (message.header.from && !message.header.from.includes(this.uuid)) return;
if (this.waitingMessageIds[message.header.messageId]) {
if (this.waitingMessageIds[message.header.messageId].timeout) {
clearTimeout(this.waitingMessageIds[message.header.messageId].timeout);
}
this.waitingMessageIds[message.header.messageId].callback(null, message.payload || message);
delete this.waitingMessageIds[message.header.messageId];
} else if (message.header.method === 'PUSH') {
const namespace = message.header ? message.header.namespace : '';
this.log('Found message');
this.emit('data', namespace, message.payload || message);
}
this.emit('rawData', message);
});
mqtt package is working with callback, but the async-mqtt is returning Promise so it is going to be good for me.
I was successfull to publish with it, and after that point I put the messageId to the array and start a timer, but when the reply came i was not been able to proocess the waitingMessageIds and return to the original point (super.controlToggleX).
Could somebody please help me. Thank
Edit:
I tried to rewrite PublishMessage with async-mqtts and it looks like this:
async publishMessage(method, namespace, payload) {
.
.
.
try {
await this.client.publish(`/appliance/${this.uuid}/subscribe`, JSON.stringify(data));
} catch (err) {
return new Error(err);
}
this.waitingMessageIds[messageId] = {};
// this.waitingMessageIds[messageId].callback = callback;
this.waitingMessageIds[messageId].timeout = setTimeout(() => {
// this.log('TIMEOUT');
if (this.waitingMessageIds[messageId].callback) {
this.waitingMessageIds[messageId].callback(new Error('Timeout'));
}
delete this.waitingMessageIds[messageId];
}, 20000);
this.emit('rawSendData', data);
return messageId;
}
Because with the await publish waits for the response I do not need check if it is a callback, i just put the messageId into the waitingmessageIds array.
When I process the incoming message in this.client.on('message' I don not know how to change this -> this.waitingMessageIds[message.header.messageId].callback(null, message.payload || message);
I am trying to create a slash command for my discord bot, but I don't know how to execute code when the command is exuted
The code I want to use will send a message to a different channel (Args: Message)
Here is the code I want to use
const channel = client.channels.cache.find(c => c.id == "834457788846833734")
channel.send(Message)
You need to listen for an event interactionCreate or INTERACTION_CREATE. See the code below, haven't tested anything, hope it works.
For discord.js v12:
client.ws.on("INTERACTION_CREATE", (interaction) => {
// Access command properties
const commandId = interaction.data.id;
const commandName = interaction.data.name;
// Do your stuff
const channel = client.channels.cache.find(c => c.id == "834457788846833734");
channel.send("your message goes here");
// Reply to an interaction
client.api.interactions(interaction.id, interaction.token).callback.post({
data: {
type: 4,
data: {
content: "Reply message"
}
}
});
});
For discord.js v13:
client.on("interactionCreate", (interaction) => {
if (interaction.isCommand()) {
// Access command properties
const commandId = interaction.commandId;
const commandName = interaction.commandName;
// Do your stuff
const channel = client.channels.cache.find(c => c.id == "834457788846833734")
channel.send("your message goes here");
// Reply to an interaction
interaction.reply("Reply message");
}
});
so I'm trying to make a help command with list of commands showed in embed. My code kinda works but it throws an error "DiscordAPIError: Cannot send an empty message" and I've tried already everything I know and what I've found but I can't fix it.
Here's the code
const Discord = require('discord.js');
const { prefix } = require('../config.json');
module.exports = {
name: 'help',
description: 'List all of my commands or info about a specific command.',
aliases: ['commands', 'cmds'],
usage: '[command name]',
cooldown: 5,
execute(msg, args) {
const data = [];
const { commands } = msg.client;
if (!args.length) {
const helpEmbed = new Discord.MessageEmbed()
.setColor('YELLOW')
.setTitle('Here\'s a list of all my commands:')
.setDescription(commands.map(cmd => cmd.name).join('\n'))
.setTimestamp()
.setFooter(`You can send \`${prefix}help [command name]\` to get info on a specific command!`);
msg.author.send(helpEmbed);
return msg.author.send(data, { split: true })
.then(() => {
if (msg.channel.type === 'dm') return;
msg.reply('I\'ve sent you a DM with all my commands!');
})
.catch(error => {
console.error(`Could not send help DM to ${msg.author.tag}.\n`, error);
msg.reply('it seems like I can\'t DM you! Do you have DMs disabled?');
});
}
const name = args[0].toLowerCase();
const command = commands.get(name) || commands.find(c => c.aliases && c.aliases.includes(name));
if (!command) {
return msg.reply('that\'s not a valid command!');
}
data.push(`**Name:** ${command.name}`);
if (command.aliases) data.push(`**Aliases:** ${command.aliases.join(', ')}`);
if (command.description) data.push(`**Description:** ${command.description}`);
if (command.usage) data.push(`**Usage:** ${prefix}${command.name} ${command.usage}`);
data.push(`**Cooldown:** ${command.cooldown || 3} second(s)`);
msg.channel.send(data, { split: true });
},
};
You should try replace this line :
msg.channel.send(data, { split: true });
with
msg.channel.send(data.join(' '), { split: true }); since your data variable is an array and not a string
The problem is as the error states. You are trying to send an empty message somewhere.
You can try replacing msg.channel.send(data) with msg.channel.send(data.join('\n')), since the data variable is an array.
I don't see why sending an array doesn't work though.
I would like to start a conversation with a user without that he already chatted with the bot.
I'm proceeding bu getting the conversationId when user install the bot app as mentionned here
Here how I catch reference and serviceUrl by using processActivity event
Then I use continueConversation + reference
const {
BotFrameworkAdapter,
} = require('botbuilder');
const adapter = new BotFrameworkAdapter({
appId: "xxxx",
appPassword: "xxxxy"
})
module.exports = async function(context, req) {
console.log(adapter);
try {
adapter.processActivity(req, context.res, async (turnContext) => {
const reference = turnContext.activity.conversation.id
const serviceUrl = turnContext.activity.serviceUrl
await adapter.continueConversation(reference, async (turnContext) => {
try {
await turnContext.sendActivity("this is proacive message");
} catch (e) {
console.log(e)
}
});
});
} catch (e) {
console.log(e)
}
return;
};
I'm getting this error
Error: BotFrameworkAdapter.sendActivity(): missing serviceUrl.
I have checked the turnContext.activity values. I get :
{"type":"event","name":"continueConversation"}" all other values are undefined ( serviceUrl also )
I noticed that turnContext.activityinside adapter.processActivity is not the same as in adapter.continueConversation and have all the serviceUrl and conversationId
How can I edit this code example to be able to send proactive messages to users?
The const reference needs to be ConversationReference not just the Id if you are using it for BotFrameworkAdapter.continueConversation().
A ConversationReference can be retrieved by using:
const reference = TurnContext.getConversationReference(turnContext.activity);
https://learn.microsoft.com/en-us/javascript/api/botbuilder/botframeworkadapter?view=botbuilder-ts-latest#continueconversation-partial-conversationreference----context--turncontext-----promise-void--
https://learn.microsoft.com/en-us/javascript/api/botbuilder-core/turncontext?view=botbuilder-ts-latest#getconversationreference-partial-activity--
First of all please note that this is not about creating a bot.
My goal is to create an application that will simply listen to any number of telegram channels that the account I will provide it with is subscribed to and retrieve all messages sent to those channels (as if I was a normal user). My guess is that I will need to
Authenticate myself using my account's phone number
Be able to setup a callback listener either per channel or a general listener for all incoming messages
I've been looking around the telegram api for a couple of days now and I am extremely confused as to how it works. After giving up on it, I started looking at readymade implementations, mostly for NodeJS but was still not able to find a concrete solution. I'm testing some things with the telegram-js api but running it directly using node didn't work. Does it need to run in a browser? Is there any more streamlined approach to this? Preferably something with good documentation.
PS: I'm fluent in Java and Javascript mostly so I've prioritized libraries based on those languages.
EDIT:
Here is the code that I've written (essentially copied an example)
var { Telegram } = require("../libs/telegram");
var TypeLanguage = require("telegram-tl-node") ;
var MTProto = require("telegram-mt-node");
var schema = require("../libs/api-schema.json");
const APP_ID = "111111";
const APP_HASH = "fb6da8f6abdf876abd6a9d7bf6";
const SERVER = { host: "111.111.111.11", port: "443" };
const config = {
id: APP_ID,
hash: APP_HASH,
version: '0.0.1',
lang_code: 'en',
authKey: null
};
let telegram = new Telegram(MTProto, TypeLanguage);
telegram.useSchema(schema);
addPublicKeys(telegram);
let connection = new MTProto.net.HttpConnection(SERVER);
let client = telegram.createClient();
client.setConnection(connection);
connection.connect(function() {
let ready = client.setup(config);
ready.then(function(client) {
// it never resolves this promise
function callback(response) {
console.log(response);
}
client.callApi("help.getConfig").then(callback, callback);
});
});
It uses those 2 libs:
telegram-mt-node
telegram-tl-node
Late answer but might help others.
You can utilize mtproto-core to authenticate with a regular telegram account and listen to updates (or do anything you can with telegram clients, really)
Here is a sample script I've written that listens to new messages from channels/supergroups the user is subscribed to:
const { MTProto, getSRPParams } = require('#mtproto/core');
const prompts = require('prompts');
const api_id = ...; // insert api_id here
const api_hash = ' ... '; // insert api_hash here
async function getPhone() {
return (await prompts({
type: 'text',
name: 'phone',
message: 'Enter your phone number:'
})).phone
}
async function getCode() {
// you can implement your code fetching strategy here
return (await prompts({
type: 'text',
name: 'code',
message: 'Enter the code sent:',
})).code
}
async function getPassword() {
return (await prompts({
type: 'text',
name: 'password',
message: 'Enter Password:',
})).password
}
const mtproto = new MTProto({
api_id,
api_hash,
});
function startListener() {
console.log('[+] starting listener')
mtproto.updates.on('updates', ({ updates }) => {
const newChannelMessages = updates.filter((update) => update._ === 'updateNewChannelMessage').map(({ message }) => message) // filter `updateNewChannelMessage` types only and extract the 'message' object
for (const message of newChannelMessages) {
// printing new channel messages
console.log(`[${message.to_id.channel_id}] ${message.message}`)
}
});
}
// checking authentication status
mtproto
.call('users.getFullUser', {
id: {
_: 'inputUserSelf',
},
})
.then(startListener) // means the user is logged in -> so start the listener
.catch(async error => {
// The user is not logged in
console.log('[+] You must log in')
const phone_number = await getPhone()
mtproto.call('auth.sendCode', {
phone_number: phone_number,
settings: {
_: 'codeSettings',
},
})
.catch(error => {
if (error.error_message.includes('_MIGRATE_')) {
const [type, nextDcId] = error.error_message.split('_MIGRATE_');
mtproto.setDefaultDc(+nextDcId);
return sendCode(phone_number);
}
})
.then(async result => {
return mtproto.call('auth.signIn', {
phone_code: await getCode(),
phone_number: phone_number,
phone_code_hash: result.phone_code_hash,
});
})
.catch(error => {
if (error.error_message === 'SESSION_PASSWORD_NEEDED') {
return mtproto.call('account.getPassword').then(async result => {
const { srp_id, current_algo, srp_B } = result;
const { salt1, salt2, g, p } = current_algo;
const { A, M1 } = await getSRPParams({
g,
p,
salt1,
salt2,
gB: srp_B,
password: await getPassword(),
});
return mtproto.call('auth.checkPassword', {
password: {
_: 'inputCheckPasswordSRP',
srp_id,
A,
M1,
},
});
});
}
})
.then(result => {
console.log('[+] successfully authenticated');
// start listener since the user has logged in now
startListener()
});
})
You can find the values for api_id and api_hash from https://my.telegram.org.
On the first run the script prompts the user for phone_number, code, and password.
[+] You must log in
√ Enter your phone number: ... <phone_number>
√ Enter the code sent: ... <code>
√ Enter Password: ... <2FA password>
and after the authentication is over
a sample run outputs:
[+] starting listener
[13820XXXXX] Ja
[13820XXXXX] Bis bald guys��
[13820XXXXX] Ja. �
[13820XXXXX] Bis später
[13820XXXXX] Jaaa�
The way I've checked the authentication status was taken from here.
Alternative libraries worth checking out that are active the time of writing (and can be used to create the same behavior with): Airgram (tdlib) and GramJs
I used gram.js library and essentially did this:
import { TelegramClient } from 'telegram'
TelegramClient().addEventHandler(handler, { chats: [1234567890] })
The bot does NOT need to be a member of the channel you want to listen to.
My code runs as a Node.js app.
You need to first create a token by talking to #BotFather with Telegram.
here is my working code by using gramjs and its purely on nodejs.
Getting all the messages from all the channels without any delay's.
import {
TelegramClient
} from "telegram";
import {
NewMessage,
NewMessageEvent
} from "telegram/events";
import {
StringSession
} from "telegram/sessions";
const input = require("input");
const apiId = 1233456677;
const apiHash = "xxxxxxxxxxxxxxxxx";
let stringSession = new StringSession("xxxxxxxxxxxxxxxxx");
(async() => {
console.log("Loading interactive example...");
const client = new TelegramClient(stringSession, apiId, apiHash, {
connectionRetries: 5,
});
await client.start({
phoneNumber: async() => await input.text("Please enter your number: "),
password: async() => await input.text("Please enter your password: "),
phoneCode: async() =>
await input.text("Please enter the code you received: "),
onError: (err) => console.log(err),
});
console.log("You should now be connected.");
const session: any = client.session.save();
stringSession = new StringSession(session); // Save this string to avoid logging in again - specially in nodemon
console.log(client.session.save()); // --> you can also copy this session from your console once you get it and paste it in line number 8 - new StringSession("XXXXXXXXXXXXXX")
// once you saved add the JWT Token on line no. 8 as mention above next time you will getting directly connected.
await client.sendMessage("me", {
message: "Hello!"
});
async function handler(event: NewMessageEvent) {
console.log("[newmessage]", event);
}
client.addEventHandler(handler, new NewMessage({}));
})();
Note - Ignore the "Run Code Snippet" as found it best way to add whole code instead of formatting.
You can use the gram.js library in the following way:
Install these:
npm install properties-reader
npm install telegram
npm install input
Then get your apiId and apiHash from Telegram Auth in the App Configuration section.
Create a file config.properties with a content similar to that:
[telegram]
apiId=12345678
apiHash=12345678901234567890123456789012
Inside of your nodejs code you can listen to a specific chat like this (see chatId inside of the code below):
const PropertiesReader = require('properties-reader');
const configs = PropertiesReader('config.properties');
getProp = (bundle, key) => {return configs.get(`${bundle}.${key}`);}
const { TelegramClient } = require("telegram");
const { StoreSession } = require("telegram/sessions");
const { NewMessage } = require("telegram/events");
const { EditedMessage } = require("telegram/events/EditedMessage");
const input = require("input");
const apiId = getProp("telegram", "apiId")
const apiHash = getProp("telegram", "apiHash")
const storeSession = new StoreSession("telegram_session"); // see: https://painor.gitbook.io/gramjs/getting-started/authorization#store-session
(async () => {
console.log("Loading interactive example...");
const client = new TelegramClient(storeSession, apiId, apiHash, {
connectionRetries: 5,
});
await client.start({
phoneNumber: async () => await input.text("Please enter your number: "),
password: async () => await input.text("Please enter your password: "),
phoneCode: async () =>
await input.text("Please enter the code you received: "),
onError: (err) => console.log(err),
});
console.log("You should now be connected.");
client.session.save(); // Save the session to avoid logging in again
async function eventPrint(event) {
// see 'node_modules/telegram/tl/custom/message.d.ts'
const message = event.message
const isNew = message.editDate === undefined
const text = message.text
const date = new Date(message.date*1000)
console.log(`The message is ${isNew ? 'new' : 'an update'}`)
console.log(`The text is: ${text}`)
console.log(`The date is: ${date}`)
}
// to get the chatId:
// option 1: open telegram on a web browser, go to the chat, and look the url in the address bar
// option 2: open telegram app, copy link to any message, it should be something like: https://t.me/c/1234567890/12345, the first number after "/c/" is the chatId
const chatId = 1234567890
client.addEventHandler(eventPrint, new NewMessage({ chats: [chatId] }));
client.addEventHandler(eventPrint, new EditedMessage({ chats: [chatId] }));
})();