mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
Merge branch 'release' into sabrecat/more-api-sunset
This commit is contained in:
@@ -432,6 +432,7 @@ api.updateEmail = {
|
||||
}
|
||||
|
||||
user.auth.local.email = req.body.newEmail.toLowerCase();
|
||||
user.auth.local.passwordResetCode = undefined;
|
||||
await user.save();
|
||||
|
||||
return res.respond(200, { email: user.auth.local.email });
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
cleanUpTask,
|
||||
createChallengeQuery,
|
||||
} from '../../libs/challenges';
|
||||
import apiError from '../../libs/apiError';
|
||||
import { apiError } from '../../libs/apiError';
|
||||
import common from '../../../common';
|
||||
import {
|
||||
clearFlags,
|
||||
|
||||
@@ -17,7 +17,10 @@ import { removeFromArray } from '../../libs/collectionManipulators';
|
||||
import { getUserInfo } from '../../libs/email';
|
||||
import * as slack from '../../libs/slack';
|
||||
import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory';
|
||||
import apiError from '../../libs/apiError';
|
||||
import bannedWords from '../../libs/bannedWords';
|
||||
import { getMatchesByWordArray } from '../../libs/stringUtils';
|
||||
import bannedSlurs from '../../libs/bannedSlurs';
|
||||
import { apiError } from '../../libs/apiError';
|
||||
import highlightMentions from '../../libs/highlightMentions';
|
||||
import { getAnalyticsServiceByEnvironment } from '../../libs/analyticsService';
|
||||
|
||||
@@ -47,6 +50,11 @@ const ACCOUNT_MIN_CHAT_AGE = Number(nconf.get('ACCOUNT_MIN_CHAT_AGE'));
|
||||
|
||||
const api = {};
|
||||
|
||||
function textContainsBannedSlur (message) {
|
||||
const bannedSlursMatched = getMatchesByWordArray(message, bannedSlurs);
|
||||
return bannedSlursMatched.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /api/v3/groups/:groupId/chat Get chat messages from a group
|
||||
* @apiName GetChat
|
||||
@@ -85,6 +93,10 @@ api.getChat = {
|
||||
},
|
||||
};
|
||||
|
||||
function getBannedWordsFromText (message) {
|
||||
return getMatchesByWordArray(message, bannedWords);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /api/v3/groups/:groupId/chat Post chat message to a group
|
||||
* @apiName PostChat
|
||||
@@ -128,6 +140,39 @@ api.postChat = {
|
||||
throw new BadRequest(res.t('featureRetired'));
|
||||
}
|
||||
|
||||
// Check message for banned slurs
|
||||
if (group && group.privacy !== 'private' && textContainsBannedSlur(req.body.message)) {
|
||||
const { message } = req.body;
|
||||
user.flags.chatRevoked = true;
|
||||
await user.save();
|
||||
|
||||
// Email the mods
|
||||
const authorEmail = getUserInfo(user, ['email']).email;
|
||||
|
||||
// Slack the mods
|
||||
slack.sendSlurNotification({
|
||||
authorEmail,
|
||||
author: user,
|
||||
group,
|
||||
message,
|
||||
});
|
||||
|
||||
throw new BadRequest(res.t('bannedSlurUsed'));
|
||||
}
|
||||
|
||||
if (group.privacy === 'public' && user.flags.chatRevoked) {
|
||||
throw new NotAuthorized(res.t('chatPrivilegesRevoked'));
|
||||
}
|
||||
|
||||
// prevent banned words being posted, except in private guilds/parties
|
||||
// and in certain public guilds with specific topics
|
||||
if (group.privacy === 'public' && !group.bannedWordsAllowed) {
|
||||
const matchedBadWords = getBannedWordsFromText(req.body.message);
|
||||
if (matchedBadWords.length > 0) {
|
||||
throw new BadRequest(res.t('bannedWordUsed', { swearWordsUsed: matchedBadWords.join(', ') }));
|
||||
}
|
||||
}
|
||||
|
||||
const chatRes = await Group.toJSONCleanChat(group, user);
|
||||
const lastClientMsg = req.query.previousMsg;
|
||||
const chatUpdated = !!(
|
||||
@@ -172,7 +217,7 @@ api.postChat = {
|
||||
});
|
||||
}
|
||||
|
||||
const newChatMessage = group.sendChat({
|
||||
const newChatMessage = await group.sendChat({
|
||||
message,
|
||||
user,
|
||||
flagCount,
|
||||
@@ -256,7 +301,7 @@ api.likeChat = {
|
||||
throw new BadRequest(res.t('featureRetired'));
|
||||
}
|
||||
|
||||
const message = await Chat.findOne({ _id: req.params.chatId }).exec();
|
||||
const message = await Chat.findOne({ _id: req.params.chatId, groupId: group._id }).exec();
|
||||
if (!message) throw new NotFound(res.t('messageGroupChatNotFound'));
|
||||
if (!message.likes) message.likes = {};
|
||||
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import nconf from 'nconf';
|
||||
import { langCodes } from '../../libs/i18n';
|
||||
import { CONTENT_CACHE_PATH, getLocalizedContentResponse } from '../../libs/content';
|
||||
import { serveContent } from '../../libs/content';
|
||||
|
||||
const IS_PROD = nconf.get('IS_PROD');
|
||||
|
||||
const api = {};
|
||||
|
||||
const MOBILE_FILTER = ['achievements', 'questSeriesAchievements', 'animalColorAchievements', 'animalSetAchievements',
|
||||
'stableAchievements', 'mystery', 'bundles', 'loginIncentives', 'pets', 'premiumPets', 'specialPets', 'questPets',
|
||||
'wackyPets', 'mounts', 'premiumMounts,specialMounts,questMounts', 'events', 'dropEggs', 'questEggs', 'dropHatchingPotions',
|
||||
'premiumHatchingPotions', 'wackyHatchingPotions', 'backgroundsFlat', 'questsByLevel', 'gear.tree', 'tasksByCategory',
|
||||
'userDefaults', 'timeTravelStable', 'gearTypes', 'cardTypes'];
|
||||
|
||||
const ANDROID_FILTER = [...MOBILE_FILTER, 'appearances.background'].join(',');
|
||||
const IOS_FILTER = [...MOBILE_FILTER, 'backgrounds'].join(',');
|
||||
|
||||
/**
|
||||
* @api {get} /api/v3/content Get all available content objects
|
||||
* @apiDescription Does not require authentication.
|
||||
@@ -65,16 +74,17 @@ api.getContent = {
|
||||
language = proposedLang;
|
||||
}
|
||||
|
||||
if (IS_PROD) {
|
||||
res.sendFile(`${CONTENT_CACHE_PATH}${language}.json`);
|
||||
} else {
|
||||
res.set({
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
const jsonResString = getLocalizedContentResponse(language);
|
||||
res.status(200).send(jsonResString);
|
||||
let filter = req.query.filter || '';
|
||||
// apply defaults for mobile clients
|
||||
if (filter === '') {
|
||||
if (req.headers['x-client'] === 'habitica-android') {
|
||||
filter = ANDROID_FILTER;
|
||||
} else if (req.headers['x-client'] === 'habitica-ios') {
|
||||
filter = IOS_FILTER;
|
||||
}
|
||||
}
|
||||
|
||||
serveContent(res, language, filter, IS_PROD);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '../../middlewares/auth';
|
||||
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
||||
import * as couponsLib from '../../libs/coupons';
|
||||
import apiError from '../../libs/apiError';
|
||||
import { apiError } from '../../libs/apiError';
|
||||
import { model as Coupon } from '../../models/coupon';
|
||||
|
||||
const api = {};
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import _ from 'lodash';
|
||||
import sinon from 'sinon';
|
||||
import moment from 'moment';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import ensureDevelpmentMode from '../../middlewares/ensureDevelpmentMode';
|
||||
import ensureDevelopmentMode from '../../middlewares/ensureDevelopmentMode';
|
||||
import ensureTimeTravelMode from '../../middlewares/ensureTimeTravelMode';
|
||||
import { BadRequest } from '../../libs/errors';
|
||||
import common from '../../../common';
|
||||
|
||||
@@ -30,7 +33,7 @@ const api = {};
|
||||
api.addTenGems = {
|
||||
method: 'POST',
|
||||
url: '/debug/add-ten-gems',
|
||||
middlewares: [ensureDevelpmentMode, authWithHeaders()],
|
||||
middlewares: [ensureDevelopmentMode, authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
@@ -53,7 +56,7 @@ api.addTenGems = {
|
||||
api.addHourglass = {
|
||||
method: 'POST',
|
||||
url: '/debug/add-hourglass',
|
||||
middlewares: [ensureDevelpmentMode, authWithHeaders()],
|
||||
middlewares: [ensureDevelopmentMode, authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
@@ -76,7 +79,7 @@ api.addHourglass = {
|
||||
api.setCron = {
|
||||
method: 'POST',
|
||||
url: '/debug/set-cron',
|
||||
middlewares: [ensureDevelpmentMode, authWithHeaders()],
|
||||
middlewares: [ensureDevelopmentMode, authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
const cron = req.body.lastCron;
|
||||
@@ -100,7 +103,7 @@ api.setCron = {
|
||||
api.makeAdmin = {
|
||||
method: 'POST',
|
||||
url: '/debug/make-admin',
|
||||
middlewares: [ensureDevelpmentMode, authWithHeaders()],
|
||||
middlewares: [ensureDevelopmentMode, authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
@@ -131,7 +134,7 @@ api.makeAdmin = {
|
||||
api.modifyInventory = {
|
||||
method: 'POST',
|
||||
url: '/debug/modify-inventory',
|
||||
middlewares: [ensureDevelpmentMode, authWithHeaders()],
|
||||
middlewares: [ensureDevelopmentMode, authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
const { gear } = req.body;
|
||||
@@ -173,7 +176,7 @@ api.modifyInventory = {
|
||||
api.questProgress = {
|
||||
method: 'POST',
|
||||
url: '/debug/quest-progress',
|
||||
middlewares: [ensureDevelpmentMode, authWithHeaders()],
|
||||
middlewares: [ensureDevelopmentMode, authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
const key = _.get(user, 'party.quest.key');
|
||||
@@ -201,4 +204,63 @@ api.questProgress = {
|
||||
},
|
||||
};
|
||||
|
||||
let clock;
|
||||
|
||||
function fakeClock () {
|
||||
if (clock) clock.restore();
|
||||
const time = new Date();
|
||||
clock = sinon.useFakeTimers({
|
||||
now: time,
|
||||
shouldAdvanceTime: true,
|
||||
});
|
||||
}
|
||||
|
||||
api.timeTravelTime = {
|
||||
method: 'GET',
|
||||
url: '/debug/time-travel-time',
|
||||
middlewares: [ensureTimeTravelMode, authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
if (clock === undefined) {
|
||||
fakeClock();
|
||||
}
|
||||
|
||||
res.respond(200, {
|
||||
time: new Date(),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
api.timeTravelAdjust = {
|
||||
method: 'POST',
|
||||
url: '/debug/jump-time',
|
||||
middlewares: [ensureTimeTravelMode, authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
if (!user.permissions.fullAccess) {
|
||||
throw new BadRequest('You do not have permission to time travel.');
|
||||
}
|
||||
|
||||
const { offsetDays, reset, disable } = req.body;
|
||||
if (reset) {
|
||||
fakeClock();
|
||||
} else if (disable) {
|
||||
clock.restore();
|
||||
clock = undefined;
|
||||
} else if (clock !== undefined) {
|
||||
try {
|
||||
clock.setSystemTime(moment().add(offsetDays, 'days').toDate());
|
||||
} catch (e) {
|
||||
throw new BadRequest('Error adjusting time');
|
||||
}
|
||||
} else {
|
||||
throw new BadRequest('Invalid command');
|
||||
}
|
||||
|
||||
res.respond(200, {
|
||||
time: new Date(),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -26,7 +26,7 @@ import common from '../../../common';
|
||||
import payments from '../../libs/payments/payments';
|
||||
import stripePayments from '../../libs/payments/stripe';
|
||||
import amzLib from '../../libs/payments/amazon';
|
||||
import apiError from '../../libs/apiError';
|
||||
import { apiError } from '../../libs/apiError';
|
||||
import { model as UserNotification } from '../../models/userNotification';
|
||||
|
||||
const { MAX_SUMMARY_SIZE_FOR_GUILDS } = common.constants;
|
||||
@@ -136,6 +136,7 @@ api.createGroup = {
|
||||
if (user.party._id) throw new NotAuthorized(res.t('messageGroupAlreadyInParty'));
|
||||
|
||||
user.party._id = group._id;
|
||||
user.party.seeking = undefined;
|
||||
}
|
||||
|
||||
let savedGroup;
|
||||
@@ -590,7 +591,7 @@ api.joinGroup = {
|
||||
// Clear all invitations of new user and reset looking for party state
|
||||
user.invitations.parties = [];
|
||||
user.invitations.party = {};
|
||||
user.party.seeking = null;
|
||||
user.party.seeking = undefined;
|
||||
|
||||
// invite new user to pending quest
|
||||
if (group.quest.key && !group.quest.active) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import common from '../../../common';
|
||||
import {
|
||||
NotFound,
|
||||
} from '../../libs/errors';
|
||||
import apiError from '../../libs/apiError';
|
||||
import { apiError } from '../../libs/apiError';
|
||||
import {
|
||||
validateItemPath,
|
||||
castItemVal,
|
||||
@@ -146,7 +146,7 @@ api.getHeroes = {
|
||||
// Note, while the following routes are called getHero / updateHero
|
||||
// they can be used by admins to get/update any user
|
||||
|
||||
const heroAdminFields = 'auth balance contributor flags items lastCron party preferences profile purchased secret permissions';
|
||||
const heroAdminFields = 'auth balance contributor flags items lastCron party preferences profile purchased secret permissions achievements';
|
||||
const heroAdminFieldsToFetch = heroAdminFields; // these variables will make more sense when...
|
||||
const heroAdminFieldsToShow = heroAdminFields; // ... apiTokenObscured is added
|
||||
|
||||
@@ -285,7 +285,7 @@ api.updateHero = {
|
||||
if (plan.dateCurrentTypeCreated) {
|
||||
hero.purchased.plan.dateCurrentTypeCreated = plan.dateCurrentTypeCreated;
|
||||
}
|
||||
if (plan.dateTerminated) {
|
||||
if (plan.dateTerminated !== hero.purchased.plan.dateTerminated) {
|
||||
hero.purchased.plan.dateTerminated = plan.dateTerminated;
|
||||
}
|
||||
if (plan.perkMonthCount) {
|
||||
@@ -342,12 +342,48 @@ api.updateHero = {
|
||||
hero.purchased.ads = updateData.purchased.ads;
|
||||
}
|
||||
|
||||
if (updateData.purchasedPath && updateData.purchasedVal !== undefined
|
||||
&& validateItemPath(updateData.purchasedPath)) {
|
||||
const parts = updateData.purchasedPath.split('.');
|
||||
const key = _.last(parts);
|
||||
const type = parts[parts.length - 2];
|
||||
// using _.set causes weird issues
|
||||
if (updateData.purchasedVal === true) {
|
||||
if (updateData.purchasedPath.indexOf('hair.') === 10) {
|
||||
if (hero.purchased.hair[type] === undefined) hero.purchased.hair[type] = {};
|
||||
hero.purchased.hair[type][key] = true;
|
||||
} else {
|
||||
if (hero.purchased[type] === undefined) hero.purchased[type] = {};
|
||||
hero.purchased[type][key] = true;
|
||||
}
|
||||
} else if (updateData.purchasedPath.indexOf('hair.') === 10) {
|
||||
delete hero.purchased.hair[type][key];
|
||||
} else {
|
||||
delete hero.purchased[type][key];
|
||||
}
|
||||
hero.markModified('purchased');
|
||||
}
|
||||
|
||||
if (updateData.achievementPath && updateData.achievementVal !== undefined) {
|
||||
const parts = updateData.achievementPath.split('.');
|
||||
const key = _.last(parts);
|
||||
const type = parts[parts.length - 2];
|
||||
// using _.set causes weird issues
|
||||
if (type !== 'achievements') {
|
||||
if (hero.achievements[type] === undefined) hero.achievements[type] = {};
|
||||
hero.achievements[type][key] = updateData.achievementVal;
|
||||
} else {
|
||||
hero.achievements[key] = updateData.achievementVal;
|
||||
}
|
||||
hero.markModified('achievements');
|
||||
}
|
||||
|
||||
// give them the Dragon Hydra pet if they're above level 6
|
||||
if (hero.contributor.level >= 6) {
|
||||
hero.items.pets['Dragon-Hydra'] = 5;
|
||||
hero.markModified('items.pets');
|
||||
}
|
||||
if (updateData.itemPath && updateData.itemVal && validateItemPath(updateData.itemPath)) {
|
||||
if (updateData.itemPath && (updateData.itemVal || updateData.itemVal === '') && validateItemPath(updateData.itemPath)) {
|
||||
// Sanitization at 5c30944 (deemed unnecessary)
|
||||
_.set(hero, updateData.itemPath, castItemVal(updateData.itemPath, updateData.itemVal));
|
||||
hero.markModified('items');
|
||||
@@ -367,6 +403,7 @@ api.updateHero = {
|
||||
if (updateData.flags && _.isBoolean(updateData.flags.chatShadowMuted)) {
|
||||
hero.flags.chatShadowMuted = updateData.flags.chatShadowMuted;
|
||||
}
|
||||
if (updateData.profile) _.assign(hero.profile, updateData.profile);
|
||||
|
||||
if (updateData.secret) {
|
||||
if (typeof updateData.secret.text !== 'undefined') {
|
||||
|
||||
@@ -756,7 +756,7 @@ api.transferGems = {
|
||||
]);
|
||||
}
|
||||
if (receiver.preferences.pushNotifications.giftedGems !== false) {
|
||||
sendPushNotification(
|
||||
await sendPushNotification(
|
||||
receiver,
|
||||
{
|
||||
title: res.t('giftedGems', receiverLang),
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '../../libs/email';
|
||||
import common from '../../../common';
|
||||
import { sendNotification as sendPushNotification } from '../../libs/pushNotifications';
|
||||
import apiError from '../../libs/apiError';
|
||||
import { apiError } from '../../libs/apiError';
|
||||
import { questActivityWebhook } from '../../libs/webhook';
|
||||
|
||||
const analytics = getAnalyticsServiceByEnvironment();
|
||||
@@ -120,10 +120,10 @@ api.inviteToQuest = {
|
||||
|
||||
// send out invites
|
||||
const inviterVars = getUserInfo(user, ['name', 'email']);
|
||||
const membersToEmail = members.filter(member => {
|
||||
const membersToEmail = members.filter(async member => {
|
||||
// send push notifications while filtering members before sending emails
|
||||
if (member.preferences.pushNotifications.invitedQuest !== false) {
|
||||
sendPushNotification(
|
||||
await sendPushNotification(
|
||||
member,
|
||||
{
|
||||
title: quest.text(member.preferences.language),
|
||||
@@ -394,7 +394,7 @@ api.cancelQuest = {
|
||||
if (group.quest.active) throw new NotAuthorized(res.t('cantCancelActiveQuest'));
|
||||
|
||||
const questName = questScrolls[group.quest.key].text('en');
|
||||
const newChatMessage = group.sendChat({
|
||||
const newChatMessage = await group.sendChat({
|
||||
message: `\`${user.profile.name} cancelled the party quest ${questName}.\``,
|
||||
info: {
|
||||
type: 'quest_cancel',
|
||||
@@ -456,7 +456,7 @@ api.abortQuest = {
|
||||
if (user._id !== group.leader && user._id !== group.quest.leader) throw new NotAuthorized(res.t('onlyLeaderAbortQuest'));
|
||||
|
||||
const questName = questScrolls[group.quest.key].text('en');
|
||||
const newChatMessage = group.sendChat({
|
||||
const newChatMessage = await group.sendChat({
|
||||
message: `\`${common.i18n.t('chatQuestAborted', { username: user.profile.name, questName }, 'en')}\``,
|
||||
info: {
|
||||
type: 'quest_abort',
|
||||
|
||||
@@ -144,4 +144,26 @@ api.getBackgroundShopItems = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @apiIgnore
|
||||
* @api {get} /api/v3/shops/customizations get the available items for the customizations shop
|
||||
* @apiName GetCustomizationShopItems
|
||||
* @apiGroup Shops
|
||||
*
|
||||
* @apiSuccess {Object} data List of available avatar customizations
|
||||
* @apiSuccess {string} message Success message
|
||||
*/
|
||||
api.getCustomizationsShop = {
|
||||
method: 'GET',
|
||||
url: '/shops/customizations',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
const resObject = shops.getCustomizationsShop(user, req.language);
|
||||
|
||||
res.respond(200, resObject);
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
requiredGroupFields,
|
||||
} from '../../libs/tasks/utils';
|
||||
import common from '../../../common';
|
||||
import apiError from '../../libs/apiError';
|
||||
import { apiError } from '../../libs/apiError';
|
||||
|
||||
/**
|
||||
* @apiDefine TaskNotFound
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import {
|
||||
moveTask,
|
||||
} from '../../../libs/tasks/utils';
|
||||
import apiError from '../../../libs/apiError';
|
||||
import { apiError } from '../../../libs/apiError';
|
||||
|
||||
const requiredGroupFields = '_id leader tasksOrder name';
|
||||
// @TODO: abstract to task lib
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
getCurrentEvent,
|
||||
getCurrentEventList,
|
||||
getWorldBoss,
|
||||
} from '../../libs/worldState';
|
||||
@@ -27,13 +26,19 @@ api.getWorldState = {
|
||||
method: 'GET',
|
||||
url: '/world-state',
|
||||
async handler (req, res) {
|
||||
const worldState = {};
|
||||
const worldState = { npcImageSuffix: '', currentEvent: null };
|
||||
|
||||
worldState.worldBoss = await getWorldBoss();
|
||||
worldState.currentEvent = getCurrentEvent();
|
||||
worldState.npcImageSuffix = worldState.currentEvent ? worldState.currentEvent.npcImageSuffix : '';
|
||||
|
||||
worldState.currentEventList = getCurrentEventList();
|
||||
if (worldState.currentEventList.length > 0) {
|
||||
[worldState.currentEvent] = worldState.currentEventList;
|
||||
}
|
||||
|
||||
worldState.currentEventList.forEach(event => {
|
||||
if (event.npcImageSuffix) {
|
||||
worldState.npcImageSuffix = event.npcImageSuffix;
|
||||
}
|
||||
});
|
||||
|
||||
res.respond(200, worldState);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user