AWS CDK - Building Telegram bot with AWS Lambda and API Gateway Proxy Integration - Part 2
Akhilesh Yadav
Posted on February 20, 2022
Welcome to part 2 of this series. This would be the final series of AWS CDK - Building Telegram bot with AWS Lambda and API Gateway Proxy Integration
. Regret for posting this lately.
You can find the sourcecode on Git repository by following below link. Checkout branch part2
https://github.com/arki7n/aws-cdk-telegram-bot-example.git
Commands for local usage:
git clone https://github.com/arki7n/aws-cdk-telegram-bot-example.git
git checkout part2
So let me start about all the update. Incase you have the main
branch of the repository, you can follow changes and execute one by one.
Let's update few packages to make sure existing code doesn't break with AWS CDK feature updates.
npm install @aws-cdk/core@1.137
npm install -g aws-cdk
Setting up a new Telegram Bot
Head to your Web browser.
Step 1: Open below link and signin in with your telegram account.
https://web.telegram.org/k/
Step 2: After successful login, search for "BotFather" bot in Telegram search bar.
Step 3: Type /help
and then bot would reply with its menu. Click on /newbot
to begin setting up new bot.
Step 4: I will be creating up a bot with the intention of saving bookmark links or texts to some database. And then be able to view the database.
Step 5: Save the API KEY token which would be required to access telegram APIs.
Example Telegram Bot: https://t.me/BookmarkmeBot
Telegram API Resources
There are 2 ways to setup bot with Telegram and hook our custom lambda function with it.
- Using HTTP long-polling on Telegram API: Active Server driven and could be expensive to keep the server running and poll for new user messages to our bot.
- Webhook: As soon as the bot receives new message, Telegram Server sends the message to our custom HTTP URL using POST method. We would use API Gateway URL and lambda function would do the rest of the work of processing data and sending response back to the telegram user.
Setting up new API Gateway URL path for Webhook in AWS CDK code.
There are some new addition in the earlier code of Part 1.
- Used npm 'path' package to get relevant directory of lambda function.
- Added dynamic description for Lambda to always upload code on new deployments irrespective of any changes in code.
- Added Lambda Versioning to track changes in AWS Lambda UI Console.
- Enabling CORS at API Gateway side to let telegram server push Webhook message without getting blocked (403 Forbidden Error).
- New Resource path
/bot/webhook
added with POST method integration with lambda. Keeping/bot
path for manual health check and see new lambda version information is available. - Output URL of API URL and cloudwatch log UI.
Find the code below for aws-cdk-telegram-bot-example\cdk-tool\lib\cdk-tool-stack.js
file. Make sure to replace the BOT_TOKEN with yours.
const cdk = require("@aws-cdk/core");
const lambda = require("@aws-cdk/aws-lambda");
const apigw = require("@aws-cdk/aws-apigateway");
const path = require('path');
const BOT_TOKEN = '5118686429:AAHtgBvYLyrTSIUJ-iNRmV5MiuTYcSfAXIYeysdf'; // PASTE Telegram API BOT TOKEN here
class CdkToolStack extends cdk.Stack {
/**
*
* @param {cdk.Construct} scope
* @param {string} id
* @param {cdk.StackProps=} props
*/
constructor(scope, id, props) {
super(scope, id, props);
// All constructs take these same three arguments : scope, id/name, props
const lambdaTelegram = new lambda.Function(this, "telegramBotHandler", {
runtime: lambda.Runtime.NODEJS_14_X,
handler: "index.handler",
code: lambda.Code.fromAsset(path.join(__dirname, '../../assets/lambda/telegram-bot')), // Get relevant path to lambda directory.
architecture: lambda.Architecture.ARM_64,
environment: {
'CURRENT_ENV': 'dev',
'BOT_TOKEN': BOT_TOKEN
},
description: `Generated on: ${new Date().toISOString()}` // added to keep pushing latest code on AWS lambda on each deployment.
});
/*Versioning every new changes and keeping track of it. Check AWS Lambda UI Console*/
const version = new lambda.Version(this, 'Ver'+new Date().toISOString(), {
lambda: lambdaTelegram,
});
// All constructs take these same three arguments : scope, id/name, props
// defines an API Gateway REST API resource backed by our "telegrambot-api" function.
const restApi = new apigw.RestApi(this, "telegrambot-api", {
deploy: false,
defaultCorsPreflightOptions: { // Enable CORS policy to allow from any origin. Customize as needed.
allowHeaders: [
'Content-Type',
'X-Amz-Date',
'Authorization',
'X-Api-Key',
],
allowMethods: ['OPTIONS', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowCredentials: false,
allowOrigins: apigw.Cors.ALL_ORIGINS,
}
});
// Let's keep this as it as and use it for normal 'Hello World' Response with GET method integration with lamhda.
restApi.root
.addResource("bot")
.addMethod("GET", new apigw.LambdaIntegration(lambdaTelegram, { proxy: true }));
// Lets add nested resource under /bot resource path and attach a POST method with same Lambda integration.
restApi.root
.getResource("bot")
.addResource("webhook")
.addMethod("POST", new apigw.LambdaIntegration(lambdaTelegram, { proxy: true }));
// All constructs take these same three arguments : scope, id/name, props
const devDeploy = new apigw.Deployment(this, "dev-deployment", { api: restApi });
// All constructs take these same three arguments : scope, id/name, props
const devStage = new apigw.Stage(this, "devStage", {
deployment: devDeploy,
stageName: 'dev' // If not passed, by default it will be 'prod'
});
// All constructs take these same three arguments : scope, id/name, props
new cdk.CfnOutput(this, "BotURL", {
value: `https://${restApi.restApiId}.execute-api.${this.region}.amazonaws.com/dev/bot`,
});
new cdk.CfnOutput(this, "BotWebhookUrl", {
value: `https://${restApi.restApiId}.execute-api.${this.region}.amazonaws.com/dev/bot/webhook`,
});
new cdk.CfnOutput(this, "Lambda Cloudwatch Log URL", {
value: `https://console.aws.amazon.com/cloudwatch/home?region=${this.region}#logsV2:log-groups/log-group/$252Faws$252Flambda$252F${lambdaTelegram.functionName}`
});
}
}
module.exports = { CdkToolStack };
Update Lambda code
As we have gone with Webhook Approach, Telegram Server would push new user messages to our set Webhook URL. (Will show how to set webhook URL).
A normal lambda event object would like below. You can find resource path info, used method and stringified JSON object of telegram bot user message within body field.
{
"resource": "/bot/webhook",
"path": "/bot/webhook",
"httpMethod": "POST",
"headers": {
"Accept-Encoding": "gzip, deflate",
....
},
"multiValueHeaders": {
"Accept-Encoding": [
"gzip, deflate"
],
.....
},
"queryStringParameters": null,
"multiValueQueryStringParameters": null,
"pathParameters": null,
"stageVariables": null,
"requestContext": {
"resourceId": "93ctxg",
"resourcePath": "/bot/webhook",
"httpMethod": "POST",
"extendedRequestId": "N1EZWE8FIAMFimA=",
"requestTime": "20/Feb/2022:07:02:06 +0000",
"path": "/dev/bot/webhook",
"accountId": "285535506992",
"protocol": "HTTP/1.1",
"stage": "dev",
.......
"domainName": "tq9rr56bhc.execute-api.us-east-1.amazonaws.com",
"apiId": "tq9rr56bhc"
},
"body": "{\"update_id\":192810399,\n\"message\":{\"message_id\":15,\"from\":{\"id\":198940317,\"is_bot\":false,\"first_name\":\"Vikit\",\"username\":\"redblueshine\",\"language_code\":\"en\"},\"chat\":{\"id\":198940317,\"first_name\":\"Vikit\",\"username\":\"redblueshine\",\"type\":\"private\"},\"date\":1645340526,\"text\":\"hi\"}}",
"isBase64Encoded": false
}
Let's parse the stringified JSON object using JSON.parse(PASTE_STRINGIFIED_DATA) method. You will find from field containing id (Telegram UserID) and text field containing message. We will be requiring this 2 field information for replying towards the message sent by bot user.
File Path: \aws-cdk-telegram-bot-example\assets\lambda\telegram-bot\index.js
Lets add few libraries in our lambda code. e.g axios
const axios = require('axios');
const telegramLink = `https://api.telegram.org/bot${process.env.BOT_TOKEN}/sendMessage`;
exports.handler = async function(event) {
console.log("request:", JSON.stringify(event, undefined, 2));
if(event.path==="/bot" || event.path==="/bot/"){
return {
statusCode: 200,
headers: { "Content-Type": "text/plain" },
body: `Hello, CDK! You've hit ${process.env.AWS_LAMBDA_FUNCTION_NAME} with ${process.env.AWS_LAMBDA_FUNCTION_VERSION}\n`
};
}
try {
if(event.body){
const jsonData = JSON.parse(event.body).message;
await sendReply(jsonData.from.id, 'Processing data:'+jsonData.text);
}
} catch(e){
console.log('Error occured:',e);
}
return {
statusCode: 200,
headers: { "Content-Type": "text/plain" },
body: `Success`
};
};
function sendReply(chatId, textReply){
var data = JSON.stringify({
"chat_id": chatId,
"text": textReply,
"disable_notification": true
});
const config = {
method: 'post',
url: telegramLink,
headers: {
'Content-Type': 'application/json'
},
data : data
};
return axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
}
Deploy CDK changes
Save all the changes and hit below command from directory path aws-cdk-telegram-bot-example/cdk-tool
cdk deploy --require-approval never
Save above 3 links(BotURL, BotWebhookURL, LambdaCloudwatchLogURL) in notepad as we would need it later on.
If incase there is error, you can destroy and recreate by executing below commands.
cdk destroy
cdk deploy --require-approval never
You can confirm changes by moving to API Gateway UI, Lambda UI and associated Cloudformation template.
Setting up Telegram Webhook
Telegram API Documentation can be found at: https://core.telegram.org/bots/api
Set webhook url for the given Telegram Bot.
bot_token=Collected while creating new telegram bot.
url_to_send_updates_to = BotWebhookURL from last step.
You can simply paste below links in web browser.
- Set Webhook https://api.telegram.org/bot{bot_token}/setWebhook?url={url_to_send_updates_to}
- Get Webhook Info which will contain info on pending replies count and last response. https://api.telegram.org/bot{bot_token}/getWebhookInfo?url={url_to_send_updates_to}
- Get some more info. https://api.telegram.org/bot{bot_token}/WebhookInfo?url={url_to_send_updates_to}
- Delete webhook and stop pushing message to API Gateway URL. https://api.telegram.org/bot{bot_token}/deleteWebhook?url={url_to_send_updates_to}
So make sure to set webhook url and check the webhook info.
Final Testing
Head over to your created Telegram bot and send some text.
Example: https://t.me/BookmarkmeBot
and testing the normal /bot
path from browser.
What's Next
I am not going to make this post lengthy. So you can write additional javascript code to save all your received data to AWS dynamoDB table or you can use Airtable API and see data with Airtable Site UI. Or build some jokes api, dictionary or anything basd on your requirements.
You can find the sourcecode on Git repository at below link. Checkout branch part2
https://github.com/arki7n/aws-cdk-telegram-bot-example.git
Commands for local usage:
git clone https://github.com/arki7n/aws-cdk-telegram-bot-example.git
git checkout part2
Do not forget to destory the cloudformation stack after testing.
Run cdk destroy
to wipe out all the created resources.
If you need some more help related to Airtable or building some additional logic, you can follow me at Twitter and I will help you to give some solution.
Follow Twitter: https://twitter.com/arki7n
Posted on February 20, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
February 20, 2022