mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
feat(api): Stop alerting about new messages for large groups
closes #6706 closes #6762
This commit is contained in:
@@ -163,6 +163,7 @@
|
|||||||
"partyOnName": "Party On",
|
"partyOnName": "Party On",
|
||||||
"partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
|
"partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
|
||||||
"partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
|
"partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
|
||||||
|
"largeGroupNote": "Note: This Guild is now too large to support notifications! Be sure to check back every day to see new messages.",
|
||||||
"groupIdRequired": "\"groupId\" must be a valid UUID",
|
"groupIdRequired": "\"groupId\" must be a valid UUID",
|
||||||
"groupNotFound": "Group not found or you don't have access.",
|
"groupNotFound": "Group not found or you don't have access.",
|
||||||
"groupTypesRequired": "You must supply a valid \"type\" query string.",
|
"groupTypesRequired": "You must supply a valid \"type\" query string.",
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export const MAX_STAT_POINTS = MAX_LEVEL;
|
|||||||
export const ATTRIBUTES = ['str', 'int', 'per', 'con'];
|
export const ATTRIBUTES = ['str', 'int', 'per', 'con'];
|
||||||
|
|
||||||
export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';
|
export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';
|
||||||
|
export const LARGE_GROUP_COUNT_MESSAGE_CUTOFF = 5000;
|
||||||
|
|||||||
@@ -17,13 +17,18 @@ import { shouldDo, daysSince } from './cron';
|
|||||||
api.shouldDo = shouldDo;
|
api.shouldDo = shouldDo;
|
||||||
api.daysSince = daysSince;
|
api.daysSince = daysSince;
|
||||||
|
|
||||||
// TODO under api.constants? and capitalize exported names too
|
|
||||||
import {
|
import {
|
||||||
MAX_HEALTH,
|
MAX_HEALTH,
|
||||||
MAX_LEVEL,
|
MAX_LEVEL,
|
||||||
MAX_STAT_POINTS,
|
MAX_STAT_POINTS,
|
||||||
TAVERN_ID,
|
TAVERN_ID,
|
||||||
|
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
|
api.constants = {
|
||||||
|
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
|
||||||
|
};
|
||||||
|
// TODO Move these under api.constants
|
||||||
api.maxLevel = MAX_LEVEL;
|
api.maxLevel = MAX_LEVEL;
|
||||||
api.maxHealth = MAX_HEALTH;
|
api.maxHealth = MAX_HEALTH;
|
||||||
api.maxStatPoints = MAX_STAT_POINTS;
|
api.maxStatPoints = MAX_STAT_POINTS;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { model as Group } from '../../../../../website/server/models/group';
|
|||||||
import { model as User } from '../../../../../website/server/models/user';
|
import { model as User } from '../../../../../website/server/models/user';
|
||||||
import { quests as questScrolls } from '../../../../../common/script/content';
|
import { quests as questScrolls } from '../../../../../common/script/content';
|
||||||
import * as email from '../../../../../website/server/libs/api-v3/email';
|
import * as email from '../../../../../website/server/libs/api-v3/email';
|
||||||
|
import validator from 'validator';
|
||||||
|
import { TAVERN_ID } from '../../../../../common/script/';
|
||||||
|
|
||||||
describe('Group Model', () => {
|
describe('Group Model', () => {
|
||||||
let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember;
|
let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember;
|
||||||
@@ -395,6 +397,147 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('Instance Methods', () => {
|
context('Instance Methods', () => {
|
||||||
|
describe('#sendChat', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox.spy(User, 'update');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('puts message at top of chat array', () => {
|
||||||
|
let oldMessage = {
|
||||||
|
text: 'a message',
|
||||||
|
};
|
||||||
|
party.chat.push(oldMessage, oldMessage, oldMessage);
|
||||||
|
|
||||||
|
party.sendChat('a new message', {_id: 'user-id', profile: { name: 'user name' }});
|
||||||
|
|
||||||
|
expect(party.chat).to.have.a.lengthOf(4);
|
||||||
|
expect(party.chat[0].text).to.eql('a new message');
|
||||||
|
expect(party.chat[0].uuid).to.eql('user-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats message', () => {
|
||||||
|
party.sendChat('a new message', {
|
||||||
|
_id: 'user-id',
|
||||||
|
profile: { name: 'user name' },
|
||||||
|
contributor: { toObject () { return 'contributor object'; } },
|
||||||
|
backer: { toObject () { return 'backer object'; } },
|
||||||
|
});
|
||||||
|
|
||||||
|
let chat = party.chat[0];
|
||||||
|
|
||||||
|
expect(chat.text).to.eql('a new message');
|
||||||
|
expect(validator.isUUID(chat.id)).to.eql(true);
|
||||||
|
expect(chat.timestamp).to.be.a('number');
|
||||||
|
expect(chat.likes).to.eql({});
|
||||||
|
expect(chat.flags).to.eql({});
|
||||||
|
expect(chat.flagCount).to.eql(0);
|
||||||
|
expect(chat.uuid).to.eql('user-id');
|
||||||
|
expect(chat.contributor).to.eql('contributor object');
|
||||||
|
expect(chat.backer).to.eql('backer object');
|
||||||
|
expect(chat.user).to.eql('user name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('formats message as system if no user is passed in', () => {
|
||||||
|
party.sendChat('a system message');
|
||||||
|
|
||||||
|
let chat = party.chat[0];
|
||||||
|
|
||||||
|
expect(chat.text).to.eql('a system message');
|
||||||
|
expect(validator.isUUID(chat.id)).to.eql(true);
|
||||||
|
expect(chat.timestamp).to.be.a('number');
|
||||||
|
expect(chat.likes).to.eql({});
|
||||||
|
expect(chat.flags).to.eql({});
|
||||||
|
expect(chat.flagCount).to.eql(0);
|
||||||
|
expect(chat.uuid).to.eql('system');
|
||||||
|
expect(chat.contributor).to.not.exist;;
|
||||||
|
expect(chat.backer).to.not.exist;;
|
||||||
|
expect(chat.user).to.not.exist;;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cuts down chat to 200 messages', () => {
|
||||||
|
for (var i = 0; i < 220; i++) {
|
||||||
|
party.chat.push({ text: 'a message' });
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(party.chat).to.have.a.lengthOf(220);
|
||||||
|
|
||||||
|
party.sendChat('message');
|
||||||
|
|
||||||
|
expect(party.chat).to.have.a.lengthOf(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates users about new messages in party', () => {
|
||||||
|
party.sendChat('message');
|
||||||
|
|
||||||
|
expect(User.update).to.be.calledOnce;
|
||||||
|
expect(User.update).to.be.calledWithMatch({
|
||||||
|
'party._id': party._id,
|
||||||
|
_id: { $ne: '' },
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
[`newMessages.${party._id}`]: {
|
||||||
|
name: party.name,
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates users about new messages in group', () => {
|
||||||
|
let group = new Group({
|
||||||
|
type: 'guild',
|
||||||
|
});
|
||||||
|
|
||||||
|
group.sendChat('message');
|
||||||
|
|
||||||
|
expect(User.update).to.be.calledOnce;
|
||||||
|
expect(User.update).to.be.calledWithMatch({
|
||||||
|
'guilds': group._id,
|
||||||
|
_id: { $ne: '' },
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
[`newMessages.${group._id}`]: {
|
||||||
|
name: group.name,
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not send update to user that sent the message', () => {
|
||||||
|
party.sendChat('message', {_id: 'user-id', profile: { name: 'user' }});
|
||||||
|
|
||||||
|
expect(User.update).to.be.calledOnce;
|
||||||
|
expect(User.update).to.be.calledWithMatch({
|
||||||
|
'party._id': party._id,
|
||||||
|
_id: { $ne: 'user-id' },
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
[`newMessages.${party._id}`]: {
|
||||||
|
name: party.name,
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips sending new message notification for guilds with > 5000 members', () => {
|
||||||
|
party.memberCount = 5001;
|
||||||
|
|
||||||
|
party.sendChat('message');
|
||||||
|
|
||||||
|
expect(User.update).to.not.be.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('skips sending messages to the tavern', () => {
|
||||||
|
party._id = TAVERN_ID;
|
||||||
|
|
||||||
|
party.sendChat('message');
|
||||||
|
|
||||||
|
expect(User.update).to.not.be.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#startQuest', () => {
|
describe('#startQuest', () => {
|
||||||
context('Failure Conditions', () => {
|
context('Failure Conditions', () => {
|
||||||
it('throws an error if group is not a party', async () => {
|
it('throws an error if group is not a party', async () => {
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ const Schema = mongoose.Schema;
|
|||||||
export const INVITES_LIMIT = 100;
|
export const INVITES_LIMIT = 100;
|
||||||
export const TAVERN_ID = shared.TAVERN_ID;
|
export const TAVERN_ID = shared.TAVERN_ID;
|
||||||
|
|
||||||
|
const NO_CHAT_NOTIFICATIONS = [TAVERN_ID];
|
||||||
|
const LARGE_GROUP_COUNT_MESSAGE_CUTOFF = shared.constants.LARGE_GROUP_COUNT_MESSAGE_CUTOFF;
|
||||||
|
|
||||||
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
|
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
|
||||||
const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
|
const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
|
||||||
|
|
||||||
@@ -300,34 +303,30 @@ export function chatDefaults (msg, user) {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NO_CHAT_NOTIFICATIONS = [TAVERN_ID];
|
|
||||||
|
|
||||||
schema.methods.sendChat = function sendChat (message, user) {
|
schema.methods.sendChat = function sendChat (message, user) {
|
||||||
this.chat.unshift(chatDefaults(message, user));
|
this.chat.unshift(chatDefaults(message, user));
|
||||||
this.chat.splice(200);
|
this.chat.splice(200);
|
||||||
|
|
||||||
// Kick off chat notifications in the background.
|
|
||||||
let lastSeenUpdate = {$set: {}};
|
|
||||||
lastSeenUpdate.$set[`newMessages.${this._id}`] = {name: this.name, value: true};
|
|
||||||
|
|
||||||
// do not send notifications for guilds with more than 5000 users and for the tavern
|
// do not send notifications for guilds with more than 5000 users and for the tavern
|
||||||
if (NO_CHAT_NOTIFICATIONS.indexOf(this._id) !== -1 || this.memberCount > 5000) {
|
if (NO_CHAT_NOTIFICATIONS.indexOf(this._id) !== -1 || this.memberCount > LARGE_GROUP_COUNT_MESSAGE_CUTOFF) {
|
||||||
// TODO For Tavern, only notify them if their name was mentioned
|
return;
|
||||||
// var profileNames = [] // get usernames from regex of @xyz. how to handle space-delimited profile names?
|
|
||||||
// User.update({'profile.name':{$in:profileNames}},lastSeenUpdate,{multi:true}).exec();
|
|
||||||
} else {
|
|
||||||
let query = {};
|
|
||||||
|
|
||||||
if (this.type === 'party') {
|
|
||||||
query['party._id'] = this._id;
|
|
||||||
} else {
|
|
||||||
query.guilds = this._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
query._id = { $ne: user ? user._id : ''};
|
|
||||||
|
|
||||||
User.update(query, lastSeenUpdate, {multi: true}).exec();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kick off chat notifications in the background.
|
||||||
|
let lastSeenUpdate = {$set: {
|
||||||
|
[`newMessages.${this._id}`]: {name: this.name, value: true},
|
||||||
|
}};
|
||||||
|
let query = {};
|
||||||
|
|
||||||
|
if (this.type === 'party') {
|
||||||
|
query['party._id'] = this._id;
|
||||||
|
} else {
|
||||||
|
query.guilds = this._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
query._id = { $ne: user ? user._id : ''};
|
||||||
|
|
||||||
|
User.update(query, lastSeenUpdate, {multi: true}).exec();
|
||||||
};
|
};
|
||||||
|
|
||||||
schema.methods.startQuest = async function startQuest (user) {
|
schema.methods.startQuest = async function startQuest (user) {
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
|||||||
.popover-content
|
.popover-content
|
||||||
markdown(text='group._editing ? groupCopy.leaderMessage : group.leaderMessage')
|
markdown(text='group._editing ? groupCopy.leaderMessage : group.leaderMessage')
|
||||||
div(ng-controller='ChatCtrl')
|
div(ng-controller='ChatCtrl')
|
||||||
|
.alert.alert-info.alert-sm(ng-if='group.memberCount > Shared.constants.LARGE_GROUP_COUNT_MESSAGE_CUTOFF')=env.t('largeGroupNote')
|
||||||
h3=env.t('chat')
|
h3=env.t('chat')
|
||||||
include ./chat-box
|
include ./chat-box
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user