mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
Merge branch 'develop' into release
This commit is contained in:
@@ -1,18 +1,5 @@
|
|||||||
FROM node:10
|
FROM node:10
|
||||||
|
WORKDIR /code
|
||||||
# Install global packages
|
COPY package*.json /code/
|
||||||
RUN npm install -g gulp-cli mocha
|
|
||||||
|
|
||||||
# Clone Habitica repo and install dependencies
|
|
||||||
RUN mkdir -p /usr/src/habitrpg
|
|
||||||
WORKDIR /usr/src/habitrpg
|
|
||||||
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
|
|
||||||
RUN cp config.json.example config.json
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
RUN npm install -g gulp-cli mocha
|
||||||
# Create Build dir
|
|
||||||
RUN mkdir -p ./website/build
|
|
||||||
|
|
||||||
# Start Habitica
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["npm", "start"]
|
|
||||||
|
|||||||
@@ -1,14 +1,45 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
|
|
||||||
client:
|
client:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile-Dev
|
||||||
|
command: ["npm", "run", "client:dev"]
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- BASE_URL=http://server:3000
|
||||||
|
image: habitica
|
||||||
|
networks:
|
||||||
|
- habitica
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- '.:/usr/src/habitrpg'
|
- .:/code
|
||||||
|
- /code/node_modules
|
||||||
server:
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile-Dev
|
||||||
|
command: ["npm", "start"]
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_DB_URI=mongodb://mongo/habitrpg
|
||||||
|
image: habitica
|
||||||
|
networks:
|
||||||
|
- habitica
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- '.:/usr/src/habitrpg'
|
- .:/code
|
||||||
|
- /code/node_modules
|
||||||
|
mongo:
|
||||||
|
image: mongo:3.4
|
||||||
|
networks:
|
||||||
|
- habitica
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
networks:
|
||||||
|
habitica:
|
||||||
|
driver: bridge
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe('auth middleware', () => {
|
|||||||
describe('auth with headers', () => {
|
describe('auth with headers', () => {
|
||||||
it('allows to specify a list of user field that we do not want to load', (done) => {
|
it('allows to specify a list of user field that we do not want to load', (done) => {
|
||||||
const authWithHeaders = authWithHeadersFactory({
|
const authWithHeaders = authWithHeadersFactory({
|
||||||
userFieldsToExclude: ['items', 'flags', 'auth.timestamps'],
|
userFieldsToExclude: ['items'],
|
||||||
});
|
});
|
||||||
|
|
||||||
req.headers['x-api-user'] = user._id;
|
req.headers['x-api-user'] = user._id;
|
||||||
@@ -27,11 +27,34 @@ describe('auth middleware', () => {
|
|||||||
|
|
||||||
const userToJSON = res.locals.user.toJSON();
|
const userToJSON = res.locals.user.toJSON();
|
||||||
expect(userToJSON.items).to.not.exist;
|
expect(userToJSON.items).to.not.exist;
|
||||||
expect(userToJSON.flags).to.not.exist;
|
expect(userToJSON.auth).to.exist;
|
||||||
expect(userToJSON.auth.timestamps).to.not.exist;
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes sure some fields are always included', (done) => {
|
||||||
|
const authWithHeaders = authWithHeadersFactory({
|
||||||
|
userFieldsToExclude: [
|
||||||
|
'items', 'auth.timestamps',
|
||||||
|
'preferences', 'notifications', '_id', 'flags', 'auth', // these are always loaded
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
req.headers['x-api-user'] = user._id;
|
||||||
|
req.headers['x-api-key'] = user.apiToken;
|
||||||
|
|
||||||
|
authWithHeaders(req, res, (err) => {
|
||||||
|
if (err) return done(err);
|
||||||
|
|
||||||
|
const userToJSON = res.locals.user.toJSON();
|
||||||
|
expect(userToJSON.items).to.not.exist;
|
||||||
|
expect(userToJSON.auth.timestamps).to.exist;
|
||||||
expect(userToJSON.auth).to.exist;
|
expect(userToJSON.auth).to.exist;
|
||||||
expect(userToJSON.notifications).to.exist;
|
expect(userToJSON.notifications).to.exist;
|
||||||
expect(userToJSON.preferences).to.exist;
|
expect(userToJSON.preferences).to.exist;
|
||||||
|
expect(userToJSON._id).to.exist;
|
||||||
|
expect(userToJSON.flags).to.exist;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
import { sleep } from '../../../helpers/api-unit.helper';
|
import { sleep, translationCheck } from '../../../helpers/api-unit.helper';
|
||||||
import {
|
import {
|
||||||
SPAM_MESSAGE_LIMIT,
|
SPAM_MESSAGE_LIMIT,
|
||||||
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
|
||||||
@@ -271,7 +271,16 @@ describe('Group Model', () => {
|
|||||||
party = await Group.findOne({_id: party._id});
|
party = await Group.findOne({_id: party._id});
|
||||||
|
|
||||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||||
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member attacks Wailing Whale for 5.0 damage.` `Wailing Whale attacks party for 7.5 damage.`');
|
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||||
|
message: '`Participating Member attacks Wailing Whale for 5.0 damage. Wailing Whale attacks party for 7.5 damage.`',
|
||||||
|
info: {
|
||||||
|
bossDamage: '7.5',
|
||||||
|
quest: 'whale',
|
||||||
|
type: 'boss_damage',
|
||||||
|
user: 'Participating Member',
|
||||||
|
userDamage: '5.0',
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('applies damage only to participating members of party', async () => {
|
it('applies damage only to participating members of party', async () => {
|
||||||
@@ -344,7 +353,10 @@ describe('Group Model', () => {
|
|||||||
party = await Group.findOne({_id: party._id});
|
party = await Group.findOne({_id: party._id});
|
||||||
|
|
||||||
expect(Group.prototype.sendChat).to.be.calledTwice;
|
expect(Group.prototype.sendChat).to.be.calledTwice;
|
||||||
expect(Group.prototype.sendChat).to.be.calledWith('`You defeated Wailing Whale! Questing party members receive the rewards of victory.`');
|
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||||
|
message: '`You defeated Wailing Whale! Questing party members receive the rewards of victory.`',
|
||||||
|
info: { quest: 'whale', type: 'boss_defeated' },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls finishQuest when boss has <= 0 hp', async () => {
|
it('calls finishQuest when boss has <= 0 hp', async () => {
|
||||||
@@ -387,7 +399,10 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
party = await Group.findOne({_id: party._id});
|
party = await Group.findOne({_id: party._id});
|
||||||
|
|
||||||
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
|
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||||
|
message: quest.boss.rage.effect('en'),
|
||||||
|
info: { quest: 'trex_undead', type: 'boss_rage' },
|
||||||
|
});
|
||||||
expect(party.quest.progress.hp).to.eql(383.5);
|
expect(party.quest.progress.hp).to.eql(383.5);
|
||||||
expect(party.quest.progress.rage).to.eql(0);
|
expect(party.quest.progress.rage).to.eql(0);
|
||||||
});
|
});
|
||||||
@@ -437,7 +452,10 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
party = await Group.findOne({_id: party._id});
|
party = await Group.findOne({_id: party._id});
|
||||||
|
|
||||||
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
|
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||||
|
message: quest.boss.rage.effect('en'),
|
||||||
|
info: { quest: 'lostMasterclasser4', type: 'boss_rage' },
|
||||||
|
});
|
||||||
expect(party.quest.progress.rage).to.eql(0);
|
expect(party.quest.progress.rage).to.eql(0);
|
||||||
|
|
||||||
let drainedUser = await User.findById(participatingMember._id);
|
let drainedUser = await User.findById(participatingMember._id);
|
||||||
@@ -488,7 +506,15 @@ describe('Group Model', () => {
|
|||||||
party = await Group.findOne({_id: party._id});
|
party = await Group.findOne({_id: party._id});
|
||||||
|
|
||||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||||
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member found 5 Bars of Soap.`');
|
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||||
|
message: '`Participating Member found 5 Bars of Soap.`',
|
||||||
|
info: {
|
||||||
|
items: { soapBars: 5 },
|
||||||
|
quest: 'atom1',
|
||||||
|
type: 'user_found_items',
|
||||||
|
user: 'Participating Member',
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends a chat message if no progress is made', async () => {
|
it('sends a chat message if no progress is made', async () => {
|
||||||
@@ -499,7 +525,15 @@ describe('Group Model', () => {
|
|||||||
party = await Group.findOne({_id: party._id});
|
party = await Group.findOne({_id: party._id});
|
||||||
|
|
||||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||||
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member found 0 Bars of Soap.`');
|
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||||
|
message: '`Participating Member found 0 Bars of Soap.`',
|
||||||
|
info: {
|
||||||
|
items: { soapBars: 0 },
|
||||||
|
quest: 'atom1',
|
||||||
|
type: 'user_found_items',
|
||||||
|
user: 'Participating Member',
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends a chat message if no progress is made on quest with multiple items', async () => {
|
it('sends a chat message if no progress is made on quest with multiple items', async () => {
|
||||||
@@ -516,9 +550,15 @@ describe('Group Model', () => {
|
|||||||
party = await Group.findOne({_id: party._id});
|
party = await Group.findOne({_id: party._id});
|
||||||
|
|
||||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/`Participating Member found/);
|
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/0 Blue Fins/);
|
message: '`Participating Member found 0 Fire Coral, 0 Blue Fins.`',
|
||||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/0 Fire Coral/);
|
info: {
|
||||||
|
items: { blueFins: 0, fireCoral: 0 },
|
||||||
|
quest: 'dilatoryDistress1',
|
||||||
|
type: 'user_found_items',
|
||||||
|
user: 'Participating Member',
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles collection quests with multiple items', async () => {
|
it('handles collection quests with multiple items', async () => {
|
||||||
@@ -535,8 +575,14 @@ describe('Group Model', () => {
|
|||||||
party = await Group.findOne({_id: party._id});
|
party = await Group.findOne({_id: party._id});
|
||||||
|
|
||||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/`Participating Member found/);
|
expect(Group.prototype.sendChat).to.be.calledWithMatch({
|
||||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/\d* (Tracks|Broken Twigs)/);
|
message: sinon.match(/`Participating Member found/).and(sinon.match(/\d* (Tracks|Broken Twigs)/)),
|
||||||
|
info: {
|
||||||
|
quest: 'evilsanta2',
|
||||||
|
type: 'user_found_items',
|
||||||
|
user: 'Participating Member',
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sends message about victory', async () => {
|
it('sends message about victory', async () => {
|
||||||
@@ -547,7 +593,10 @@ describe('Group Model', () => {
|
|||||||
party = await Group.findOne({_id: party._id});
|
party = await Group.findOne({_id: party._id});
|
||||||
|
|
||||||
expect(Group.prototype.sendChat).to.be.calledTwice;
|
expect(Group.prototype.sendChat).to.be.calledTwice;
|
||||||
expect(Group.prototype.sendChat).to.be.calledWith('`All items found! Party has received their rewards.`');
|
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||||
|
message: '`All items found! Party has received their rewards.`',
|
||||||
|
info: { type: 'all_items_found' },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls finishQuest when all items are found', async () => {
|
it('calls finishQuest when all items are found', async () => {
|
||||||
@@ -718,6 +767,258 @@ describe('Group Model', () => {
|
|||||||
expect(res.t).to.not.be.called;
|
expect(res.t).to.not.be.called;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('translateSystemMessages', () => {
|
||||||
|
it('translate quest_start', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'quest_start',
|
||||||
|
quest: 'basilist',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate boss_damage', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'boss_damage',
|
||||||
|
user: questLeader.profile.name,
|
||||||
|
quest: 'basilist',
|
||||||
|
userDamage: 15.3,
|
||||||
|
bossDamage: 3.7,
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate boss_dont_attack', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'boss_dont_attack',
|
||||||
|
user: questLeader.profile.name,
|
||||||
|
quest: 'basilist',
|
||||||
|
userDamage: 15.3,
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate boss_rage', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'boss_rage',
|
||||||
|
quest: 'lostMasterclasser3',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate boss_defeated', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'boss_defeated',
|
||||||
|
quest: 'lostMasterclasser3',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate user_found_items', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'user_found_items',
|
||||||
|
user: questLeader.profile.name,
|
||||||
|
quest: 'lostMasterclasser1',
|
||||||
|
items: {
|
||||||
|
ancientTome: 3,
|
||||||
|
forbiddenTome: 2,
|
||||||
|
hiddenTome: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate all_items_found', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'all_items_found',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate spell_cast_party', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'spell_cast_party',
|
||||||
|
user: questLeader.profile.name,
|
||||||
|
class: 'wizard',
|
||||||
|
spell: 'earth',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate spell_cast_user', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'spell_cast_user',
|
||||||
|
user: questLeader.profile.name,
|
||||||
|
class: 'special',
|
||||||
|
spell: 'snowball',
|
||||||
|
target: participatingMember.profile.name,
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate quest_cancel', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'quest_cancel',
|
||||||
|
user: questLeader.profile.name,
|
||||||
|
quest: 'basilist',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate quest_abort', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'quest_abort',
|
||||||
|
user: questLeader.profile.name,
|
||||||
|
quest: 'basilist',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate tavern_quest_completed', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'tavern_quest_completed',
|
||||||
|
quest: 'stressbeast',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate tavern_boss_rage_tired', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'tavern_boss_rage_tired',
|
||||||
|
quest: 'stressbeast',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate tavern_boss_rage', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'tavern_boss_rage',
|
||||||
|
quest: 'dysheartener',
|
||||||
|
scene: 'market',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate tavern_boss_desperation', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'tavern_boss_desperation',
|
||||||
|
quest: 'stressbeast',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('translate claim_task', async () => {
|
||||||
|
questLeader.preferences.language = 'en';
|
||||||
|
party.chat = [{
|
||||||
|
info: {
|
||||||
|
type: 'claim_task',
|
||||||
|
user: questLeader.profile.name,
|
||||||
|
task: 'Feed the pet',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
translationCheck(toJSON.chat[0].text);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toJSONCleanChat', () => {
|
||||||
|
it('shows messages with 1 flag to non-admins', async () => {
|
||||||
|
party.chat = [{
|
||||||
|
flagCount: 1,
|
||||||
|
info: {
|
||||||
|
type: 'quest_start',
|
||||||
|
quest: 'basilist',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
expect(toJSON.chat.length).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows messages with >= 2 flag to admins', async () => {
|
||||||
|
party.chat = [{
|
||||||
|
flagCount: 3,
|
||||||
|
info: {
|
||||||
|
type: 'quest_start',
|
||||||
|
quest: 'basilist',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
const admin = new User({'contributor.admin': true});
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, admin);
|
||||||
|
expect(toJSON.chat.length).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('doesn\'t show flagged messages to non-admins', async () => {
|
||||||
|
party.chat = [{
|
||||||
|
flagCount: 3,
|
||||||
|
info: {
|
||||||
|
type: 'quest_start',
|
||||||
|
quest: 'basilist',
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
let toJSON = await Group.toJSONCleanChat(party, questLeader);
|
||||||
|
expect(toJSON.chat.length).to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Instance Methods', () => {
|
context('Instance Methods', () => {
|
||||||
@@ -1007,7 +1308,8 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('formats message', () => {
|
it('formats message', () => {
|
||||||
const chatMessage = party.sendChat('a new message', {
|
const chatMessage = party.sendChat({
|
||||||
|
message: 'a new message', user: {
|
||||||
_id: 'user-id',
|
_id: 'user-id',
|
||||||
profile: { name: 'user name' },
|
profile: { name: 'user name' },
|
||||||
contributor: {
|
contributor: {
|
||||||
@@ -1020,7 +1322,8 @@ describe('Group Model', () => {
|
|||||||
return 'backer object';
|
return 'backer object';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
}}
|
||||||
|
);
|
||||||
|
|
||||||
const chat = chatMessage;
|
const chat = chatMessage;
|
||||||
|
|
||||||
@@ -1037,7 +1340,7 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('formats message as system if no user is passed in', () => {
|
it('formats message as system if no user is passed in', () => {
|
||||||
const chat = party.sendChat('a system message');
|
const chat = party.sendChat({message: 'a system message'});
|
||||||
|
|
||||||
expect(chat.text).to.eql('a system message');
|
expect(chat.text).to.eql('a system message');
|
||||||
expect(validator.isUUID(chat.id)).to.eql(true);
|
expect(validator.isUUID(chat.id)).to.eql(true);
|
||||||
@@ -1052,7 +1355,7 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates users about new messages in party', () => {
|
it('updates users about new messages in party', () => {
|
||||||
party.sendChat('message');
|
party.sendChat({message: 'message'});
|
||||||
|
|
||||||
expect(User.update).to.be.calledOnce;
|
expect(User.update).to.be.calledOnce;
|
||||||
expect(User.update).to.be.calledWithMatch({
|
expect(User.update).to.be.calledWithMatch({
|
||||||
@@ -1066,7 +1369,7 @@ describe('Group Model', () => {
|
|||||||
type: 'guild',
|
type: 'guild',
|
||||||
});
|
});
|
||||||
|
|
||||||
group.sendChat('message');
|
group.sendChat({message: 'message'});
|
||||||
|
|
||||||
expect(User.update).to.be.calledOnce;
|
expect(User.update).to.be.calledOnce;
|
||||||
expect(User.update).to.be.calledWithMatch({
|
expect(User.update).to.be.calledWithMatch({
|
||||||
@@ -1076,7 +1379,7 @@ describe('Group Model', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not send update to user that sent the message', () => {
|
it('does not send update to user that sent the message', () => {
|
||||||
party.sendChat('message', {_id: 'user-id', profile: { name: 'user' }});
|
party.sendChat({message: 'message', user: {_id: 'user-id', profile: { name: 'user' }}});
|
||||||
|
|
||||||
expect(User.update).to.be.calledOnce;
|
expect(User.update).to.be.calledOnce;
|
||||||
expect(User.update).to.be.calledWithMatch({
|
expect(User.update).to.be.calledWithMatch({
|
||||||
@@ -1088,7 +1391,7 @@ describe('Group Model', () => {
|
|||||||
it('skips sending new message notification for guilds with > 5000 members', () => {
|
it('skips sending new message notification for guilds with > 5000 members', () => {
|
||||||
party.memberCount = 5001;
|
party.memberCount = 5001;
|
||||||
|
|
||||||
party.sendChat('message');
|
party.sendChat({message: 'message'});
|
||||||
|
|
||||||
expect(User.update).to.not.be.called;
|
expect(User.update).to.not.be.called;
|
||||||
});
|
});
|
||||||
@@ -1096,7 +1399,7 @@ describe('Group Model', () => {
|
|||||||
it('skips sending messages to the tavern', () => {
|
it('skips sending messages to the tavern', () => {
|
||||||
party._id = TAVERN_ID;
|
party._id = TAVERN_ID;
|
||||||
|
|
||||||
party.sendChat('message');
|
party.sendChat({message: 'message'});
|
||||||
|
|
||||||
expect(User.update).to.not.be.called;
|
expect(User.update).to.not.be.called;
|
||||||
});
|
});
|
||||||
@@ -1928,7 +2231,7 @@ describe('Group Model', () => {
|
|||||||
|
|
||||||
await guild.save();
|
await guild.save();
|
||||||
|
|
||||||
const groupMessage = guild.sendChat('Test message.');
|
const groupMessage = guild.sendChat({message: 'Test message.'});
|
||||||
await groupMessage.save();
|
await groupMessage.save();
|
||||||
|
|
||||||
await sleep();
|
await sleep();
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ describe('POST /group', () => {
|
|||||||
).to.eventually.be.rejected.and.eql({
|
).to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('cannotCreatePublicGuildWhenMuted'),
|
message: t('chatPrivilegesRevoked'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('cannotInviteWhenMuted'),
|
message: t('chatPrivilegesRevoked'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('cannotInviteWhenMuted'),
|
message: t('chatPrivilegesRevoked'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -436,7 +436,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('cannotInviteWhenMuted'),
|
message: t('chatPrivilegesRevoked'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -526,7 +526,7 @@ describe('Post /groups/:groupId/invite', () => {
|
|||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('cannotInviteWhenMuted'),
|
message: t('chatPrivilegesRevoked'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,13 @@ describe('POST /groups/:groupId/quests/abort', () => {
|
|||||||
members: {},
|
members: {},
|
||||||
});
|
});
|
||||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/aborted the party quest Wail of the Whale.`/);
|
expect(Group.prototype.sendChat).to.be.calledWithMatch({
|
||||||
|
message: sinon.match(/aborted the party quest Wail of the Whale.`/),
|
||||||
|
info: {
|
||||||
|
quest: 'whale',
|
||||||
|
type: 'quest_abort',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
stub.restore();
|
stub.restore();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -141,7 +141,14 @@ describe('POST /groups/:groupId/quests/cancel', () => {
|
|||||||
members: {},
|
members: {},
|
||||||
});
|
});
|
||||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||||
expect(Group.prototype.sendChat).to.be.calledWithMatch(/cancelled the party quest Wail of the Whale.`/);
|
expect(Group.prototype.sendChat).to.be.calledWithMatch({
|
||||||
|
message: sinon.match(/cancelled the party quest Wail of the Whale.`/),
|
||||||
|
info: {
|
||||||
|
quest: 'whale',
|
||||||
|
type: 'quest_cancel',
|
||||||
|
user: sinon.match.any,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
stub.restore();
|
stub.restore();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import mongo from './mongo'; // eslint-disable-line
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import i18n from '../../website/common/script/i18n';
|
import i18n from '../../website/common/script/i18n';
|
||||||
import * as Tasks from '../../website/server/models/task';
|
import * as Tasks from '../../website/server/models/task';
|
||||||
|
export { translationCheck } from './translate';
|
||||||
|
|
||||||
afterEach((done) => {
|
afterEach((done) => {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
|
|||||||
@@ -16,3 +16,9 @@ export function translate (key, variables, language) {
|
|||||||
|
|
||||||
return translatedString;
|
return translatedString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function translationCheck (translatedString) {
|
||||||
|
expect(translatedString).to.not.be.empty;
|
||||||
|
expect(translatedString).to.not.eql(STRING_ERROR_MSG);
|
||||||
|
expect(translatedString).to.not.match(STRING_DOES_NOT_EXIST_MSG);
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ sidebar-section(:title="$t('questDetailsTitle')")
|
|||||||
.grey-progress-bar
|
.grey-progress-bar
|
||||||
.collect-progress-bar(:style="{width: (group.quest.progress.collect[key] / value.count) * 100 + '%'}")
|
.collect-progress-bar(:style="{width: (group.quest.progress.collect[key] / value.count) * 100 + '%'}")
|
||||||
strong {{group.quest.progress.collect[key]}} / {{value.count}}
|
strong {{group.quest.progress.collect[key]}} / {{value.count}}
|
||||||
div.text-right {{parseFloat(user.party.quest.progress.collectedItems) || 0}} items found
|
div.text-right(v-if='userIsOnQuest') {{parseFloat(user.party.quest.progress.collectedItems) || 0}} items found
|
||||||
.boss-info(v-if='questData.boss')
|
.boss-info(v-if='questData.boss')
|
||||||
.row
|
.row
|
||||||
.col-6
|
.col-6
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ div
|
|||||||
report-flag-modal
|
report-flag-modal
|
||||||
send-gems-modal
|
send-gems-modal
|
||||||
b-navbar.topbar.navbar-inverse.static-top(toggleable="lg", type="dark", :class="navbarZIndexClass")
|
b-navbar.topbar.navbar-inverse.static-top(toggleable="lg", type="dark", :class="navbarZIndexClass")
|
||||||
b-navbar-brand.brand
|
b-navbar-brand.brand(aria-label="Habitica")
|
||||||
.logo.svg-icon.d-none.d-xl-block(v-html="icons.logo")
|
.logo.svg-icon.d-none.d-xl-block(v-html="icons.logo")
|
||||||
.svg-icon.gryphon.d-xs-block.d-xl-none
|
.svg-icon.gryphon.d-xs-block.d-xl-none
|
||||||
b-navbar-toggle(target='menu_collapse').menu-toggle
|
b-navbar-toggle(target='menu_collapse').menu-toggle
|
||||||
.quick-menu.mobile-only.form-inline
|
.quick-menu.mobile-only.form-inline
|
||||||
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')")
|
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')", :aria-label="$t('sync')")
|
||||||
.top-menu-icon.svg-icon(v-html="icons.sync")
|
.top-menu-icon.svg-icon(v-html="icons.sync")
|
||||||
notification-menu.item-with-icon
|
notification-menu.item-with-icon
|
||||||
user-dropdown.item-with-icon
|
user-dropdown.item-with-icon
|
||||||
@@ -64,13 +64,13 @@ div
|
|||||||
.top-menu-icon.svg-icon(v-html="icons.hourglasses", v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')")
|
.top-menu-icon.svg-icon(v-html="icons.hourglasses", v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')")
|
||||||
span {{ userHourglasses }}
|
span {{ userHourglasses }}
|
||||||
.item-with-icon
|
.item-with-icon
|
||||||
.top-menu-icon.svg-icon.gem(v-html="icons.gem", @click='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')")
|
a.top-menu-icon.svg-icon.gem(:aria-label="$t('gems')", href="#buy-gems" v-html="icons.gem", @click.prevent='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')")
|
||||||
span {{userGems}}
|
span {{userGems}}
|
||||||
.item-with-icon.gold
|
.item-with-icon.gold
|
||||||
.top-menu-icon.svg-icon(v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')")
|
.top-menu-icon.svg-icon(:aria-label="$t('gold')", v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')")
|
||||||
span {{Math.floor(user.stats.gp * 100) / 100}}
|
span {{Math.floor(user.stats.gp * 100) / 100}}
|
||||||
.form-inline.desktop-only
|
.form-inline.desktop-only
|
||||||
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')")
|
a.item-with-icon(@click="sync", @keyup.enter="sync", role="link", :aria-label="$t('sync')", tabindex="0", v-b-tooltip.hover.bottom="$t('sync')")
|
||||||
.top-menu-icon.svg-icon(v-html="icons.sync")
|
.top-menu-icon.svg-icon(v-html="icons.sync")
|
||||||
notification-menu.item-with-icon
|
notification-menu.item-with-icon
|
||||||
user-dropdown.item-with-icon
|
user-dropdown.item-with-icon
|
||||||
@@ -290,6 +290,7 @@ div
|
|||||||
margin-right: 24px;
|
margin-right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus /deep/ .top-menu-icon.svg-icon,
|
||||||
&:hover /deep/ .top-menu-icon.svg-icon {
|
&:hover /deep/ .top-menu-icon.svg-icon {
|
||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
menu-dropdown.item-notifications(:right="true", @toggled="handleOpenStatusChange", :openStatus="openStatus")
|
menu-dropdown.item-notifications(:right="true", @toggled="handleOpenStatusChange", :openStatus="openStatus")
|
||||||
div(slot="dropdown-toggle")
|
div(slot="dropdown-toggle")
|
||||||
div(v-b-tooltip.hover.bottom="$t('notifications')")
|
div(:aria-label="$t('notifications')", v-b-tooltip.hover.bottom="$t('notifications')")
|
||||||
message-count(
|
message-count(
|
||||||
v-if='notificationsCount > 0',
|
v-if='notificationsCount > 0',
|
||||||
:count="notificationsCount",
|
:count="notificationsCount",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
menu-dropdown.item-user(:right="true")
|
menu-dropdown.item-user(:right="true")
|
||||||
div(slot="dropdown-toggle")
|
div(slot="dropdown-toggle")
|
||||||
div(v-b-tooltip.hover.bottom="$t('user')")
|
div(:aria-label="$t('user')", v-b-tooltip.hover.bottom="$t('user')")
|
||||||
message-count(v-if='user.inbox.newMessages > 0', :count="user.inbox.newMessages", :top="true")
|
message-count(v-if='user.inbox.newMessages > 0', :count="user.inbox.newMessages", :top="true")
|
||||||
.top-menu-icon.svg-icon.user(v-html="icons.user")
|
.top-menu-icon.svg-icon.user(v-html="icons.user")
|
||||||
.user-dropdown(slot="dropdown-content")
|
.user-dropdown(slot="dropdown-content")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ A simplified dropdown component that doesn't rely on buttons as toggles like bo
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
.habitica-menu-dropdown.dropdown(@click="toggleDropdown()", :class="{open: isOpen}")
|
.habitica-menu-dropdown.dropdown(@click="toggleDropdown()", @keypress.enter.space.stop.prevent="toggleDropdown()", role="button", tabindex="0", :class="{open: isOpen}", :aria-pressed="isPressed")
|
||||||
.habitica-menu-dropdown-toggle
|
.habitica-menu-dropdown-toggle
|
||||||
slot(name="dropdown-toggle")
|
slot(name="dropdown-toggle")
|
||||||
.dropdown-menu(:class="{'dropdown-menu-right': right}")
|
.dropdown-menu(:class="{'dropdown-menu-right': right}")
|
||||||
@@ -12,12 +12,18 @@ A simplified dropdown component that doesn't rely on buttons as toggles like bo
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '~client/assets/scss/colors.scss';
|
@import '~client/assets/scss/colors.scss';
|
||||||
|
.habitica-menu-dropdown {
|
||||||
|
&:hover,
|
||||||
|
&:focus { // NB focus styles match the hover styles for .svg-icon
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.habitica-menu-dropdown.open {
|
&.open {
|
||||||
.habitica-menu-dropdown-toggle .svg-icon {
|
.habitica-menu-dropdown-toggle .svg-icon {
|
||||||
color: $white !important;
|
color: $white !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
@import '~client/assets/scss/colors.scss';
|
@import '~client/assets/scss/colors.scss';
|
||||||
@@ -66,6 +72,9 @@ export default {
|
|||||||
if (this.openStatus !== undefined) return this.openStatus === 1 ? true : false;
|
if (this.openStatus !== undefined) return this.openStatus === 1 ? true : false;
|
||||||
return this.isDropdownOpen;
|
return this.isDropdownOpen;
|
||||||
},
|
},
|
||||||
|
isPressed () {
|
||||||
|
return this.isOpen ? 'true' : 'false';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
document.documentElement.addEventListener('click', this._clickOutListener);
|
document.documentElement.addEventListener('click', this._clickOutListener);
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ export default {
|
|||||||
if (this.user.flags.chatRevoked) {
|
if (this.user.flags.chatRevoked) {
|
||||||
return {
|
return {
|
||||||
title: this.$t('PMPlaceholderTitleRevoked'),
|
title: this.$t('PMPlaceholderTitleRevoked'),
|
||||||
description: this.$t('PMPlaceholderDescriptionRevoked'),
|
description: this.$t('chatPrivilegesRevoked'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ export default function markdown (el, {value, oldValue}) {
|
|||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
el.innerHTML = habiticaMarkdown.render(String(value));
|
el.innerHTML = habiticaMarkdown.render(String(value));
|
||||||
|
} else {
|
||||||
|
el.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
el.classList.add('markdown');
|
el.classList.add('markdown');
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
HabitRPG's translations are managed on [Transifex](https://www.transifex.com/projects/p/habitrpg/).
|
HabitRPG's translations are managed at http://translate.habitica.com/
|
||||||
|
|
||||||
The files in this folder are automatically pulled from Transifex, with
|
The files in this folder are automatically pulled from there, with
|
||||||
exception of the original American-English strings which are managed
|
exception of the original American-English strings which are managed
|
||||||
directly through GitHub in `locales/en`.
|
directly through GitHub in `locales/en`.
|
||||||
|
|
||||||
When you need to change any text, edit only the files in `locales/en`.
|
When you need to change any text, edit only the files in `locales/en`.
|
||||||
Do not edit files in any other locales directory. You do not need to
|
Do not edit files in any other locales directory. You do not need to
|
||||||
request that your changes be translated; changes are automatically
|
request that your changes be translated; changes are automatically
|
||||||
copied to Transifex on a regular basis.
|
copied to the translation website on a regular basis.
|
||||||
|
|
||||||
If you want to help with translations, please first read [Guidance for
|
If you want to help with translations, please first read [Guidance for
|
||||||
Linguists](http://habitica.fandom.com/wiki/Guidance_for_Linguists) and
|
Linguists](http://habitica.fandom.com/wiki/Guidance_for_Linguists) and
|
||||||
note especially its information about the [Translations Trello
|
note especially its information about the [Translations Trello card](https://trello.com/c/SvTsLdRF/12-translations).
|
||||||
card](https://trello.com/c/SvTsLdRF/12-translations).
|
|
||||||
|
|||||||
@@ -175,6 +175,8 @@
|
|||||||
"youCast": "You cast <%= spell %>.",
|
"youCast": "You cast <%= spell %>.",
|
||||||
"youCastTarget": "You cast <%= spell %> on <%= target %>.",
|
"youCastTarget": "You cast <%= spell %> on <%= target %>.",
|
||||||
"youCastParty": "You cast <%= spell %> for the party.",
|
"youCastParty": "You cast <%= spell %> for the party.",
|
||||||
|
"chatCastSpellParty": "<%= username %> casts <%= spell %> for the party.",
|
||||||
|
"chatCastSpellUser": "<%= username %> casts <%= spell %> on <%= target %>.",
|
||||||
"critBonus": "Critical Hit! Bonus: ",
|
"critBonus": "Critical Hit! Bonus: ",
|
||||||
"gainedGold": "You gained some Gold",
|
"gainedGold": "You gained some Gold",
|
||||||
"gainedMana": "You gained some Mana",
|
"gainedMana": "You gained some Mana",
|
||||||
|
|||||||
@@ -267,7 +267,7 @@
|
|||||||
"missingNewPassword": "Missing new password.",
|
"missingNewPassword": "Missing new password.",
|
||||||
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
|
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
|
||||||
"wrongPassword": "Wrong password.",
|
"wrongPassword": "Wrong password.",
|
||||||
"incorrectDeletePhrase": "Please type <%= magicWord %> in all caps to delete your account.",
|
"incorrectDeletePhrase": "Please type <%= magicWord %> in all capital letters to delete your account.",
|
||||||
"notAnEmail": "Invalid email address.",
|
"notAnEmail": "Invalid email address.",
|
||||||
"emailTaken": "Email address is already used in an account.",
|
"emailTaken": "Email address is already used in an account.",
|
||||||
"newEmailRequired": "Missing new email address.",
|
"newEmailRequired": "Missing new email address.",
|
||||||
@@ -283,7 +283,7 @@
|
|||||||
"passwordResetEmailHtml": "If you requested a password reset for <strong><%= username %></strong> on Habitica, <a href=\"<%= passwordResetLink %>\">click here</a> to set a new one. The link will expire after 24 hours.<br/><br>If you haven't requested a password reset, please ignore this email.",
|
"passwordResetEmailHtml": "If you requested a password reset for <strong><%= username %></strong> on Habitica, <a href=\"<%= passwordResetLink %>\">click here</a> to set a new one. The link will expire after 24 hours.<br/><br>If you haven't requested a password reset, please ignore this email.",
|
||||||
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
|
||||||
"invalidCredentials": "There is no account that uses those credentials.",
|
"invalidCredentials": "There is no account that uses those credentials.",
|
||||||
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the [Community Guidelines](https://habitica.com/static/community-guidelines) or [Terms of Service](https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please copy your User ID into the email and include your username.",
|
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the Community Guidelines (https://habitica.com/static/community-guidelines) or Terms of Service (https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please include your @Username in the email.",
|
||||||
"accountSuspendedTitle": "Account has been suspended",
|
"accountSuspendedTitle": "Account has been suspended",
|
||||||
"unsupportedNetwork": "This network is not currently supported.",
|
"unsupportedNetwork": "This network is not currently supported.",
|
||||||
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
||||||
|
|||||||
@@ -137,7 +137,6 @@
|
|||||||
"PMPlaceholderTitle": "Nothing Here Yet",
|
"PMPlaceholderTitle": "Nothing Here Yet",
|
||||||
"PMPlaceholderDescription": "Select a conversation on the left",
|
"PMPlaceholderDescription": "Select a conversation on the left",
|
||||||
"PMPlaceholderTitleRevoked": "Your chat privileges have been revoked",
|
"PMPlaceholderTitleRevoked": "Your chat privileges have been revoked",
|
||||||
"PMPlaceholderDescriptionRevoked": "You are not able to send private messages because your chat privileges have been revoked. If you have questions or concerns about this, please email <a href=\"mailto:admin@habitica.com\">admin@habitica.com</a> to discuss it with the staff.",
|
|
||||||
"PMReceive": "Receive Private Messages",
|
"PMReceive": "Receive Private Messages",
|
||||||
"PMEnabledOptPopoverText": "Private Messages are enabled. Users can contact you via your profile.",
|
"PMEnabledOptPopoverText": "Private Messages are enabled. Users can contact you via your profile.",
|
||||||
"PMDisabledOptPopoverText": "Private Messages are disabled. Enable this option to allow users to contact you via your profile.",
|
"PMDisabledOptPopoverText": "Private Messages are disabled. Enable this option to allow users to contact you via your profile.",
|
||||||
@@ -266,9 +265,7 @@
|
|||||||
"userRequestsApproval": "<%= userName %> requests approval",
|
"userRequestsApproval": "<%= userName %> requests approval",
|
||||||
"userCountRequestsApproval": "<%= userCount %> members request approval",
|
"userCountRequestsApproval": "<%= userCount %> members request approval",
|
||||||
"youAreRequestingApproval": "You are requesting approval",
|
"youAreRequestingApproval": "You are requesting approval",
|
||||||
"chatPrivilegesRevoked": "You cannot do that because your chat privileges have been revoked.",
|
"chatPrivilegesRevoked": "You cannot do this because your chat privileges have been removed. For details or to ask if your privileges can be returned, please email our Community Manager at admin@habitica.com or ask your parent or guardian to email them. Please include your @Username in the email. If a moderator has already told you that your chat ban is temporary, you do not need to send an email.",
|
||||||
"cannotCreatePublicGuildWhenMuted": "You cannot create a public guild because your chat privileges have been revoked.",
|
|
||||||
"cannotInviteWhenMuted": "You cannot invite anyone to a guild or party because your chat privileges have been revoked.",
|
|
||||||
"newChatMessagePlainNotification": "New message in <%= groupName %> by <%= authorName %>. Click here to open the chat page!",
|
"newChatMessagePlainNotification": "New message in <%= groupName %> by <%= authorName %>. Click here to open the chat page!",
|
||||||
"newChatMessageTitle": "New message in <%= groupName %>",
|
"newChatMessageTitle": "New message in <%= groupName %>",
|
||||||
"exportInbox": "Export Messages",
|
"exportInbox": "Export Messages",
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
"messageGroupChatFlagAlreadyReported": "You have already reported this message",
|
"messageGroupChatFlagAlreadyReported": "You have already reported this message",
|
||||||
"messageGroupChatNotFound": "Message not found!",
|
"messageGroupChatNotFound": "Message not found!",
|
||||||
"messageGroupChatAdminClearFlagCount": "Only an admin can clear the flag count!",
|
"messageGroupChatAdminClearFlagCount": "Only an admin can clear the flag count!",
|
||||||
"messageCannotFlagSystemMessages": "You cannot flag a system message. If you need to report a violation of the Community Guidelines related to this message, please email a screenshot and explanation to Lemoness at <%= communityManagerEmail %>.",
|
"messageCannotFlagSystemMessages": "You cannot report a system message. If you need to report a violation of the Community Guidelines related to this message, please email a screenshot and explanation to our Community Manager at <%= communityManagerEmail %>.",
|
||||||
"messageGroupChatSpam": "Whoops, looks like you're posting too many messages! Please wait a minute and try again. The Tavern chat only holds 200 messages at a time, so Habitica encourages posting longer, more thoughtful messages and consolidating replies. Can't wait to hear what you have to say. :)",
|
"messageGroupChatSpam": "Whoops, looks like you're posting too many messages! Please wait a minute and try again. The Tavern chat only holds 200 messages at a time, so Habitica encourages posting longer, more thoughtful messages and consolidating replies. Can't wait to hear what you have to say. :)",
|
||||||
"messageCannotLeaveWhileQuesting": "You cannot accept this party invitation while you are in a quest. If you'd like to join this party, you must first abort your quest, which you can do from your party screen. You will be given back the quest scroll.",
|
"messageCannotLeaveWhileQuesting": "You cannot accept this party invitation while you are in a quest. If you'd like to join this party, you must first abort your quest, which you can do from your party screen. You will be given back the quest scroll.",
|
||||||
|
|
||||||
|
|||||||
@@ -127,5 +127,14 @@
|
|||||||
"bossHealth": "<%= currentHealth %> / <%= maxHealth %> Health",
|
"bossHealth": "<%= currentHealth %> / <%= maxHealth %> Health",
|
||||||
"rageAttack": "Rage Attack:",
|
"rageAttack": "Rage Attack:",
|
||||||
"bossRage": "<%= currentRage %> / <%= maxRage %> Rage",
|
"bossRage": "<%= currentRage %> / <%= maxRage %> Rage",
|
||||||
"rageStrikes": "Rage Strikes"
|
"rageStrikes": "Rage Strikes",
|
||||||
|
"chatQuestStarted": "Your quest, <%= questName %>, has started.",
|
||||||
|
"chatBossDamage": "<%= username %> attacks <%= bossName %> for <%= userDamage %> damage. <%= bossName %> attacks party for <%= bossDamage %> damage.",
|
||||||
|
"chatBossDontAttack": "<%= username %> attacks <%= bossName %> for <%= userDamage %> damage. <%= bossName %> does not attack, because it respects the fact that there are some bugs post-maintenance, and it doesn't want to hurt anyone unfairly. It will continue its rampage soon!",
|
||||||
|
"chatBossDefeated": "You defeated <%= bossName %>! Questing party members receive the rewards of victory.",
|
||||||
|
"chatFindItems": "<%= username %> found <%= items %>.",
|
||||||
|
"chatItemQuestFinish": "All items found! Party has received their rewards.",
|
||||||
|
"chatQuestAborted": "<%= username %> aborted the party quest <%= questName %>.",
|
||||||
|
"chatQuestCancelled": "<%= username %> cancelled the party quest <%= questName %>.",
|
||||||
|
"tavernBossTired": "<%= bossName %> tries to unleash <%= rageName %> but is too tired."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -442,7 +442,8 @@ api.getGroupChallenges = {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/challenges/groups/:groupId',
|
url: '/challenges/groups/:groupId',
|
||||||
middlewares: [authWithHeaders({
|
middlewares: [authWithHeaders({
|
||||||
userFieldsToInclude: ['_id', 'party', 'guilds'],
|
// Some fields (including _id) are always loaded (see middlewares/auth)
|
||||||
|
userFieldsToInclude: ['party', 'guilds'], // Some fields are always loaded (see middlewares/auth)
|
||||||
})],
|
})],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
let user = res.locals.user;
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ api.postChat = {
|
|||||||
if (client) {
|
if (client) {
|
||||||
client = client.replace('habitica-', '');
|
client = client.replace('habitica-', '');
|
||||||
}
|
}
|
||||||
const newChatMessage = group.sendChat(req.body.message, user, null, client);
|
const newChatMessage = group.sendChat({message: req.body.message, user, metaData: null, client});
|
||||||
let toSave = [newChatMessage.save()];
|
let toSave = [newChatMessage.save()];
|
||||||
|
|
||||||
if (group.type === 'party') {
|
if (group.type === 'party') {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ let api = {};
|
|||||||
* @apiError (401) {NotAuthorized} messageInsufficientGems User does not have enough gems (4)
|
* @apiError (401) {NotAuthorized} messageInsufficientGems User does not have enough gems (4)
|
||||||
* @apiError (401) {NotAuthorized} partyMustbePrivate Party must have privacy set to private
|
* @apiError (401) {NotAuthorized} partyMustbePrivate Party must have privacy set to private
|
||||||
* @apiError (401) {NotAuthorized} messageGroupAlreadyInParty
|
* @apiError (401) {NotAuthorized} messageGroupAlreadyInParty
|
||||||
* @apiError (401) {NotAuthorized} cannotCreatePublicGuildWhenMuted You cannot create a public guild because your chat privileges have been revoked.
|
* @apiError (401) {NotAuthorized} chatPrivilegesRevoked You cannot do this because your chat privileges have been removed...
|
||||||
*
|
*
|
||||||
* @apiSuccess (201) {Object} data The created group (See <a href="https://github.com/HabitRPG/habitica/blob/develop/website/server/models/group.js" target="_blank">/website/server/models/group.js</a>)
|
* @apiSuccess (201) {Object} data The created group (See <a href="https://github.com/HabitRPG/habitica/blob/develop/website/server/models/group.js" target="_blank">/website/server/models/group.js</a>)
|
||||||
*
|
*
|
||||||
@@ -117,7 +117,7 @@ api.createGroup = {
|
|||||||
group.leader = user._id;
|
group.leader = user._id;
|
||||||
|
|
||||||
if (group.type === 'guild') {
|
if (group.type === 'guild') {
|
||||||
if (group.privacy === 'public' && user.flags.chatRevoked) throw new NotAuthorized(res.t('cannotCreatePublicGuildWhenMuted'));
|
if (group.privacy === 'public' && user.flags.chatRevoked) throw new NotAuthorized(res.t('chatPrivilegesRevoked'));
|
||||||
if (user.balance < 1) throw new NotAuthorized(res.t('messageInsufficientGems'));
|
if (user.balance < 1) throw new NotAuthorized(res.t('messageInsufficientGems'));
|
||||||
|
|
||||||
group.balance = 1;
|
group.balance = 1;
|
||||||
@@ -375,7 +375,8 @@ api.getGroup = {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/groups/:groupId',
|
url: '/groups/:groupId',
|
||||||
middlewares: [authWithHeaders({
|
middlewares: [authWithHeaders({
|
||||||
userFieldsToInclude: ['_id', 'party', 'guilds', 'contributor'],
|
// Some fields (including _id, preferences) are always loaded (see middlewares/auth)
|
||||||
|
userFieldsToInclude: ['party', 'guilds', 'contributor'],
|
||||||
})],
|
})],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
let user = res.locals.user;
|
||||||
@@ -1011,7 +1012,7 @@ api.inviteToGroup = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const user = res.locals.user;
|
const user = res.locals.user;
|
||||||
|
|
||||||
if (user.flags.chatRevoked) throw new NotAuthorized(res.t('cannotInviteWhenMuted'));
|
if (user.flags.chatRevoked) throw new NotAuthorized(res.t('chatPrivilegesRevoked'));
|
||||||
|
|
||||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||||
|
|
||||||
|
|||||||
@@ -371,7 +371,14 @@ api.cancelQuest = {
|
|||||||
if (group.quest.active) throw new NotAuthorized(res.t('cantCancelActiveQuest'));
|
if (group.quest.active) throw new NotAuthorized(res.t('cantCancelActiveQuest'));
|
||||||
|
|
||||||
let questName = questScrolls[group.quest.key].text('en');
|
let questName = questScrolls[group.quest.key].text('en');
|
||||||
const newChatMessage = group.sendChat(`\`${user.profile.name} cancelled the party quest ${questName}.\``);
|
const newChatMessage = group.sendChat({
|
||||||
|
message: `\`${user.profile.name} cancelled the party quest ${questName}.\``,
|
||||||
|
info: {
|
||||||
|
type: 'quest_cancel',
|
||||||
|
user: user.profile.name,
|
||||||
|
quest: group.quest.key,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
group.quest = Group.cleanGroupQuest();
|
group.quest = Group.cleanGroupQuest();
|
||||||
group.markModified('quest');
|
group.markModified('quest');
|
||||||
@@ -427,7 +434,14 @@ api.abortQuest = {
|
|||||||
if (user._id !== group.leader && user._id !== group.quest.leader) throw new NotAuthorized(res.t('onlyLeaderAbortQuest'));
|
if (user._id !== group.leader && user._id !== group.quest.leader) throw new NotAuthorized(res.t('onlyLeaderAbortQuest'));
|
||||||
|
|
||||||
let questName = questScrolls[group.quest.key].text('en');
|
let questName = questScrolls[group.quest.key].text('en');
|
||||||
const newChatMessage = group.sendChat(`\`${user.profile.name} aborted the party quest ${questName}.\``);
|
const newChatMessage = group.sendChat({
|
||||||
|
message: `\`${common.i18n.t('chatQuestAborted', {username: user.profile.name, questName}, 'en')}\``,
|
||||||
|
info: {
|
||||||
|
type: 'quest_abort',
|
||||||
|
user: user.profile.name,
|
||||||
|
quest: group.quest.key,
|
||||||
|
},
|
||||||
|
});
|
||||||
await newChatMessage.save();
|
await newChatMessage.save();
|
||||||
|
|
||||||
let memberUpdates = User.update({
|
let memberUpdates = User.update({
|
||||||
|
|||||||
@@ -285,7 +285,8 @@ api.getUserTasks = {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/tasks/user',
|
url: '/tasks/user',
|
||||||
middlewares: [authWithHeaders({
|
middlewares: [authWithHeaders({
|
||||||
userFieldsToInclude: ['_id', 'tasksOrder', 'preferences'],
|
// Some fields (including _id, preferences) are always loaded (see middlewares/auth)
|
||||||
|
userFieldsToInclude: ['tasksOrder'],
|
||||||
})],
|
})],
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let types = Tasks.tasksTypes.map(type => `${type}s`);
|
let types = Tasks.tasksTypes.map(type => `${type}s`);
|
||||||
|
|||||||
@@ -204,7 +204,14 @@ api.assignTask = {
|
|||||||
// User is claiming the task
|
// User is claiming the task
|
||||||
if (user._id === assignedUserId) {
|
if (user._id === assignedUserId) {
|
||||||
let message = res.t('userIsClamingTask', {username: user.profile.name, task: task.text});
|
let message = res.t('userIsClamingTask', {username: user.profile.name, task: task.text});
|
||||||
const newMessage = group.sendChat(message);
|
const newMessage = group.sendChat({
|
||||||
|
message,
|
||||||
|
info: {
|
||||||
|
type: 'claim_task',
|
||||||
|
user: user.profile.name,
|
||||||
|
task: task.text,
|
||||||
|
},
|
||||||
|
});
|
||||||
promises.push(newMessage.save());
|
promises.push(newMessage.save());
|
||||||
} else {
|
} else {
|
||||||
const taskText = task.text;
|
const taskText = task.text;
|
||||||
|
|||||||
@@ -91,6 +91,11 @@ let bannedWords = [
|
|||||||
'buggery',
|
'buggery',
|
||||||
'buggering',
|
'buggering',
|
||||||
'buggered',
|
'buggered',
|
||||||
|
'bullshit',
|
||||||
|
'bullshiter',
|
||||||
|
'bullshitter',
|
||||||
|
'bullshiting',
|
||||||
|
'bullshitting',
|
||||||
'shiz',
|
'shiz',
|
||||||
'shit',
|
'shit',
|
||||||
'shitty',
|
'shitty',
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { chatModel as Chat } from '../../models/message';
|
import { chatModel as Chat } from '../../models/message';
|
||||||
|
import shared from '../../../common';
|
||||||
|
import _ from 'lodash';
|
||||||
import { MAX_CHAT_COUNT, MAX_SUBBED_GROUP_CHAT_COUNT } from '../../models/group';
|
import { MAX_CHAT_COUNT, MAX_SUBBED_GROUP_CHAT_COUNT } from '../../models/group';
|
||||||
|
|
||||||
|
const questScrolls = shared.content.quests;
|
||||||
|
|
||||||
// @TODO: Don't use this method when the group can be saved.
|
// @TODO: Don't use this method when the group can be saved.
|
||||||
export async function getGroupChat (group) {
|
export async function getGroupChat (group) {
|
||||||
const maxChatCount = group.isSubscribed() ? MAX_SUBBED_GROUP_CHAT_COUNT : MAX_CHAT_COUNT;
|
const maxChatCount = group.isSubscribed() ? MAX_SUBBED_GROUP_CHAT_COUNT : MAX_CHAT_COUNT;
|
||||||
@@ -22,3 +26,82 @@ export async function getGroupChat (group) {
|
|||||||
return previous;
|
return previous;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function translateMessage (lang, info) {
|
||||||
|
let msg;
|
||||||
|
let foundText = '';
|
||||||
|
let spells = shared.content.spells;
|
||||||
|
let quests = shared.content.quests;
|
||||||
|
|
||||||
|
switch (info.type) {
|
||||||
|
case 'quest_start':
|
||||||
|
msg = `\`${shared.i18n.t('chatQuestStarted', {questName: questScrolls[info.quest].text(lang)}, lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'boss_damage':
|
||||||
|
msg = `\`${shared.i18n.t('chatBossDamage', {username: info.user, bossName: questScrolls[info.quest].boss.name(lang), userDamage: info.userDamage, bossDamage: info.bossDamage}, lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'boss_dont_attack':
|
||||||
|
msg = `\`${shared.i18n.t('chatBossDontAttack', {username: info.user, bossName: questScrolls[info.quest].boss.name(lang), userDamage: info.userDamage}, lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'boss_rage':
|
||||||
|
msg = `\`${questScrolls[info.quest].boss.rage.effect(lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'boss_defeated':
|
||||||
|
msg = `\`${shared.i18n.t('chatBossDefeated', {bossName: questScrolls[info.quest].boss.name(lang)}, lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'user_found_items':
|
||||||
|
foundText = _.reduce(info.items, (m, v, k) => {
|
||||||
|
m.push(`${v} ${questScrolls[info.quest].collect[k].text(lang)}`);
|
||||||
|
return m;
|
||||||
|
}, []).join(', ');
|
||||||
|
msg = `\`${shared.i18n.t('chatFindItems', {username: info.user, items: foundText}, lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'all_items_found':
|
||||||
|
msg = `\`${shared.i18n.t('chatItemQuestFinish', lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'spell_cast_party':
|
||||||
|
msg = `\`${shared.i18n.t('chatCastSpellParty', {username: info.user, spell: spells[info.class][info.spell].text(lang)}, lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'spell_cast_user':
|
||||||
|
msg = `\`${shared.i18n.t('chatCastSpellUser', {username: info.user, spell: spells[info.class][info.spell].text(lang), target: info.target}, lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'quest_cancel':
|
||||||
|
msg = `\`${shared.i18n.t('chatQuestCancelled', {username: info.user, questName: questScrolls[info.quest].text(lang)}, lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'quest_abort':
|
||||||
|
msg = `\`${shared.i18n.t('chatQuestAborted', {username: info.user, questName: questScrolls[info.quest].text(lang)}, lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tavern_quest_completed':
|
||||||
|
msg = `\`${quests[info.quest].completionChat(lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tavern_boss_rage_tired':
|
||||||
|
msg = `\`${shared.i18n.t('tavernBossTired', {rageName: quests[info.quest].boss.rage.title(lang), bossName: quests[info.quest].boss.name(lang)}, lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tavern_boss_rage':
|
||||||
|
msg = `\`${quests[info.quest].boss.rage[info.scene](lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tavern_boss_desperation':
|
||||||
|
msg = `\`${quests[info.quest].boss.desperation.text(lang)}\``;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'claim_task':
|
||||||
|
msg = `${shared.i18n.t('userIsClamingTask', {username: info.user, task: info.task}, lang)}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|||||||
@@ -197,9 +197,30 @@ async function castSpell (req, res, {isV3 = false}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (party && !spell.silent) {
|
if (party && !spell.silent) {
|
||||||
let message = `\`${user.profile.name} casts ${spell.text()}${targetType === 'user' ? ` on ${partyMembers.profile.name}` : ' for the party'}.\``;
|
if (targetType === 'user') {
|
||||||
const newChatMessage = party.sendChat(message);
|
const newChatMessage = party.sendChat({
|
||||||
|
message: `\`${common.i18n.t('chatCastSpellUser', {username: user.profile.name, spell: spell.text(), target: partyMembers.profile.name}, 'en')}\``,
|
||||||
|
info: {
|
||||||
|
type: 'spell_cast_user',
|
||||||
|
user: user.profile.name,
|
||||||
|
class: klass,
|
||||||
|
spell: spellId,
|
||||||
|
target: partyMembers.profile.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
await newChatMessage.save();
|
await newChatMessage.save();
|
||||||
|
} else {
|
||||||
|
const newChatMessage = party.sendChat({
|
||||||
|
message: `\`${common.i18n.t('chatCastSpellParty', {username: user.profile.name, spell: spell.text()}, 'en')}\``,
|
||||||
|
info: {
|
||||||
|
type: 'spell_cast_party',
|
||||||
|
user: user.profile.name,
|
||||||
|
class: klass,
|
||||||
|
spell: spellId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await newChatMessage.save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,22 +9,27 @@ import url from 'url';
|
|||||||
import gcpStackdriverTracer from '../libs/gcpTraceAgent';
|
import gcpStackdriverTracer from '../libs/gcpTraceAgent';
|
||||||
|
|
||||||
const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL');
|
const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL');
|
||||||
|
const USER_FIELDS_ALWAYS_LOADED = ['_id', 'notifications', 'preferences', 'auth', 'flags'];
|
||||||
|
|
||||||
function getUserFields (options, req) {
|
function getUserFields (options, req) {
|
||||||
// A list of user fields that aren't needed for the route and are not loaded from the db.
|
// A list of user fields that aren't needed for the route and are not loaded from the db.
|
||||||
// Must be an array
|
// Must be an array
|
||||||
if (options.userFieldsToExclude) {
|
if (options.userFieldsToExclude) {
|
||||||
return options.userFieldsToExclude.map(field => {
|
return options.userFieldsToExclude
|
||||||
|
.filter(field => {
|
||||||
|
return !USER_FIELDS_ALWAYS_LOADED.find(fieldToInclude => field.startsWith(fieldToInclude));
|
||||||
|
})
|
||||||
|
.map(field => {
|
||||||
return `-${field}`; // -${field} means exclude ${field} in mongodb
|
return `-${field}`; // -${field} means exclude ${field} in mongodb
|
||||||
}).join(' ');
|
})
|
||||||
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.userFieldsToInclude) {
|
if (options.userFieldsToInclude) {
|
||||||
return options.userFieldsToInclude.join(' ');
|
return options.userFieldsToInclude.concat(USER_FIELDS_ALWAYS_LOADED).join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allows GET requests to /user to specify a list of user fields to return instead of the entire doc
|
// Allows GET requests to /user to specify a list of user fields to return instead of the entire doc
|
||||||
// Notifications are always included
|
|
||||||
const urlPath = url.parse(req.url).pathname;
|
const urlPath = url.parse(req.url).pathname;
|
||||||
const userFields = req.query.userFields;
|
const userFields = req.query.userFields;
|
||||||
if (!userFields || urlPath !== '/user') return '';
|
if (!userFields || urlPath !== '/user') return '';
|
||||||
@@ -32,7 +37,7 @@ function getUserFields (options, req) {
|
|||||||
const userFieldOptions = userFields.split(',');
|
const userFieldOptions = userFields.split(',');
|
||||||
if (userFieldOptions.length === 0) return '';
|
if (userFieldOptions.length === 0) return '';
|
||||||
|
|
||||||
return `notifications ${userFieldOptions.join(' ')}`;
|
return userFieldOptions.concat(USER_FIELDS_ALWAYS_LOADED).join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure stackdriver traces are storing the user id
|
// Make sure stackdriver traces are storing the user id
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import {
|
|||||||
} from './subscriptionPlan';
|
} from './subscriptionPlan';
|
||||||
import amazonPayments from '../libs/payments/amazon';
|
import amazonPayments from '../libs/payments/amazon';
|
||||||
import stripePayments from '../libs/payments/stripe';
|
import stripePayments from '../libs/payments/stripe';
|
||||||
import { getGroupChat } from '../libs/chat/group-chat';
|
import { getGroupChat, translateMessage } from '../libs/chat/group-chat';
|
||||||
import { model as UserNotification } from './userNotification';
|
import { model as UserNotification } from './userNotification';
|
||||||
|
|
||||||
const questScrolls = shared.content.quests;
|
const questScrolls = shared.content.quests;
|
||||||
@@ -344,26 +344,38 @@ schema.statics.toJSONCleanChat = async function groupToJSONCleanChat (group, use
|
|||||||
await getGroupChat(group);
|
await getGroupChat(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
let toJSON = group.toJSON();
|
const groupToJson = group.toJSON();
|
||||||
|
const userLang = user.preferences.language;
|
||||||
|
|
||||||
if (!user.contributor.admin) {
|
groupToJson.chat = groupToJson.chat
|
||||||
_.remove(toJSON.chat, chatMsg => {
|
.map(chatMsg => {
|
||||||
chatMsg.flags = {};
|
// Translate system messages
|
||||||
if (chatMsg._meta) chatMsg._meta = undefined;
|
if (!_.isEmpty(chatMsg.info)) {
|
||||||
return user._id !== chatMsg.uuid && chatMsg.flagCount >= 2;
|
chatMsg.text = translateMessage(userLang, chatMsg.info);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to timestamps because Android expects it
|
// Convert to timestamps because Android expects it
|
||||||
toJSON.chat.forEach(chat => {
|
|
||||||
// old chats are saved with a numeric timestamp
|
// old chats are saved with a numeric timestamp
|
||||||
// new chats use `Date` which then has to be converted to the numeric timestamp
|
// new chats use `Date` which then has to be converted to the numeric timestamp
|
||||||
if (chat.timestamp && chat.timestamp.getTime) {
|
if (chatMsg.timestamp && chatMsg.timestamp.getTime) {
|
||||||
chat.timestamp = chat.timestamp.getTime();
|
chatMsg.timestamp = chatMsg.timestamp.getTime();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return toJSON;
|
if (!user.contributor.admin) {
|
||||||
|
// Flags are hidden to non admins
|
||||||
|
chatMsg.flags = {};
|
||||||
|
if (chatMsg._meta) chatMsg._meta = undefined;
|
||||||
|
|
||||||
|
// Messages with >= 2 flags are hidden to non admins and non authors
|
||||||
|
if (user._id !== chatMsg.uuid && chatMsg.flagCount >= 2) return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chatMsg;
|
||||||
|
})
|
||||||
|
// Used to filter for undefined chat messages that should not be shown to non-admins
|
||||||
|
.filter(chatMsg => chatMsg !== undefined);
|
||||||
|
|
||||||
|
return groupToJson;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getInviteError (uuids, emails, usernames) {
|
function getInviteError (uuids, emails, usernames) {
|
||||||
@@ -496,8 +508,9 @@ schema.methods.getMemberCount = async function getMemberCount () {
|
|||||||
return await User.count(query).exec();
|
return await User.count(query).exec();
|
||||||
};
|
};
|
||||||
|
|
||||||
schema.methods.sendChat = function sendChat (message, user, metaData, client) {
|
schema.methods.sendChat = function sendChat (options = {}) {
|
||||||
let newMessage = messageDefaults(message, user, client);
|
const {message, user, metaData, client, info = {}} = options;
|
||||||
|
let newMessage = messageDefaults(message, user, client, info);
|
||||||
let newChatMessage = new Chat();
|
let newChatMessage = new Chat();
|
||||||
newChatMessage = Object.assign(newChatMessage, newMessage);
|
newChatMessage = Object.assign(newChatMessage, newMessage);
|
||||||
newChatMessage.groupId = this._id;
|
newChatMessage.groupId = this._id;
|
||||||
@@ -653,8 +666,15 @@ schema.methods.startQuest = async function startQuest (user) {
|
|||||||
}, _cleanQuestParty(),
|
}, _cleanQuestParty(),
|
||||||
{ multi: true }).exec();
|
{ multi: true }).exec();
|
||||||
|
|
||||||
const newMessage = this.sendChat(`\`Your quest, ${quest.text('en')}, has started.\``, null, {
|
const newMessage = this.sendChat({
|
||||||
|
message: `\`${shared.i18n.t('chatQuestStarted', {questName: quest.text('en')}, 'en')}\``,
|
||||||
|
metaData: {
|
||||||
participatingMembers: this.getParticipatingQuestMembers().join(', '),
|
participatingMembers: this.getParticipatingQuestMembers().join(', '),
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
type: 'quest_start',
|
||||||
|
quest: quest.key,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
await newMessage.save();
|
await newMessage.save();
|
||||||
|
|
||||||
@@ -913,18 +933,42 @@ schema.methods._processBossQuest = async function processBossQuest (options) {
|
|||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
group.quest.progress.hp -= progress.up;
|
group.quest.progress.hp -= progress.up;
|
||||||
// TODO Create a party preferred language option so emits like this can be localized. Suggestion: Always display the English version too. Or, if English is not displayed to the players, at least include it in a new field in the chat object that's visible in the database - essential for admins when troubleshooting quests!
|
if (CRON_SAFE_MODE || CRON_SEMI_SAFE_MODE) {
|
||||||
let playerAttack = `${user.profile.name} attacks ${quest.boss.name('en')} for ${progress.up.toFixed(1)} damage.`;
|
const groupMessage = group.sendChat({
|
||||||
let bossAttack = CRON_SAFE_MODE || CRON_SEMI_SAFE_MODE ? `${quest.boss.name('en')} does not attack, because it respects the fact that there are some bugs\` \`post-maintenance and it doesn't want to hurt anyone unfairly. It will continue its rampage soon!` : `${quest.boss.name('en')} attacks party for ${Math.abs(down).toFixed(1)} damage.`;
|
message: `\`${shared.i18n.t('chatBossDontAttack', {bossName: quest.boss.name('en')}, 'en')}\``,
|
||||||
// TODO Consider putting the safe mode boss attack message in an ENV var
|
info: {
|
||||||
const groupMessage = group.sendChat(`\`${playerAttack}\` \`${bossAttack}\``);
|
type: 'boss_dont_attack',
|
||||||
|
user: user.profile.name,
|
||||||
|
quest: group.quest.key,
|
||||||
|
userDamage: progress.up.toFixed(1),
|
||||||
|
},
|
||||||
|
});
|
||||||
promises.push(groupMessage.save());
|
promises.push(groupMessage.save());
|
||||||
|
} else {
|
||||||
|
const groupMessage = group.sendChat({
|
||||||
|
message: `\`${shared.i18n.t('chatBossDamage', {username: user.profile.name, bossName: quest.boss.name('en'), userDamage: progress.up.toFixed(1), bossDamage: Math.abs(down).toFixed(1)}, user.preferences.language)}\``,
|
||||||
|
info: {
|
||||||
|
type: 'boss_damage',
|
||||||
|
user: user.profile.name,
|
||||||
|
quest: group.quest.key,
|
||||||
|
userDamage: progress.up.toFixed(1),
|
||||||
|
bossDamage: Math.abs(down).toFixed(1),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
promises.push(groupMessage.save());
|
||||||
|
}
|
||||||
|
|
||||||
// If boss has Rage, increment Rage as well
|
// If boss has Rage, increment Rage as well
|
||||||
if (quest.boss.rage) {
|
if (quest.boss.rage) {
|
||||||
group.quest.progress.rage += Math.abs(down);
|
group.quest.progress.rage += Math.abs(down);
|
||||||
if (group.quest.progress.rage >= quest.boss.rage.value) {
|
if (group.quest.progress.rage >= quest.boss.rage.value) {
|
||||||
const rageMessage = group.sendChat(quest.boss.rage.effect('en'));
|
const rageMessage = group.sendChat({
|
||||||
|
message: quest.boss.rage.effect('en'),
|
||||||
|
info: {
|
||||||
|
type: 'boss_rage',
|
||||||
|
quest: quest.key,
|
||||||
|
},
|
||||||
|
});
|
||||||
promises.push(rageMessage.save());
|
promises.push(rageMessage.save());
|
||||||
group.quest.progress.rage = 0;
|
group.quest.progress.rage = 0;
|
||||||
|
|
||||||
@@ -952,7 +996,13 @@ schema.methods._processBossQuest = async function processBossQuest (options) {
|
|||||||
|
|
||||||
// Boss slain, finish quest
|
// Boss slain, finish quest
|
||||||
if (group.quest.progress.hp <= 0) {
|
if (group.quest.progress.hp <= 0) {
|
||||||
const questFinishChat = group.sendChat(`\`You defeated ${quest.boss.name('en')}! Questing party members receive the rewards of victory.\``);
|
const questFinishChat = group.sendChat({
|
||||||
|
message: `\`${shared.i18n.t('chatBossDefeated', {bossName: quest.boss.name('en')}, 'en')}\``,
|
||||||
|
info: {
|
||||||
|
type: 'boss_defeated',
|
||||||
|
quest: quest.key,
|
||||||
|
},
|
||||||
|
});
|
||||||
promises.push(questFinishChat.save());
|
promises.push(questFinishChat.save());
|
||||||
|
|
||||||
// Participants: Grant rewards & achievements, finish quest
|
// Participants: Grant rewards & achievements, finish quest
|
||||||
@@ -1005,7 +1055,15 @@ schema.methods._processCollectionQuest = async function processCollectionQuest (
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
foundText = foundText.join(', ');
|
foundText = foundText.join(', ');
|
||||||
const foundChat = group.sendChat(`\`${user.profile.name} found ${foundText}.\``);
|
const foundChat = group.sendChat({
|
||||||
|
message: `\`${shared.i18n.t('chatFindItems', {username: user.profile.name, items: foundText}, 'en')}\``,
|
||||||
|
info: {
|
||||||
|
type: 'user_found_items',
|
||||||
|
user: user.profile.name,
|
||||||
|
quest: quest.key,
|
||||||
|
items: itemsFound,
|
||||||
|
},
|
||||||
|
});
|
||||||
group.markModified('quest.progress.collect');
|
group.markModified('quest.progress.collect');
|
||||||
|
|
||||||
// Still needs completing
|
// Still needs completing
|
||||||
@@ -1018,7 +1076,12 @@ schema.methods._processCollectionQuest = async function processCollectionQuest (
|
|||||||
}
|
}
|
||||||
|
|
||||||
await group.finishQuest(quest);
|
await group.finishQuest(quest);
|
||||||
const allItemsFoundChat = group.sendChat('`All items found! Party has received their rewards.`');
|
const allItemsFoundChat = group.sendChat({
|
||||||
|
message: `\`${shared.i18n.t('chatItemQuestFinish', 'en')}\``,
|
||||||
|
info: {
|
||||||
|
type: 'all_items_found',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const promises = [group.save(), foundChat.save(), allItemsFoundChat.save()];
|
const promises = [group.save(), foundChat.save(), allItemsFoundChat.save()];
|
||||||
|
|
||||||
@@ -1082,7 +1145,13 @@ schema.statics.tavernBoss = async function tavernBoss (user, progress) {
|
|||||||
const chatPromises = [];
|
const chatPromises = [];
|
||||||
|
|
||||||
if (tavern.quest.progress.hp <= 0) {
|
if (tavern.quest.progress.hp <= 0) {
|
||||||
const completeChat = tavern.sendChat(quest.completionChat('en'));
|
const completeChat = tavern.sendChat({
|
||||||
|
message: quest.completionChat('en'),
|
||||||
|
info: {
|
||||||
|
type: 'tavern_quest_completed',
|
||||||
|
quest: quest.key,
|
||||||
|
},
|
||||||
|
});
|
||||||
chatPromises.push(completeChat.save());
|
chatPromises.push(completeChat.save());
|
||||||
await tavern.finishQuest(quest);
|
await tavern.finishQuest(quest);
|
||||||
_.assign(tavernQuest, {extra: null});
|
_.assign(tavernQuest, {extra: null});
|
||||||
@@ -1111,11 +1180,24 @@ schema.statics.tavernBoss = async function tavernBoss (user, progress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
const tiredChat = tavern.sendChat(`\`${quest.boss.name('en')} tries to unleash ${quest.boss.rage.title('en')} but is too tired.\``);
|
const tiredChat = tavern.sendChat({
|
||||||
|
message: `\`${shared.i18n.t('tavernBossTired', {rageName: quest.boss.rage.title('en'), bossName: quest.boss.name('en')}, 'en')}\``,
|
||||||
|
info: {
|
||||||
|
type: 'tavern_boss_rage_tired',
|
||||||
|
quest: quest.key,
|
||||||
|
},
|
||||||
|
});
|
||||||
chatPromises.push(tiredChat.save());
|
chatPromises.push(tiredChat.save());
|
||||||
tavern.quest.progress.rage = 0; // quest.boss.rage.value;
|
tavern.quest.progress.rage = 0; // quest.boss.rage.value;
|
||||||
} else {
|
} else {
|
||||||
const rageChat = tavern.sendChat(quest.boss.rage[scene]('en'));
|
const rageChat = tavern.sendChat({
|
||||||
|
message: quest.boss.rage[scene]('en'),
|
||||||
|
info: {
|
||||||
|
type: 'tavern_boss_rage',
|
||||||
|
quest: quest.key,
|
||||||
|
scene,
|
||||||
|
},
|
||||||
|
});
|
||||||
chatPromises.push(rageChat.save());
|
chatPromises.push(rageChat.save());
|
||||||
tavern.quest.extra.worldDmg[scene] = true;
|
tavern.quest.extra.worldDmg[scene] = true;
|
||||||
tavern.markModified('quest.extra.worldDmg');
|
tavern.markModified('quest.extra.worldDmg');
|
||||||
@@ -1127,7 +1209,13 @@ schema.statics.tavernBoss = async function tavernBoss (user, progress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (quest.boss.desperation && tavern.quest.progress.hp < quest.boss.desperation.threshold && !tavern.quest.extra.desperate) {
|
if (quest.boss.desperation && tavern.quest.progress.hp < quest.boss.desperation.threshold && !tavern.quest.extra.desperate) {
|
||||||
const progressChat = tavern.sendChat(quest.boss.desperation.text('en'));
|
const progressChat = tavern.sendChat({
|
||||||
|
message: quest.boss.desperation.text('en'),
|
||||||
|
info: {
|
||||||
|
type: 'tavern_boss_desperation',
|
||||||
|
quest: quest.key,
|
||||||
|
},
|
||||||
|
});
|
||||||
chatPromises.push(progressChat.save());
|
chatPromises.push(progressChat.save());
|
||||||
tavern.quest.extra.desperate = true;
|
tavern.quest.extra.desperate = true;
|
||||||
tavern.quest.extra.def = quest.boss.desperation.def;
|
tavern.quest.extra.def = quest.boss.desperation.def;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const defaultSchema = () => ({
|
|||||||
id: String,
|
id: String,
|
||||||
timestamp: Date,
|
timestamp: Date,
|
||||||
text: String,
|
text: String,
|
||||||
|
info: {$type: mongoose.Schema.Types.Mixed},
|
||||||
|
|
||||||
// sender properties
|
// sender properties
|
||||||
user: String, // profile name (unfortunately)
|
user: String, // profile name (unfortunately)
|
||||||
@@ -101,12 +102,13 @@ export function setUserStyles (newMessage, user) {
|
|||||||
newMessage.markModified('userStyles');
|
newMessage.markModified('userStyles');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function messageDefaults (msg, user, client) {
|
export function messageDefaults (msg, user, client, info = {}) {
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
const message = {
|
const message = {
|
||||||
id,
|
id,
|
||||||
_id: id,
|
_id: id,
|
||||||
text: msg.substring(0, 3000),
|
text: msg.substring(0, 3000),
|
||||||
|
info,
|
||||||
timestamp: Number(new Date()),
|
timestamp: Number(new Date()),
|
||||||
likes: {},
|
likes: {},
|
||||||
flags: {},
|
flags: {},
|
||||||
|
|||||||
Reference in New Issue
Block a user