Telegraf and Heroku not sending the first reply on webhook [nodejs] - node.js

I am tring to write a simple bot that after some task reply with a text.
The bot offline with app.polling is working perfectly. But when I deploy it on heroku, if I write only one ctx.reply(), no message is sent. If I write it twice, one message is sent on telegram. You can see the code on the snippet below, I included only the necessary code (the log show me with console.log that all the function are working and the final text is ready to be sent, I also commented the code a little to exlpain better the situation).
So this appear strange to me,can anyone explain why?
const Telegraf = require('telegraf');
const request = require('request');
const date = require('date-and-time');
const API_TOKEN = process.env.API_TOKEN || ''; //the api token is into env var
const PORT = process.env.PORT || 3000;
const URL = process.env.URL || 'url of my heroku account';
const app = new Telegraf(API_TOKEN);
app.telegram.setWebhook(`${URL}/bot${API_TOKEN}`);
app.startWebhook(`/bot${API_TOKEN}`, null, PORT);
function getdata(ctx,stazione1,stazione2){
let tempo = gettempo();
let linkget = '....';
var options = {
url: linkget,
headers: {
'Referer':'http://m.trenord.it/site-lite/index.html',
'secret': '...'
}
};
let linkget1 = '...';
var options1 = {
url: linkget1,
headers: {
'Referer':'...',
'secret': '...'
}
};
request(options, function(error, response, body){
request(options1, async function(error1, response1, body1){
let text = await gettext(body,stazione1,stazione2);//return text
let text1 = await gettext(body1,stazione2,stazione1);//return text
let final = await text+"\r\n\r\n"+text1;
console.log(ctx);
//here is the problem, if i write only one reply no message is sent on the bot, but if i wrtite it twice one message is sent.
ctx.replyWithMarkdown(final);
ctx.replyWithMarkdown(final);
});
});
}//getdata
app.command('pl', function(ctx){
getdata(ctx,stazione1,stazione2);
});
NEWS
I want to add some feedback while the server works, so I add a ctx.reply("searching...") after the command right before the function getdata is launched. Now all two messages are sent to telegram. On the previous case is possible that heroku "shut down the webhook" and at the first ctx.reply was wake up and than at the second ctx.reply the message was sent?

I'm using node-telegram-bot-api for node.js/Heroku and this config work well for me:
const options = {
webHook: {
// Port to which you should bind is assigned to $PORT variable
// See: https://devcenter.heroku.com/articles/dynos#local-environment-variables
port: process.env.PORT,
// you do NOT need to set up certificates since Heroku provides
// the SSL certs already (https://<app-name>.herokuapp.com)
// Also no need to pass IP because on Heroku you need to bind to 0.0.0.0
},
};
// okk
// Heroku routes from port :443 to $PORT
// Add URL of your app to env variable or enable Dyno Metadata
// to get this automatically
// See: https://devcenter.heroku.com/articles/dyno-metadata
const url = process.env.APP_URL || 'https://my-bot.herokuapp.com:443';
const bot = new Tg(config.BOT_TOKEN, options);
// This informs the Telegram servers of the new webhook.
// Note: we do not need to pass in the cert, as it already provided
bot.setWebHook(`${url}/bot${config.BOT_TOKEN}`);

Related

websocket request with nodeJS

I am trying to initiate a websocket request using nodeJS. I am new to WS, and I am quite stuck with this, and all examples I could find seem to be the same one with building a chat, repeated over and over in every media lol, and it didn't help me better understand.
I have an API that provides real-time data (forex). I am able to successfully subscribe to sources of real-time data on the front-end, and do what I want. BTW, I don't get any CORS issue doing that, for some reason I don't understand.... :-o
However, i'd like to get this real-time data through my server, so my API key doesn't appear clearly.
So, I want to initiate a request to my server; this request open a channel to the supplier of forex data's API. Then I open a channel to receive this real-time data from my server.
I have thought of the following, but that doesn't work.
var enableWs = require('express-ws');
enableWs(app);
const WebSocket = require('ws');
const WebSocketServer = require('ws').Server;
const URL = 'wss://API_ENDPOINT?api_key=MY_KEY';
app.ws('/ws', function (ws, req) {
const wss = new WebSocket(URL);
let msg = { action: 'subscribe', symbol : 'EUR-USD'};
wss.onopen = (event) => {
wss.send(JSON.stringify(msg));
};
wss.onmessage = function (event) {
jsonData = JSON.parse(event.data);
// now, i want to send the RT data received in jsonData to another channel so I can display the data on the FE
const wssReact = new WebSocketServer({ port: 7100 });
wssReact.send(jsonData);
}
wss.onclose = function (event) {
wss.terminate();
};
});
On the front-end, I expected to get a result when opening a socket as follows :
const socket = new WebSocket('ws://localhost:7100');
ok, I figured how to do what i wanted to achieve. I was close :-) And now it seems obvious lol.
My incoming WS data from the data provider is inconsistent, that's one reason why I couldn't figure the solution earlier.
app.ws('/ws', function (ws, req) {
const wss = new WebSocket(URL);
let msg = { action: 'subscribe', symbol : 'EUR-USD'};
wss.onopen = (event) => {
wss.send(JSON.stringify(msg));
};
wss.onmessage = function (event) {
ws.send(event.data);
}
wss.onclose = function (event) {
wss.terminate();
};
});
I hope it can be of any help to someone else :)

Getting multiple 404 and 405 errors in Teams Bot

I'm doing some investigating around a Teams Bot that I currently have in development. I'm seeing a lot of 404, and in some other cases, 405 errors when I look in Application Insights - I'm trying to understand if I've missed anything.
I have the App Service set to 'Always On' so my assumption is that it's polling the service every 5 minutes to keep it from idling out. However, I'm seeing a lot of 404 failures, specifically pointing to the GET/ endpoint and in other cases a 405 error as well, which is pointing to the api/messages endpoint.
I have the App ID and App Password set in the environment variables and I've set the storage using a Cosmos DB too as shown in the index.js file below. I have also checked the Teams manifest to ensure it's pointing to the Bot ID and recently added the bot domain as well to see if that makes a difference.
const restify = require('restify');
const path = require('path');
// Import required bot services.
// See https://aka.ms/bot-services to learn more about the different parts of a bot.
const { BotFrameworkAdapter, ConversationState, UserState } = require('botbuilder');
// Import required services for bot telemetry
const { ApplicationInsightsTelemetryClient, TelemetryInitializerMiddleware } = require('botbuilder-applicationinsights');
const { TelemetryLoggerMiddleware, NullTelemetryClient } = require('botbuilder-core');
// Import our custom bot class that provides a turn handling function.
const { DialogBot } = require('./bots/dialogBot');
const { ProvisioningProfileDialog } = require('./dialogs/provisioningProfileDialog');
// Read environment variables from .env file
const ENV_FILE = path.join(__dirname, '.env');
require('dotenv').config({ path: ENV_FILE });
// Create the adapter. See https://aka.ms/about-bot-adapter to learn more about using information from
// the .bot file when configuring your adapter.
const adapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
});
// Define the state store for your bot.
const { CosmosDbPartitionedStorage } = require('botbuilder-azure');
const cosmosStorage = new CosmosDbPartitionedStorage({
cosmosDbEndpoint: process.env.CosmosDbEndpoint,
authKey: process.env.CosmosDbAuthKey,
databaseId: process.env.CosmosDbDatabaseId,
containerId: process.env.CosmosDbContainerId,
compatibilityMode: false
});
// Create conversation state with storage provider.
const conversationState = new ConversationState(cosmosStorage);
const userState = new UserState(cosmosStorage);
// Create the main dialog.
const dialog = new ProvisioningProfileDialog(userState);
const bot = new DialogBot(conversationState, userState, dialog);
dialog.telemetryClient = telemetryClient;
// Catch-all for errors.
const onTurnErrorHandler = async (context, error) => {
// This check writes out errors to console log .vs. app insights.
// NOTE: In production environment, you should consider logging this to Azure
// application insights.
console.error(`\n [onTurnError] unhandled error: ${ error }`);
// Send a trace activity, which will be displayed in Bot Framework Emulator
await context.sendTraceActivity(
'OnTurnError Trace',
`${ error }`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
// Send a message to the user
await context.sendActivity('The bot encountered an error or bug.');
await context.sendActivity('To continue to run this bot, please fix the bot source code.');
// Clear out state
await conversationState.delete(context);
};
// Set the onTurnError for the singleton BotFrameworkAdapter.
adapter.onTurnError = onTurnErrorHandler;
// Add telemetry middleware to the adapter middleware pipeline
var telemetryClient = getTelemetryClient(process.env.InstrumentationKey);
var telemetryLoggerMiddleware = new TelemetryLoggerMiddleware(telemetryClient);
var initializerMiddleware = new TelemetryInitializerMiddleware(telemetryLoggerMiddleware);
adapter.use(initializerMiddleware);
// Creates a new TelemetryClient based on a instrumentation key
function getTelemetryClient(instrumentationKey) {
if (instrumentationKey) {
return new ApplicationInsightsTelemetryClient(instrumentationKey);
}
return new NullTelemetryClient();
}
// Create HTTP server.
const server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }.`);
console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');
console.log('\nTo talk to your bot, open the emulator select "Open Bot"');
});
// Listen for incoming requests.
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (context) => {
// Route the message to the bot's main handler.
await bot.run(context);
});
});
Whilst the Bot appears to run okay for the most part, am I missing something with these errors or is this expected behaviour since it's polling for a response?
Thanks in advance
Does your bot contain a web page as well? The node samples do not, but .NET samples do. If not, it would make sense, of course, to receive a 404. I tend to agree with you that the polling might be the cause.
Bots typically (especially when created from template or sample), do not handle GET endpoints to /api/messages. Everything is handled using POST.

Change issue closing pattern for gitlab user account

I want to close an issue matching the file name pushed with Issue title (My source files are named with unique integers, e.g. 34521.cpp and there are corresponding issues on Gitlab e.g. Problem #34521).
How can I do so?
The default pattern is not suitable as I have 2000+ issues and I do not want to refer issues with the issue ID's each time. I want it to be automated. So I was checking the page :
Change the issue closing pattern.
It says I need to have access to the server where gitlab is installed. Does that mean I cannot change the issue closing pattern for Gitlab cloud's user account hosted at http://gitlab.com ?
You can't define a custom closing pattern on gitlab.com, only on your own hosted gitlab instance. But what you can do is to use webhooks to listen on push events on a remote server. You can then parse the commit messages yourself and take decision on closing issues. You can use Gitlab API to close issue on your server instance (with a hard coded access token)
This can be tested locally using an http tunnel like ngrok
The following nodejs script starts a server serving a /webhook endpoint. This webhook endpoint is called when any push occurs on your repo.
const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios');
const to = require('await-to-js').to;
const port = 3000;
const projectId = "4316159";
const accessToken = "YOUR_ACCESS_TOKEN";
const app = express();
app.use(bodyParser.json())
app.post('/webhook', async function(req, res) {
console.log("received push event");
let result, err, closeRes;
for (var i = 0; i < req.body.commits.length; i++) {
for (var j = 0; j < req.body.commits[i].added.length; j++) {
filenameWithoutExt = req.body.commits[i].added[j].split('.').slice(0, -1).join('.');
[err, result] = await to(axios({
url: `https://gitlab.com/api/v4/projects/${projectId}/issues?search=#${filenameWithoutExt}`,
method: 'GET',
headers: {
"PRIVATE-TOKEN": accessToken
}
}));
if (err) {
console.log(err);
} else {
if (result.data.length !== 0) {
//close those issues
for (item in result.data) {
console.log(`closing issue #${result.data[item].iid} with title ${result.data[item].title}`);
[err, closeRes] = await to(axios({
url: `https://gitlab.com/api/v4/projects/${projectId}/issues/${result.data[item].iid}?state_event=close`,
method: 'PUT',
headers: {
"PRIVATE-TOKEN": accessToken
}
}));
if (err) {
console.log(err);
} else {
console.log(`closing status : ${closeRes.status}`);
}
}
} else {
console.log("no issue were found");
}
}
}
}
res.sendStatus(200);
});
app.listen(port, () => console.log(`listening on port ${port}!`))
In the above you need to change the access token value & projectId. Also note that above code will check only added file, you can modify it to include updated or deleted file matching your requirements.
Launch ngrok on port 3000 ngrok http 3000 & copy the given url in integrations sections of your repo :
Now when you add any file it will check for the filename without extension and search all issue with within title #filename_without_extension and close it right away

Kubernetes Websockets API pod exec node.js client send method

I'm having a very hard time sending commands to an interactive bash shell using a node.js websockets client. I'm running a cluster with kops and I'm able to establish the connection with a pod and get its prompt but I am unable to send commands and receive a response.
So my connection looks like:
const WebSocket = require('ws');
const fs = require('fs');
const readline = require('readline');
const unescape = require('querystring').unescape;
const escape = require('querystring').escape;
const channel = '0';
const access_token = "[the_api_token]";
const pod_name = 'nginx-726417742-s1nv2';
const host_address = 'wss://[the_api_url]';
const cmd = `${escape('/bin/bash')}&command=-i`;
const params = `stdout=1&stdin=1&stderr=1&tty=1&container=nginx&command=${cmd}`;
const url = `${host_address}/api/v1/namespaces/default/pods/${pod_name}/exec?${params}`;
const options = {
headers: {
'Authorization': `Bearer ${access_token}`
},
ca: fs.readFileSync('ca.crt'),
}
const ws = new WebSocket(url, options);
ws.on('open', () => {
console.log('connected');
});
ws.on('message', (data, flags) => {
process.stdout.write(data);
});
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('line', function (cmd) {
const data = channel + cmd;
if (ws && ws.readyState === 1) {
ws.send(data);
}
});
The ws.on('message', (data, flags) part prints the shell prompt perfectly root#nginx-726417742-s1nv2:/# but then I can type whatever and it goes trough ws.send(data) but no message is ever received and no error is generated. I have already tried to convert the data to base64 and send char by char instead of the whole line, but the result and behaviour is always the same.
If I proxy the API through kubectl proxy --disable-filter=true --port=8080 and use https://github.com/kubernetes-ui/container-terminal pointing to ws://localhost:8080 and using /api/v1/namespaces/default/pods/${pod_name}/exec I can get a working terminal, but local proxying is not an option for me.
Any help will be much appreciated.
I know that my answer comes late. But maybe I can help someone else with this. I have noticed that often the Kubernetes API requires to set an origin when using websockets. Otherwise the API returns strange errors sometimes. In your case you could try to complete your options as follows:
const options = {
headers: {
'Authorization': `Bearer ${access_token}`
},
ca: fs.readFileSync('ca.crt'),
origin: 'https://<the_api_url>:<port>'
}
Furthermore I think that it is not required to set wss as protocol (in your host_address variable). Usually the protocol should be automatically upgraded from https to wss during the connection process.

Alexa Skill works locally but not when testing in the skill developer portal

I am running an Node.js mqtt subscriber server on my raspberry pi that listens for published events by my lambda aws code.The publish event is triggered via an Alexa Skill event.
Example;
"Alexa, tell TV to change to channel 10" - This intent is then recognised by my Lambda code as a ChannelNumberIntent intent which then publishes the correct message with the channel number to my mqtt server on my Raspi. Raspi then does the changing channel logic.
I've run this all locally using the alexa-app-server and it works completely fine. Response from Alexa works and mqtt server receives the correct digits/message packet.
The problem arises when I test using the Alexa skill in the developer skill portal on the test tab. It runs fine, returns a response from the lambda saying message received but the mqtt subscriber never receives the channel digits/message packet? I believe somewhere after hours of puzzling my lambda code is messed up. Here is my code;
LAMBDA AWS CODE
'use strict';
module.change_code = 1;
var Alexa = require('alexa-app');
var skill = new Alexa.app('tvRemote');
var awsIot = require('aws-iot-device-sdk');
var deviceName = "tvRemote";
var _ = require('lodash');
var path = require('path');
var host = "randomletters.iot.us-east-1.amazonaws.com";
//App id is the skill being used.
var app_id = "amzn1.ask.skill.more random letters";
var thingShadows = awsIot.thingShadow({
keyPath: path.join(__dirname, '/Raspi.private.key'),
certPath: path.join(__dirname, '/Raspi.cert.pem'),
caPath: path.join(__dirname, '/root-CA.crt'),
clientId: deviceName,
region: "us-east-1",
});
var reprompt = 'I didn\'t hear a channel, tell me a channel number or name to change to that channel';
skill.launch(function (request, response) {
var prompt = 'To change channel, tell me a channel number.';
response.say(prompt).reprompt(reprompt).shouldEndSession(true);
});
skill.intent('ChannelNumberIntent', {
'slots': {
'CHANNELNUMBER': 'CHANNELID'
},
'utterances': ['{|Change|put} {|the|on} {|channel} {|to} {-|CHANNELNUMBER}']
},
function (request, response) {
var channelNumber = request.slot('CHANNELNUMBER');
if (_.isEmpty(channelNumber)) {
var prompt = 'I didn\'t hear a channel code. Tell me a channel code.';
response.say(prompt).shouldEndSession(true);
return true;
} else {
thingShadows.publish('lambda/channelNumber', channelNumber, function () {
var prompt1 = 'Okay.';
response.say(prompt1).shouldEndSession(false);
return true;
});
return true;
}
}
);
module.exports = skill;
MQTT SERVER
'use strict'
var mqtt = require('mqtt')
var fs = require('fs')
var path = require('path')
var KEY = fs.readFileSync(path.join(__dirname, '/Raspi.private.key'))
var CERT = fs.readFileSync(path.join(__dirname, '/Raspi.cert.pem'))
var TRUSTED_CA_LIST = fs.readFileSync(path.join(__dirname, '/root-CA.crt'))
var PORT = Corretportnumber
var HOST = 'RASPIHOSTWORDSASDFSDF.iot.us-east-1.amazonaws.com'
var options = {
port: PORT,
host: HOST,
key: KEY,
cert: CERT,
rejectUnauthorized: true,
// The CA list will be used to determine if server is authorized
ca: TRUSTED_CA_LIST
}
var client = mqtt.connect('mqtts://' + HOST, options)
client.subscribe('lambda/channelNumber')
client.on('connect', function () {
console.log('Connected')
})
client.on('message', function (topic, message) {
if (topic === 'lambda/channelNumber') {
// logic.changeChannelCode(message);
console.log(message.toString());
}
})
Obviously hid host and port numbers for reasons

Resources