how do I program a slackbot to send a regular message automatically every week - slack

I am building a slackbot that will remind people in my organisation to perform certain admin (hours expenses etc) every week. I know this can be very easily done by each person creating a recurring reminder. What i want is to create a bot that will send a preconfigured message to people every week. I've looked online extensively, and haven't yet found out how slackbot can send a message without an event or being otherwise prompted.
I'm currently testing this on a local ngrok server with the following backend:
const { WebClient } = require('#slack/web-api');
const { createEventAdapter } = require('#slack/events-api');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackToken = process.env.SLACK_TOKEN;
const port = process.env.SLACK_PORT || 3000;
const slackEvents = createEventAdapter(slackSigningSecret);
const slackClient = new WebClient(slackToken);
slackEvents.on('app_mention', (event) => {
console.log(`Got message from user ${event.user}: ${event.text}`);
(async () => {
try {
await slackClient.chat.postMessage({ channel: event.channel, text: `Hello <#${event.user}>! Have you completed your Time sheets for this week yet?` })
} catch (error) {
console.log(error.data)
}
})();
});
slackEvents.on('error', console.error);
slackEvents.start(port).then(() => {
console.log(`Server started on port ${port}`)
});
Once this reminder is done, i intend to build upon it (more features, just need a beginning) so please don't recommend alternative ways my organisation can send reminders to people.

You can try using the chat.scheduleMessage method instead (https://api.slack.com/methods/chat.scheduleMessage). Since you won't rely on an event you may want to store the necessary conversations ids so that they're ready when the app needs to call the method.

Related

Sails.js sails.io.js, blueprints not getting events from .publish(), how to subscribe to all events?

I don't know why I'm not getting notification events from sails models from model.publish().
In pre-1.x sailsjs, similar client-side code had worked and I would get every event when records are created, updated or deleted. So, I must be misunderstanding something.
How do I subscribe to all events for any records from CRUD operations?
On the server side, I have Job.js and JobController.js.
In Job.js model, this test just creates a new record every 10 secs:
test: async function(dataset) {
let count = 0;
setInterval(async function() {
count++;
let newjob = {
dataset: dataset,
state: 'delayed',
name: "job name "+count
};
let job = await Job.create(newjob).fetch()
sails.log.info('created test job: ',JSON.stringify(job));
Job.publish([job.id],job);
},10000);
}
In JobController.js, called by the client and starts the test rolling:
submittest: async function(req,res) {
let dataset = await Dataset.Get({});
await Job.test(dataset[0].id);
return res.ok({status:'ok'});
}
In the client test.html, io.socket.get operations are successful, but I never see an event:
...
<script>
io.socket.get('/job', function(body, JWR) {
console.log('and with status code: ', JWR.statusCode);
setTimeout(function() {
io.socket.get('/job/submittest', function (body,JWR) {
io.socket.on('job', function(msg) {
console.log("job event",msg); // not getting here. why?
});
});
},2000)
});
</script>
This all runs fine but the problem is, no events are seen from the client side. Why? Am I not subscribed to events with the initial io.socket.get('/job')?
Essentially, what is happening here, is you are shouting into an empty box about a new record in your model, but no one is listening to you in that empty box.
In other words, you need to subscribe the socket connection to the model updates.
See: https://sailsjs.com/documentation/reference/web-sockets/resourceful-pub-sub/subscribe
Also, checkout the answer to this question for a quick how to.

Bot Framework V4 MS teams channel and GDPR

Working on a Teams chatbot (V4/Node) and need to address GDPR.
In short, users of the chatbot need to be able to export or delete their personal data stored by the chatbot. Personal data is any information which is related to an identified or identifiable natural person. So also a user-ID in a state object.
I read a blog about GDPR and bots but this one does not address the Teams channel. And it is about V3
The personal data given by the user in dialogs (written by me) is
the easy part. I will write some dialogs to show and delete them
(like Bill does in his answer).
The content in the actual conversations is part of the Teams platform and will\should be adressed in Teams itself.
The bit I don't know how to address is the data for the bot to actually run (Bot state etc). What if a user needs to delete the fact that he or she participated in a certain conversation. That is probably stored in some state objects (in my case in Blob storage). But which ones?
I would appreciate some ideas\guidance in how to address this.
Disclaimer: I'm not a GDPR expert but I believe the following to be sufficient.
From a bot standpoint the data stored is the same in Teams channel. You have the conversation state and user state data which is typically (and in most of the examples) set up using Blob storage. I use the conversationState and userState nomenclature for these items.
In my use case, I am storing account number in userState and user name/email in conversationState. Note that there are other things that the bot stores (particularly in conversationState I believe) around the state of the dialog and other bot specific things that are rather meaningless generally but I don't know if they would be considered part of GDPR. Regardless we will be wiping these entire objects out.
To do that, I created a dialog to manage the user profile which displays the key information stored (I'm specifically accessing account number, user name, and email) and then prompts the user for if they want to delete the information. It looks like this in nodejs.
const { ConfirmPrompt, ComponentDialog, WaterfallDialog } = require('botbuilder-dialogs');
const { ActivityTypes } = require('botbuilder');
const WATERFALL_DIALOG = 'waterfallDialog';
const CONFIRM_PROMPT = 'confirmPrompt';
class manageProfileDialog extends ComponentDialog {
constructor(dialogId, userDialogStateAccessor, userState, appInsightsClient, dialogState, conversationState) {
super(dialogId);
this.dialogs.add(new ConfirmPrompt(CONFIRM_PROMPT));
this.dialogs.add(new WaterfallDialog(WATERFALL_DIALOG, [
this.showInfoAndPrompt.bind(this),
this.confirmDelete.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
// State accessors
this.userDialogStateAccessor = userDialogStateAccessor;
this.userState = userState;
this.dialogState = dialogState;
this.conversationState = conversationState;
this.appInsightsClient = appInsightsClient;
} // End constructor
async showInfoAndPrompt(step) {
this.appInsightsClient.trackEvent({name:'manageProfileDialog', properties:{instanceId:step._info.values.instanceId, channel: step.context.activity.channelId}});
this.appInsightsClient.trackMetric({name: 'showInfoAndPrompt', value: 1});
const userProfile = await this.userDialogStateAccessor.get(step.context, {});
const conversationData = await this.dialogState.get(step.context, {});
if (!userProfile.accountNumber & !conversationData.userEmail & !conversationData.userFullName & !conversationData.orderType) {
this.appInsightsClient.trackEvent({name:'manageProfileDialogEnd', properties:{instanceId:step._info.values.instanceId, channel: step.context.activity.channelId}});
this.appInsightsClient.trackMetric({name: 'confirmDelete', value: 1});
await step.context.sendActivity(`I don't have any of your information stored.`);
return await step.endDialog();
} else {
var storedData = '';
if (userProfile.accountNumber) {
storedData += ` \n**Account Number:** ${userProfile.accountNumber}`;
}
if (conversationData.userFullName) {
storedData += ` \n**Name:** ${conversationData.userFullName}`;
}
if (conversationData.userEmail) {
storedData += ` \n**Email:** ${conversationData.userEmail}`;
}
if (conversationData.orderType) {
storedData += ` \n**Default order type:** ${conversationData.orderType}`;
}
await step.context.sendActivity(`Here is the informaiton I have stored: \n ${storedData} \n\n I will forget everything except your account number after the end of this conversation.`);
await step.context.sendActivity({ type: ActivityTypes.Typing });
await new Promise(resolve => setTimeout(resolve, process.env.DIALOG_DELAY));
return await step.prompt(CONFIRM_PROMPT, `I can clear your information if you don't want me to store it or if you want to reneter it. Would you like me to clear your information now?`,['Yes','No']);
}
}
async confirmDelete(step) {
this.appInsightsClient.trackEvent({name:'manageProfileDialogEnd', properties:{instanceId:step._info.values.instanceId, channel: step.context.activity.channelId}});
if (step.result) {
const userProfile = await this.userDialogStateAccessor.delete(step.context, {});
const conversationData = await this.dialogState.delete(step.context, {});
await step.context.sendActivity(`OK, I have cleared your information.`);
return await step.endDialog();
} else {
await step.context.sendActivity(`OK, I won't clear your information. You can ask again at any time.`);
this.appInsightsClient.trackMetric({name: 'confirmDelete', value: 1});
return await step.endDialog();
}
}
}
module.exports.ManageProfileDialog = manageProfileDialog;
One thing I am uncertain of regarding GDPR is if you are storing transcripts or activity data elsewhere in the course of running the bot. For example, I am storing conversation transcripts in CosmosDB, which could include things like names and email addresses if they were provided during the course of the conversation. I don't have a good way to clear this information even if I wanted to. Also, I am storing LUIS traces and other information in Application Insights, which in many cases includes the activity which may have things like user name or ID attached. I'm not even sure it would be possible to delete those traces from Application Insights. I do not know if these fall under the realm of GDPR since they are operational, but if that is a potential concern just be careful about what you are storing in your logging and/or transcript applications.

Override the timestamp format in the webchat

When setting the timestamp format to 'absolute', the webchat with locale set to fr-FR prints the time portion of the timestamp using the 'short' time format of globalizejs library as 8:36 AM. See: https://github.com/globalizejs/globalize/blob/master/doc/api/date/date-formatter.md.
Can I override the time format to display the time in 24-hour notation, i.e. instead of 8:36 AM to print 8h 36?
Web Chat is integrated into a webpage using JavaScript (not React):
v. 4.8.1, https://cdn.botframework.com/botframework-webchat/latest/webchat.js
If you are using the non-React version of Web Chat, then, no, there isn't an in-built method for updating or changing the timestamp.
However, you can use Web Chat's store for accessing the activity's timestamp to overwrite the HTML element, as shown below. My example is updating the element with only the time. You will want to add functionality to capture any other bits (date, day, time offsets, etc.).
Also, you will need to account for the in-built auto-updating of the time element by Web Chat. For instance, when a minute has passed following an activity's arrival, the time element changes to "1 minute ago", then to "2 minutes ago", and so on.
You may be able to utilize an event listener that looks for changes to the time element which, when triggered, continues to update the time element to fit your needs.
Please note: There are inherent risks in manipulating the DOM directly. The biggest risk is your bot becomes subject to breaking changes should the Web Chat team decide to update, remove, or otherwise alter some underlying component in the future. I would recommended you consider switching to the React version of Web Chat that, among many other features, allows this change while functioning within Web Chat's space.
Lastly, any refreshing of the page will reset the time elements back to Web Chat defaults (in case you have your bot setup for persistent chat across sessions).
<script>
( async function () {
const store = window.WebChat.createStore( {}, ({dispatch}) => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
const { activity } = action.payload;
if (activity.type === 'message') {
console.log('MESSAGE ', activity);
setTimeout(() => {
const webChatRow = document.querySelectorAll('.webchat__row');
const rowLen = webChatRow.length;
const timeParent = webChatRow[ rowLen - 1 ].children;
let timeText = timeParent[ 0 ].children[ 1 ].innerText;
let time = new Date(activity.timestamp);
let hours = time.getHours();
let mins = time.getMinutes();
timeParent[ 0 ].children[ 1 ].innerText = `${hours}:${mins}`
console.log(timeText)
}, 300);
}
}
next(action);
} );
const res = await fetch( 'http://localhost:3500/directline/token', { method: 'POST' } );
const { token } = await res.json();
window.WebChat.renderWebChat(
{
directLine: window.WebChat.createDirectLine( {
token: token
} ),
store: store
},
document.getElementById( 'webchat' )
);
document.querySelector( '#webchat > *' ).focus();
} )().catch( err => console.error( err ) );
</script>
Hope of help!
Take a look at the Customize activity status Web Chat sample. It shows how you can use the activityStatusMiddleware to customize the timestamp.

Mentioning a user in the System.History

I'm trying to add a new comment to a work item which mentions a user, but using the traditional "#adamh" as you would do on the website does not seem to work via the API.
The data updates fine, however the "#adamh" is just plain text, I need to be able to somehow chuck an identity into here. Can anyone point me in the right direction?
Thanks!
A snippet is here
const vsts = require('vso-node-api');
const item = require('vso-node-api/WorkItemTrackingApi')
const ti = require('vso-node-api/interfaces/WorkItemTrackingInterfaces');
// your collection url
const collectionUrl = "https://myArea.visualstudio.com/defaultcollection";
// ideally from config
const token = "helloWorld";
async function run() {
let authHandler = vsts.getPersonalAccessTokenHandler(token);
let connection = new vsts.WebApi(collectionUrl, authHandler);
let itemTracking = await connection.getWorkItemTrackingApi();
//Add all task data to new array
let taskData = await itemTracking.getWorkItems([15795,15796])
let newData = taskData[0]
let wijson = [
{
"op": "add",
"path": "/fields/System.History",
"value": "#adamh"
}
];
const updateItem = itemTracking.updateWorkItem(null, wijson, 15795).catch(err => {
console.log(err)
}).then(() => console.log("updated"))
return newData
}
const express = require('express')
const app = express()
app.get('/', async (req, res) => {
let data = await run()
res.send(data)
})
app.listen(3000, () => console.log('Example app listening on port 3000!'))
You can use the format shown here as part of the text value for your new comment:
...
This will create a mention link to that user. The link text can be the person's name or any other text you choose to put there. An email alert will be sent to the mentioned user if your system is configured to do so (same as in the UI).
To get your users' userid strings, you can follow the method shown here.
You can use the # to notify another team member about the discussion. Simply type # and their name.
It's using the #mention control , the person you #mention will receive an email alert with your comment and a link to the work item, commit, changeset, or shelveset.
There is not any public API shows how this work in VSTS, you could try to use F12 in google browser to track the process. Another workaround is directly using API to send a notification to the user you want to mention at.

How to manage user inputs in a short time interval?

i would like to implement a way of managing a user sending many messages in a time interval (for example 3 seconds), so that the chatbot only responds to the last one.
Example of inputs (in a gap of 3 seconds):
-Hi
-Hi
-Hi
-Help
Result: The chatbot only responds to the Help message.
Thanks in advance.
You can leverage Middleware feature to intercept every message, with which you can store every user's every message in cache, when your bot receive a new message, you can compaire with those info in cache, then dicide whether the flow needs to go forward.
Npde.js code snippet for quick test:
const moment = require('moment');
let lastMessage = null;
let lastMessageTime = null;
bot.use({
receive: (session, next) => {
let currentMessage = session
if (currentMessage.text !== lastMessage) {
lastMessage = currentMessage.text;
lastMessageTime = currentMessage.timestamp;
next();
} else {
if (moment(currentMessage.timestamp) - moment(lastMessageTime) >= 3000) {
lastMessageTime = currentMessage.timestamp;
next();
}
}
}
})
What needs you paying attention is that, in production env, you need to store the message with session/user id. E.G. Using session/user id as prefix of message and timesamp key in cache.
Please refer to https://learn.microsoft.com/en-us/bot-framework/dotnet/bot-builder-dotnet-middleware for how to intercept messages in C#,
and refer to https://learn.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-intercept-messages for Node.js version.
Hope it helps.

Resources