I would like to retrieve meeting infos using the react TeamsFx library
this is how I do :
const meetingInfos = (graph: Client)=> {
return new Promise((resolve,reject)=>{
microsoftTeams.getContext( async context=>{
console.log("contexxt",context);
let meeting;
const meetingId= atob(context.meetingId || '')
try {
meeting = await graph.api(`/me/onlineMeetings/${context.chatId}`).get();
console.log("meeeting",meeting);
resolve(meeting);
} catch (error) {
console.error("reject error",error);
reject(error);
}
console.log(meeting);
})
})
}
I tried also to convert context.meetingIdto b64 but I get same error ( I figured out that chatId encoded to b64 = meetingId )
Actually I'm getting this error:
"Invalid meeting id
19:meeting_MzU0MzFhYTQtNjlmOS00NGI4LTg1MTYtMGI3ZTkwOWYwMzk4#thread.v2."
How to get the correct meeting id ?
Could you please follow this way to get the meetinng info.
GET /me/onlineMeetings?$filter=JoinWebUrl%20eq%20'{joinWebUrl}'
Ref Doc-https://learn.microsoft.com/en-us/graph/api/onlinemeeting-get?view=graph-rest-1.0&tabs=http#example-3-retrieve-an-online-meeting-by-joinweburl
Related
I would like to use TeamsFx React package to call MS Graph Api.
I tried to do separated component
import { useContext } from "react";
import { useGraph } from "#microsoft/teamsfx-react";
import { TeamsFxContext } from "../Context";
import { TeamsFxProvider } from "#microsoft/mgt-teamsfx-provider";
import { Providers, ProviderState } from "#microsoft/mgt-element";
import * as microsoftTeams from "#microsoft/teams-js";
export function MeetingContext(props: { showFunction?: boolean; environment?: string }) {
const { teamsfx } = useContext(TeamsFxContext);
const { loading, error, data, reload } = useGraph(
async (graph, teamsfx, scope) => {
// Call graph api directly to get user profile information
let profile;
try {
profile = await graph.api("/me").get();
} catch (error) {
console.log(error);
}
// Initialize Graph Toolkit TeamsFx provider
const provider = new TeamsFxProvider(teamsfx, scope);
Providers.globalProvider = provider;
Providers.globalProvider.setState(ProviderState.SignedIn);
let photoUrl = "";
let meeting =null;
try {
const photo = await graph.api("/me/photo/$value").get();
photoUrl = URL.createObjectURL(photo);
microsoftTeams.getContext(async (context)=>{
console.log(context);
try {
meeting = await graph.api(`/me/onlineMeetings/${context.meetingId}`).get();
} catch (error) {
console.error(error);
}
})
} catch {
// Could not fetch photo from user's profile, return empty string as placeholder.
}
console.log(meeting);
return { meeting };
},
{ scope: ["User.Read","User.Read","OnlineMeetingArtifact.Read.All"," OnlineMeetings.Read"], teamsfx: teamsfx }
);
return (
<>
</>
)
}
When I debug my interpreter stops on
profile = await graph.api("/me").get();
Then it does not pass after.
I would like also to know what should I put in scope field ?
Should I put the authorisations listed here ?
Should I also Add them in registered app in Azure Portal ?
Update:
I'm getting response from
Failed to get access token cache silently, please login first: you need login first before get access token.
I'm using the teams toolkit and I'm already logged in . I don't know what Should I do to be considered as logged in ?
Update :
I have updated my app api authorisations in azure portal now I'm not getting anymore this error.
But I'm getting a new error :
meeting = await graph.api(`/me/onlineMeetings/${context.chatId}`).get();
"Invalid meeting id
19:meeting_MzU0MzFhYTQtNjlmOS00NGI4LTg1MTYtMGI3ZTkwOWYwMzk4#thread.v2."
I'll post a new question about this as it not the original problem
You can get the details of an online meeting using videoTeleconferenceId, meeting ID, or joinWebURL.
Instead of ChatID you have to use meeting ID or you have to use filter with videoTeleconferenceId or joinWebURL.
The invalid meeting ID error get because
chatID is not the correct parameter for this graph API.
You can refer below API for getting meeting information.
To get an onlineMeeting using meeting ID with delegated (/me) and app (/users/{userId}) permission:
GET /me/onlineMeetings/{meetingId}
GET /users/{userId}/onlineMeetings/{meetingId}
To get an onlineMeeting using videoTeleconferenceId with app permission*:
GET /communications/onlineMeetings/?$filter=VideoTeleconferenceId%20eq%20'{videoTeleconferenceId}'
To get an onlineMeeting using joinWebUrl with delegated and app permission:
GET /me/onlineMeetings?$filter=JoinWebUrl%20eq%20'{joinWebUrl}'
GET /users/{userId}/onlineMeetings?$filter=JoinWebUrl%20eq%20'{joinWebUrl}'
Ref Doc: https://learn.microsoft.com/en-us/graph/api/onlinemeeting-get?view=graph-rest-1.0&tabs=http
We've been having issues intermittently where we get an error when downloading email item content from EWS "AttachmentId is malformed". This is for ItemAttachment (Especially .eml files)
We could not figure why or how this is happening and noticed that the ones that were failing had + and / in the id's. Searching across the web landed me on this article. Although this article is from 2015, wondering if this is still happening.
This article blew my mind and made sense (kind of) and implementing the conversion of + -> _ and / -> - worked fine, for a while.
We are now receiving the same error 'AttachmentId is malformed' and again could not find why, I removed the custom sanitizer function that replaces these characters and it started working again.
I have no idea what and why is this happening and how to reliably get attachment content. Currently, I've moved the sanitizer into the catch handler, so if for some reason the AttachmentId fails, we'll retry it by sanitizing it. Will have to keep an eye on how many fail.
Any light on this issue will be really appreciated.
Update 1.0 - Sample Code
Front-end
//At this point we've got the email and got the files
//We call EWS only if file.type == Office.MailboxEnums.AttachmentType.Item
//For all other files we call REST endpoint ~ Office.context.mailbox.restUrl + '/v2.0/'......
//Sample code below if only for EWS call
let files = this.email.attachments || [];
files.map(file => {
this._getEmailContent(file)
.then(res => {
return res;
});
})
//Get content from EWS
_getEmailContent(file, _failed){
//attachmentId
//Most of the times this will be fine, but at times when Id has a `+` or `/` if fails, Was expecting the convertToEwsId to handle any sanitization required.
let attachmentId = Office.context.mailbox.convertToEwsId(file.id, Office.MailboxEnums.RestVersion.v2_0);
return this.getToken(EWS)
.then(token => {
return this.http.post(`${endpoint}/downloadAttachment`,{
token: token,
url: Office.context.mailbox.ewsUrl,
id: attachmentId
},{
responseType: 'arraybuffer',
}).then(res => res.data);
}).catch(err => {
attachmentId = attachmentId.replace(/\+/g, "_");
this._getEmailContent(attachmentId, true);
})
}
Back-end
[HttpPost]
public DownloadAttachment(Request model){
var data = service.DownloadAttachment(model);
if(data == null)
{
return BadRequest("Error downloading content...");
}
return new HttpResponseMessage(HttpStatusCode.OK)
{
return data;
}
}
//Inside service
public byte[] DownloadAttachment(Request request){
var ser = new ExchangeService
{
Credentials: request.token,
Url = request.url
}
//Here it fails intermittently, returning AttachmentId is malformed.
var attachment = ser.GetAttachments(new [] {request.attachmentId}, null, null).First();
if (attachment is FileAttachment)
{
FileAttachment fileAttachment = attachment as FileAttachment;
fileAttachment.Load();
return fileAttachment.Content;
}
}
I'm developing a bot using the bot framework for node.js v4. Imagine the following scenario:
user: Hello
bot: How can I help you?
user: What is the deadline for completing the transfer?
bot: What is the value of the transfer?
user: $ 5,000
At this time, I am executing the textprompt to request the value of the transfer and I need to validate if the user entity ($ 5,000) has been identified as the money entity.
This is the dialog stack:
this.addDialog(new WaterfallDialog(DUVIDA_NIVEL_APROVACAO_DIALOG, [
this.initializeStateStep.bind(this),
this.moneyStep.bind(this),
this.captureMoney.bind(this),
this.endConversation.bind(this)
]));
this.addDialog(new TextPrompt(MONEY_PROMPT, this.validateMoneyInput));
And the validate method:
async validateMoneyInput(validatorContext) {
const value = validatorContext.recognized.value; //how to get entities?
if (value == 'money') {
return VALIDATION_SUCCEEDED;
} else {
await validatorContext.context.sendActivity(`What is the value of the transfer?`);
return VALIDATION_FAILED;
}
}
However, in the callback to validate the textprompt, I have only the text sent by the user.
How can I get the entities extracted by Luis within the textprompt validation method?
To get any LUIS results into the dialog waterfall, you first need to capture the results on the turnContext, like so:
if (turnContext.activity.type === ActivityTypes.Message) {
// Returns LUIS matched results
const results = await this.luisRecognizer.recognize(turnContext);
// Results are assigned to the turnContext object and is passed into the dialog stack
turnContext.topIntent = results.luisResult.topScoringIntent;
turnContext.topIntent.entities = results.luisResult.entities;
turnContext.topIntent.value = results.luisResult.query;
// Create a dialog context object.
const dc = await this.dialogs.createContext(turnContext);
const utterance = (turnContext.activity.text || '').trim().toLowerCase();
if (utterance === 'cancel') {
if (dc.activeDialog) {
await dc.cancelAllDialogs();
await dc.context.sendActivity(`Ok... canceled.`);
} else {
await dc.context.sendActivity(`Nothing to cancel.`);
}
}
// If the bot has not yet responded, continue processing the current dialog.
await dc.continueDialog();
// Start the sample dialog in response to any other input.
if (!turnContext.responded) {
await dc.beginDialog(DUVIDA_NIVEL_APROVACAO_DIALOG);
}
}
Now that the results have been passed in, you can access the results via the step.context object, like so:
this.dialogs.add(new TextPrompt(MONEY_PROMPT, this.validateMoneyInput.bind(this)));
async moneyStep(step) {
await step.prompt(MONEY_PROMPT, `What is the value of the transfer?`,
{
retryPrompt: 'Try again. What is the value of the transfer?'
}
);
}
async validateMoneyInput(step) {
// LUIS results passed into turnContext are retrieved
const intent = step.context.topIntent['intent'];
const entity = step.context.topIntent.entities;
console.log(entity);
// Validation based on matched intent
if (intent == 'Money') {
return await step.context.sendActivity('Validation succeeded');
} else if (intent != 'Money') {
return await step.context.sendActivity('Validation failed');
}
}
I also assigned the entities value to a variable for accessing since you were asking about it.
Hope of help!
I'm facing the following error when I try to get the attachment in Microsoft Teams with Bot Builder v4:
{"message":"Authorization has been denied for this request."}
everything works fine with the version 3, as far as I know in Teams is necessary a token in order get the binary array of the file.
In the v3 I'm able to get the jwt token in this way:
connector.getAccessToken.bind(connector)
and then I use it in the header of the GET request =>
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/octet-stream'
}
In the v4:
context.adapter.getUserToken(step.context, CONNECTION_SETTING_NAME);
is there another way to get a valid token in the v4?
To get the token just call the prompt again. You can find the auth sample here for node. The sample happens to use a waterfall dialog, which may not be needed in your case
let prompt = await step.prompt(OAUTH_PROMPT);
If the token is valid and not expired you can get the token like below, if the token is not valid or the user does not have a token they will be prompted to log in. Otherwise the token will be in the prompt result.
var tokenResponse = prompt.result;
if (tokenResponse != null) {
await step.context.sendActivity(`Here is your token: ${ tokenResponse.token }`);
}
These comments from the sample should help explain
// Call the prompt again because we need the token. The reasons for this are:
// 1. If the user is already logged in we do not need to store the token locally in the bot and worry
// about refreshing it. We can always just call the prompt again to get the token.
// 2. We never know how long it will take a user to respond. By the time the
// user responds the token may have expired. The user would then be prompted to login again.
//
// There is no reason to store the token locally in the bot because we can always just call
// the OAuth prompt to get the token or get a new token if needed.
these are the steps
/**
* WaterfallDialogStep to process the user's picture.
* #param {WaterfallStepContext} step WaterfallStepContext
*/
async processPhotoStep(step) {
await this.writeLogInTheStorage('Start downloading picture....');
await this.handleIncomingAttachment(step);
return await step.endDialog();
};
/**
* responds to the user with information about the saved attachment or an error.
* #param {Object} turnContext
*/
async handleIncomingAttachment(step) {
// Prepare Promises to download each attachment and then execute each Promise.
const attachment = step.context.activity.attachments[0];
const tokenIsRequired = await this.checkRequiresToken(step.context);
const dc = await this.dialogs.createContext(step.context);
const token = await dc.beginDialog(LOGIN_PROMPT); //await step.context.adapter.getUserToken(step.context, CONNECTION_SETTING_NAME);
let file = undefined;
if (tokenIsRequired) {
file = await this.downloadAttachment(token.result.token, attachment.contentUrl);
}
else {
file = await requestX(attachment.contentUrl);
}
await OAuthHelpers.postPhoto(step.context, token.result, file);
}
async downloadAttachment(token, url) {
const p = new Promise((resolve, reject) => {
request({
url: url,
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/octet-stream'
}
}, async function (err, response, body) {
const result = body
if (err) {
console.log(err);
//await this.writeLogInTheStorage('err 1 : ' + err);
reject(err);
} else if (result.error) {
console.log(result.error);
//await this.writeLogInTheStorage('err 2 : ' + err);
reject(result.error.message);
} else {
// The value of the body will be an array.
console.log(result);
//await this.writeLogInTheStorage('success : ' + result);
resolve(result);
}
});
});
return p;
}
I am trying to create an identity card using the javascript API of Hyperledger Composer. Here is the code:
const BusinessNetworkConnection = require('composer-
client').BusinessNetworkConnection;
async function identityIssue() {
let businessNetworkConnection = new BusinessNetworkConnection();
try {
await businessNetworkConnection.connect('admin#demoNetwork');
let result = await businessNetworkConnection.issueIdentity('org.acme.demoNetwork.Participant#testUser', 'test');
console.log(`userID = ${result.userID}`);
console.log(`userSecret = ${result.userSecret}`);
await businessNetworkConnection.disconnect();
} catch(error) {
console.log(error);
process.exit(1);
}
}
identityIssue();
I already have a participant testUser.
Although the code succeeds and I get the userID and userSecret, no card is created.
Does anyone have an idea how to do it instead of using the cli?
You haven't imported the card.
eg,
async function importCardForIdentity(cardName, identity) {
const metadata = {
userName: identity.userID,
version: 1,
enrollmentSecret: identity.userSecret,
businessNetwork: businessNetworkName
};
const card = new IdCard(metadata, connectionProfile);
await adminConnection.importCard(cardName, card);
}
See also nodejs test hyperledger composer v0.15 fails with Error: Card not found: PeerAdmin#hlfv1 (answer) it has an example too.
After importing, you should connect with your card to download the cert/key to the wallet eg await businessNetworkConnection.connect(cardName);