您需要为此工作设置 cron 作业和主动消息传递。幸运的是,已经有一些帖子可以参考,其中介绍了如何做到这两点。
cron 作业将允许您设置执行主动消息的时间。这个 Stack Overflow post 讨论了如何创建一个可以与您的机器人一起运行的简单项目。或者,您也可以在 Azure Function 中运行 cron 作业,类似地,它会按照设定的时间表调用您的主动消息 API。
关于主动消息,请查看此 Stack Overflow post,其中详细介绍了设置此服务。特定于该用户问题的某些要点不适用于您,可以忽略。这个来自 BotBuilder-Samples 存储库的 sample 也可以作为一个很好的参考点。
希望有帮助!
[编辑]
以下是调用 API 以发送主动消息的基本设置,该消息也会启动特定的对话流。显然,您需要进行更改以满足您的需求,但这应该会让您走上正确的道路。
简而言之,对您的机器人公开的 API 进行调用,其中包含 conversationId 作为参数。当 API 被命中时,会创建一个conversationReference 并用于发送英雄卡。英雄卡询问用户他们是否接受过培训(是/否),当他们回复时发送PostBack。 PostBack 是通过中断方法在组件对话框中监视的触发器。当进行匹配时,“主动”对话框开始。当用户完成时,“主动”对话框从堆栈中弹出,然后用户返回到对话停止的地方(如果他们在一个对话中)。
请注意,主动消息需要令牌和 conversationId。用户需要事先与机器人进行过交谈,或者您需要事先通过机器人生成令牌和对话 ID,然后使用用户的 Slack user.id 发送这些内容以开始。
Index.js
const conversationReferences = {};
const dialog = new MainDialog( 'MainDialog', userState, conversationState );
const bot = new WelcomeBot( conversationState, userState, dialog, conversationReferences );
server.post( '/api/message', async ( req, res ) => {
[...]
}
server.get( '/api/notify/:conversationID', async ( req, res ) => {
const { conversationID, query } = req.params;
const conversationReference = conversationReferences[ conversationID ];
await adapter.continueConversation( conversationReference, async turnContext => {
var reply = { type: ActivityTypes.Message };
const yesBtn = { type: ActionTypes.PostBack, title: 'Yes', value: 'Yes' };
const noBtn = { type: ActionTypes.PostBack, title: 'No', value: 'No' };
const card = CardFactory.heroCard(
'Have you trained today?',
null,
[ yesBtn, noBtn ]
);
reply.attachments = [ card ];
await turnContext.sendActivity( reply );
return { status: DialogTurnStatus.waiting };
} );
res.setHeader( 'Content-Type', 'text/html' );
res.writeHead( 200 );
res.write( '<html><body><h1>Proactive messages have been sent.</h1></body></html>' );
res.end();
} );
mainDialog.js
const { DialogSet } = require( 'botbuilder-dialogs' );
const { InterruptionDialog} = require( './interruptionDialog' );
const MAIN_WATERFALL_DIALOG = 'MainWaterfallDialog';
const ADAPTIVE_CARD = 'AdaptiveCard';
class MainDialog extends CancelAndHelpDialog {
constructor ( id, userState, conversationState ) {
this.mainId = id;
this.userState = userState;
this.conversationState = conversationState;
[...]
};
async run ( turnContext, accessor ) {
const dialogSet = new DialogSet( accessor );
this.id = this.mainId;
dialogSet.add( this );
const dialogContext = await dialogSet.createContext( turnContext );
const results = await dialogContext.continueDialog();
if ( results.status === DialogTurnStatus.empty ) {
return await dialogContext.beginDialog( this.id );
}
};
[...]
};
interruptionDialog.js
const { ProactiveDialog, PROACTIVE_DIALOG } = require( './proactiveDialog' );
class InterruptionDialog extends ComponentDialog {
constructor ( id ) {
super( id );
this.addDialog( new ConfirmPrompt( 'ConfirmPrompt' ) );
this.addDialog( new ProactiveDialog() );
}
async onBeginDialog ( innerDc, options ) {
const result = await this.interrupt( innerDc );
if ( result ) {
return result;
}
return await super.onBeginDialog( innerDc, options );
}
async onContinueDialog ( innerDc ) {
const result = await this.interrupt( innerDc );
if ( result ) {
return result;
}
return await super.onContinueDialog( innerDc );
}
async onEndDialog ( innerDc ) {
const result = await this.interrupt( innerDc );
if ( result ) {
return result;
}
return await super.onEndDialog( innerDc );
}
async interrupt ( innerDc, next ) {
if ( innerDc.context.activity.type === 'message' ) {
if ( activity.channelId === 'slack' && activity.channelData.Payload ) {
if ( activity.channelData.Payload.actions[ 0 ].name === 'postBack' ) {
return await innerDc.beginDialog( PROACTIVE_DIALOG );
}
}
}
}
}
module.exports.InterruptionDialog = InterruptionDialog;
proactiveDialog.js
const {
NumberPrompt,
ComponentDialog,
DialogTurnStatus,
WaterfallDialog
} = require( 'botbuilder-dialogs' );
const PROACTIVE_DIALOG = 'proactiveDialog';
const WATERFALL_DIALOG = 'WATERFALL_DIALOG';
const NUMBER_PROMPT = 'NUMBER_PROMPT';
class ProactiveDialog extends ComponentDialog {
constructor () {
super( PROACTIVE_DIALOG );
this.addDialog( new NumberPrompt( NUMBER_PROMPT ) );
this.addDialog( new WaterfallDialog( WATERFALL_DIALOG, [
this.didTrainStep.bind( this ),
this.trainingStep.bind( this )
] ) );
this.initialDialogId = WATERFALL_DIALOG;
}
async didTrainStep ( stepContext ) {
const activity = stepContext.context.activity;
const response = activity.channelData.Payload.actions[ 0 ].value.toLowerCase();
if ( response === 'yes' ) {
return await stepContext.prompt( NUMBER_PROMPT, 'Fantastic! How many minutes?' )
} else if ( response === 'no' ) {
await stepContext.context.sendActivity( 'Rubbish...serious rubbish.' )
}
return await stepContext.next();
}
async trainingStep ( stepContext ) {
const activity = stepContext.context.activity;
const stepResult = stepContext.result;
const textResponse = activity.text.toLowerCase();
if ( textResponse === 'no' ) {
await stepContext.context.sendActivity( "I would recommend at least 5-10 mins of training." )
} else
if ( typeof ( stepResult ) === 'number' && stepResult > 0 ) {
await stepContext.context.sendActivity( "I'll log that for you." );
} else if ( stepResult <= 0 ) {
await stepContext.context.sendActivity( "I can't log that value." )
}
return { status: DialogTurnStatus.complete }
}
}
module.exports.ProactiveDialog = ProactiveDialog;
module.exports.PROACTIVE_DIALOG = PROACTIVE_DIALOG;