我有一个现有的节点/快速聊天机器人应用程序,可以使用ExpressJS连接到多个聊天平台。 next(),next()中间件设计模式。我在最开始发送200响应以确认收到消息,并发送新的POST请求以从我的上一个中间件发送消息。
app.post("/bots", receiveMsg, doStuff, formatAndSendMsg, catchErrors);
现在我想将Skype整合为我的机器人的渠道,但是bot-framework的NodeJS库有一种完全不同的做事方式,使用事件和我尚未完全理解的魔术: / p>
var connector = new builder.ConsoleConnector();
app.post("/skype", connector.listen());
var bot = new builder.UniversalBot(connector, function (session) {
session.send("You said: %s", session.message.text);
});
看起来这些都不是兼容的方式,所以接收消息然后向用户发送响应的最佳方式是什么,而无需更改我的快速路由以适应bot-builder ?我可以使用Session.send()获取Session对象来响应吗?我是否必须手动完成所有寻址?有没有类似的方法:
app.post("/skype", (req, res, next) => {
const address = req.body.id;
const message = new builder.Message(address, messageBody).send()
}
或者:
app.post("/skype", connector.listen(), (req, res, next) => {
// (res.locals is available in every express middleware function)
const session = res.locals.botFrameworkSession;
// do stuff
session.send(message);
}
答案 0 :(得分:1)
您可以在现有快递应用程序中注册bot应用程序。 Bot builder SDK在expressjs框架中也兼容。您可以参考同时利用快递的official sample。
不要忘记将机器人注册中的消息端点修改为机器人的终端,例如:
https://yourdomain/stuff
在您的方案中。有关详细信息,请参阅https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration。
答案 1 :(得分:0)
使用官方bot框架NodeJS库可以构建消息,解决它们并发送这些消息。我无法对该库进行的操作是接收消息并在我的路由上验证其真实性,而不对我的设计进行重大更改(使用请求中间件 - next() - 处理传入的请求)已经在生产中与其他机器人并不容易改变。
以下是我的解决方法:首先是我根据此处的Microsoft文档创建的BotFrameworkAuthenticator类:https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-authentication 它使用您的Bot Framework应用程序中的appID和appPassword进行实例化。
import axios from "axios";
import * as jwt from "jsonwebtoken";
import * as jwkToPem from 'jwk-to-pem';
export class BotFrameworkAuthenticator {
private appId: string;
private appPassword: string;
private openIdMetadata: any;
// The response body specifies the document in the JWK format but also includes an additional property for each key: endorsements.
private validSigningKeys: any;
// The list of keys is relatively stable and may be cached for long periods of time (by default, 5 days within the Bot Builder SDK).
private signingKeyRefreshRate: number = 432000; // in seconds (432000 = 5 days)
constructor(appId, appPassword) {
this.appId = appId;
this.appPassword = appPassword;
this.getListOfSigningKeys();
}
// response data should contain "jwks_uri" property that contains address to request list of valid signing keys.
public async getOpenIdMetaData() {
// This is a static URL that you can hardcode into your application. - MS Bot Framework docs
await axios.get("https://login.botframework.com/v1/.well-known/openidconfiguration").then(response => {
this.openIdMetadata = response.data;
logger.info("OpenID metadata document recieved for Bot Framework.");
}).catch(err => {
logger.warn(err.message, "Could not get OpenID metadata document for Bot Framework. Retrying in 15 seconds...");
setTimeout(this.getListOfSigningKeys, 15000);
})
}
public async getListOfSigningKeys() {
await this.getOpenIdMetaData();
if (this.openIdMetadata && this.openIdMetadata.jwks_uri) {
// previous function getOpenIdMetaData() succeeded
await axios.get(this.openIdMetadata.jwks_uri).then(response => {
logger.info(`Signing keys recieved for Bot Framework. Caching for ${this.signingKeyRefreshRate / 86400} days.`);
this.validSigningKeys = response.data.keys;
setTimeout(this.getListOfSigningKeys, (this.signingKeyRefreshRate * 1000));
}).catch(err => {
logger.error(err.message, "Could not get list of valid signing keys for Bot Framework. Retrying in 15 seconds");
setTimeout(this.getListOfSigningKeys, 15000);
});
} else {
// previous function getOpenIdMetaData() failed, but has already queued this function to run again. Will continue until succeeds.
return;
}
}
/**
* Verifies that the message was sent from Bot Framework by checking values as specified in Bot Framework Documentation:
* https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-authentication#step-4-verify-the-jwt-token
* Retrieves the Bearer token from the authorization header, decodes the token so we can match the key id (kid) to a key in the OpenID
* document, then converts that key to PEM format so that jwt/crypto can consume it to verify that the bearer token is
* cryptographically signed.
* If the serviceUrl property in the token doe not match the serviceUrl property in the message, it should also be rejected.
*/
public verifyMsgAuthenticity(serviceUrl: string, headers: any) {
try {
const token = headers.authorization.replace("Bearer ", "");
const decoded = jwt.decode(token, { complete: true }) as any;
const verifyOptions = {
issuer: "https://api.botframework.com",
audience: this.appId,
clockTolerance: 300, // (seconds) The token is within its validity period. Industry-standard clock-skew is 5 minutes. (Bot Framework documentation);
}
const jwk = this.lookupKey(decoded.header.kid)
const pem = jwkToPem(jwk);
const verified = jwt.verify(token, pem, verifyOptions) as any;
if (!serviceUrl || serviceUrl !== verified.serviceurl) {
logger.warn("Non-matching serviceUrl in Bot Framework verified token!")
return false;
}
return true;
} catch (err) {
logger.warn("Received invalid/unsigned message on Bot Framework endpoint!", err.message)
return false;
}
}
// Finds the relevant key from the openID list. Does not transform the key.
private lookupKey(kid) {
const jwk = this.validSigningKeys.find((key) => {
return (key.kid === kid);
});
return jwk;
}
}

在快速路线的最开始使用这样的BotFrameworkAuthenticator类来验证所有传入的请求是否有效。
const botFrameworkAuthenticator = new BotFrameworkAuthenticator(appID, appPassword);
router.post("/", (req: Request, res: Response, next: NextFunction) => {
if (botFrameworkAuthenticator.verifyMsgAuthenticity(req.body.serviceUrl, req.headers) === true) {
res.status(200).send();
next();
} else {
// unsafe to process
res.status(403).send();
return;
}
});

使用常规Bot框架库发送消息,而不会在收到传入消息时通常由Bot Framework库创建的Session对象:
import * as builder from "botbuilder";
// instantiate the chatConnector (only once, not in the same function as the sending occurs)
const botFrameworkSender = new builder.ChatConnector({ appId, appPassword });
//---------------------------------------------
const skypeMsg = req.body;
const address = {
channelId: skypeMsg.channelId,
user: skypeMsg.from,
bot: skypeMsg.recipient,
conversation: skypeMsg.conversation
};
const response = new builder.Message().text(someText).address(address).toMessage();
const formattedResponses = [response];
botFrameworkSender.send(formattedResponses, logErrorsToConsole);

请注意,可以使用所有builder.Message() - .attachment(),. images()等... - 函数,而不仅仅是text()