How would I go about returning a list of rooms depending on how many people are in each one?
I'm using socket.userID for each connection which I'm sure could be used to calculate this.
This may be done with the following two functions (the first one is taken from this topic):
var getRoomUserNum = function(roomName, namespace) {
if (!namespace) namespace = '/';
var room = io.nsps[namespace].adapter.rooms[roomName];
if (!room) return null;
return Object.keys(room).length;
}
var getRoomsSortedByUserNum = function(namespace) {
if (!namespace) namespace = '/';
var roomsUsersNum = {};
var roomNames = [];
for (var roomName in io.nsps[namespace].adapter.rooms) {
roomsUsersNum[roomName] = getRoomUserNum(roomName, namespace);
roomNames.push(roomName);
}
roomNames.sort(function(a, b) {
return roomsUsersNum[a] < roomsUsersNum[b] ? 1 : -1;
});
return roomNames;
}
Then you just call getRoomsSortedByUserNum(); optionally passing your namespace as an argument (which is / by default), and it returns an array of room names sorted by number of users in each one.
But keep in mind that when each socket is connected, it's own room is automatically created. So if you have 20 sockets and 3 manually created rooms, you actually have 23 rooms.
Related
My goals are to obtain the users nickname by using their ID.
Their ID's are stored as variables which are being collected from a reaction collector.
I have tried a few methods and failed, most of which either return nothing or errors.
The below code returns nothing, the getnames() function is empty. This method was recommended to me buy 2 people from a nodejs discord server which aims to help solve issues, similar to here.
// returns player ID's
function getPlayers() {
let players = [];
players.push(queue.tank[0]); // First (1) in TANK queue
players.push(queue.heal[0]); // First (1) in HEAL queue
players.push(queue.dps[0]); // First (2) in DPS queue
players.push(queue.dps[1]);
return players;
}
// get nick names from ID's
function getnames() {
let players = getPlayers();
let playerNicks = [];
let newPlayer = "";
players.forEach(async player => {
newPlayer = await message.guild.members.fetch(player).then(function (user) {return user.displayName });
playerNicks.push(newPlayer)
return playerNicks;
})}
//formats nicknames into string
function formatnicknames() {
let formatted_string2 = '';
let playerNicks = getnames();
if (playerNicks)
formatted_string2 = `${playerNicks[0]} \n${playerNicks[1]} \n${playerNicks[2]} \n${playerNicks[3]}`;
return formatted_string2;
}
I have also tried a few variations of the below code, still unable to obtain nickname.
message.guild.members.cache.get(user.id)
Edit #1
now tried the following code with no success. (boost1ID contains the ID of 1 user)
var mem1 = message.guild.members.fetch(boost1ID).nickname
Edit #2
tried a new method of obtaining displayname from ID.
var guild = client.guilds.cache.get('guildid');
var mem1 = guild.member(boost1ID);
var mem2 = guild.member(boost2ID);
var mem3 = guild.member(boost3ID);
var mem4 = guild.member(boost4ID);
var nickname1 = mem1 ? mem1.displayName : null;
var nickname2 = mem2 ? mem2.displayName : null;
var nickname3 = mem3 ? mem3.displayName : null;
var nickname4 = mem4 ? mem4.displayName : null;
var Allnicknames = `${nickname1} ${nickname2} ${nickname3} ${nickname4}`
message.channel.send(`testing nicknames: ${Allnicknames}`)
I managed to only return my own name since i dont have a nickname on this server, but the other three users who does have a nickname returned null.
This is the simplest solution:
// your users ids
const IDs = [ '84847448498748974', '48477847847844' ];
const promises = IDs.map((userID) => {
return new Promise(async (resolve) => {
const member = message.guild.member(userID) || await message.guild.members.fetch(userID);
resolve(member.displayName || member.user.username);
});
});
const nicknames = await Promise.all(promises);
// you now have access to ALL the nicknames, even if the members were not cached!
The members you are trying to get the nicknames of are not necessarily cached, and this fixes that.
I made an example that could help you.
let testUsers = [];
module.exports = class extends Command {
constructor(...args) {
super(...args, {
description: 'Testing.',
category: "Information",
});
}
async run(message) {
function getNicknames(userArr, guild) {
let playerNicks = [];
for(var i = 0; i < userArr.length; i++) {
playerNicks.push(guild.member(userArr[i]).displayName);
}
return playerNicks;
}
let testUser = message.guild.members.cache.get(message.author.id);
testUsers.push(testUser);
let guild = message.guild;
console.log(getNicknames(testUsers, guild));
}
}
I created a function getNicknames that takes in two parameters. The first one is an Array of users (as you get one from your function getPlayers()) and the second one is the guild you are playing in. You need to provide the guild, because every user should be a GuildMember, because you want to use .displayName. I created a user Array outside of my command code, because otherwise there will only be one user in the Array everytime you use the command. Inside of the getNicknames() function I have created a new Array playerNicks that I basically fill with the user nicknames we get from our provided user Array.
Now you have to implement that into your code.
The call of the function getNicknames(), for your code should look like this:
getNicknames(getPlayers(), message.guild);
I'm using my own rooms implementation in Socket.io (got my own Room/Player classes), since I need to perform several filters the room. For now, I save all sockets inside "players" property in a Room, and implemented my own "emit" to the room, the loops players and emits to their sockets.
Is it considerably slower to the traditional broadcast.to('room')? or it basically does what I did to my own room implementation? I'm aiming on having thousands of rooms with 2-4 players in each...
Thanks :)
As you can see by looking at the code for the socket.io adapter .broadcast() on GitHub, all socket.io is doing is looping over a list of sockets and sending a packet to each one (see code below).
So, if your code is doing something similar, then performance would likely be something similar.
Where you might notice a feature difference is if you are using a custom adapter such as the redis adapter that is used with clustering, then the logic of broadcasting to users connected to different servers would be handled for you by the built-in adapter, but may be something you'd have to implement yourself if doing your own broadcast.
Here's the socket.io-adapter version of .broadcast():
Adapter.prototype.broadcast = function(packet, opts){
var rooms = opts.rooms || [];
var except = opts.except || [];
var flags = opts.flags || {};
var packetOpts = {
preEncoded: true,
volatile: flags.volatile,
compress: flags.compress
};
var ids = {};
var self = this;
var socket;
packet.nsp = this.nsp.name;
this.encoder.encode(packet, function(encodedPackets) {
if (rooms.length) {
for (var i = 0; i < rooms.length; i++) {
var room = self.rooms[rooms[i]];
if (!room) continue;
var sockets = room.sockets;
for (var id in sockets) {
if (sockets.hasOwnProperty(id)) {
if (ids[id] || ~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) {
socket.packet(encodedPackets, packetOpts);
ids[id] = true;
}
}
}
}
} else {
for (var id in self.sids) {
if (self.sids.hasOwnProperty(id)) {
if (~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) socket.packet(encodedPackets, packetOpts);
}
}
}
});
};
I am trying to adapt a piece of code that was coded with socket.io 0.9 that returns a list of clients in a specific room and list of rooms(typical chat-room example)
Users in room
var usersInRoom = io.sockets.clients(room);
List of rooms
socket.on('rooms', function() {
var tmp = io.sockets.manager.rooms;
socket.emit('rooms', tmp);
});
tmp looks like this
{
0: Array[1],
1: /Lobby: Array[1]
}
So I can show the list in the client with this javascript run on the browser.
socket.on('rooms', function(rooms) {
$('#room-list').empty();
debugger;
for(var room in rooms) {
room = room.substring(1, room.length);
if (room != '') {
$('#room-list').append(divEscapedContentElement(room));
}
}
$('#room-list div').click(function() {
chatApp.processCommand('/join ' + $(this).text());
$('#send-message').focus();
});
});
But for version >1.x I just found the clients/rooms changed.
Following some links I found here, I could manage to get a list of rooms by doing this:
socket.on('rooms', function(){
var tmp = socket.rooms;
socket.emit('rooms', tmp);
});
The problem here is that socket.rooms returns
{
0: "RandomString",
1: "Lobby",
length: 2
}
And I just need to pass the 'Lobby' room. I don't know from where the random string come from.
EDIT
Through some debugging I discovered, the randomstring is the socket.id ... Is it normal this behavior? Returning the room and the socket.id together?
Updated
I finally got some results
Users in room
var usersInRoom = getUsersByRoom('/', room);
function getUsersByRoom(nsp, room) {
var users = []
for (var id in io.of(nsp).adapter.rooms[room]) {
users.push(io.of(nsp).adapter.nsp.connected[id]);
};
return users;
};
List of rooms
function getRooms(io){
var allRooms = io.sockets.adapter.rooms;
var allClients = io.engine.clients;
var result = [];
for(var room in allRooms){
// check the value is not a 'client-socket-id' but a room's name
if(!allClients.hasOwnProperty(room)){
result.push(room);
}
}
return result;
}
Better and more straightforward ways to achieve the results?
These are the links I checked:
How to get room's clients list in socket.io 1.0
socket.io get rooms which socket is currently in
It seems there is no better approach, so I will use my own answer. In case any of you has a better solution, I would update the accepted one.
Users in room
var usersInRoom = getUsersByRoom('/', room);
function getUsersByRoom(nsp, room) {
var users = []
for (var id in io.of(nsp).adapter.rooms[room]) {
users.push(io.of(nsp).adapter.nsp.connected[id]);
};
return users;
};
List of rooms
function getRooms(io){
var allRooms = io.sockets.adapter.rooms;
var allClients = io.engine.clients;
var result = [];
for(var room in allRooms){
// check the value is not a 'client-socket-id' but a room's name
if(!allClients.hasOwnProperty(room)){
result.push(room);
}
}
return result;
}
I have code like that
var subscribeNewMessages = require("redis").createClient(config.redis.port, config.redis.host);
subscribeNewMessages.subscribe('new-messages');
io.of('/new-messages').on('connection', function (client) {
subscribeNewMessages.on("message", function(channel, message) {
var obj = JSON.parse(message);
if (client.userId == obj.toUserId || client.guestId == obj.toUserId) {
client.send(message);
}
obj = null;
});
})
And how can I optimize it? Because this code parses string json for each new messages.
Also when I try to publish to redis chanel I need to JSON.stringify
redis1.publish(channelPrefix, JSON.stringify(clientData));
There isn't going to be a way to avoid JSON.parse()/JSON.stringify() as long as you're storing js objects. You could use a different serialization format like msgpack, but you're still calling functions to serialize/unserialize your data (also JSON.parse()/JSON.stringify() are already pretty hard to beat performance-wise in node).
I think the only real performance adjustment you could make with the code you've provided is to only parse the JSON once for all clients instead of for each client. Example:
var subscribeNewMessages = require('redis').createClient(config.redis.port, config.redis.host);
subscribeNewMessages.subscribe('new-messages');
var nsNewMsgs = io.of('/new-messages');
subscribeNewMessages.on('message', function(channel, message) {
var obj = JSON.parse(message),
clients = nsNewMsgs.connected,
ids = Object.keys(clients);
for (var i = 0, len = ids.length, client; i < len; ++i) {
client = clients[ids[i]];
if (client.userId == obj.toUserId || client.guestId == obj.toUserId)
client.send(message);
}
});
Depending on your application, you might even be able to avoid the for-loop entirely if you can store the socket.id values in your published messages, then you can simply look up clients[obj.userSockId] and clients[obj.guestSockId] because the connected is keyed on socket.id.
I'm trying to count the total number of users in a specific room and broadcast it to all the people in that room.
Here is what I have but I get an error:
var clients = io.sockets.clients(cc.lowerCase(data.roomname)).length;
io.sockets.in(cc.lowerCase(data.roomname)).emit('updatetotal', { total: clients });
ERROR:
TypeError: Object #<Namespace> has no method 'clients'
Thanks.
Since socket.io 1.0 its API was significantly changed so the old code might not work.
To get the number of clients in a room you can use this function:
var getUsersInRoomNumber = function(roomName, namespace) {
if (!namespace) namespace = '/';
var room = io.nsps[namespace].adapter.rooms[roomName];
if (!room) return null;
var num = 0;
for (var i in room) num++;
return num;
}
or more laconically:
var getUsersInRoomNumber = function(roomName, namespace) {
if (!namespace) namespace = '/';
var room = io.nsps[namespace].adapter.rooms[roomName];
if (!room) return null;
return Object.keys(room).length;
}
This function takes two agruments:
roomName
namespace (optional) default = '/'
To send message to users of this room only use .to method:
io.to(yourRoomName).emit('updatetotal', { total: getUsersInRoomNumber(yourRoomName) });
to get total users in a room
io.sockets.adapter.rooms.get(roomName).size