How to include what's being defined in the defining variable? - node.js

Good day, I have a question. I have here a function that doesn't give me Client with a valid manager. Here's the function:
function SetupClient(user) {
let Client = {
client: new SteamUser(),
community: new SteamCommunity(),
manager: new TradeOfferManager({
steam: this.client,
community: this.community,
language: 'en'
}),
details: user
}
SteamClients[user.username] = Client;
Debug.emit('message', `Client setup: ${SteamClients[user.username]}`);
}
I am trying to get the client that is being created as it's being created... I guess? Hope my intentions are shown in the code accurately.

I messed up, this works, I just called community, instead of calling it as a property of Client. Oops...

Related

Graph API does not accept my application permissions' scope

Edit:
The problem was a simple typo in the Header. You're probably wasting your time here
In essence, I have the same problem as described here. It's a somewhat different usecase and I'll try to provide as much context as I can in the hopes that someone will be able to solve the problem.
So, this has to do with Azure, which seems to be an Alias for "Crazy problem generator". My apologies.
I'm trying to write a Service in NodeJS which has the purpose of synchronizing another app's database with data from Azure.
For that reason, I'm using msal-node's Client Credential Flow as described here.
I find their comment // replace with your resource quite ridiculous, as I have not found a single full example online that specifies the format that should be used.
Intuitively, I would use something like
['GroupMember.Read.All']
//or
['https://graph.microsoft.com/GroupMember.Read.All']
Unfortunately, this does not work. Luckily, I get an error that describes the problem (even if only when this is the only scope I use, othewise the error is garbage):
{
// ...
errorMessage: '1002012 - [2022-05-23 11:39:00Z]: AADSTS1002012: The provided value for scope https://graph.microsoft.com/bla openid profile offline_access is not valid. Client credential flows must have a scope value with /.default suffixed to the resource identifier (application ID URI).\r\n'
}
Okay, let's do that:
['https://graph.microsoft.com/GroupMember.Read.All/.default']
Now, the app actually performs a request, but unfortunately, I get
{
// ...
errorCode: 'invalid_resource',
errorMessage: '500011 - [2022-05-23 11:42:31Z]: AADSTS500011: The resource principal named https://graph.microsoft.com/GroupMember.Read.All was not found in the tenant named <My company name, not an ID as shown in some places>. This can happen if the application has not
been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.\r\n' +
'Trace ID: <some id>\r\n' +
'Correlation ID: <some id>\r\n' +
'Timestamp: 2022-05-23 11:42:31Z - Correlation ID: <some id> - Trace ID: <some id>',
}
And yet it is there
And I am able to get a token for the .default scope. That's just not good for anything.
The important parts of the actual code:
import fetch from 'isomorphic-fetch';
import * as msal from '#azure/msal-node';
// got env variables using dotenv package
// this is Typescript
const msalConfig = {
auth: {
clientId: process.env.OAUTH_APP_ID!,
authority: process.env.OAUTH_AUTHORITY!,
clientSecret: process.env.OAUTH_APP_SECRET!
},
system: {
loggerOptions: {
loggerCallback(loglevel: any, message: any, containsPii: any) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
const msalClient = new msal.ConfidentialClientApplication(msalConfig);
const allCompanyMembersGroupId = '<some id>';
const tokenRequest = {
scopes: ['https://graph.microsoft.com/GroupMember.Read.All/.default']
};
msalClient.acquireTokenByClientCredential(tokenRequest).then(response => {
console.log('Got token:', response);
fetch(`https://graph.microsoft.com/v1.0/groups/${allCompanyMembersGroupId}/members`, {
method: 'GET',
headers: {
Authority: `Bearer ${response!.accessToken}`
}
}).then((res: any) => {
console.log('Got response:', res);
})
});
As mentioned, the request isn't performed with my GroupMember.Read.All scope. With the default scope, I get a 401 unauthorized error.
So, these are my questions:
How to fix this?
Okay, if it you don't know how to fix it, what is the exact format required for the scope? Is the prefix https://graph.microsoft.com correct, even for my specific app?
Is this the correct library to use, or is this just broken code or not intended for such use? The other question I linked to above mentions that requests were successful using Postman, just not this lib...
Thanks heaps for any advice!
It's a problem with the headers. It should be Authorization instead of Authority.
The error is in the following variable:
const tokenRequest = {
scopes: ['https://graph.microsoft.com/GroupMember.Read.All/.default']
};
the correct one would be:
const tokenRequest = {
scopes: ['https://graph.microsoft.com/.default']
};
Note: When using Client Credentials flow, it is a must to specify the scope string tailing with "/.default".
For eg:
If you are trying to get an access-token using client credentials
flow for MS Graph API, the scope should be
"https://graph.microsoft.com/.default".
If you are trying to get an
access-token using client credentials flow for a custom API which is
registered in AAD, the scope should be either
"api://{API-Domain-Name}/.default" or
"api://{API-Client-Id}/.default".

Sending proactive messages to a channel in Teams

So,
I searched far and wide, read everything I could find on the topic and I am still failing at this. I have managed to send proactive message to user, reply to a topic in team, etc. but I am unable to send a proactive message (create new post) in a team channel.
Is there an available example (I was unable to find any)? MS Docs for NodeJS seem to show an example of messaging each user in the team, but not the channel itself.
I explored the source code, and channelData is hardcoded to null inside botFrameworkAdapter.js, which only adds to the confusion.
So, basic code is:
const builder = require('botbuilder');
const adapter = new builder.BotFrameworkAdapter({
appId: 'XXX',
appPassword: 'YYY'
});
const conversation = {
channelData: {
//I have all this (saved from event when bot joined the Team)
},
...
// WHAT THIS OBJECT NEEDS TO BE TO SEND A SIMPLE "HELLO" TO A CHANNEL?
// I have all the d
};
adapter.createConversation(conversation, async (turnContext) => {
turnContext.sendActivity('HELLO'); //This may or may not be needed?
});
Has anyone done this with Node ? If so, can anyone show me a working example (with properly constructed conversation object)?
* EDIT *
As Hilton suggested in the answer below, I tried using ConnectorClient directly, but it returns resource unavailable (/v3/conversations)
Here is the code that I am using (it's literally only that, just trying to send demo message):
const path = require('path');
const { ConnectorClient, MicrosoftAppCredentials } = require('botframework-connector');
const ENV_FILE = path.join(__dirname, '.env');
require('dotenv').config({ path: ENV_FILE });
const serviceUrl = 'https://smba.trafficmanager.net/emea/';
async function sendToChannel() {
MicrosoftAppCredentials.trustServiceUrl(serviceUrl);
var credentials = new MicrosoftAppCredentials(process.env.MicrosoftAppId, process.env.MicrosoftAppPassword);
var client = new ConnectorClient(credentials, { baseUri: serviceUrl });
var conversationResponse = await client.conversations.createConversation({
bot: {
id: process.env.MicrosoftAppId,
name: process.env.BotName
},
isGroup: true,
conversationType: "channel",
id: "19:XXX#thread.tacv2"
});
var acivityResponse = await client.conversations.sendToConversation(conversationResponse.id, {
type: 'message',
from: { id: process.env.MicrosoftAppId },
text: 'This a message from Bot Connector Client (NodeJS)'
});
}
sendToChannel();
What am I doing wrong?
Okay, so, this is how I made it work. I am posting it here for future reference.
DISCLAIMER: I still have no idea how to use it with botbuilder as was asked in my initial question, and this answer is going to use ConnectorClient, which is acceptable (for me, at least). Based on Hilton's directions and a GitHub issue that I saw earlier ( https://github.com/OfficeDev/BotBuilder-MicrosoftTeams/issues/162#issuecomment-434978847 ), I made it work finally. MS Documentation is not that helpful, since they always use context variable which is available when your Bot is responding to a message or activity, and they keep a record of these contexts internally while the Bot is running. However, if your Bot is restarted for some reason or you want to store your data in your database to be used later, this is the way to go.
So, the code (NodeJS):
const path = require('path');
const { ConnectorClient, MicrosoftAppCredentials } = require('botframework-connector');
const ENV_FILE = path.join(__dirname, '.env');
require('dotenv').config({ path: ENV_FILE });
const serviceUrl = 'https://smba.trafficmanager.net/emea/';
async function sendToChannel() {
MicrosoftAppCredentials.trustServiceUrl(serviceUrl);
var credentials = new MicrosoftAppCredentials(process.env.MicrosoftAppId, process.env.MicrosoftAppPassword);
var client = new ConnectorClient(credentials, { baseUri: serviceUrl });
var conversationResponse = await client.conversations.createConversation({
bot: {
id: process.env.MicrosoftAppId,
name: process.env.BotName
},
isGroup: true,
conversationType: "channel",
channelData: {
channel: { id: "19:XXX#thread.tacv2" }
},
activity: {
type: 'message',
text: 'This a message from Bot Connector Client (NodeJS)'
}
});
}
sendToChannel();
NOTE: As Hilton pointed out, serviceUrl also needs to be loaded from your database, along with the channel id. It is available inside the activity which you receive initially when your Bot is added to team / channel / group along with channelId which you will also need, and you need to store those for future reference (do not hardcode them like in the example).
So, there is no separate createConversation and sendActivity, it's all in one call.
Thanks Hilton for your time, and a blurred image of my hand to MS Docs :)
Hope this helps someone else
(I'm replacing my previous answer as I think this fits the situation much better).
I've looked more into this and done a Fiddler trace to get you a more complete answer. I'm not a Node guy, so I'm not sure this will translate 100%, but let's see.
Basically, you're wanting to send to the following endpoint:
https://smba.trafficmanager.net/emea/v3/conversations/19:[RestOfYourChannelId]/activities
and you'll be posting a message like the following:
{
"type": "message",
"from": {
"id": "28:[rest of bot user id]",
"name": "[bot name]"
},
"conversation": {
"isGroup": true,
"conversationType": "channel",
"id": "19:[RestOfYourChannelId]"
},
"text": "Test Message"
}
However, to post to that endpoint, you need to authenticate to it properly. It's possible to do that, and communicate with the endpoint directly, but it's actually easier to just use the built-in mechanisms. This means getting and storing the conversationreference when the bot is first installed to the channel. This file shows how to do that (see how it gets and stores the conversationReference in the this.onConversationUpdate function). In that same sample, in a different file, it shows how to use that conversation reference to actually send the pro-active message - see here, where it uses adapter.continueConversation.
One of the Microsoft bot team members also shows this in similar detail over here. He also adds MicrosoftAppCredentials.trustServiceUrl(ref.serviceUrl); which can be necessary under certain circumstances (if you're having security issues, give that a try).
That -should- cover what you need, so give it a go, and let me know if you're still having difficulties.

issue with creating role and channel automatically from private message with bot

Not sure how the create channel and create role isn't working inside the following code, towards the bottom. (EDIT: Nothing is sent to the console and nothing happens regardng the code. It is like it is entirely ignored.) This is a snippet from code that User A challenges User B. User B is messaged, alerting them that a challenge has been issued to them via a Private Message. If the challenge is accepted, I want the bot to 1)Make a role specifically for User A and User B named "User A vs User B" 2) take User A and User B and put them both into that new role and 3) Make a battlefield named "User A vs User B" inside a specific category inside the server the bot is on.
I am unsure if the problem lies in how the bot is trying to make the role and channel in a sever while the bot is talking to the user in a private message instead of on the server. I thought putting the "server" variable as the server ID would help but it doesn't seem to do anything after the accept message.
// Awaits reply from user
if (message.channel.id === '541736552582086656') return target.send("Do you accept the challenge? Please reply with 'accept' or 'deny'.")
.then((newmsg) => {
newmsg.channel.awaitMessages(response => response.content, {
max: 1,
time: 150000,
errors: ['time'],
}).then((collected) => {
// Grabs the first (and only) message from the collection.
const reply = collected.first();
if (reply.content === 'accept'){
reply.channel.send(`You have ***accepted *** the challenge from ${challenger}. Please wait while your battlefield is made...`);
message.author.send(`${target} has accepted your challenge! Please wait while the channel is made for your brawl...`)
/// Problems start here
function createChannel(message){
var server = "SERVER ID";
var name = `${target} vs ${challenger}`;
message.guild.createRole({
role: {
name: `${target} vs ${challenger}`,
color: "#00fffa",
permissions: [] }
}).then(role => {
target.addRole(role, name)
challenger.addRole(role, name)
.catch(error => client.catch(error))
}).catch(error => client.catch(error))
server.createChannel(Name, name).then(
(channel) => {
channel.setParent("CATEGORY ID")
})
} // problems end here
} else if (reply.content === 'deny') {
reply.channel.send("You have ***denied *** the challenge.")
} else {
reply.channel.send("Your response wasn't valid.");
}
})
})
}
I have been wondering if I need to go about making the channel and role in a different way since it is trying to be made from a private message and not inside the server..
Thanks for any and all help! I also apologize if I'm using stack overflow too much for problems like this... You guys are great at helping me see different ways to do things and what I'm doing wrong, so I am learning, but I don't want to feel like I'm abusing it too much.
I think the problem is the fact that you create a function called createChannel with the code to create a rol and channel, but you never call said function.
You can either call the function after you've declared it or (which is in my opinion better) you can remove the following lines
function createChannel(message){
} // problems end here

newbie node.js bot integration with database lookup (looking for best practice)

OK, new to Node.js and botframework. I built first bot using Azure site and downloaded the code. Chose the Luis integrated bot template.
I understand (finally) the event driven model of node.js and the concept of callbacks.
I have the code snippet below. When Luis finds an intent of "Help" it triggers this function. In turn, I have database calls to lookup the entity. Within the database I have an entity, response (if entity is bottle, answer "Recycle").
I have that code working too.
Below the first block is a function handleHelpRequester which is the callback function, I have that working as well. Where I am a little stuck (best practice) is that in this callback function I want to send something to the session object (session.send toward the bottom of the function).
Since I don't create the session object (directly) I'm not sure on the options.
Should I pass the session object to the database function, then pass it back?
Should I create a global variable and set it to the session object (I'm concerned that if multiple people are using this bot then this approach won't work).
I'm open for suggestions.
Thanks
.matches('Help', (session, args) => {
var entities = args.entities;
var itype = builder.EntityRecognizer.findEntity(args.entities, 'ItemTypes');
var respondToUser = '';
var msg = 'Initialized';
// if there is an entity provided, perform a lookup of the entity.
if (itype.entity !== null) {
//Use session.sendTyping so the user thinks something is happening rather than being ignored while we do the lookup in SharePoint.
session.sendTyping();
//perform lookup
respondToUser = sp.lookupEntity(itype.entity, handleHelpRequest);
};
})
function handleHelpRequest(err, respondToUser) {
var msg = 'uninitialized';
if (err = 'success') {
console.log('Respond to user from matches:'.concat(respondToUser));
//return from lookup
if (respondToUser === 'No match found') {
msg = 'I think you are asking for help, but I don\'t understand what you need help with.';
}
else {
msg = 'I can help you with that, \'%s\'.', respondToUser;
}
console.log(msg);
session.send(msg);
//The following two lines are for debugging
session.send('How may I assist you? ' + JSON.stringify(args));
session.send('Value of entity you said: \'%s\'.', itype.entity);
}
else {
console.log('an error occurred');
}
}
If you want to have access to the session object, then pass it as a parameter to your helper function.
Example:
function handleHelpRequest(session, err, respondToUser) {
// do session stuff
}

Windows Azure node.js Push notification for Windows store 8.1 - How to use 'createRawTemplateRegistration' template?

Please explain with one example as I am getting Error: 400 - The specified resource description is invalid.
Basically, I want to update badge value. But there is no template for badge registration in WnsService API document (http://azure.github.io/azure-sdk-for-node/azure-sb/latest/WnsService.html). So, I am trying with "createRawTemplateRegistration" template to update the badge value.
Please help me on this.
You can directly use the function sendBadge() to push badge value to client devices.
Please try the following code:
var azure = require('azure');
var notificationHubService = azure.createNotificationHubService('<hubname>', '<connectionstring>');
notificationHubService.wns.sendBadge(null,99,function(error,response){
if(error) console.log(error);
console.log(response);
})
Any further concern, please feel free to let me know.
update
Do you mean that you want only one template and to handle all the types of notifications including Raw, Toast, Badge? If so, I think the answer is negative. According the description http://azure.github.io/azure-sdk-for-node/azure-sb/latest/WnsService.html#createRawTemplateRegistration:
Remember that you have to specify the X-WNS-Type header
So the header option is required. And according the REST API which is invoked via this api in nodejs is Create Registration, and we can find the description:
The BodyTemplate element is mandatory, as is the X-WNS-Type header.
So we should specify the notification type for the template.
update1
This code sample works fine on my side:
var channel = '<devicetoken>';
var templateMessage = { text1: '$(message)' };
notificationHubService.wns.createRawTemplateRegistration(channel,'tag',JSON.stringify(templateMessage), {headers: { 'X-WNS-Type': 'wns/raw' }},
function (e, r) {
if (e) {
console.log(e);
} else {
console.log({
id: r.RegistrationId,
deviceToken: r.DeviceToken,
expires: r.ExpirationTime
});
}
}
)

Resources