mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-15 05:37:22 +01:00
Merge branch 'develop' into client-monorepo
This commit is contained in:
60
test/api/unit/libs/highlightMentions.js
Normal file
60
test/api/unit/libs/highlightMentions.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import mongoose from 'mongoose';
|
||||
import {
|
||||
highlightMentions,
|
||||
} from '../../../../website/server/libs/highlightMentions';
|
||||
|
||||
describe('highlightMentions', () => {
|
||||
beforeEach(() => {
|
||||
const mockFind = {
|
||||
select () {
|
||||
return this;
|
||||
},
|
||||
lean () {
|
||||
return this;
|
||||
},
|
||||
exec () {
|
||||
return Promise.resolve([{
|
||||
auth: { local: { username: 'user' } }, _id: '111',
|
||||
}, { auth: { local: { username: 'user2' } }, _id: '222' }, { auth: { local: { username: 'user3' } }, _id: '333' }, { auth: { local: { username: 'user-dash' } }, _id: '444' }, { auth: { local: { username: 'user_underscore' } }, _id: '555' },
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
sinon.stub(mongoose.Model, 'find').returns(mockFind);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('doesn\'t change text without mentions', async () => {
|
||||
const text = 'some chat text';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
it('highlights existing users', async () => {
|
||||
const text = '@user: message';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('[@user](/profile/111): message');
|
||||
});
|
||||
it('highlights special characters', async () => {
|
||||
const text = '@user-dash: message @user_underscore';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('[@user-dash](/profile/444): message [@user_underscore](/profile/555)');
|
||||
});
|
||||
it('doesn\'t highlight nonexisting users', async () => {
|
||||
const text = '@nouser message';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('@nouser message');
|
||||
});
|
||||
it('highlights multiple existing users', async () => {
|
||||
const text = '@user message (@user2) @user3 @user';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal('[@user](/profile/111) message ([@user2](/profile/222)) [@user3](/profile/333) [@user](/profile/111)');
|
||||
});
|
||||
it('doesn\'t highlight more than 5 users', async () => {
|
||||
const text = '@user @user2 @user3 @user4 @user5 @user6';
|
||||
const result = await highlightMentions(text);
|
||||
expect(result[0]).to.equal(text);
|
||||
});
|
||||
});
|
||||
@@ -386,6 +386,7 @@ describe('User Model', () => {
|
||||
user = await user.save();
|
||||
// verify that it's been awarded
|
||||
expect(user.achievements.beastMaster).to.equal(true);
|
||||
expect(user.notifications.find(notification => notification.type === 'ACHIEVEMENT_BEAST_MASTER')).to.exist;
|
||||
|
||||
// reset the user
|
||||
user.achievements.beastMasterCount = 0;
|
||||
@@ -417,6 +418,28 @@ describe('User Model', () => {
|
||||
expect(user.achievements.beastMaster).to.not.equal(true);
|
||||
});
|
||||
|
||||
it('adds achievements to notification list', async () => {
|
||||
let user = new User();
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
// Create conditions for achievements to be awarded
|
||||
user.achievements.beastMasterCount = 3;
|
||||
user.achievements.mountMasterCount = 3;
|
||||
user.achievements.triadBingoCount = 3;
|
||||
// verify that it was not awarded initially
|
||||
expect(user.achievements.beastMaster).to.not.equal(true);
|
||||
// verify that it was not awarded initially
|
||||
expect(user.achievements.mountMaster).to.not.equal(true);
|
||||
// verify that it was not awarded initially
|
||||
expect(user.achievements.triadBingo).to.not.equal(true);
|
||||
|
||||
user = await user.save();
|
||||
// verify that it's been awarded
|
||||
expect(user.notifications.find(notification => notification.type === 'ACHIEVEMENT_BEAST_MASTER')).to.exist;
|
||||
expect(user.notifications.find(notification => notification.type === 'ACHIEVEMENT_MOUNT_MASTER')).to.exist;
|
||||
expect(user.notifications.find(notification => notification.type === 'ACHIEVEMENT_TRIAD_BINGO')).to.exist;
|
||||
});
|
||||
|
||||
context('manage unallocated stats points notifications', () => {
|
||||
it('doesn\'t add a notification if there are no points to allocate', async () => {
|
||||
let user = new User();
|
||||
|
||||
@@ -318,6 +318,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
name: 'Testing Party',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
await leader.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [member._id],
|
||||
});
|
||||
@@ -329,7 +330,9 @@ describe('POST /group/:groupId/join', () => {
|
||||
await leader.sync();
|
||||
|
||||
expect(member).to.have.nested.property('achievements.partyUp', true);
|
||||
expect(member.notifications.find(notification => notification.type === 'ACHIEVEMENT_PARTY_UP')).to.exist;
|
||||
expect(leader).to.have.nested.property('achievements.partyUp', true);
|
||||
expect(leader.notifications.find(notification => notification.type === 'ACHIEVEMENT_PARTY_UP')).to.exist;
|
||||
});
|
||||
|
||||
it('does not award Party On achievement to party of size 2', async () => {
|
||||
@@ -353,7 +356,9 @@ describe('POST /group/:groupId/join', () => {
|
||||
await leader.sync();
|
||||
|
||||
expect(member).to.have.nested.property('achievements.partyOn', true);
|
||||
expect(member.notifications.find(notification => notification.type === 'ACHIEVEMENT_PARTY_ON')).to.exist;
|
||||
expect(leader).to.have.nested.property('achievements.partyOn', true);
|
||||
expect(leader.notifications.find(notification => notification.type === 'ACHIEVEMENT_PARTY_ON')).to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<span v-if="msg.client && user.contributor.level >= 4">({{ msg.client }})</span>
|
||||
</p>
|
||||
<div
|
||||
ref="markdownContainer"
|
||||
class="text"
|
||||
v-html="atHighlight(parseMarkdown(msg.text))"
|
||||
></div>
|
||||
@@ -139,7 +140,6 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/tiers.scss';
|
||||
|
||||
.mentioned-icon {
|
||||
width: 16px;
|
||||
@@ -313,6 +313,16 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
const links = this.$refs.markdownContainer.getElementsByTagName('a');
|
||||
for (let i = 0; i < links.length; i += 1) {
|
||||
const link = links[i];
|
||||
if (links[i].getAttribute('href').startsWith('/profile/')) {
|
||||
links[i].onclick = ev => {
|
||||
ev.preventDefault();
|
||||
this.$router.push({ path: link.getAttribute('href') });
|
||||
};
|
||||
}
|
||||
}
|
||||
this.CHAT_FLAG_LIMIT_FOR_HIDING = CHAT_FLAG_LIMIT_FOR_HIDING;
|
||||
this.CHAT_FLAG_FROM_SHADOW_MUTE = CHAT_FLAG_FROM_SHADOW_MUTE;
|
||||
this.$emit('chat-card-mounted', this.msg.id);
|
||||
|
||||
@@ -563,7 +563,6 @@ export default {
|
||||
if (this.isParty) {
|
||||
await this.$store.dispatch('party:getParty', true);
|
||||
this.group = this.$store.state.party.data;
|
||||
this.checkForAchievements();
|
||||
} else {
|
||||
const group = await this.$store.dispatch('guilds:getGroup', { groupId: this.searchId });
|
||||
this.$set(this, 'group', group);
|
||||
@@ -584,21 +583,6 @@ export default {
|
||||
|
||||
return this.user.notifications.some(n => n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupId);
|
||||
},
|
||||
checkForAchievements () {
|
||||
// Checks if user's party has reached 2 players for the first time.
|
||||
if (!this.user.achievements.partyUp && this.group.memberCount >= 2) {
|
||||
// @TODO
|
||||
// User.set({'achievements.partyUp':true});
|
||||
// Achievement.displayAchievement('partyUp');
|
||||
}
|
||||
|
||||
// Checks if user's party has reached 4 players for the first time.
|
||||
if (!this.user.achievements.partyOn && this.group.memberCount >= 4) {
|
||||
// @TODO
|
||||
// User.set({'achievements.partyOn':true});
|
||||
// Achievement.displayAchievement('partyOn');
|
||||
}
|
||||
},
|
||||
async join () {
|
||||
if (this.group.cancelledPlan && !window.confirm(this.$t('aboutToJoinCancelledGroupPlan'))) {
|
||||
return;
|
||||
|
||||
@@ -175,6 +175,43 @@ const NOTIFICATIONS = {
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementAridAuthority')}`,
|
||||
modalId: 'generic-achievement',
|
||||
},
|
||||
ACHIEVEMENT_PARTY_UP: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementPartyUp')}`,
|
||||
modalId: 'generic-achievement',
|
||||
},
|
||||
ACHIEVEMENT_PARTY_ON: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('achievementPartyOn')}`,
|
||||
modalId: 'generic-achievement',
|
||||
},
|
||||
ACHIEVEMENT_BEAST_MASTER: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('beastAchievement')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
message: $t => $t('achievement'),
|
||||
modalText: $t => $t('mountAchievement'),
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_MOUNT_MASTER: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('mountAchievement')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
message: $t => $t('achievement'),
|
||||
modalText: $t => $t('mountAchievement'),
|
||||
},
|
||||
},
|
||||
ACHIEVEMENT_TRIAD_BINGO: {
|
||||
achievement: true,
|
||||
label: $t => `${$t('achievement')}: ${$t('triadBingoAchievement')}`,
|
||||
modalId: 'generic-achievement',
|
||||
data: {
|
||||
message: $t => $t('achievement'),
|
||||
modalText: $t => $t('triadBingoAchievement'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
@@ -229,7 +266,8 @@ export default {
|
||||
'ULTIMATE_GEAR_ACHIEVEMENT', 'REBIRTH_ACHIEVEMENT', 'GUILD_JOINED_ACHIEVEMENT',
|
||||
'CHALLENGE_JOINED_ACHIEVEMENT', 'INVITED_FRIEND_ACHIEVEMENT', 'NEW_CONTRIBUTOR_LEVEL',
|
||||
'CRON', 'SCORED_TASK', 'LOGIN_INCENTIVE', 'ACHIEVEMENT_ALL_YOUR_BASE', 'ACHIEVEMENT_BACK_TO_BASICS',
|
||||
'ACHIEVEMENT_DUST_DEVIL', 'ACHIEVEMENT_ARID_AUTHORITY', 'GENERIC_ACHIEVEMENT',
|
||||
'GENERIC_ACHIEVEMENT', 'ACHIEVEMENT_PARTY_UP', 'ACHIEVEMENT_PARTY_ON', 'ACHIEVEMENT_BEAST_MASTER',
|
||||
'ACHIEVEMENT_MOUNT_MASTER', 'ACHIEVEMENT_TRIAD_BINGO', 'ACHIEVEMENT_DUST_DEVIL', 'ACHIEVEMENT_ARID_AUTHORITY',
|
||||
].forEach(type => {
|
||||
handledNotifications[type] = true;
|
||||
});
|
||||
@@ -380,21 +418,30 @@ export default {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.achievement) {
|
||||
this.playSound('Achievement_Unlocked');
|
||||
} else if (config.sound) {
|
||||
this.playSound(config.sound);
|
||||
}
|
||||
|
||||
let data = {};
|
||||
if (notification.data) {
|
||||
this.notificationData = notification.data;
|
||||
data = notification.data;
|
||||
}
|
||||
|
||||
if (!data.modalText && config.data.modalText) {
|
||||
data.modalText = config.data.modalText(this.$t);
|
||||
}
|
||||
if (!data.message && config.data.message) {
|
||||
data.message = config.data.message(this.$t);
|
||||
}
|
||||
|
||||
this.notificationData = data;
|
||||
if (forceToModal) {
|
||||
this.$root.$emit('bv::show::modal', config.modalId);
|
||||
} else {
|
||||
this.text(config.label(this.$t), () => {
|
||||
this.notificationData = data;
|
||||
this.$root.$emit('bv::show::modal', config.modalId);
|
||||
}, false);
|
||||
}
|
||||
@@ -619,6 +666,11 @@ export default {
|
||||
case 'ACHIEVEMENT_BACK_TO_BASICS':
|
||||
case 'ACHIEVEMENT_DUST_DEVIL':
|
||||
case 'ACHIEVEMENT_ARID_AUTHORITY':
|
||||
case 'ACHIEVEMENT_PARTY_UP':
|
||||
case 'ACHIEVEMENT_PARTY_ON':
|
||||
case 'ACHIEVEMENT_BEAST_MASTER':
|
||||
case 'ACHIEVEMENT_MOUNT_MASTER':
|
||||
case 'ACHIEVEMENT_TRIAD_BINGO':
|
||||
case 'GENERIC_ACHIEVEMENT':
|
||||
this.showNotificationWithModal(notification);
|
||||
break;
|
||||
|
||||
@@ -178,7 +178,6 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/tiers.scss';
|
||||
|
||||
.header-wrap {
|
||||
padding: 0.5em;
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
"achievementDustDevil": "Dust Devil",
|
||||
"achievementDustDevilText": "Has collected all Desert Pets.",
|
||||
"achievementDustDevilModalText": "You collected all the Desert Pets!",
|
||||
"achievementPartyUp": "You teamed up with a party member!",
|
||||
"achievementAridAuthority": "Arid Authority",
|
||||
"achievementAridAuthorityText": "Has tamed all Desert Mounts.",
|
||||
"achievementAridAuthorityModalText": "You tamed all the Desert Mounts!",
|
||||
"achievementKickstarter2019": "Pin Kickstarter Backer",
|
||||
"achievementKickstarter2019Text": "Backed the 2019 Pin Kickstarter Project"
|
||||
"achievementKickstarter2019Text": "Backed the 2019 Pin Kickstarter Project",
|
||||
"achievementPartyOn": "Your party grew to 4 members!"
|
||||
}
|
||||
|
||||
@@ -205,6 +205,10 @@
|
||||
"goToSettings": "Go to Settings",
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "We will be transitioning login names to unique, public usernames soon. This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"verifyUsernameVeteranPet": "One of these Veteran Pets will be waiting for you after you've finished confirming!"
|
||||
"changeUsernameDisclaimer": "This username will be used for invitations, @mentions in chat, and messaging.",
|
||||
"verifyUsernameVeteranPet": "One of these Veteran Pets will be waiting for you after you've finished confirming!",
|
||||
"mentioning": "Mentioning",
|
||||
"suggestMyUsername": "Suggest my username",
|
||||
"everywhere": "Everywhere",
|
||||
"onlyPrivateSpaces": "Only in private spaces"
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import guildsAllowingBannedWords from '../../libs/guildsAllowingBannedWords';
|
||||
import { getMatchesByWordArray } from '../../libs/stringUtils';
|
||||
import bannedSlurs from '../../libs/bannedSlurs';
|
||||
import apiError from '../../libs/apiError';
|
||||
import { highlightMentions } from '../../libs/highlightMentions';
|
||||
|
||||
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map(email => ({ email, canSend: true }));
|
||||
|
||||
@@ -89,7 +90,6 @@ function getBannedWordsFromText (message) {
|
||||
}
|
||||
|
||||
|
||||
const mentionRegex = new RegExp('\\B@[-\\w]+', 'g');
|
||||
/**
|
||||
* @api {post} /api/v3/groups/:groupId/chat Post chat message to a group
|
||||
* @apiName PostChat
|
||||
@@ -187,6 +187,7 @@ api.postChat = {
|
||||
throw new NotAuthorized(res.t('messageGroupChatSpam'));
|
||||
}
|
||||
|
||||
const [message, mentions, mentionedMembers] = await highlightMentions(req.body.message);
|
||||
let client = req.headers['x-client'] || '3rd Party';
|
||||
if (client) {
|
||||
client = client.replace('habitica-', '');
|
||||
@@ -195,7 +196,6 @@ api.postChat = {
|
||||
let flagCount = 0;
|
||||
if (group.privacy === 'public' && user.flags.chatShadowMuted) {
|
||||
flagCount = common.constants.CHAT_FLAG_FROM_SHADOW_MUTE;
|
||||
const { message } = req.body;
|
||||
|
||||
// Email the mods
|
||||
const authorEmail = getUserInfo(user, ['email']).email;
|
||||
@@ -228,7 +228,14 @@ api.postChat = {
|
||||
}
|
||||
|
||||
const newChatMessage = group.sendChat({
|
||||
message: req.body.message, user, flagCount, metaData: null, client, translate: res.t,
|
||||
message: req.body.message,
|
||||
user,
|
||||
flagCount,
|
||||
metaData: null,
|
||||
client,
|
||||
translate: res.t,
|
||||
mentions,
|
||||
mentionedMembers,
|
||||
});
|
||||
const toSave = [newChatMessage.save()];
|
||||
|
||||
@@ -237,6 +244,7 @@ api.postChat = {
|
||||
toSave.push(user.save());
|
||||
}
|
||||
|
||||
|
||||
await Promise.all(toSave);
|
||||
|
||||
const analyticsObject = {
|
||||
@@ -248,7 +256,6 @@ api.postChat = {
|
||||
headers: req.headers,
|
||||
};
|
||||
|
||||
const mentions = req.body.message.match(mentionRegex);
|
||||
if (mentions) {
|
||||
analyticsObject.mentionsCount = mentions.length;
|
||||
} else {
|
||||
|
||||
@@ -630,16 +630,47 @@ api.joinGroup = {
|
||||
|
||||
if (group.type === 'party' && inviter) {
|
||||
if (group.memberCount > 1) {
|
||||
promises.push(User.update({
|
||||
$or: [{ 'party._id': group._id }, { _id: user._id }],
|
||||
'achievements.partyUp': { $ne: true },
|
||||
}, { $set: { 'achievements.partyUp': true } }, { multi: true }).exec());
|
||||
promises.push(User.update(
|
||||
{
|
||||
$or: [{ 'party._id': group._id }, { _id: user._id }],
|
||||
'achievements.partyUp': { $ne: true },
|
||||
},
|
||||
{
|
||||
$set: { 'achievements.partyUp': true },
|
||||
$push: { notifications: { type: 'ACHIEVEMENT_PARTY_UP' } },
|
||||
},
|
||||
{ multi: true },
|
||||
).exec());
|
||||
|
||||
if (inviter) {
|
||||
if (inviter.achievements.partyUp !== true) {
|
||||
// Since the notification list of the inviter is already
|
||||
// updated in this save we need to add the notification here
|
||||
inviter.addNotification('ACHIEVEMENT_PARTY_UP');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (group.memberCount > 3) {
|
||||
promises.push(User.update({
|
||||
$or: [{ 'party._id': group._id }, { _id: user._id }],
|
||||
'achievements.partyOn': { $ne: true },
|
||||
}, { $set: { 'achievements.partyOn': true } }, { multi: true }).exec());
|
||||
promises.push(User.update(
|
||||
{
|
||||
$or: [{ 'party._id': group._id }, { _id: user._id }],
|
||||
'achievements.partyOn': { $ne: true },
|
||||
},
|
||||
{
|
||||
$set: { 'achievements.partyOn': true },
|
||||
$push: { notifications: { type: 'ACHIEVEMENT_PARTY_ON' } },
|
||||
},
|
||||
{ multi: true },
|
||||
).exec());
|
||||
|
||||
if (inviter) {
|
||||
if (inviter.achievements.partyOn !== true) {
|
||||
// Since the notification list of the inviter is already
|
||||
// updated in this save we need to add the notification here
|
||||
inviter.addNotification('ACHIEVEMENT_PARTY_ON');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
import { sendNotification as sendPushNotification } from '../../libs/pushNotifications';
|
||||
import common from '../../../common';
|
||||
import { sentMessage } from '../../libs/inbox';
|
||||
import { highlightMentions } from '../../libs/highlightMentions';
|
||||
|
||||
const { achievements } = common;
|
||||
|
||||
@@ -676,7 +677,7 @@ api.sendPrivateMessage = {
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
const sender = res.locals.user;
|
||||
const { message } = req.body;
|
||||
const message = (await highlightMentions(req.body.message))[0];
|
||||
|
||||
const receiver = await User.findById(req.body.toUserId).exec();
|
||||
if (!receiver) throw new NotFound(res.t('userNotFound'));
|
||||
|
||||
@@ -17,15 +17,19 @@ export async function getAuthorEmailFromMessage (message) {
|
||||
return 'Author Account Deleted';
|
||||
}
|
||||
|
||||
export async function sendChatPushNotifications (user, group, message, translate) {
|
||||
export async function sendChatPushNotifications (user, group, message, mentions, translate) {
|
||||
const members = await User.find({
|
||||
'party._id': group._id,
|
||||
_id: { $ne: user._id },
|
||||
})
|
||||
.select('preferences.pushNotifications preferences.language profile.name pushDevices')
|
||||
.select('preferences.pushNotifications preferences.language profile.name pushDevices auth.local.username')
|
||||
.exec();
|
||||
|
||||
members.forEach(member => {
|
||||
if (member.preferences.pushNotifications.partyActivity !== false) {
|
||||
if (mentions && mentions.includes(`@${member.auth.local.username}`) && member.preferences.pushNotifications.mentionParty !== false) {
|
||||
return;
|
||||
}
|
||||
sendPushNotification(
|
||||
member,
|
||||
{
|
||||
|
||||
23
website/server/libs/highlightMentions.js
Normal file
23
website/server/libs/highlightMentions.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { model as User } from '../models/user';
|
||||
|
||||
const mentionRegex = new RegExp('\\B@[-\\w]+', 'g');
|
||||
|
||||
export async function highlightMentions (text) { // eslint-disable-line import/prefer-default-export
|
||||
const mentions = text.match(mentionRegex);
|
||||
let members = [];
|
||||
|
||||
if (mentions !== null && mentions.length <= 5) {
|
||||
const usernames = mentions.map(mention => mention.substr(1));
|
||||
members = await User
|
||||
.find({ 'auth.local.username': { $in: usernames }, 'flags.verifiedUsername': true })
|
||||
.select(['auth.local.username', '_id', 'preferences.pushNotifications', 'pushDevices'])
|
||||
.lean()
|
||||
.exec();
|
||||
members.forEach(member => {
|
||||
const { username } = member.auth.local;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
text = text.replace(new RegExp(`@${username}(?![\\-\\w])`, 'g'), `[@${username}](/profile/${member._id})`);
|
||||
});
|
||||
}
|
||||
return [text, mentions, members];
|
||||
}
|
||||
@@ -541,9 +541,12 @@ schema.methods.getMemberCount = async function getMemberCount () {
|
||||
|
||||
schema.methods.sendChat = function sendChat (options = {}) {
|
||||
const {
|
||||
message, user, metaData, client, flagCount = 0, info = {}, translate,
|
||||
message, user, metaData,
|
||||
client, flagCount = 0, info = {},
|
||||
translate, mentions, mentionedMembers,
|
||||
} = options;
|
||||
const newMessage = messageDefaults(message, user, client, flagCount, info);
|
||||
|
||||
let newChatMessage = new Chat();
|
||||
newChatMessage = Object.assign(newChatMessage, newMessage);
|
||||
newChatMessage.groupId = this._id;
|
||||
@@ -609,9 +612,31 @@ schema.methods.sendChat = function sendChat (options = {}) {
|
||||
});
|
||||
|
||||
if (this.type === 'party' && user) {
|
||||
sendChatPushNotifications(user, this, newChatMessage, translate);
|
||||
sendChatPushNotifications(user, this, newChatMessage, mentions, translate);
|
||||
}
|
||||
if (mentionedMembers) {
|
||||
mentionedMembers.forEach(member => {
|
||||
if (member._id === user._id) return;
|
||||
const pushNotifPrefs = member.preferences.pushNotifications;
|
||||
if (this.type === 'party') {
|
||||
if (pushNotifPrefs.mentionParty !== true || !this.isMember(member)) {
|
||||
return;
|
||||
}
|
||||
} else if (this.isMember(member)) {
|
||||
if (pushNotifPrefs.mentionJoinedGuild !== true) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (this.privacy !== 'public') {
|
||||
return;
|
||||
}
|
||||
if (pushNotifPrefs.mentionUnjoinedGuild !== true) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
sendPushNotification(member, { identifier: 'chatMention', title: `${user.profile.name} mentioned you in ${this.name}`, message });
|
||||
});
|
||||
}
|
||||
|
||||
return newChatMessage;
|
||||
};
|
||||
|
||||
|
||||
@@ -190,23 +190,35 @@ schema.pre('save', true, function preSaveUser (next, done) {
|
||||
// Determines if Beast Master should be awarded
|
||||
const beastMasterProgress = common.count.beastMasterProgress(this.items.pets);
|
||||
|
||||
if (beastMasterProgress >= 90 || this.achievements.beastMasterCount > 0) {
|
||||
if (
|
||||
(beastMasterProgress >= 90 || this.achievements.beastMasterCount > 0)
|
||||
&& this.achievements.beastMaster !== true
|
||||
) {
|
||||
this.achievements.beastMaster = true;
|
||||
this.addNotification('ACHIEVEMENT_BEAST_MASTER');
|
||||
}
|
||||
|
||||
// Determines if Mount Master should be awarded
|
||||
const mountMasterProgress = common.count.mountMasterProgress(this.items.mounts);
|
||||
|
||||
if (mountMasterProgress >= 90 || this.achievements.mountMasterCount > 0) {
|
||||
if (
|
||||
(mountMasterProgress >= 90 || this.achievements.mountMasterCount > 0)
|
||||
&& this.achievements.mountMaster !== true
|
||||
) {
|
||||
this.achievements.mountMaster = true;
|
||||
this.addNotification('ACHIEVEMENT_MOUNT_MASTER');
|
||||
}
|
||||
|
||||
// Determines if Triad Bingo should be awarded
|
||||
const dropPetCount = common.count.dropPetsCurrentlyOwned(this.items.pets);
|
||||
const qualifiesForTriad = dropPetCount >= 90 && mountMasterProgress >= 90;
|
||||
|
||||
if (qualifiesForTriad || this.achievements.triadBingoCount > 0) {
|
||||
if (
|
||||
(qualifiesForTriad || this.achievements.triadBingoCount > 0)
|
||||
&& this.achievements.triadBingo !== true
|
||||
) {
|
||||
this.achievements.triadBingo = true;
|
||||
this.addNotification('ACHIEVEMENT_TRIAD_BINGO');
|
||||
}
|
||||
|
||||
// EXAMPLE CODE for allowing all existing and new players to be
|
||||
|
||||
@@ -40,6 +40,11 @@ const NOTIFICATION_TYPES = [
|
||||
'ACHIEVEMENT_MIND_OVER_MATTER',
|
||||
'ACHIEVEMENT_DUST_DEVIL',
|
||||
'ACHIEVEMENT_ARID_AUTHORITY',
|
||||
'ACHIEVEMENT_PARTY_UP',
|
||||
'ACHIEVEMENT_PARTY_ON',
|
||||
'ACHIEVEMENT_BEAST_MASTER',
|
||||
'ACHIEVEMENT_MOUNT_MASTER',
|
||||
'ACHIEVEMENT_TRIAD_BINGO',
|
||||
];
|
||||
|
||||
const { Schema } = mongoose;
|
||||
|
||||
Reference in New Issue
Block a user