Telegram (simplest) inline bot send photo 2 times on iOS - ios

I have an inline bot similar to #pic and it works ok, when I type query word I can see pictures appear so I can choose one and send to a chat. The problem is when I do - 2 copies of same result are sent. This happens only on iOS client. On Android, PC and other platforms only one picture is being sent. I have checked all logs (the bot is done in Node.js) and for every request I have a single response. It seems to be a iOS client bug, although #pic bot works fine. Has someone encountered this bug or have an idea of what can cause it?
Example of answerInlineQuery response object
{
"inline_query_id": "817150058382989968",
"results": [
{
"type": "photo",
"id": "se090",
"photo_url": "http://www.shadowera.com/secardbot361/se090.jpg",
"thumb_url": "http://www.shadowera.com/secardbot361/se090.jpg",
"photo_width": 344,
"photo_height": 480,
"title": "Tracking Gear",
"description": "You can view the hands of opposing players.",
"caption": "king"
},
{...
UPDATE:
So I have created a simplest possible inline bot in node.js #iosinlinebot (you can try it) AND you have the same exact behaviour: only on iOS devices you will send 2 images to the chat once tapped on the result.
Here is the code:
exports.handler = function(event, context) {
console.log(event);
const https = require("https");
let answer = {
inline_query_id: event.inline_query.id,
results: [{
type: "photo",
id: "abcd",
photo_url: "https://lh3.googleusercontent.com/jVXglyWWL5J2y1vRN-7Jy3_ozvvZc4w5486IAkbAIrWcNN_vn7YuIvhc1JDtGq43BqGl=s180",
thumb_url: "https://lh3.googleusercontent.com/jVXglyWWL5J2y1vRN-7Jy3_ozvvZc4w5486IAkbAIrWcNN_vn7YuIvhc1JDtGq43BqGl=s180",
photo_width: 180,
photo_height: 180,
title: "title",
description: "description",
caption: "test"
}],
cache_time:1
};
let postBody = JSON.stringify(answer);
let options = {
hostname: "api.telegram.org",
port: 443,
path: "/bot" + process.env.TOKEN + "/answerInlineQuery",
method: "POST",
headers: {
'Content-Type': 'application/json',
'Content-Length': postBody.length
}
};
let postreq = https.request(options, (res) => {
res.setEncoding('utf8');
const body = [];
res.on('data', (chunk) => body.push(chunk));
res.on('end', () => {
let j = body.join('');
console.log(j);
//context.done(JSON.parse(j));
});
});
postreq.write(postBody);
postreq.end();
};
this is an event object (coming from telegram):
{
"update_id": 12345678,
"inline_query": {
"id": "123456789123456789",
"from": {
"id": 123456789,
"is_bot": false,
"first_name": "Firstname",
"username": "username",
"language_code": "it-IT"
},
"query": "test",
"offset": ""
}
}
UPDATE:
Thanks to Sedric Heidarizarei we were able to find the problem. It is a telegram iOS client bug. If InlineQueryResultPhoto object contains caption field, you user will post 2 images to the chat.

It is very important to close the Begin and the End of your regex with ^ and $.
For example a user with this regex /^[/]start/ can use start and start a and start b as Bot command And will allow Them to receive your photo, But with /^[/]start$/, The user must enter the exact /start Command.
1: Use This Module: node-telegram-bot-api
2: And Send Your Photo:
bot.onText(/^[/]start$/, (msg) => {
const opts = {
parse_mode: 'Markdown',
reply_markup: {
inline_keyboard: [[{
text: '🔙',
callback_data: 'back'
}]]
}
};
bot.sendPhoto(msg.chat.id, 'AgADBAADn64xBoABCx8L8trMV9eMqgDAAEC', opts); // Your Photo id
});
Notice:
Open an empty project and just use and check your InlineQueryResultPhoto.
update:
That is a Telegram bug for For temporary use, remove caption from your let answer ={}

Related

forEach() iterates too fast with ASYNC AWAIT - Skipping data

I am re-writing my website and am trying to import my members from array of JSON objects.
Each object represents a band that the user has uploaded. So I wrote a quick file that works with the list and my new database / authentication system, auth0.
As outline in the comments below...
First I Request an access token so I can create an account for each member using Auth0. Then I iterate through my array of objects representing bands. For each band object I have the code do a few things... First, it creates a new account on my auth system. This sends back a user ID. I add the user ID to the band object to be stored in my database. It adds the location as well as adds a few posts that the user may have had from the old system.
This all works well if I do one user... But if I let it iterate through the whole array it doesn't work. It added about 15 bands to the auth system and 50 bands to the database... It seems like its going too fast to successfully publish them all. Any ideas on how to fix this?
** Also tried this with a for loop instead of for each. Moving everything inside the for loop creates both the same number of bands and user accounts, but now it times out at about 10 bands instead of doing all of them.
Thanks!
const uuidv4 = require('uuid')
const BulkBands = require('./BulkBands')
const fetch = require("node-fetch");
let bandCount = 0
//First I need to get an Access Token for my Auth Proider, Auth0
const getAccessToken = async () => {
try {
let response = await fetch(`https://nm-music.auth0.com/oauth/token`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: '{"client_id":"xxxxxxxxx","client_secret":"xxxxxxxxx", "audience":"https://xxxxxxxx.com/api/v2/","grant_type":"client_credentials"}'
})
const responseData = await response.json()
//Now I start The Process of Adding the Bands.
addTheBands(responseData.access_token)
} catch (error) {
console.log(error)
}
}
//Here I use the array method, forEach() to iterate over the list of about 77 bands.
const addTheBands = (accessToken) => {
BulkBands.forEach(band => {
//The first thing I want to do for each band is create an account for them on the Auth system.
createNewAccount(band, accessToken)
})
}
//Here is the function to create the account.
const createNewAccount = async (band, accessToken) => {
try {
const response = await fetch(`https://xxxxxxxxx.com/api/v2/users`, {
method: 'POST',
headers: {
'content-type': 'application/json;',
"Authorization": `Bearer ${accessToken}`,
},
body: JSON.stringify({
"connection": 'Username-Password-Authentication',
"email": band.bandEmail,
// "phone_number": "+19995550123",
"user_metadata": {},
"blocked": false,
"email_verified": false,
// "phone_verified": false,
"app_metadata": {},
// "given_name": band.bandName,
// "family_name": band.bandName,
"name": band.bandEmail,
// "nickname": "Johnny",
// "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png",
// "user_id": "",
// "connection": "Initial-Connection",
"password": "xxxxxxxx",
"verify_email": false,
// "username": band.bandEmail
})
})
let newUserResponse = await response.json()
//Now that their account has been made, I want to add the band to my database, and use the Account ID from newUserResponse as the userId for the band. That way the band is associted with their account.
addTheBand({
quoteGenerator: [],
userId: newUserResponse.identities[0].user_id,
posts: [
{
"type": "band",
"data": "",
"date": new Date(),
"postId": uuidv4(),
"approved": null,
"rockOn": []
},
],
type: 'band',
bandName: band.bandName,
bandEmail: band.bandEmail,
bandGenre: band.bandGenre,
bandBio: band.bandBio,
youtube: [band.youtube1, band.youtube2],
published: false,
cancellationPolicy: 'You may cancel this performance up to 2 weeks prior to the performance date without penalty. If this performance is cancelled within 2 weeks of the performance date the booking party will be required to pay 50% of the booking fee to the band. If the booking is cancelled within 3 days of the performance date the full payment is required to be paid to the band. ',
baseCost: band.baseCost,
mainDate: {
charge: 0,
sunday: 0,
monday: 0,
tuesday: 0,
wednesday: 0,
thursday: 0,
friday: 0,
saturday: 0,
}
}, band)
} catch (error) {
console.log(error)
}
}
//Now to start adding the band...
const addTheBand = async (newBand, band) => {
let newLocation = []
let accessToken = ''
//I need to geo-locate each band for my new system. So I use the google api to do so.
try {
let resposne = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${band.bandLocation}&key=xxxxxxxxx`)
const responseLocation = await resposne.json()
newLocation = [responseLocation.results[0].geometry.location.lng, responseLocation.results[0].geometry.location.lat ]
// I store the geo location for the band and now I post the new band to my database. The Geo Location is Indexed and does not work if I post it - I need to do this after the band is posted - no clue why.
response = await fetch('http://localhost:5000/api/autoquotegenerators', {
method: 'POST',
headers: {
"Content-Type": 'application/json; charset=UTF-8'
},
body: JSON.stringify(newBand)
})
const responseData = await response.json()
if(responseData){
console.log('Band Added');
//Like I said before... I need to add the geo-location now. So this function will do so.
updateBandLocation({
geometry: {
coordinates: newLocation,
city: band.bandLocation,
}
}, responseData._id, responseData, band)
}
} catch (error) {
console.log(error)
}
}
//I send the geo-location data to this function... it also takes care of a few last minute things such as posts, and quote figures and such. It all works.
const updateBandLocation = async (newLocation, bandUserId, band, oldBand) => {
console.log('adding location...')
let newPosts = [...band.posts]
let newQuoteGenerator = []
if(oldBand.timedRate > 0 ){
newQuoteGenerator.push({
cardType: "timed",
inputType: "select",
cardTitle: "Hourly Rate",
charge: oldBand.timedRate,
price: 0,
cardId: uuidv4()
})
}
if(oldBand.youtube1){
newPosts.push({
type: "video",
data: oldBand.youtube1,
date: new Date(),
postId: uuidv4(),
rockOn: []
})
}
if(oldBand.youtube2){
newPosts.push({
type: "video",
data: oldBand.youtube2,
date: new Date(),
postId: uuidv4(),
rockOn: []
})
}
try {
const response = await fetch(`http://localhost:5000/api/autoquotegenerators/${bandUserId}`, {
method: 'PUT',
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
body: JSON.stringify({
quoteGenerator: newQuoteGenerator,
bandLocation: newLocation,
posts: newPosts
})
})
const responseData = await response.json()
bandCount += 1
console.log(bandCount)
} catch (error) {
console.log(error)
}
}
getAccessToken()

Google Cloud Print Node API TICKET not working

This is my code and I am using npm node-gcp and sadly, the ticket is not working, I tried to make the copies = 3 to see if the printer will print 3 and to see if the ticket is working or not, But it's not working :(
I tried all the resources i can find, php ticket, c# ticket, But all are not working,
I am inside the code of npm node-gcp and I am trying to modify its code and see what will work and not, because the original code is he is not passing any ticket.
return preq({
method: "POST",
uri: "https://www.google.com/cloudprint/submit",
form: {
title: "Print job title",
content:
"test print",
ticket: {
version: "1.0",
print: {
copies: {
copies: 3
},
page_orientation: {
type: 0
},
margins: {
top_microns: 0,
bottom_microns: 0,
left_microns: 0,
right_microns: 0
}
}
},
contentType: "url", //optional, default = url
printerid: xxxxxxxxxxxxx,
tags: ["tag1", "tag2"] //optional, default = [],
},
headers: {
"X-CloudPrint-Proxy": "node-gcp",
Authorization: "OAuth " + this.options.accessToken
},
json: true
})
.then(function(result) {
return result;
})
.nodeify(cb);
});
i am expecting a response of number of pages is 3 but all I am getting is 1 piece of paper.
I just had to deal with this issue and discovered that ticket needs to be sent as a stringified json object.
For form, try this instead:
form: {
title: "Print job title",
content:
"test print",
ticket: JSON.stringify({
version: "1.0",
print: {
copies: {
copies: 3
},
page_orientation: {
type: 0
},
margins: {
top_microns: 0,
bottom_microns: 0,
left_microns: 0,
right_microns: 0
}
})
},

Is there a way to replace URL Link of Text in Google Docs API?

I started exploring Google Docs API in Python. It does pretty much everything I want it to do except for one thing.
I can replace the text of a document but I can't change the value of the hyperlinks.
Meaning if a link looks like this : a link, I can change the value of the text a link but not the target URL.
I've been going through the documentation but I can't find anything about it. Could it be a missing feature or am I missing the way to do that?
You can modify the hyperlink using UpdateTextStyleRequest of the batchupdate method in Google Docs API. At this time, please set the property of Link of TextStyle.
Endpoint
POST https://docs.googleapis.com/v1/documents/{file ID}:batchUpdate
Request body:
{
"requests": [
{
"updateTextStyle": {
"textStyle": {
"link": {
"url": "https://sampleUrl" # Please set the modified URL here.
}
},
"range": {
"startIndex": 1,
"endIndex": 2
},
"fields": "link"
}
}
]
}
Note:
From your question, I could understand that you have already used Google Docs API and you can modify the text of the link text. I think that you can modify the link using above request body and the script you have.
References:
UpdateTextStyleRequest
TextStyle
Link
If this was not useful for your situation, I apologize.
Edit:
You want to retrieve the text with the hyperlink.
From your reply comment, I could understand like above. When my understanding is correct, you can retrieve it using documents.get method. In this case, when fields is used, the response become to easily read.
Endpoint:
GET https://docs.googleapis.com/v1/documents/{file ID}?fields=body(content(paragraph(elements(endIndex%2CstartIndex%2CtextRun(content%2CtextStyle%2Flink%2Furl)))))
In this endpoint, body(content(paragraph(elements(endIndex,startIndex,textRun(content,textStyle/link/url))))) is used as fields.
Sample response:
For example, when the following texts are put in a Google Document and def has a hyperlink,
abc
def
The response is as follows. From the following result, you can retrieve the position of text with the hyperlink can be retrieved. Using this, you can modify the hyperlink.
{
"body": {
"content": [
{},
{
"paragraph": {
"elements": [
{
"startIndex": 1,
"endIndex": 5,
"textRun": {
"content": "abc\n",
"textStyle": {}
}
}
]
}
},
{
"paragraph": {
"elements": [
{
"startIndex": 5,
"endIndex": 8,
"textRun": {
"content": "def",
"textStyle": {
"link": {
"url": "https://sample/"
}
}
}
},
{
"startIndex": 8,
"endIndex": 9,
"textRun": {
"content": "\n",
"textStyle": {}
}
}
]
}
}
]
}
}
Reference:
documents.get
batchUpdate requires to know position of text, we can get document with all content and find positions of links
In my case I implement it as:
Copy template to new place with final name
Replace link texts and other parts of text
Get document
Find links positions in doc
Update link URLs
Here example in nodejs
const {google, docs_v1} = require('googleapis');
async function replaceInDoc(doc) {
let documentId = 'some doc id'
let auth = 'auth value for user'
let linkNewUrl = 'https://github.com/googleapis/google-api-nodejs-client'
google.options({auth: auth})
var docs = new docs_v1.Docs({}, google)
// document should have link with http://repo-url.com text, we will update it
var requests = [
{
replaceAllText: {
containsText: {
text: 'http://repo-url.com',
matchCase: true,
},
replaceText: linkNewUrl,
},
}
]
var updateRes = await docs.documents.batchUpdate({
documentId: documentId,
resource: {
requests: requests,
},
});
var docInfo = await docs.documents.get({documentId: documentId})
var linkPos = findLinksInDoc(docInfo)
// set new url to link by position of link in the document
var requests = [
{
updateTextStyle: {
textStyle: {
link: {
url: linkNewUrl
}
},
range: {
startIndex: linkPos[linkNewUrl][0],
endIndex: linkPos[linkNewUrl][1]
},
fields: "link"
}
}
]
var updateRes = await docs.documents.batchUpdate({
documentId: documentId,
resource: {
requests: requests,
},
});
}
// returns hash as { 'http://example.com': [startPosition, endPosition] }
function findLinksInDoc(doc) {
var links = {}
doc.data.body.content.forEach(section => {
if (section.paragraph) {
section.paragraph.elements.forEach(element => {
if (element.textRun && element.textRun.textStyle.link) {
links[element.textRun.content] = [element.startIndex, element.endIndex]
}
})
}
})
return links
}

actions on google, Oauth account Linking

I have been trying to connect an assistant action to my backend server
I am using my own Oauth server and followed the instructions on
https://developers.google.com/actions/identity/oauth2?oauth=code
I am using actions_intent_Sign_in for my dialogflow event intent (like https://actions-on-google.github.io/actions-on-google-nodejs/classes/conversation_helper.signin.html)
when i use my action to sign in, i get the login window to my server, i do the account linking and i can see that i generated the tokens on my server but i cant find the token in (conv.user.access.token)
and this is the code for my intent using "actions on google sdk "
'use strict';
var _ = require('lodash');
var path = require('path')
var express = require('express')
var http = require('http')
const bodyParser = require('body-parser');
var expressApp = express().use(bodyParser.json());
var server = http.createServer(expressApp).listen(3000)
const {
dialogflow,
SignIn
} = require('actions-on-google');
const app = dialogflow({
debug: true,
clientId: '7b4a6dfc-4b35-11e9-8646-d663bd873d93'
});
app.intent('Start Sign-in', conv => {
conv.ask(new SignIn());
});
app.intent('Get Sign-in', (conv, params, signin) => {
console.log("get sign in ");
console.log(JSON.stringify(signin));
if (signin.status === 'OK') {
const access = conv.user.access.token
console.log("the access token is " + access);
conv.ask('Great, thanks for signing in! What do you want to do next?');
} else {
conv.ask('I wont be able to save your data, but what do you want to do next?.');
}
});
and the response comes back as
{"#type":"type.googleapis.com/google.actions.v2.SignInValue","status":"OK"}
the access token is undefined
Response {
"status": 200,
"headers": {
"content-type": "application/json;charset=utf-8"
},
"body": {
"payload": {
"google": {
"expectUserResponse": true,
"richResponse": {
"items": [
{
"simpleResponse": {
"textToSpeech": "Great, thanks for signing in! What do you want to do next?"
}
}
]
}
}
}
}
}
the user object of conv has only this data
"user": {
"raw": {
"lastSeen": "2019-03-20T12:46:23Z",
"locale": "en-US",
"userId": "okdhyeGSk5tofgLjEepIUrA6mmewCESY8MjklZRPvQJgv6-uybfPobwdfgtrGZJ3bE2sM9ninhst"
},
"storage": {},
"_id": "okdhyeGSk5tofgLjEepIUrA6mmewCESY8MjklZRPvQJgv6-uybfPobwdfgtrGZJ3bE2sM9ninhst",
"locale": "en-US",
"permissions": [],
"last": {
"seen": "2019-03-20T12:46:23.000Z"
},
"name": {},
"entitlements": [],
"access": {},
"profile": {}
}
i dont know where the access/refresh token can be found or if there is any requirement for the post to send from my oauth server that i missed
so finally i managed to get it working with the help of Actions on Google Support Team
the problem was me having another google account logged-in in another tab, even though i had the AoG and dialogflow agent connected with the same account
tried all using incognito window and it works

JIRA API after POST returns { errorMessages: [ 'Internal server error' ], errors: {} }

I am trying to create a new issue utilizing the JIRA REST API and whenever I try, I get back the following generic error:
{ errorMessages: [ 'Internal server error' ], errors: {} }
I can successfully GET from the API, and the credentials I'm connecting with have full Admin access to JIRA (so it's not an Auth issue), but I get this error every time with POST. Below is a snippet of the JSON data I'm sending. Am I missing anything obvious?
Below is my JavaScript code. Note I'm using jira-connector from npm. (Real domain replaced with mydomain for this sample code)
const JiraClient = require('jira-connector');
const dotenv = require('dotenv').config();
function createNewIssue(fields) {
const encoded = process.env.JIRA_ENCODED_PW;
const jira = new JiraClient({
host: 'mydomain.atlassian.net',
basic_auth: {
base64: encoded
}
});
return new Promise((resolve, reject) => {
jira.issue.createIssue(fields, (error, issue) => {
if (error) {
console.log(error);
reject(error);
} else {
console.log(issue);
resolve(encoded);
}
});
})
}
Below is the JSON that's being passed into fields in the JS above. Note customfield_17300 is a radio button, and customfield_17300 is a multi-select box. For both cases, I've tried using the "id" and also the actual string "name" value. All IDs below were taken straight from a API GET of the same issue in question:
{
"fields": {
"project": {
"id": "13400"
},
"summary": "TEST API TICKET - 01",
"issuetype": {
"id": "11701"
},
"customfield_14804": { "id": "13716" },
"customfield_14607": "Hardware",
"customfield_17300": [
{
"id": "18322"
}
] ,
"customfield_16301": "Customer PO",
"customfield_14800": "LA, California",
"customfield_16302": "FEDEX 234982347g"
}
}
sigh I figured it out... other posts that said this cryptic error was due to a malformed JSON were correct.
In my route, I passed fields as coming from req.body.fields which actually dove into the fields values instead of passing it straight through. This made it so that when the JSON was sent to JIRA the fields outer wrapper was missing. I changed my route to pass along req.body instead of req.body.fields and all was well.
...that was a fun 4 hours...

Resources