How to add values from coinmarketcap API to telegram message? - node.js

I'm trying to finish telegram bot that will after several commands respond with message... Lost any hope of trying that i can solve this alone. Those commands with predefined message are done and working like a charm.. But now im stucked on the /price command which should show coin value in the message from coinmarket API respond..
I tried many variants but following results always called for API Call error: or message like [object Object]..
ALQO: $0.0443407142 | 9.73% 🙂
ETH: 0.000313592 | 10.14% 🙂
BTC: 0.0000107949 | 9.5% 🙂
Cap: $2,545,718
This text above is correct respond from bot... Unfortunately with free API from CMC i can do only price with USD so correct answer should be
Coinname: Price | Change%
Cap: Marketcap
My code of /price command
//This is /price command code
'use strict';
const Telegram = require('telegram-node-bot');
const rp = require('request-promise');
const requestOptions = {
method: 'GET',
uri: 'https://pro-
api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?
id=3501&convert=USD',
headers: {
'X-CMC_PRO_API_KEY': 'MYFREEAPIKEYFROMCMC'
},
json: true,
gzip: true
};
rp(requestOptions).then(response => {
console.log('API call response:', response['data'][3501]);
}).catch((err) => {
console.log('API call error:', err.message);
});
class PriceController extends Telegram.TelegramBaseController {
PriceHandler($) {
rp(requestOptions).then(response => {
console.log('API call response:', response['data'][3501]);
$.sendMessage('Cryptosoul: price', response['data']['USD']['price']
[3501]);
}).catch((err) => {
$.sendMessage('API call error:', err.message);
});
}
get routes() {
return {
'priceCommand': 'PriceHandler'
};
};
}
module.exports = PriceController;
Respond from API after node index.js (turning bot on, (message from visual studio terminal)
API call response: { id: 3501,
name: 'CryptoSoul',
symbol: 'SOUL',
slug: 'cryptosoul',
circulating_supply: 143362580.31,
total_supply: 499280500,
max_supply: null,
date_added: '2018-10-25T00:00:00.000Z',
num_market_pairs: 3,
tags: [],
platform:
{ id: 1027,
name: 'Ethereum',
symbol: 'ETH',
slug: 'ethereum',
token_address: '0xbb1f24c0c1554b9990222f036b0aad6ee4caec29' },
cmc_rank: 1194,
last_updated: '2019-04-01T23:03:07.000Z',
quote:
{ USD:
{ price: 0.000188038816143,
volume_24h: 11691.5261174775,
percent_change_1h: 0.29247,
percent_change_24h: 0.0222015,
percent_change_7d: 4.69888,
market_cap: 26957.72988069816,
last_updated: '2019-04-01T23:03:07.000Z' } } }
The messages that appears after /price command triggered
"API call error:"
"[object Object]"
"Error while running node index.js (bad code)"Chat with Bot

As I could see, you are incorrectly accessing the resulting json response object here:
$.sendMessage('Cryptosoul: price', response['data']['USD']['price']
[3501])
Just pretty printing that response object gives a correct way to access certain properties.
{
"status": {
"timestamp": "2019-04-02T08:38:09.230Z",
"error_code": 0,
"error_message": null,
"elapsed": 14,
"credit_count": 1
},
"data": {
"3501": {
"id": 3501,
"name": "CryptoSoul",
"symbol": "SOUL",
"slug": "cryptosoul",
"circulating_supply": 143362580.31,
"total_supply": 499280500,
"max_supply": null,
"date_added": "2018-10-25T00:00:00.000Z",
"num_market_pairs": 3,
"tags": [],
"platform": {
"id": 1027,
"name": "Ethereum",
"symbol": "ETH",
"slug": "ethereum",
"token_address": "0xbb1f24c0c1554b9990222f036b0aad6ee4caec29"
},
"cmc_rank": 1232,
"last_updated": "2019-04-02T08:37:08.000Z",
"quote": {
"USD": {
"price": 0.000201447607597,
"volume_24h": 12118.3983544441,
"percent_change_1h": 1.48854,
"percent_change_24h": 6.88076,
"percent_change_7d": 12.4484,
"market_cap": 28880.04882238228,
"last_updated": "2019-04-02T08:37:08.000Z"
}
}
}
}
}
So we could see that price field is located under USD object wich itself located under quote object, which is missing in your code.
Proper way to get it would be:
const price = response["data"][3501]["quote"]["USD"]["price"];
PriceHandler code:
PriceHandler($) {
rp(requestOptions)
.then((response) => {
const price = response["data"][3501]["quote"]["USD"]["price"];
$.sendMessage("Cryptosoul: price", price);
})
.catch((err) => {
console.error("API call error:", err.message);
});
}

Related

AJV validation doesn't return multiple errors when different values are missing in the fastify request body

I have a fastify server with a post endpoint. It accepts a JSON request body with a validation. Server code below:
const fastify = require("fastify");
const server = fastify({
ajv: {
customOptions: {
allErrors: true,
},
},
logger: true,
});
const schema = {
schema: {
body: {
type: "object",
properties: {
data: {
type: "array",
items: {
type: "object",
properties: {
foo: {
type: "string",
},
bar: {
type: "string",
},
},
required: ["foo", "bar"],
},
},
},
required: ["data"],
},
},
};
server.post("/", schema, function (request, reply) {
console.log({
request: {
body: JSON.stringify(request.body),
},
});
reply.send({ message: "hello" });
});
server.listen(3000, function (err, address) {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log(`server listening on ${address}`);
});
Below is a valid request body.
{ "data":[{ "bar": "bar exists", "foo": "foo exists" }]}
When I try to access the same server with multiple values in input missing i.e.,
{ "data":[{ "bar": "bar exists, foo missing" }, {}] }
I am getting the below response.
{
"statusCode": 400,
"error": "Bad Request",
"message": "body.data[0] should have required property 'foo', body.data[1] should have required property 'foo', body.data[1] should have required property 'bar'"
}
I want to get each error separately, instead of getting a single large error message as this request can go very large. I have tried a bit of trial around the ajv options but couldn't find anything.
Any help is appreciated. Cheers :)
You need to have a custom parser after the error is caught.
In order to achieve this approach, there is a method called setErrorHandler.
According to Fastify documentation:
Set a function that will be called whenever an error happens.
This is a simple parser, but you may need to change it to your taste:
server.setErrorHandler(function (error, request, reply) {
if (error.validation) {
reply.status(400).send({
statusCode: error.statusCode,
error: 'Bad Request',
message: error.validation.map(err => `${err.instancePath} ${err.message}`)
});
}
})
// rest of code

Stream.io Chat API - Member state omitted from queryChannels response

When I do the following:
const queryChannelsResponse = await this.client.queryChannels(
{ id: channelId },
{ last_updated_at: -1 },
{ state: true },
This does not include the members. How am I able to get the member information?
I am writing a webhook and I want it to send a push notification (I am currently sending these myself via expo) to all offline users.
I am migrating from pusher chatkit which is now being discontinued. They had a new_message_users_offline hook for this purpose.
In the message.new webhook payload in the documentation the members are present but they are not present in the request body:
{
"type": "message.new",
"cid": "messaging:394f36fd-d512-4f2b-a785-ab8dfe82af49",
"message": {
"id": "f73ee1a8-f6fd-450b-bc64-0840b4df8fd9-2b4908ad-e267-4c48-8f41-8c26c8f769ce",
"text": "Ffddf",
"html": "<p>Ffddf</p>\n",
"type": "regular",
"user": {
"id": "f73ee1a8-f6fd-450b-bc64-0840b4df8fd9",
"role": "user",
"created_at": "2020-04-06T14:06:37.979584Z",
"updated_at": "2020-04-06T19:45:39.556842Z",
"last_active": "2020-04-06T19:45:39.54939Z",
"banned": false,
"online": true,
"name": "Mark Everett",
"image": "https://8dc-user-files-dev.s3.eu-west-1.amazonaws.com/MEMBER_PROFILE_IMAGE-f73ee1a8-f6fd-450b-bc64-0840b4df8fd9.png?v=6"
},
"attachments": [],
"latest_reactions": [],
"own_reactions": [],
"reaction_counts": null,
"reaction_scores": {},
"reply_count": 0,
"created_at": "2020-04-06T19:51:14.114803Z",
"updated_at": "2020-04-06T19:51:14.114803Z",
"mentioned_users": []
},
"user": {
"id": "f73ee1a8-f6fd-450b-bc64-0840b4df8fd9",
"role": "user",
"created_at": "2020-04-06T14:06:37.979584Z",
"updated_at": "2020-04-06T19:45:39.556842Z",
"last_active": "2020-04-06T19:45:39.54939Z",
"banned": false,
"online": true,
"channel_unread_count": 0,
"channel_last_read_at": "1970-01-01T00:00:00Z",
"total_unread_count": 0,
"unread_channels": 0,
"unread_count": 0,
"image": "https://8dc-user-files-dev.s3.eu-west-1.amazonaws.com/MEMBER_PROFILE_IMAGE-f73ee1a8-f6fd-450b-bc64-0840b4df8fd9.png?v=6",
"name": "Mark Everett"
},
"watcher_count": 1,
"created_at": "2020-04-06T19:51:14.121213459Z",
"channel_type": "messaging",
"channel_id": "394f36fd-d512-4f2b-a785-ab8dfe82af49"
}
My plan is do do something like this:
public async getOfflineUserIds(channelId: string): Promise<string[]> {
try {
// Get the channel
const queryChannelsResponse = await this.client.queryChannels(
{ id: channelId },
{ last_updated_at: -1 },
{ message_limit: 0, limit: 1, state: true},
)
const channel = queryChannelsResponse[0]
console.log('channel: ', channel)
// Get the channels members
const userIds: string[] = []
// tslint:disable-next-line: forin
for (const index in channel.state.members) {
userIds.push(channel.state.members[index].user_id)
}
console.log('userIds:', userIds)
const queryUsersResponse = await this.client.queryUsers(
{ id: { $in: userIds } },
{ last_active: -1 },
{},
)
console.log('queryUsersResponse:', queryUsersResponse)
// Work out who is offline/online
const offlineUserIds = queryUsersResponse.users
.filter(u => !u.online)
.map(u => u.id)
return offlineUserIds
} catch (err) {
throw new InternalServerErrorException(
'Error getting offline users for channel.',
err,
)
}
}
This is now resolved.
I did not add the members to the channel with channel.addMembers. I create and add members on the server as this works perfectly for my use case.
If it helps anyone I ended up with these two methods:
public async getChannelUserIds(channelId: string): Promise<string[]> {
try {
const queryChannelsResponse = await this.client.queryChannels(
{ id: channelId },
{ last_updated_at: -1 },
{ message_limit: 0, limit: 1, state: true },
)
const channel = queryChannelsResponse[0]
const userIds = Object.keys(channel.state.members)
console.log('userIds:', userIds)
return userIds
} catch (err) {
throw new InternalServerErrorException(
`Error getting user ids for channel ('${channelId}').`,
err,
)
}
}
public async getOfflineUserIds(userIds: string[]): Promise<string[]> {
try {
const queryUsersResponse = await this.client.queryUsers(
{ id: { $in: userIds } },
{ last_active: -1 },
{},
)
console.log('queryUsersResponse:', queryUsersResponse)
const offlineUserIds = queryUsersResponse.users
.filter(u => !u.online)
.map(u => u.user_id)
return offlineUserIds
} catch (err) {
throw new InternalServerErrorException(
`Error getting offline user ids from ('${JSON.stringify(
userIds,
null,
2,
)}').`,
err,
)
}
}
And then in my webhook I:
#Post('stream/messages')
public async onReceive(
#Req() req: Request,
#Headers('x-signature') signature: string,
#Body() body: any,
) {
try {
console.debug('webhooks-stream.messages.onReceive')
this.chatService.verifyWebhook((req as any).rawBody, signature)
console.log('DEBUG WEBHOOK BODY', JSON.stringify(body, null, 2))
switch (body.type) {
case 'message.new': {
const offlineMemberIds = await this.chatService.getOfflineUserIds(
body.members.map(member => member.user_id),
)
...

How to post and populate bill

I've got a relational json called "client" inside Bill's model. This is my code:
const mongoose = require("mongoose");
const { Schema } = mongoose;
const billSchema = new Schema({
number: Number,
date: { type: Date, default: Date.now() },
type: String,
local: String,
client: { type: mongoose.Schema.Types.ObjectId, ref: "clients", required: true },
detail: [
{
quantity: Number,
product: { code: Number, name: String, price: Number },
undertotal: Number
}
],
total: Number
});
mongoose.model("bills", billSchema);
this is my post route:
app.post("/api/bills", async (req, res) => {
const { number, type, local, client, detail, total } = req.body;
await Client.findById(req.body.client._id).then(client => {
if (!client) {
return res.status(404).json({
message: "client not found"
});
}
});
const bill = new Bill({
number,
date: new Date(),
type,
local,
client,
detail,
total
});
try {
let newBill = await bill.save();
res.status(201).send(newBill);
} catch (err) {
if (err.name === "MongoError") {
res.status(409).send(err.message);
}
res.status(500).send(err);
}
});
//my get route
app.get("/api/bills", function(req, res) {
Bill.find({}, function(err, bills) {
Client.populate(bills, { path: "clients" }, function(err, bills) {
res.status(200).send(bills);
});
});
});
I want something like this:
{
"number": 302,
"type": "c",
"local": "porstmouth",
"client": {
"address": {
"street": "victoria street",
"number": 1001,
"floor": "2",
"flat": 4
},
"_id": "5dab929613fb682b48e4ca6b",
"name": "luke skywalker",
"mail": "l.skywalker#yahoo.com",
"cuil": "39193219",
"phone": 128391,
"__v": 0
},
"detail": [
{
"quantity": 500,
"product": {
"code": 300,
"name": "P2",
"price": 800
},
"undertotal": 5000
}
],
"total": 11000
}
But I see this result:
{
"date": "2019-10-20T12:27:17.162Z",
"_id": "5dac52a577e09b4acc45718d",
"number": 302,
"type": "c",
"local": "porstmouth ",
"client": "5dab929613fb682b48e4ca6b",
"detail": [
{
"_id": "5dac52a577e09b4acc45718e",
"quantity": 500,
"product": {
"code": 300,
"name": "P2",
"price": 800
},
"undertotal": 5000
}
],
"total": 11000,
"__v": 0
}
I don't want to see id client only. I want to see all content from client inside bill.
I tried to do with populate method, but I haven't results.
So, Which is form to post and populate a nested json relational object in this case?
While posting only clientId is enough.
So your post route can be like this (you both used await and then, which is incorrect, so I refactored it to use only await)
app.post('/api/bills', async (req, res) => {
const { number, type, local, client, detail, total } = req.body;
let existingClient = await Client.findById(req.body.client._id)
if (!existingClient) {
return res.status(404).json({
message: "client not found"
});
}
const bill = new Bill({
number,
date: new Date(),
type,
local,
client: req.body.client._id
detail,
total
})
try {
let newBill = await bill.save();
res.status(201).send(newBill);
} catch (err) {
if (err.name === 'MongoError') {
res.status(409).send(err.message);
}
res.status(500).send(err);
}
});
And in the get route to retrieve all the client info you need to populate it like this:
app.get('/api/bills', async (req, res) => {
try {
const bills = await Bill.find({}).populate("clients");
res.status(200).send(bills);
} catch (err) {
console.log(err);
res.status(500).send(err);
}
}
)

FCM iOS: Push notifications throw invalid argument

I am attempting to implement pushNotifications using Cloud Functions and FCM for iOS but I am consistently thrown this error:
2018-05-21T13:04:00.087Z I sendPushNotifications: Error sending
message: { Error: Request contains an invalid argument.
at FirebaseMessagingError.Error (native)
at FirebaseMessagingError.FirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:39:28)
at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:85:28)
at new FirebaseMessagingError (/user_code/node_modules/firebase-admin/lib/utils/error.js:241:16)
at Function.FirebaseMessagingError.fromServerError (/user_code/node_modules/firebase-admin/lib/utils/error.js:271:16)
at /user_code/node_modules/firebase-admin/lib/messaging/messaging-api-request.js:149:50
at process._tickDomainCallback (internal/process/next_tick.js:135:7) errorInfo: { code:
'messaging/invalid-argument',
message: 'Request contains an invalid argument.' }, codePrefix: 'messaging' }
My implementation in cloud functions are as follows:
exports.sendPushNotifications = functions.database.ref('/conversations/{userUid}/').onWrite((snap, context) => {
const userUid = context.params.userUid
console.log("Triggered user ", userUid)
return admin.database().ref('/fcmToken/' + userUid).once('value', snapshot => {
const values = snapshot.val()
const fcmToken = values.fcmToken
var message = {
"token": fcmToken,
"notification": {
"body": "New message"
},
"apns": {
"headers": {
"apns-priority": "5"
},
"payload": {
"aps": {
"alert": {
"body": "New message"
},
"badge": "1",
"sound": "default"
}
}
}
};
return admin.messaging().send(message)
.then((response) => {
return console.log('Successfully sent message:', response);
})
.catch((error) => {
return console.log('Error sending message:', error);
});
})
})
The frustrating thing is that when I remove the whole "apns" node the code actually works, ie I can receive the push notifications. I suppose this means my setup are all done properly. Once I included the "apns", it starts throwing the above error. I also reference these three posts, this, this and this, and made sure that I have carefully followed the code and instructions. For some reasons I cannot get it to work.
I also attempted to remove the "notification" node as the docs did mention that only use common keys when targetting all platforms. Since I am targetting only iOS for now, I suppose I should remove the "notification" key. But again it also throws the same error.
Ok, so it was a rookie mistake. It is correct that common keys should not be used if I am targetting iOS only. In addition to that, the badge should be an Int and not String.
This code worked:
var message = {
"token": fcmToken,
"apns": {
"headers": {
"apns-priority": "5"
},
"payload": {
"aps": {
"alert": {
"body": "New message"
},
"badge": 1,
"sound": "default"
}
}
}
}
Hope it helps anyone out there facing the same problem.
Just to add to this answer. If you are using both IOS and android while having custom sounds for both, the code below will work cross platform and avoid this issue.
const payload = {
token,
notification: {
title: `title text`,
body: `body text`,
},
android: {
// android
priority: "high", // legacy HTTP protocol (this can also be set to 10)
notification: {
channel_id: "call1",
priority: "high", // HTTP v1 protocol
notification_priority: "PRIORITY_MAX",
sound: "sound",
default_sound: true,
visibility: "PUBLIC",
},
},
apns: {
// apple
headers: { "apns-priority": "10" },
payload: {
aps: {
// If present with notification: {...}, this will create errors
// "alert": {
// "title": `title text`,
// "body": `body text`,
// },
badge: 1,
sound: {
critical: 1,
name: "sound.aiff",
volume: 1,
},
category: "call1",
},
},
},

How do I dynamically update the fields of a document?

I'm building a basic crud app for storing movie/tv titles. I am using version 2.4.4 of elasticsearch because my project manager chose this version. I am using the express framework along with the native elasticsearch nodejs driver.
Currently this is what my mappings look like for the "titles" index for a "title" type.
client.indices.create({
index: "titles",
body: {
"mappings": {
"title": {
"properties": {
"seriesName": { "type": "string", "index": "not_analyzed" },
"seriesEpisodeNumber": { "type": "long", "index": "not_analyzed" },
"seriesEpisodeTitle": { "type": "string", "index": "not_analyzed" },
"isDeleted": { "type": "boolean", "index": "not_analyzed" }
}
}
}
}
}).
I want to have an endpoint on my server that updates the document by its id. This is what I have so far for this endpoint " POST /api/titles/:titleID/updateTitleByID"
titlesController.updateTitleByID = function(req,res){
const titleID = req.params.titleID;
es.updateByQuery({
index: "titles",
type: "title",
body: {
query: {
bool: {
must: [{
term: {
_id: titleID
}},{
term: {
isDeleted: false
}}]
}
},
script: {
inline: "ctx._source.seriesName = seriesName;ctx._source.seriesEpisodeNumber = seriesEpisodeNumber; ctx._source.seriesEpisodeTitle = seriesEpisodeTitle;",
params: { ...req.body }
}
}}).then(results => {
logger.info(results);
res.status(200).json({
message: "OK"
});
}).catch(err => {
logger.error(err);
res.status(500).json({
error: "Internal server error"
});
});
};
What I want to achieve is to update the document with what ever I get from my req.body. For example if I get a seriesName I from the request body. That is only what I want to update and leave the rest untouched. How do I achieve this?
Edit: Here is the full stack trace
Error: [remote_transport_exception] [Agon][127.0.0.1:9300][indices:data/write/update[s]]
at respond (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/elasticsearch/src/lib/transport.js:289:15)
at checkRespForFailure (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/elasticsearch/src/lib/transport.js:248:7)
at HttpConnector.<anonymous> (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/elasticsearch/src/lib/connectors/http.js:164:7)
at IncomingMessage.wrapper (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/lodash/lodash.js:4968:19)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:186:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:74:11)
at process._tickDomainCallback (internal/process/next_tick.js:122:9)
You don't need to use the update-by-query endpoint if you want to update a single document, the Update API is more than sufficient since it supports partial updates, which is exactly what you're after. You need to use the update call instead:
titlesController.updateDocByID = function(req,res){
const titleID = req.params.titleID;
const partialFields = req.body;
// TODO do some validations on the received fields
es.update({
index: "titles",
type: "title",
id: titleID,
body: {
doc: partialFields
}}).then(results => {
logger.info(results);
res.status(200).json({
message: "OK"
});
}).catch(err => {
logger.error(err);
res.status(500).json({
error: "Internal server error"
});
});
};
If you need to only update the document if the isDeleted flag is false, then you can use the same API call but with a script instead, like this:
titlesController.updateDocByID = function(req,res){
const titleID = req.params.titleID;
const partialFields = req.body;
// TODO do some validations on the received fields
es.update({
index: "titles",
type: "title",
id: titleID,
body: {
script: "if (!ctx._source.isDeleted) { ctx._source.putAll(params.partialFields) }",
params: {partialFields: partialFields}
}}).then(results => {
logger.info(results);
res.status(200).json({
message: "OK"
});
}).catch(err => {
logger.error(err);
res.status(500).json({
error: "Internal server error"
});
});
};
If you use this second method, you'll need to enable dynamic scripting in your elasticsearch.yml configuration file.

Resources