Typical chat app. Using the presence channel to tell who is online, but looking for an elegant way to mark a User in the presence channel with an idle flag.
The full solution to this is probably reasonably complicated and it also depends on the runtime - I'm going to assume web web client.
Ultimately you need a way of doing two things:
to detect a user as being "idle"
to signal all other users about that user being idel
To detect a user is idle
window.onblur so you know your app window is no longer focused
mousemove tracking to see if the user is actually doing anything within your application.
In order to achieve this you probably just want a timeout and only if that timeout triggers do you send an event to indicate the user is idle:
var timeout = null;
function sendUserIdle() {
// see part 2
}
function resetIdleTracking() {
if( timeout !== null ) {
// reset
clearTimeout( timeout );
}
timeout = setTimeout( sendUserIdle, 3*60*1000 ); // 3 minutes
}
window.addEventListener( 'mousemove', resetIdleTracking );
Signal other users about idle users
A missing feature of Pusher presence channels IMO is the ability to update presence information. So, you need another way of achieving this. I think you have two solutions:
Enable client events on the presence channel and trigger an event from the idle user when your code detects the user becoming idle.
Send a message to the server from the idle client. The server then triggers a message telling the users that the user is idle.
See: accessing channel members.
1. Using client events
function sendUserIdle() {
var channel = pusher.channel( 'presence-<your-channel>' );
// client events have to have a 'client-' prefix
channel.trigger( 'client-user-idle', channel.members.me );
}
2. Sending to the server
function sendUserIdle() {
makeAjaxRequest( '/idle-notification-endpoint', channel.members.me );
}
Note: you can serialise channel.members.me using JSON.stringify( channel.members.me )
On the server (in pseudo code):
userInfo = getUserInfoFromRequest();
pusher.trigger( 'presence-<your-channel>', 'user-idle', userInfo );
Showing a client is idle
Upon receipt of the event you would update the list of users UI accordingly (mark that user as idle).
channel.bind( 'user-idle', function( user ) {
var uniqueUserId = user.id;
// update UI
}
Related
I have this simple command where I and one other user can only use it to set the "Playing" status of the bot, whilst it's pretty useful for changing it on the fly, I also want to set it when I start up the bot so that I don't have to set it myself every time I restart the bot. Is it possible I can implement this code in the Bot.cs file so that when it starts up, it has the status set and ready
[Command("status")]
public async Task SetBotStatus(CommandContext ctx, string message)
{
if (ctx.User.Id == 572877986223751188 || ctx.User.Id == 327845261692895232)
{
DiscordActivity activity = new DiscordActivity();
DiscordClient discord = ctx.Client;
activity.Name = message;
await discord.UpdateStatusAsync(activity);
return;
}
Basically, the way this current command works is that it's based on an if statement stating only I and another user can use this command. The command has a requirement that you pass over a string which is the text that will be applied to the status and that is simply updated using discord.UpdateStatusAsync
With Dsharp+ this is a pretty easy fix to implement!
In your Main Async method or in a global/public context, you will want to define your activity status which will be the default activity and then set a client-ready handler.
In this example I am using a sharded client, this is not necessary to do so yourself. Your bot must lose its presence in a server before the ready event will fire on starting so it will need to be offline for a couple of minutes for Discord to catch that the client is offline and revoke the presence of the bot from servers. This means in testing you must let the bot lose its presence from being offline, and in production your bot will not lose its set activity from a momentary disconnection or a couple of missed heartbeats.
If 5 consecutive heartbeats are missed the next successful heartbeat will fire the ready event resetting the bot's activity status to the default status. Dsharp+ will tell you this happened by warning you the client has become a zombie.
static async Task MainAsync()
{
DiscordActivity activity = new();
activity.Name = "Testing...";
activity.ActivityType = ActivityType.Streaming;
//The client I have here is sharded but that is not necessary.
var discord = new DiscordShardedClient(new DiscordConfiguration()
{
Token = botToken,
TokenType = botTokenType,
Intents = botIntents,
MinimumLogLevel = botLogLevel
});
// the handler for ready will set with UpdateStatusAsync()
discord.Ready += async (client, readyEventArgs) =>
await discord.UpdateStatusAsync(activity);
// StartAsync() MUST come after this and any other handler.
await discord.StartAsync();
await Task.Delay(-1);
}
I'm providing my users with live notifications.
I'm debating two options and can't decide which is the best way to go when polling the DB.
(The notifications are transmitted using WebSockets.)
Option 1 (current):
I hold a list of all the logged in users.
Every 1000 ms, I check the db for new notifications and if there is any, I send a message via WS to the appropriate user.
Pros:
This task is rather not expensive on resources
Cons:
In off-times, where's there's only a new notification every 1 minute, I poll the DB 60 times for no reason.
In a sense, it's not real-time because it takes 1 full second for new notifications to update. Had it been a chat service, 1 second is a very long time to wait.
Option 2:
Create a hook that whenever a new notification is saved (or deleted), the db get polled.
Pros:
The db does not get polled when there are no new notifications
Actual real-time response.
Cons:
In rush-hour, when there might be as many as 10 new notifications generated every second, the db will be polled very often, potentially blocking its response time for other elements of the site.
In case a user was not logged in when their notification was generated, the notification update will be lost (since I only poll the db for logged in users), unless I also perform a count whenever a user logs in to retrieve their notifications for when they were offline. So now, not only do I poll the DB when ever my notification hook is triggered, but also I poll the db every time a user logs-in. If I have notifications generated every second, and 10 log-ins every second, I will end up polling my DB 20 times a second, which is very expensive for this task.
Which option would you choose? 1, 2? or neither? Why?
Here is the code I am currently using (option 1):
var activeSockets = [] //whenever a user logs in or out, the array gets updated to only contain the logged-in users in any given moment
var count = function () {
process.nextTick(function () {
var ids = Object.keys(activeSockets) //the ids of all the logged in users
//every user document has a field called newNotification that updates whenever a new notification is available. 0=no new notifications, >0=there are new notifications
User.find({_id:{$in:ids}}).select({newNotifications:1}).lean().exec(function (err,users) {
for(var i=0;i<users.length;i++) {
var ws = activeSockets[String(users[i]._id)]
if(ws!=undefined) {
if (ws.readyState === ws.OPEN) {
//send the ws message only if it wasn't sent before.
if(ws.notifCount!=users[i].newNotifications) {
ws.send(JSON.stringify({notifications:users[i].newNotifications}));
activeSockets[String(users[i]._id)].notifCount = users[i].newNotifications
}
}
else {
//if the user logged out while I was polling, remove them from the active users array
delete activeSockets[String(users[i]._id)]
}
}
}
setTimeout(function () {
count()
},1000)
})
})
}
The implementation of Option 2 would be just as simple. Instead of calling
count()
using
setTimeout()
I only call it in my "new notification", "delete notification", and "log-in" hooks.
Code:
var activeSockets = [] //whenever a user logs in or out, the array gets updated to only contain the logged-in users in any given moment
var count = function () {
process.nextTick(function () {
var ids = Object.keys(activeSockets) //the ids of all the logged in users
//every user document has a field called newNotification that updates whenever a new notification is available. 0=no new notifications, >0=there are new notifications
User.find({_id:{$in:ids}}).select({newNotifications:1}).lean().exec(function (err,users) {
for(var i=0;i<users.length;i++) {
var ws = activeSockets[String(users[i]._id)]
if(ws!=undefined) {
if (ws.readyState === ws.OPEN) {
//send the ws message only if it wasn't sent before.
if(ws.notifCount!=users[i].newNotifications) {
ws.send(JSON.stringify({notifications:users[i].newNotifications}));
activeSockets[String(users[i]._id)].notifCount = users[i].newNotifications
}
}
else {
//if the user logged out while I was polling, remove them from the active users array
delete activeSockets[String(users[i]._id)]
}
}
}
//setTimeout was removed
})
})
}
Hooks:
hooks = {
notifications : {
add: function () {
count()
//and other actions
},
remove: function () {
count()
//and other actions
}
},
users: {
logIn: function () {
count()
//and other actions
}
}
}
So, Which option would you choose? 1, 2? or neither? Why?
So i working on my Facebook Messenger Bot.
I want to know ho can i catch a answer for a question like
Bot: Enter your E-mail
User: enters e-mail
Bot: adress was added
My code looks like the sample app from Facebook
app.post('/webhook', function (req, res) {
var data = req.body;
// Make sure this is a page subscription
if (data.object == 'page') {
// Iterate over each entry
// There may be multiple if batched
data.entry.forEach(function(pageEntry) {
var pageID = pageEntry.id;
var timeOfEvent = pageEntry.time;
// Iterate over each messaging event
pageEntry.messaging.forEach(function(messagingEvent) {
if (messagingEvent.optin) {
receivedAuthentication(messagingEvent);
} else if (messagingEvent.message) {
receivedMessage(messagingEvent);
} else if (messagingEvent.delivery) {
receivedDeliveryConfirmation(messagingEvent);
} else if (messagingEvent.postback) {
receivedPostback(messagingEvent);
} else {
console.log("Webhook received unknown messagingEvent: ", messagingEvent);
}
});
});
// Assume all went well.
//
// You must send back a 200, within 20 seconds, to let us know you've
// successfully received the callback. Otherwise, the request will time out.
res.sendStatus(200);
}
});
You can set a flag for their ID that the E-Mail prompt was sent, and then after they respond check to see if it's an E-mail, and if so, then save it and echo it back to them.
If the bot is based on question/answer, what I normally do to handle response tracking is treat the bot like a finite state automata. Assign every "state" your bot can be in to some unique state identifier, and use said state identifier to determine what the user is replying to. You could also store callbacks instead of state ids, but high level this will behave the same way.
For Example:
First define a finite automata. In this case, lets assume it's:
0 --> 1 --> 2
Where 0 means new user, 1 means waiting for email response, 2 means user successfully completed registration.
User messages bot
We check our database and see it's a new user. We assume
state==0.
Because state is 0, we ignore what was sent and prompt for email
Change state to 1 to denote the email was prompted.
User replies with email.
We check database and see state==1. We use the "1" routine to do fancy stuff to verify the email and store it.
Change state to 2 to denote the email was received and the program has ended.
Note:
If the conversation id for the platform you're targeting is reset
after a certain amount of inactivity (or if you just want the bot to
mimic real conversations), store the time of each user's last
interaction and purge all inactive conversations well after the
conversation has been terminated.
I have an online chat. It uses rooms. If a user sends message and the other user is not online, it should increment the "missed messages" counter. I tried to create a timeout with setTimeout and if they emit an event it clears that timeout.
However chatOnline doesn't fire as I expected to, which leads to it always reporting the user is offline and incrementing the counter for missed_texts column in rethinkdb (which is not shown because it isn't relevant).
How can I retrieve if the user is online from socket.io? My goal is to avoid having to store presence info in the database, which could get out of control quickly.
Code I tried:
socket.on('chatSend',function(data){
if(socket.client.user.room_id !== null){
//were storing some crap in the socket object for easy retrieval.
data.user_id = socket.client.user.id;
data.room_id = socket.client.user.room_id;
data.timestamp = ~~(new Date() / 1000);
//insert message into chat table
r.table('chat').insert(data).run().then(function(res){
//retrive generated record from table
r.table('chat').get(res.generated_keys[0]).run().then(function(data2){
io.sockets.in(data2.room_id).emit('chatNew',data);//emit to all users in room
log('chatSend');
//attempt to see if the other user is online
getOtherUser(data2.room_id,socket.client.user.id,function(tid){
log('other user id: %d',tid);
//all users automatically join a room matching their user id when connecting.
//unsure how to see if the user is online. this doesnt work.
//this is what i need help with. retrieving the other users socket resource if they are online,
//and if they are not then return null or false, etc so i can work with that.
var othersocket = io.sockets.in(tid);
//if timeout completes before they respond, they are not online.
var tmptime = setTimeout(function(){
log('other user not online.');
othersocket.removeListener('chatOnline',tmpfunc);
missedTexts(data2.room_id,tid,'INCR');
},5000);
var tmpfunc = function(){
clearTimeout(tmptime);
//remove the listener
othersocket.removeListener('chatOnline',tmpfunc);
};
//emit chatOnline to other user socket
//when they respond, cleartimeout, resulting in counter not being incremented.
othersocket.on('chatOnline',tmpfunc);
othersocket.emit('chatOnline');
});
});
});
}
});
i am very new to pusher.com:
I am trying to set up a presence-channel Chat.
Here is my code:
var PresenceChannel = pusher.subscribe('presence-test_channel');
PresenceChannel.bind('pusher:subscription_succeeded', function(members){
$("#chatMembers").empty();
members.each(function(member) {
$("#chatMembers").prepend("<li id='"+member.info.employee_id+"'>"+member.info.customer_id+"</li>");
});
});
PresenceChannel.bind('pusher:member_added',function(member){
$("#chatMembers").prepend("<li id='"+member.info.employee_id+"'>"+member.info.customer_id+"</li>");
});
PresenceChannel.bind('pusher:member_removed',function(member){
$("li#"+member.info.employee_id).remove();
});
Its working as expected.
But i have a problem:
When i refresh one of the opened browser windows, the following events get fired:
PresenceChannel.bind('pusher:member_added',function(member){...
And directly after that,
PresenceChannel.bind('pusher:member_removed',function(member){...
get fired.
So, after a refresh of one window, the user get removed from my list, and
1 second later, the user again is added to the list....
1) Reload 1 browser window
2) The other window triggers 'pusher:member_removed': User removed from List
3) The other window triggers 'pusher:member_added': User added to the list agein
What to do ?
The 2nd window receives a pusher:member_removed because the 1st window has unloaded and the user had therefore left the presence channel. When the 2nd window reloads and the user resubscribes to the presence channel the pusher:member_added is triggered.
This is expected behaviour.
However, Pusher do add a delay to these event in order to try and stop events being triggered in this scenario. In your case it would seem that the delay in not long enough to stop that happening. In your situation there is an FAQ which provides some information about what you can do to work around this:
How can I stop users going offline for an instant when they navigate between pages?
It is simply solved.
Try this.
Pusher dashboard -> Webhooks
and add Webhook url & event type to Presense.
$app_secret = 'YOUR PUSHER SECRET KEY';
$app_key = $request->headers->get('X-Pusher-Key');
$webhook_signature = $request->headers->get('X-Pusher-Signature');
$body = file_get_contents('php://input');
$expected_signature = hash_hmac( 'sha256', $body, $app_secret, false );
if($webhook_signature == $expected_signature) {
// decode as associative array
$payload = json_decode( $body, true );
foreach($payload['events'] as &$event) {
// do something with the event
if ($event['name'] == 'member_added') {
// do process user joind & trigger message
$this->setAddMember($event);
} elseif ($event['name'] == 'member_removed') {
// do process user out & trigger message
$this->setRemoveMember($event);
}
}
header("Status: 200 OK");
}
else {
header("Status: 401 Not authenticated");
}
More detailed information see document this.
https://pusher.com/docs/webhooks