I have used the the calendar api https://www.googleapis.com/calendar/v3/calendars/ for creating an event but not getting meeting link. Also i have tested the api in postman but getting bad request attaching the body
resource:{conferenceData:{createRequest:{requestId:7qxalsvy02}}}
conferenceDataVersion: 1
summary: demo14
start: { dateTime: 14/10/2020, timeZone: UTC}
end: { dateTime: 14/10/2020, timeZone: UTC}
attendees: [{email: burhanuddin.hussain#pragtech.co.in, self: True}]
description:des
Attaching code
bearer = 'Bearer '+login_user_id.access_token
payload = {}
headers = {
'Content-Type': "application/json",
'Authorization':bearer
}
attendees_list = []
attendees = self.sudo().partner_ids
for i in attendees:
attendees_list.append({"email" :i.email})
resource={"conferenceData":{"createRequest":{"requestId": "7qxalsvy02"}}}
body={
"resource": resource,
"conferenceDataVersion": 1,
"summary" : self.name,
"start": { "dateTime": start_datetime, "timeZone": "UTC"},
"end": { "dateTime": end_datetime, "timeZone": "UTC"},
"attendees":attendees_list,
"description":self.description,
}
data_json = json.dumps(body)
url='https://www.googleapis.com/calendar/v3/calendars/'+login_user_id.calendar_id+'/events'
hangout_meet_response = requests.request("POST", url, headers=headers, data=data_json)
if hangout_meet_response.status_code == 200:
data_rec = hangout_meet_response.json()
self.write({"meet_url":data_rec.get('hangoutLink'),"meet_id":data_rec.get('id')})
hangout_meet_link = data_rec.get('hangoutLink')
if hangout_meet_link:
self.write({"meet_code": hangout_meet_link.split('/')[3]})
elif hangout_meet_response.status_code == 401:
raise UserError("Please Authenticate with Hangouts Meet.")
conferenceDataVersion is not a part of the request body, it needs to be incorporated into the request URL:
url='https://www.googleapis.com/calendar/v3/calendars/'+login_user_id.calendar_id+'/events?conferenceDataVersion=1';
body = {
"conferenceData":{"createRequest":{"requestId": "7qxalsvy02"}},
"summary" : self.name,
"start": { "dateTime": start_datetime, "timeZone": "UTC"},
"end": { "dateTime": end_datetime, "timeZone": "UTC"},
"attendees":attendees_list,
"description":self.description
}
Related
Versions
Package: botbuilder#4.11.0
Nodejs: v16.19.0
Teams Android Client: 1416/1.0.0/2023012702/0115
I have a chatbot that functions as a notification bot that sends an adaptive card notification to our users.
On the card, the user can reply to the notification by typing in the Input.Text field and then clicking a send button that triggers an Action.Execute, our Nodejs bot will then process the activity by sending notifications to corresponding user and then updating the card with a new card.
This works perfectly on Web and Desktop app but in the Teams Android App the card will briefly shows an error message saying "Something went wrong" before our card is finally updated.
These is the card that I send:
{
"type": "AdaptiveCard",
"appId": process.env.MicrosoftBotAppId,
"body": [
{
"type": "TextBlock",
"text": message,
"wrap": true
},
{
"id": "history_title",
"type": "TextBlock",
"text": cardString.pastEvents,
"wrap": true
},
{
"type": "Container",
"spacing": "None",
"items": [
{
"id": "last_trail_name",
"type": "TextBlock",
"text": lastTrail ? cardString.action(lastTrail.userName, lastTrailAction) : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "last_trail_comment",
"type": "TextBlock",
"text": lastTrail ? `${lastTrail.comment.replace(/<\/?[^>]+(>|$)/g, "")}` : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "last_trail_date",
"type": "TextBlock",
"text": lastTrail ? `${lastTrailDateString}` : "",
"spacing": "None",
"isSubtle": true,
"size": "Small",
"wrap": true
}
]
},
{
"type": "Container",
"items": [
{
"id": "second_last_trail_name",
"type": "TextBlock",
"text": secondLastTrail ? cardString.action(secondLastTrail.userName, secondLastTrailAction) : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "second_last_trail_comment",
"type": "TextBlock",
"text": secondLastTrail ? `${secondLastTrail.comment.replace(/<\/?[^>]+(>|$)/g, "")}` : "",
"spacing": "None",
// "size": "Small",
"wrap": true
},
{
"id": "second_last_trail_date",
"type": "TextBlock",
"text": secondLastTrail ? `${secondLastTrailDateString}` : "",
"spacing": "None",
"isSubtle": true,
"size": "Small",
"wrap": true
}
]
},
{
"id": "comment",
"isRequired": true,
"type": "Input.Text",
"placeholder": cardString.commentPlaceholder,
"isMultiline": true
},
{
"id": "success-msg",
"type": "TextBlock",
"text": success ? cardString.commentSentAlert : "",
"spacing": "None",
"isSubtle": true,
"size": "Small",
"wrap": true,
"color": "good"
}
],
"actions": [
{
// "tooltip": isPremium ? cardString.send : cardString.premiumTooltip,
// "isEnabled": isPremium,
"type": "Action.Execute",
"verb": "comment",
"title": cardString.send,
"data": data,
},
{
"type": "Action.OpenUrl",
"title": cardString.goToTicketButton,
"url": deeplink
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4"
}
And this is the bot that processed the action:
class Bot extends TeamsActivityHandler {
constructor() {
super();
this.onMembersAdded(async (turnContext: TurnContext, next: () => Promise<any>) => {
const gettingStartedUrl: string = 'https://www.teamswork.app/gettingstarted';
const membersAdded = turnContext.activity.membersAdded;
for (let cnt = 0; cnt < membersAdded.length; cnt++) {
if (membersAdded[cnt].id !== turnContext.activity.recipient.id) {
const welcomeMessage = "Welcome message"
// add conv references
if (await this.storeConversationReference(turnContext))
await turnContext.sendActivity(welcomeMessage); // Only send notification if conversation reference store (one time)
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
// Listener for activity e.g. comment activity on notification card
async onInvokeActivity(turnContext: TurnContext): Promise<InvokeResponse<any>> {
// Listen for activity invocation and check if the comment action on adaptiveCard has been called
const activity: Activity = turnContext.activity;
// context.log('activity: ', activity);
// turnContext.sendActivity("Hello World!");
let card: object;
if (activity.name === "adaptiveCard/action") {
// context.log("action activity");
const action = activity.value.action;
// context.log("action : ", action);
if (action.verb === "comment") {
const userName: string = activity.from.name;
const userId: string = activity.from.aadObjectId;
const data: IActionData = action.data;
// context.log('data: ', action.data);
const date: Date = new Date();
const tenantId: string = activity.conversation.tenantId;
const comment: string = data.comment;
const lang: string = data.lang || "en";
// assignee Ticket == user commennt
if ((data.ticket.assigneeId == userId) && (data.ticket.firstTimeResponse == "")) {
this.updateTicket(data.ticket)
}
// Check for ticketId
if (!!!data.ticket) {
context.log("No ticketId found, aborting");
// throw ("No ticketId found, aborting")
}
// Construct new audit trail / comment
let newComment: IAuditTrail = {
userName: userName,
userId: userId,
ticketId: data.ticket.id,
action: "commented",
comment: comment,
date: date,
}
// initialize parameters for card to be sent
const cardContent: ICardContent = {
userName: userName,
type: "comment",
action: "commented",
comment: comment,
isRequestor: false,
}
// Prepare response message
const encodedDeeplink: string = constructDeeplink(data.ticket, data.ticketingAppUrl, data.channelId, data.entityId);
const message: string = constructMessage(data.ticket, data.cardContent, encodedDeeplink, lang);
const isPremium: boolean = await getPremium(tenantId);
// const card = invokeSuccessResponse(encodedDeeplink, message, data);
// Save audit trail
const insertCommentEndpoint: string = process.env["InsertCommentEndpoint"];
try {
await cosmosService.insertItem(insertCommentEndpoint + encodeQueryParams({ instanceId: data.ticket.instanceId }), { comment: newComment });
} catch (error) {
context.log(error);
}
// Send request to SendCardNotification API
try {
// Notification recipient id array
let recipientIds: string[] = [];
if (data.ticket.requestorId)
recipientIds.push(data.ticket.requestorId)
if (data.ticket.assigneeId)
recipientIds.push(data.ticket.assigneeId)
// If ticket have custom fields get people from them
if (data.ticket.customFields) {
// Get instance so we can get customFieldSetting
const instanceContainer: Container = ticketDatabase.container(process.env["InstanceContainer"]);
const { resource: instance } = await instanceContainer.item(data.ticket.instanceId, tenantId).read<IInstance>();
// Create array for user who will receive the notification
let activePersonaCustomFields: ICustomField[] = [];
if (instance.customFieldsLeft)
activePersonaCustomFields.push(...instance.customFieldsLeft);
if (instance.customFieldsRight)
activePersonaCustomFields.push(...instance.customFieldsRight);
activePersonaCustomFields = activePersonaCustomFields.filter((value) => value.type.key === "6_peoplepicker");
activePersonaCustomFields = activePersonaCustomFields.filter((value) => value.isReceiveNotification);
if (activePersonaCustomFields.length > 0) {
for (const fields of activePersonaCustomFields) {
if (data.ticket.customFields[fields.id]?.length > 0) {
const personas: IPersonaProps[] = data.ticket.customFields[fields.id];
for (const element of personas) {
recipientIds.push(element.id)
}
}
}
}
}
// Remove message sender from recipient list
recipientIds = recipientIds.filter((id) => id !== userId);
// Get unique notification recipient array
const uniqueRecipientIds: string[] = [... new Set(recipientIds)];
context.log("Sending notifications");
for (const id of uniqueRecipientIds) {
await this.sendNotification(cardContent, id, tenantId, data);
}
card = NotificationCard(encodedDeeplink, message, data, true, isPremium, lang);
const cardAttachment: Attachment = CardFactory.adaptiveCard(card);
const activityPayload = MessageFactory.attachment(cardAttachment);
activityPayload.id = turnContext.activity.replyToId;
context.log("Updating cards");
await turnContext.updateActivity(activityPayload);
} catch (error) {
context.log(error)
}
}
}
const cardRes = {
statusCode: StatusCodes.OK,
type: 'application/vnd.microsoft.card.adaptive',
value: card
};
const res = {
status: StatusCodes.OK,
body: cardRes
};
return res;
}
}
It actually does not matter what I do on the card, even if I do nothing and then only return the InvokeResponse, the error still happens.
Expected behavior
The card is updated without any error message shown.
Screenshots
I'm currently writing a custom adapter in Typescript to connect Google Assistant to Microsoft's Botframework. In this adapter I'm attempting to capture the Google Assistant conversation object through a webook call and change it using my bot.
At this moment the only thing that my bot is doing is receive the request from Actions on Google and parsing the request body into an ActionsOnGoogleConversation object. After this I call conv.ask() to try a simple conversation between the two services.
Api Endpoint:
app.post("/api/google", (req, res) => {
googleAdapter.processActivity(req, res, async (context) => {
await bot.run(context);
});
});
Adapter processActivity function:
public async processActivity(req: WebRequest, res: WebResponse, logic: (context: TurnContext) => Promise<void>): Promise<void> {
const body = req.body;
let conv = new ActionsSdkConversation();
Object.assign(conv, body);
res.status(200);
res.send(conv.ask("Boo"));
};
When I try to start the conversation I get the following error in the Action on Google console.
UnparseableJsonResponse
API Version 2: Failed to parse JSON response string with
'INVALID_ARGUMENT' error: "availableSurfaces: Cannot find field." HTTP
Status Code: 200.
I've already checked the response and I can find a field called availableSurfaces in the AoG console and when I call my bot using Postman.
Response:
{
"responses": [
"Boo"
],
"expectUserResponse": true,
"digested": false,
"noInputs": [],
"speechBiasing": [],
"_responded": true,
"_ordersv3": false,
"request": {},
"headers": {},
"_init": {},
"sandbox": false,
"input": {},
"surface": {
"capabilities": [
{
"name": "actions.capability.MEDIA_RESPONSE_AUDIO"
},
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.ACCOUNT_LINKING"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
}
]
},
"available": {
"surfaces": {
"list": [],
"capabilities": {
"surfaces": []
}
}
},
"user": {
"locale": "en-US",
"lastSeen": "2019-11-14T12:40:52Z",
"userStorage": "{\"data\":{\"userId\":\"c1a4b8ab-06bb-4270-80f5-958cfdff57bd\"}}",
"userVerificationStatus": "VERIFIED"
},
"arguments": {
"parsed": {
"input": {},
"list": []
},
"status": {
"input": {},
"list": []
},
"raw": {
"list": [],
"input": {}
}
},
"device": {},
"screen": false,
"body": {},
"version": 2,
"action": "",
"intent": "",
"parameters": {},
"contexts": {
"input": {},
"output": {}
},
"incoming": {
"parsed": []
},
"query": "",
"data": {},
"conversation": {
"conversationId": "ABwppHEky66Iy1-qJ_4g08i3Z1HNHe2aDTrVTqY4otnNmdOgY2CC0VDbyt9lIM-_WkJA8emxbMPVxS5uutYHW2BzRQ",
"type": "NEW"
},
"inputs": [
{
"intent": "actions.intent.MAIN",
"rawInputs": [
{
"inputType": "VOICE",
"query": "Talk to My test app"
}
]
}
],
"availableSurfaces": [
{
"capabilities": [
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
},
{
"name": "actions.capability.WEB_BROWSER"
}
]
}
]
}
Does anyone know what might be causing this? I personally feel that creating the ActionsSdkConversation could be the cause, but I've not found any examples of using Google Assistant without getting the conv object from the standard intent handeling setup.
So I managed to fix it by changing the approach, instead of having an API point that fits the structure of bot framework I changed it to the intenthandler of AoG.
Google controller
export class GoogleController {
public endpoint: GoogleEndpoint;
private adapter: GoogleAssistantAdapter;
private bot: SampleBot;
constructor(bot: SampleBot) {
this.bot = bot;
this.adapter = new GoogleAssistantAdapter();
this.endpoint = actionssdk();
this.setupIntents(this.endpoint);
};
private setupIntents(endpoint: GoogleEndpoint) {
endpoint.intent(GoogleIntentTypes.Start, (conv: ActionsSdkConversation) => {
this.sendMessageToBotFramework(conv);
});
endpoint.intent(GoogleIntentTypes.Text, conv => {
this.sendMessageToBotFramework(conv);
});
};
private sendMessageToBotFramework(conv: ActionsSdkConversation) {
this.adapter.processActivity(conv, async (context) => {
await this.bot.run(context);
});
};
};
interface GoogleEndpoint extends OmniHandler, BaseApp , ActionsSdkApp <{}, {}, ActionsSdkConversation<{}, {}>> {};
Once the conv object was in the adapter, I used the conv object to create an activity which the bot used to do its things and saved it in state using context.turnState()
Adapter ProcessActivity
public async processActivity(conv: ActionsSdkConversation, logic: (context: TurnContext) => Promise<void>): Promise<ActionsSdkConversation> {
const activty = this.createActivityFromGoogleConversation(conv);
const context = this.createContext(activty);
context.turnState.set("httpBody", conv);
await this.runMiddleware(context, logic);
const result = context.turnState.get("httpBody");
return result;
};
Bot
export class SampleBot extends ActivityHandler {
constructor() {
super();
this.onMessage(async (context, next) => {
await context.sendActivity(`You said: ${context.activity.text}`);
await next();
});
}
Once the bot send a response, I used the result to modify the conv object, save it and then return it in processActivity().
private createGoogleConversationFromActivity(activity: Partial<Activity>, context: TurnContext) {
const conv = context.turnState.get("httpBody");
if (activity.speak) {
const response = new SimpleResponse({
text: activity.text,
speech: activity.speak
});
conv.ask(response);
} else {
if (!activity.text) {
throw Error("Activity text cannot be undefined");
};
conv.ask(activity.text);
};
context.turnState.set("httpBody", conv);
return;
};
That resulted into a simple conversation between Google Assistant and Bot Framework.
I have a POST auth endpoint that returns a token and user object data, along with some metadata.
Im running into an issue because I am not trying to return an object from my Entity, I just want to return a value...
here is a sample JSON response from my endpoint:
{
"status": "success",
"request_time": 0.108006,
"records": 1,
"msg": "some msg",
"user": {
"id": "someidstringformongodb",
"created_at": "2017-07-10T17:15:15.334-07:00",
"updated_at": "2017-07-10T17:15:15.334-07:00",
"active": true,
"email": "email#test.com",
"first_name": "John",
"last_name": "Doe",
"suffix": null,
"title": "Cool Title",
"admin": false,
"phone": "777-777-7777"
},
"access_key": {
"token": "xxxsomerandomtokenxxx"
}
}
desired JSON response
{
// ...
"user": {
//...
},
"access_key": "xxxsomerandomtokenxxx"
}
// or
{
// ...
"user": {
//...
},
"token": "xxxsomerandomtokenxxx"
}
relevant endpoint code
post '/' do
email_pattern = /^#{ sanitize_user_input(params[:email]) }$/i
user = SomeModule::User.find_by(email: email_pattern)
error!('Invalid email/password combination') if user.nil?
error!('Invalid email/password combination') unless user.password == params[:password]
error!('This account is no longer active') unless user.active
access_key = SomeModule::AccessKey.new_key_for_user(user)
access_key.save
present_success user, "some msg"
present :user, user, with: SomeModule::Entities::UserBase
## what should happen to this, in order to return only the value instead of an object?
present :access_key, access_key, with: SomeModule::Entities::AccessKey
end
relevant entity code
class AccessKey < Grape::Entity
expose :token
end
I am returning a Uri as the location header of the response from my web-api controller, as shown below:
[HttpPost]
public HttpResponseMessage PostTenant([FromBody] JObject value)
{
string tenantName;
try
{
tenantName = value.GetValue("name").ToString();
}
catch (Exception e)
{
throw new HttpResponseException(
this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, e));
}
try
{
DbInteract.InsertTenant(tenantName.Replace(" ", string.Empty));
var uri = Url.Link("TenantHttpRoute2", new { tenantName });
var response = new HttpResponseMessage(HttpStatusCode.Created);
response.Headers.Location = new Uri(uri);
return response;
}
catch...
}
The swagger-ui client makes the POST request, but the response headers don't contain the Location uri. It appears like this:
POST request from Swagger-ui
As you can see in the image, the RESPONSE HEADERS are
{
"content-type": null
}
The swagger JSON for this post request is:
"post": {
"tags": [
"Tenant"
],
"summary": "Add a new tenant",
"description": "Adds a tenant and returns admin user account information to be used in further calls.",
"consumes": [
"application/json",
"application/xml"
],
"produces": [
"application/json"
],
"parameters": [
{
"in": "body",
"name": "Tenant",
"description": "Name of the tenant must be alphanumeric without any special characters.",
"required": true,
"schema": {
"$ref": "#/definitions/CreateTenant"
}
}
],
"responses": {
"201": {
"description": "Tenant Inserted",
"headers": [
{
"description": "Location",
"type": "string"
}
]
},
"400": {
"description": "Invalid JSON Format of input"
},
"405": {
"description": "Tenant by this name already exists; Use PUT instead"
},
"500": {
"description": "InternalServerError"
}
}
}
What's wrong here? Why can't I see the response header in swagger-ui? Please let me know if you need more information. Thanks
The intent seems clear enough: a successful response returns a 201 Created with a Location header pointing at the newly created resource. Should work ...
I might remove the "produces" attribute, since you haven't defined any response anywhere.
Try with:
"headers" : { "Location" : { "description": "...", "type" : "string" } }
Instead of:
"headers" : []
so object instead of arrays.
I've already tried to authenticate using OAuth sucessfully with golang.com/x/oauth2 library.
// provider variable is oauth2.Config
// scope is: https://www.googleapis.com/auth/userinfo.email
url := provider.AuthCodeURL(``) // redirect URL
after getting redirected back from the client, I send the auth_code successfully
auth_code := ctx.Request.URL.RawQuery // code=XXXX
if len(auth_code) > 5 {
auth_code = auth_code[5:] // XXXX
}
tok, err := provider.Exchange(oauth2.NoContext, auth_code)
if err == nil {
client := provider.Client(oauth2.NoContext, tok)
email_url := `https://www.googleapis.com/auth/userinfo.email`
//Log.Describe(client)
response, err := client.Get(email_url)
if err == nil {
ctx.Render(`login_oauth`, response)
//handled = true
}
}
//Log.Describe(err)
I found nothing that tells the e-mail part on the response (the Body is empty):
{
"Status": "200 OK",
"StatusCode": 200,
"Proto": "HTTP/1.1",
"ProtoMajor": 1,
"ProtoMinor": 1,
"Header": {
"Alternate-Protocol": [
"443:quic,p=0.5"
],
"Cache-Control": [
"private, max-age=0"
],
"Content-Type": [
"text/plain"
],
"Date": [
"Tue, 14 Apr 2015 05:52:17 GMT"
],
"Expires": [
"Tue, 14 Apr 2015 05:52:17 GMT"
],
"Server": [
"GSE"
],
"X-Content-Type-Options": [
"nosniff"
],
"X-Frame-Options": [
"SAMEORIGIN"
],
"X-Xss-Protection": [
"1; mode=block"
]
},
"Body": {}, // empty!!!
"ContentLength": -1,
"TransferEncoding": [
"chunked"
],
"Close": false,
"Trailer": null,
"Request": {
"Method": "GET",
"URL": {
"Scheme": "https",
"Opaque": "",
"User": null,
"Host": "www.googleapis.com",
"Path": "/auth/userinfo.email",
"RawQuery": "",
"Fragment": ""
},
"Proto": "HTTP/1.1",
"ProtoMajor": 1,
"ProtoMinor": 1,
"Header": {
"Authorization": [
"Bearer ya29.VQFRHDe21t7g2cUhN8sUwjpRRi10XldgLe0RFhMe2ZxgyRo7q90HoKES5WmcucwKqtjZdq_KvYjKiQ"
]
},
"Body": null,
"ContentLength": 0,
"TransferEncoding": null,
"Close": false,
"Host": "www.googleapis.com",
"Form": null,
"PostForm": null,
"MultipartForm": null,
"Trailer": null,
"RemoteAddr": "",
"RequestURI": "",
"TLS": null
},
"TLS": {
// really long output
}
}
First question, how to get the e-mail correctly? without using Google+ API.
edit #2 I've tried using another scope for oauth2.Config:
https://www.googleapis.com/auth/plus.profile.emails.read
https://www.googleapis.com/auth/plus.login
https://www.googleapis.com/auth/plus.me
and try to retrieve the e-mail using newer API:
https://www.googleapis.com/plus/v1/people/me
but it gives 403 Forbidden
edit #3 I've tried using another scope:
openid
profile
email
and try to retrieve the e-mail using this URL:
https://www.googleapis.com/oauth2/v3/userinfo
but it still give empty Body as before.
Second question, can I reuse the oauth2.Config (provider) variable for another user? or should I create a copies for each user?
My bad, I should read the response.Body first, for example:
response, err = client.Get(`https://accounts.google.com/.well-known/openid-configuration`)
body, err := ioutil.ReadAll(response.Body)
response.Body.Close()
According to this document, we should fetch from that URL first, then fetch from userinfo_endpoint from the result above to retrieve the e-mail, for example:
// json := json_to_map(body)
// get json[`userinfo_endpoint`]
// response, err = client.Get(json[`userinfo_endpoint`])
// body, err := ioutil.ReadAll(response.Body)
// response.Body.Close()
// json = json_to_map(body)
// json[`email`]
For the second question, the oauth2.Config struct is reusable.