diff --git a/src/client.js b/src/client.js index 22cae3e87..c942acb9d 100644 --- a/src/client.js +++ b/src/client.js @@ -2262,6 +2262,27 @@ MatrixClient.prototype.mxcUrlToHttp = ); }; +/** + * Sets a new status message for the user. The message may be null/falsey + * to clear the message. + * @param {string} newMessage The new message to set. + * @return {module:client.Promise} Resolves: to nothing + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixClient.prototype._unstable_setStatusMessage = function(newMessage) { + return Promise.all(this.getRooms().map((room) => { + const isJoined = room.getMyMembership() === "join"; + const looksLikeDm = room.getInvitedAndJoinedMemberCount() === 2; + if (isJoined && looksLikeDm) { + return this.sendStateEvent(room.roomId, "im.vector.user_status", { + status: newMessage, + }, this.getUserId()); + } else { + return Promise.resolve(); + } + })); +}; + /** * @param {Object} opts Options to apply * @param {string} opts.presence One of "online", "offline" or "unavailable" diff --git a/src/models/room-state.js b/src/models/room-state.js index ac5e20077..3dbd7ff88 100644 --- a/src/models/room-state.js +++ b/src/models/room-state.js @@ -154,6 +154,16 @@ RoomState.prototype.getMembers = function() { return utils.values(this.members); }; +/** + * Get all RoomMembers in this room, excluding the user IDs provided. + * @param {Array} excludedIds The user IDs to exclude. + * @return {Array} A list of RoomMembers. + */ +RoomState.prototype.getMembersExcept = function(excludedIds) { + return utils.values(this.members) + .filter((m) => !excludedIds.includes(m.userId)); +}; + /** * Get a room member by their user ID. * @param {string} userId The room member's user ID. diff --git a/src/models/user.js b/src/models/user.js index dbfaeb791..ec2f495af 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -39,6 +39,9 @@ limitations under the License. * when a user was last active. * @prop {Boolean} currentlyActive Whether we should consider lastActiveAgo to be * an approximation and that the user should be seen as active 'now' + * @prop {string} _unstable_statusMessage The status message for the user, if known. This is + * different from the presenceStatusMsg in that this is not tied to + * the user's presence, and should be represented differently. * @prop {Object} events The events describing this user. * @prop {MatrixEvent} events.presence The m.presence event for this user. */ @@ -46,6 +49,7 @@ function User(userId) { this.userId = userId; this.presence = "offline"; this.presenceStatusMsg = null; + this._unstable_statusMessage = ""; this.displayName = userId; this.rawDisplayName = userId; this.avatarUrl = null; @@ -179,6 +183,16 @@ User.prototype.getLastActiveTs = function() { return this.lastPresenceTs - this.lastActiveAgo; }; +/** + * Manually set the user's status message. + * @param {MatrixEvent} event The im.vector.user_status event. + */ +User.prototype._unstable_updateStatusMessage = function(event) { + if (!event.getContent()) this._unstable_statusMessage = ""; + else this._unstable_statusMessage = event.getContent()["status"]; + this._updateModifiedTime(); +}; + /** * The User class. */ diff --git a/src/sync.js b/src/sync.js index 1ce75ba82..f4ea0bf88 100644 --- a/src/sync.js +++ b/src/sync.js @@ -1172,6 +1172,16 @@ SyncApi.prototype._processSyncResponse = async function( if (e.isState() && e.getType() == "m.room.encryption" && self.opts.crypto) { await self.opts.crypto.onCryptoEvent(e); } + if (e.isState() && e.getType() === "im.vector.user_status") { + let user = client.store.getUser(e.getStateKey()); + if (user) { + user._unstable_updateStatusMessage(e); + } else { + user = createNewUser(client, e.getStateKey()); + user._unstable_updateStatusMessage(e); + client.store.storeUser(user); + } + } } await Promise.mapSeries(stateEvents, processRoomEvent);