Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW] User Status Messages #12710

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/api/server/v1/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ API.v1.addRoute('users.update', { authRequired: true }, {
name: Match.Maybe(String),
password: Match.Maybe(String),
username: Match.Maybe(String),
statusMessage: Match.Maybe(String),
active: Match.Maybe(Boolean),
roles: Match.Maybe(Array),
joinDefaultChannels: Match.Maybe(Boolean),
Expand Down Expand Up @@ -368,6 +369,7 @@ API.v1.addRoute('users.updateOwnBasicInfo', { authRequired: true }, {
email: Match.Maybe(String),
name: Match.Maybe(String),
username: Match.Maybe(String),
statusMessage: Match.Maybe(String),
currentPassword: Match.Maybe(String),
newPassword: Match.Maybe(String),
}),
Expand All @@ -378,6 +380,7 @@ API.v1.addRoute('users.updateOwnBasicInfo', { authRequired: true }, {
email: this.bodyParams.data.email,
realname: this.bodyParams.data.name,
username: this.bodyParams.data.username,
statusMessage: this.bodyParams.data.statusMessage,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be really cool if like setAvatar this was a rest call. Imagining the ability to set status from music players, and many other much more practical integrations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like my smart toaster! "toast is done".

I'll get this coded up when I wake up unless someone else wants to give it a whack before then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, pushed. @geekgonecrazy please take a look at the changes and let me know if I should change it.

newPassword: this.bodyParams.data.newPassword,
typedPassword: this.bodyParams.data.currentPassword,
};
Expand Down
9 changes: 9 additions & 0 deletions app/lib/lib/roomTypes/direct.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ export class DirectMessageRoomType extends RoomTypeConfig {
return Session.get(`user_${ subscription.name }_status`);
}

getUserStatusMessage(roomId) {
const subscription = Subscriptions.findOne({ rid: roomId });
if (subscription == null) {
return;
}

return Session.get(`user_${ subscription.name }_status_message`);
}

getDisplayName(room) {
return room.usernames.join(' x ');
}
Expand Down
1 change: 1 addition & 0 deletions app/lib/server/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export { saveUser } from './saveUser';
export { sendMessage } from './sendMessage';
export { setEmail } from './setEmail';
export { setRealName, _setRealName } from './setRealName';
export { setStatusMessage, _setStatusMessage } from './setStatusMessage';
export { setUserAvatar } from './setUserAvatar';
export { _setUsername, setUsername } from './setUsername';
export { unarchiveRoom } from './unarchiveRoom';
Expand Down
13 changes: 12 additions & 1 deletion app/lib/server/functions/saveUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Gravatar } from 'meteor/jparker:gravatar';
import { getRoles, hasPermission } from '../../../authorization';
import { settings } from '../../../settings';
import PasswordPolicy from '../lib/PasswordPolicyClass';
import { checkEmailAvailability, checkUsernameAvailability, setUserAvatar, setEmail, setRealName, setUsername } from '.';
import { checkEmailAvailability, checkUsernameAvailability, setUserAvatar, setEmail, setRealName, setUsername, setStatusMessage } from '.';
import { validateEmailDomain } from '../lib';

const passwordPolicy = new PasswordPolicy();
Expand Down Expand Up @@ -131,6 +131,13 @@ function validateUserEditing(userId, userData) {
});
}

if (userData.statusMessage && !settings.get('Accounts_AllowUserStatusMessageChange') && (!canEditOtherUserInfo || editingMyself)) {
throw new Meteor.Error('error-action-not-allowed', 'Edit user status is not allowed', {
method: 'insertOrUpdateUser',
action: 'Update_user',
});
}

if (userData.name && !settings.get('Accounts_AllowRealNameChange') && (!canEditOtherUserInfo || editingMyself)) {
throw new Meteor.Error('error-action-not-allowed', 'Edit user real name is not allowed', {
method: 'insertOrUpdateUser',
Expand Down Expand Up @@ -240,6 +247,10 @@ export const saveUser = function(userId, userData) {
setRealName(userData._id, userData.name);
}

if (userData.statusMessage) {
setStatusMessage(userData._id, userData.statusMessage);
}

if (userData.email) {
const shouldSendVerificationEmailToUser = userData.verified !== true;
setEmail(userData._id, userData.email, shouldSendVerificationEmailToUser);
Expand Down
44 changes: 44 additions & 0 deletions app/lib/server/functions/setStatusMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Meteor } from 'meteor/meteor';
import { Users, Subscriptions } from '../../../models';
import { Notifications } from '../../../notifications';
import { hasPermission } from '../../../authorization';
import { RateLimiter } from '../lib';
import s from 'underscore.string';

export const _setStatusMessage = function(userId, statusMessage) {
statusMessage = s.trim(statusMessage);

if (!userId) {
return false;
}

if (statusMessage.length > 120) {
throw new Meteor.Error('error-status-message-too-long', 'Status message too long.');
}

const user = Users.findOneById(userId);

// User already has desired statusMessage, return
if (user.statusMessage === statusMessage) {
return user;
}

// Set new statusMessage
Users.setStatusMessage(user._id, statusMessage);
user.statusMessage = statusMessage;

Subscriptions.updateUserStatusMessage(user._id, statusMessage);

Notifications.notifyLogged('Users:StatusMessageChanged', {
_id: user._id,
name: user.name,
username: user.username,
statusMessage: user.statusMessage,
});

return true;
};

export const setStatusMessage = RateLimiter.limitFunction(_setStatusMessage, 1, 60000, {
0() { return !Meteor.userId() || !hasPermission(Meteor.userId(), 'edit-other-user-info'); }, // Administrators have permission to change others status, so don't limit those
});
1 change: 1 addition & 0 deletions app/lib/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import './methods/sendSMTPTestEmail';
import './methods/setAdminStatus';
import './methods/setEmail';
import './methods/setRealName';
import './methods/setStatusMessage';
import './methods/setUsername';
import './methods/unarchiveRoom';
import './methods/unblockUser';
Expand Down
36 changes: 36 additions & 0 deletions app/lib/server/methods/setStatusMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { settings } from '../../../settings';
import { setStatusMessage } from '../functions';
import { RateLimiter } from '../lib';

Meteor.methods({
setStatusMessage(statusMessage) {

check(statusMessage, String);

if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'setStatusMessage',
});
}

if (!settings.get('Accounts_AllowUserStatusMessageChange')) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
method: 'setStatusMessage',
});
}

if (!setStatusMessage(Meteor.userId(), statusMessage)) {
throw new Meteor.Error('error-could-not-change-status-message', 'Could not change status message', {
method: 'setStatusMessage',
});
}

return statusMessage;
},
});

RateLimiter.limitMethod('setStatusMessage', 1, 1000, {
userId: () => true,
});
4 changes: 4 additions & 0 deletions app/lib/server/startup/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ settings.addGroup('Accounts', function() {
type: 'boolean',
public: true,
});
this.add('Accounts_AllowUserStatusMessageChange', true, {
type: 'boolean',
public: true,
});
this.add('Accounts_AllowUsernameChange', true, {
type: 'boolean',
public: true,
Expand Down
14 changes: 14 additions & 0 deletions app/models/server/models/Subscriptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,20 @@ export class Subscriptions extends Base {
return this.update(query, update, { multi: true });
}

updateUserStatusMessage(userId, statusMessage) {
const query = {
'u._id': userId,
};

const update = {
$set: {
statusMessage,
},
};

return this.update(query, update, { multi: true });
}

// INSERT
createWithRoomAndUser(room, user, extraData) {
const subscription = {
Expand Down
11 changes: 11 additions & 0 deletions app/models/server/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class Users extends Base {
this.tryEnsureIndex({ name: 1 });
this.tryEnsureIndex({ lastLogin: 1 });
this.tryEnsureIndex({ status: 1 });
this.tryEnsureIndex({ statusMessage: 1 });
this.tryEnsureIndex({ active: 1 }, { sparse: 1 });
this.tryEnsureIndex({ statusConnection: 1 }, { sparse: 1 });
this.tryEnsureIndex({ type: 1 });
Expand Down Expand Up @@ -761,6 +762,16 @@ export class Users extends Base {
return this.update(_id, update);
}

setStatusMessage(_id, statusMessage) {
const update = {
$set: {
statusMessage,
},
};

return this.update(_id, update);
}

setAllUsersActive(active) {
const update = {
$set: {
Expand Down
1 change: 1 addition & 0 deletions app/slashcommands-status/client/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '../lib/status';
8 changes: 8 additions & 0 deletions app/slashcommands-status/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Meteor } from 'meteor/meteor';

if (Meteor.isClient) {
module.exports = require('./client/index.js');
}
if (Meteor.isServer) {
module.exports = require('./server/index.js');
}
54 changes: 54 additions & 0 deletions app/slashcommands-status/lib/status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Meteor } from 'meteor/meteor';
import { handleError, slashCommands } from '../../utils';
import { hasPermission } from '../../authorization';
import { TAPi18n } from 'meteor/tap:i18n';
import { Random } from 'meteor/random';
import { Notifications } from '../../notifications';
/*
* Join is a named function that will replace /status commands
* @param {Object} message - The message object
*/

function Status(command, params, item) {
if (command === 'status') {
if ((Meteor.isClient && hasPermission('edit-other-user-info')) || (Meteor.isServer && hasPermission(Meteor.userId(), 'edit-other-user-info'))) {
const user = Meteor.users.findOne(Meteor.userId());
Meteor.call('setStatusMessage', params, (err) => {
if (err) {
if (Meteor.isClient) {
return handleError(err);
} else {
if (err.error === 'error-not-allowed') {
Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('StatusMessage_Change_Disabled', null, user.language),
});
} else if (err.error === 'error-status-message-too-long') {
Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('StatusMessage_Too_Long', null, user.language),
});
}
throw err;
}
} else {
Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: item.rid,
ts: new Date,
msg: TAPi18n.__('StatusMessage_Changed_Successfully', null, user.language),
});
}
});
}
}
}

slashCommands.add('status', Status, {
description: 'Slash_Status_Description',
params: 'Slash_Status_Params',
});
1 change: 1 addition & 0 deletions app/slashcommands-status/server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '../lib/status';
9 changes: 7 additions & 2 deletions app/theme/client/imports/components/header.css
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,13 @@
text-overflow: ellipsis;
}

&-visual-status {
text-transform: capitalize;
&__visual-status {
overflow: hidden;

width: 100%;
max-width: fit-content;

text-overflow: ellipsis;
}

&__status {
Expand Down
15 changes: 15 additions & 0 deletions app/ui-account/client/accountProfile.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@
{{/if}}
</div>
</div>
<div class="rc-form-group rc-grid">
{{# with canChange=allowStatusMessageChange}}
<div class="rc-input rc-w100 padded {{#if statusMessageInvalid}}rc-input--error{{/if}}">
<label class="rc-input__label">
<div class="rc-input__title">{{_ "StatusMessage"}}</div>
<div class="rc-input__wrapper">
<input type="text" class="rc-input__element" maxlength="120" name="statusMessage" id="statusMessage" placeholder="{{_ "StatusMessage_Placeholder" }}" value="{{statusMessage}}" {{ifThenElse canChange '' 'disabled'}}>
</div>
</label>
{{# unless canChange}}
<div class="rc-input__description">{{_ 'StatusMessage_Change_Disabled'}}</div>
{{/unless}}
</div>
{{/with}}
</div>
<div class="rc-form-group rc-grid">
{{# with canChange=allowRealNameChange}}
<div class="rc-input rc-w50 padded {{#if nameInvalid}}rc-input--error{{/if}}">
Expand Down
Loading