Compare commits

..

66 Commits

Author SHA1 Message Date
Sabe Jones 1b5bd8e1ab 4.138.4 2020-04-01 06:31:49 -05:00
Sabe Jones e39eafd3f0 feat(event): April Foolin 2020-04-01 06:31:39 -05:00
Sabe Jones 92cf506bad 4.138.3 2020-03-30 16:12:20 -05:00
Sabe Jones 5f97cb31b8 chore(sprites): compile 2020-03-30 16:12:11 -05:00
Sabe Jones 6d26fbc5f2 feat(content): April subscriber items 2020-03-30 16:12:03 -05:00
Sabe Jones bb9912de89 fix(analytics): problems 2020-03-30 15:38:41 -05:00
Sabe Jones 947e8a1836 4.138.2 2020-03-26 15:15:42 -05:00
Sabe Jones 7bdc974704 Merge branch 'develop' into release 2020-03-26 15:15:38 -05:00
Sabe Jones fe8780d49c chore(news): Bailey 2020-03-26 15:15:25 -05:00
Matteo Pagliazzi 2fc4d0f00c fix(logger): only two arguments to logger when logging an unhandled promise rejection 2020-03-26 17:10:19 +01:00
Matteo Pagliazzi 4300c7b1bf Merge branch 'develop' of github.com:HabitRPG/habitica into develop 2020-03-24 20:31:27 +01:00
Matteo Pagliazzi 2cd0ed5973 fix(logger): improve logging and make sure no data is lost 2020-03-24 20:29:31 +01:00
Sabe Jones 6e8bdf4cdf Merge branch 'release' into develop 2020-03-24 13:14:15 -05:00
Sabe Jones 0bac1102cc 4.138.1 2020-03-24 13:13:55 -05:00
Sabe Jones 3e96e54ad8 feat(event): eggy eggy 2020-03-24 13:13:50 -05:00
Matteo Pagliazzi 3458d89c1d fix(webhook tests): do not rely on toLocaleString when checking for two dates to be close 2020-03-24 12:28:15 +01:00
Denys Dorokhov 25e72ad907 Reward with negative cost can no longer be created, fixes #11855 (#11870)
* Minor refactoring in scoreTask.js

* Reward value validation added (should be >= 0)
2020-03-24 12:10:10 +01:00
Bence László 5cf6a67a36 (website/client/src/components/settings/site.vue): Fix refresh the de-register social media buttons. (#11992) 2020-03-23 17:49:00 +01:00
Matteo Pagliazzi 9dcce382a3 fix(webhook tests): more reliable date test 2020-03-23 17:17:27 +01:00
chan_gami f6484c872a Fixes Japanese conversion bugs on Safari and Chrome (#11917)
decided to fix using flags since KeyboardEvent.isComposing behaves differently depending on the browser
2020-03-23 16:35:17 +01:00
Alys 249ba77c01 edit string to say Pet Food, not food (#11994)
Also capitalise terms that are usually capitalised these days.
2020-03-23 16:32:37 +01:00
dependabot-preview[bot] 7ff590cd88 build(deps): bump mongoose from 5.9.3 to 5.9.5 (#12001)
Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.9.3 to 5.9.5.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/History.md)
- [Commits](https://github.com/Automattic/mongoose/compare/5.9.3...5.9.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-23 10:58:24 +01:00
dependabot-preview[bot] f297fef89e build(deps): bump @babel/preset-env from 7.8.7 to 7.9.0 (#12007)
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.8.7 to 7.9.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.7...v7.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-23 10:49:47 +01:00
dependabot-preview[bot] b037cb0722 build(deps): bump @babel/register from 7.8.6 to 7.9.0 (#12009)
Bumps [@babel/register](https://github.com/babel/babel) from 7.8.6 to 7.9.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.6...v7.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-23 10:36:18 +01:00
dependabot-preview[bot] b8c58a7e4f build(deps): bump morgan from 1.9.1 to 1.10.0 (#12000)
Bumps [morgan](https://github.com/expressjs/morgan) from 1.9.1 to 1.10.0.
- [Release notes](https://github.com/expressjs/morgan/releases)
- [Changelog](https://github.com/expressjs/morgan/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/morgan/compare/1.9.1...1.10.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-23 10:35:30 +01:00
dependabot-preview[bot] f973bf1038 build(deps): [security] bump acorn in /website/client (#11996)
Bumps [acorn](https://github.com/acornjs/acorn) from 6.3.0 to 6.4.1. **This update includes a security fix.**
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.3.0...6.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-23 10:35:10 +01:00
dependabot-preview[bot] aaea985cf2 build(deps): bump bootstrap-vue from 2.7.0 to 2.8.0 in /website/client (#11997)
Bumps [bootstrap-vue](https://github.com/bootstrap-vue/bootstrap-vue) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/bootstrap-vue/bootstrap-vue/releases)
- [Changelog](https://github.com/bootstrap-vue/bootstrap-vue/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/bootstrap-vue/bootstrap-vue/compare/v2.7.0...v2.8.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-23 10:34:57 +01:00
dependabot-preview[bot] 1d0e08419f build(deps): bump @babel/core from 7.8.7 to 7.9.0 (#12003)
Bumps [@babel/core](https://github.com/babel/babel) from 7.8.7 to 7.9.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.8.7...v7.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-23 10:34:20 +01:00
dependabot-preview[bot] fd6244eb15 build(deps): bump aws-sdk from 2.639.0 to 2.643.0 (#12004)
Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.639.0 to 2.643.0.
- [Release notes](https://github.com/aws/aws-sdk-js/releases)
- [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js/compare/v2.639.0...v2.643.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-23 10:34:03 +01:00
Matteo Pagliazzi f8aa756d52 Disable Failing Webhooks (#11966)
* todo comment

* add failures field to webhooks and sanitize

* implement logic

* use update instead of save

* specify timeout and maximum number of retries

* add tests
2020-03-20 23:26:21 +01:00
Matteo Pagliazzi ae7df804cb fix(promise): make sure every promise is handled 2020-03-20 20:07:13 +01:00
Sabe Jones de37eb1bb2 Merge branch 'release' into develop 2020-03-19 08:12:00 -05:00
Sabe Jones cf03261bbf 4.138.0 2020-03-19 08:11:37 -05:00
Sabe Jones 3ec95ad821 fix(event): feature something from 2020 2020-03-19 08:11:30 -05:00
Sabe Jones 57d11d5b20 Merge branch 'develop' into release 2020-03-19 08:10:56 -05:00
Matteo Pagliazzi 039e7d40b8 fix(tests): do not rely on emails order when user joins group plan 2020-03-18 23:31:01 +01:00
Matteo Pagliazzi 4389a9b478 Merge branch 'release' into develop 2020-03-18 20:39:14 +01:00
Sabe Jones fd2c4e3265 fix(sprites): CUBBB 2020-03-18 13:35:53 -05:00
Matteo Pagliazzi dd91bada8f Merge branch 'release' into develop 2020-03-18 19:30:11 +01:00
Matteo Pagliazzi d724933640 update mongoose options 2020-03-18 19:20:09 +01:00
Sabe Jones e4b74bc347 fix(content): feature shinies 2020-03-18 11:15:49 -05:00
Sabe Jones c609db09c1 fix(content): feature shinies 2020-03-18 11:15:10 -05:00
Sabe Jones 55feebdf9e Merge branch 'release' into develop 2020-03-17 15:36:35 -05:00
Sabe Jones d8a99647e7 chore(sprites): compile 2020-03-17 15:35:49 -05:00
Sabe Jones 353b4aed05 feat(content): Magic Hatching Potions 2020-03-17 15:35:38 -05:00
Sabe Jones 411f82202b feat(content): Spring Fling 2020 2020-03-17 14:49:22 -05:00
Sabe Jones 5a5a6e4c5d feat(content): Spring Fling 2020 2020-03-17 14:48:49 -05:00
Sabe Jones 914eee015e Merge branch 'release' into develop 2020-03-17 09:12:29 -05:00
tsukimi2 a301f817e9 Fix for party members missing from header after viewing private messages / inbox / PMs (#11912)
* Fix bug in missing party members in app header

* Modified by running lint

* Change code for handling missing party members in app header from within computed hiddenHeader property to watcher function on hiddenHeader

Co-authored-by: osiris <eynsan@yahoo.co.uk>
2020-03-16 22:17:52 +01:00
tsukimi2 519af8f1b6 Fix for search guilds result being inconsistent between "My Guilds" and "Discover Guilds" (#11903)
* Fix bug to allow guild summary and description to match against search term in MyGuilds component

* Add unit test to groupUtilities to test filterGroup function

* Changes made after running npm:run:lint

* Fix bug when filter guild function does not match against guild size correctly when the guild has member count = 100 or 1000

According to habitica wiki Guilds Guide, gold-tier guilds are guilds with 1000 or more members.  However, under the current code of filter guild function, it matches guilds as gold-tier as strictly more than 1000 members, excluding 1000 members.  Similar silver-tier guilds should have 100 to 999 members, but the current code it matches guilds as silver-tier for members between 101 and 999 members.

* Added unit tests to test the newly added code in the groupsUtilities mixin for the current issue

* Add unit testing to test search guild name, summary, and description in myGuilds component

* Add suggestions from lint

* Added searching by guild summary and white space handling in search terms.

For discover guilds component, added the following:
1) handling of searching by guild summary
2) preventing white space in search terms to display all guilds
3) added test cases for testing the search functionality in discove guilds to ensure consistent behaviour between the searching in MyGuilds and public guilds.

* Remove console statements from test file

* Implement suggestions from lint.

Co-authored-by: osiris <eynsan@yahoo.co.uk>
2020-03-16 20:03:48 +01:00
dependabot-preview[bot] a71abea032 build(deps): bump @storybook/addon-knobs in /website/client (#11977)
Bumps [@storybook/addon-knobs](https://github.com/storybookjs/storybook/tree/HEAD/addons/knobs) from 5.3.14 to 5.3.17.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v5.3.17/addons/knobs)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:26:27 +01:00
dependabot-preview[bot] 3c623b08c4 build(deps): [security] bump acorn from 6.3.0 to 6.4.1 (#11970)
Bumps [acorn](https://github.com/acornjs/acorn) from 6.3.0 to 6.4.1. **This update includes a security fix.**
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.3.0...6.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:24:30 +01:00
dependabot-preview[bot] ddfa3f8a91 build(deps): bump @storybook/vue in /website/client (#11984)
Bumps [@storybook/vue](https://github.com/storybookjs/storybook/tree/HEAD/app/vue) from 5.3.14 to 5.3.17.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v5.3.17/app/vue)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:22:52 +01:00
dependabot-preview[bot] e2d1de0cf0 build(deps): bump @storybook/addon-links in /website/client (#11975)
Bumps [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/addons/links) from 5.3.14 to 5.3.17.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v5.3.17/addons/links)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:22:36 +01:00
dependabot-preview[bot] 9281de1801 build(deps): bump regenerator-runtime from 0.13.3 to 0.13.5 (#11976)
Bumps [regenerator-runtime](https://github.com/facebook/regenerator) from 0.13.3 to 0.13.5.
- [Release notes](https://github.com/facebook/regenerator/releases)
- [Commits](https://github.com/facebook/regenerator/compare/regenerator-runtime@0.13.3...regenerator-runtime@0.13.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:20:11 +01:00
dependabot-preview[bot] 4960171565 build(deps): bump sass from 1.26.2 to 1.26.3 in /website/client (#11973)
Bumps [sass](https://github.com/sass/dart-sass) from 1.26.2 to 1.26.3.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.26.2...1.26.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:19:54 +01:00
dependabot-preview[bot] d063a57faa build(deps): bump aws-sdk from 2.635.0 to 2.639.0 (#11978)
Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.635.0 to 2.639.0.
- [Release notes](https://github.com/aws/aws-sdk-js/releases)
- [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js/compare/v2.635.0...v2.639.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:19:41 +01:00
dependabot-preview[bot] 0960eaf571 build(deps): bump amplitude-js from 5.9.0 to 5.10.0 in /website/client (#11979)
Bumps [amplitude-js](https://github.com/amplitude/amplitude-javascript) from 5.9.0 to 5.10.0.
- [Release notes](https://github.com/amplitude/amplitude-javascript/releases)
- [Changelog](https://github.com/amplitude/Amplitude-JavaScript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/amplitude/amplitude-javascript/compare/v5.9.0...v5.10.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:19:33 +01:00
dependabot-preview[bot] fd0ec41c53 build(deps): bump vue2-perfect-scrollbar in /website/client (#11983)
Bumps [vue2-perfect-scrollbar](https://github.com/mercs600/vue2-perfect-scrollbar) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/mercs600/vue2-perfect-scrollbar/releases)
- [Commits](https://github.com/mercs600/vue2-perfect-scrollbar/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:18:53 +01:00
dependabot-preview[bot] 1420e1c8d7 build(deps): bump bootstrap-vue from 2.6.1 to 2.7.0 in /website/client (#11985)
Bumps [bootstrap-vue](https://github.com/bootstrap-vue/bootstrap-vue) from 2.6.1 to 2.7.0.
- [Release notes](https://github.com/bootstrap-vue/bootstrap-vue/releases)
- [Changelog](https://github.com/bootstrap-vue/bootstrap-vue/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/bootstrap-vue/bootstrap-vue/compare/v2.6.1...v2.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:18:40 +01:00
Matteo Pagliazzi c7c854664f build(deps): bump @storybook/addon-notes in /website/client (#11986)
Bumps [@storybook/addon-notes](https://github.com/storybookjs/storybook/tree/HEAD/addons/notes) from 5.3.14 to 5.3.17.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/v5.3.17/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v5.3.17/addons/notes)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:18:31 +01:00
dependabot-preview[bot] 68e5679340 build(deps): bump @storybook/addon-actions in /website/client (#11987)
Bumps [@storybook/addon-actions](https://github.com/storybookjs/storybook/tree/HEAD/addons/actions) from 5.3.14 to 5.3.17.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v5.3.17/addons/actions)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-16 15:18:25 +01:00
dependabot-preview[bot] 32a9dda2c6 build(deps): bump @storybook/addon-notes in /website/client
Bumps [@storybook/addon-notes](https://github.com/storybookjs/storybook/tree/HEAD/addons/notes) from 5.3.14 to 5.3.17.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/v5.3.17/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v5.3.17/addons/notes)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-16 08:29:26 +00:00
Sabe Jones 8b19c0ad69 Merge branch 'release' into develop 2020-03-14 06:50:23 -05:00
Alys 33e8b64df6 remove excess 'the' from two locales strings 2020-03-14 20:47:50 +10:00
Sabe Jones 427251ed1d fix(test): food expectation, sigh 2020-03-13 11:10:08 -05:00
320 changed files with 15603 additions and 13749 deletions
+2
View File
@@ -1,4 +1,6 @@
node_modules/**
content_cache
content_cache/**
website/client/**
test/**
.git/**
+2 -1
View File
@@ -74,5 +74,6 @@
"TRANSIFEX_SLACK_CHANNEL": "transifex",
"WEB_CONCURRENCY": 1,
"SKIP_SSL_CHECK_KEY": "key",
"ENABLE_STACKDRIVER_TRACING": "false"
"ENABLE_STACKDRIVER_TRACING": "false",
"BLOCKED_IPS": ""
}
+5 -4
View File
@@ -1,19 +1,20 @@
import gulp from 'gulp';
import babel from 'gulp-babel';
gulp.task('build:src', () => gulp.src('website/server/**/*.js')
gulp.task('build:babel:server', () => gulp.src('website/server/**/*.js')
.pipe(babel())
.pipe(gulp.dest('website/transpiled-babel/')));
gulp.task('build:common', () => gulp.src('website/common/script/**/*.js')
gulp.task('build:babel:common', () => gulp.src('website/common/script/**/*.js')
.pipe(babel())
.pipe(gulp.dest('website/common/transpiled-babel/')));
gulp.task('build:server', gulp.series('build:src', 'build:common', done => done()));
gulp.task('build:babel', gulp.parallel('build:babel:server', 'build:babel:common', done => done()));
gulp.task('build:prod', gulp.series(
'build:server',
'build:babel',
'apidoc',
'content:cache',
done => done(),
));
+34
View File
@@ -0,0 +1,34 @@
import gulp from 'gulp';
import fs from 'fs';
// TODO parallelize, use gulp file helpers
gulp.task('content:cache', done => {
// Requiring at runtime because these files access `common`
// code which in production works only if transpiled so after
// gulp build:babel:common has run
const { CONTENT_CACHE_PATH, getLocalizedContent } = require('../website/server/libs/content'); // eslint-disable-line global-require
const { langCodes } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
try {
// create the cache folder (if it doesn't exist)
try {
fs.mkdirSync(CONTENT_CACHE_PATH);
} catch (err) {
if (err.code !== 'EEXIST') throw err;
}
// clone the content for each language and save
// localize it
// save the result
langCodes.forEach(langCode => {
fs.writeFileSync(
`${CONTENT_CACHE_PATH}${langCode}.json`,
getLocalizedContent(langCode),
'utf8',
);
});
done();
} catch (err) {
done(err);
}
});
+9 -1
View File
@@ -13,8 +13,16 @@ const gulp = require('gulp');
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
require('./gulp/gulp-apidoc'); // eslint-disable-line global-require
require('./gulp/gulp-content'); // eslint-disable-line global-require
require('./gulp/gulp-build'); // eslint-disable-line global-require
} else {
require('glob').sync('./gulp/gulp-*').forEach(require); // eslint-disable-line global-require
require('./gulp/gulp-apidoc'); // eslint-disable-line global-require
require('./gulp/gulp-content'); // eslint-disable-line global-require
require('./gulp/gulp-build'); // eslint-disable-line global-require
require('./gulp/gulp-console'); // eslint-disable-line global-require
require('./gulp/gulp-sprites'); // eslint-disable-line global-require
require('./gulp/gulp-start'); // eslint-disable-line global-require
require('./gulp/gulp-tests'); // eslint-disable-line global-require
require('./gulp/gulp-transifex-test'); // eslint-disable-line global-require
require('gulp').task('default', gulp.series('test')); // eslint-disable-line global-require
}
+1 -1
View File
@@ -18,7 +18,7 @@ function setUpServer () {
setUpServer();
// Replace this with your migration
const processUsers = () => {}; // require('').default;
const processUsers = require().default;
processUsers()
.then(() => {
@@ -0,0 +1,54 @@
// @migrationName = 'RewardsMigrationFlipNegativeCostsValues';
// @authorName = 'hamboomger';
// @authorUuid = '80b61b73-2278-4947-b713-a10112cfe7f5';
/*
* For each reward with negative cost, make it positive
* by assigning it an absolute value of itself
*/
import { Task } from '../../website/server/models/task';
async function flipNegativeCostsValues () {
const query = {
type: 'reward',
value: { $lt: 0 },
};
const fields = {
_id: 1,
value: 1,
};
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-await-in-loop
const rewards = await Task
.find(query)
.limit(250)
.sort({ _id: 1 })
.select(fields)
.lean()
.exec();
if (rewards.length === 0) {
break;
}
const promises = rewards.map(reward => {
const positiveValue = Math.abs(reward.value);
return Task.update({ _id: reward._id }, { $set: { value: positiveValue } }).exec();
});
// eslint-disable-next-line no-await-in-loop
await Promise.all(promises);
query._id = {
$gt: rewards[rewards.length - 1]._id,
};
}
console.log('All rewards with negative values were updated, migration finished');
}
export default flipNegativeCostsValues;
+4 -1
View File
@@ -33,6 +33,9 @@ async function updateUser (user) {
each(keys(content.specialPets), pet => {
set[`items.pets.${pet}`] = 5;
});
each(keys(content.wackyPets), pet => {
set[`items.pets.${pet}`] = 5;
});
each(keys(content.mounts), mount => {
set[`items.mounts.${mount}`] = true;
});
@@ -54,7 +57,7 @@ async function updateUser (user) {
export default async function processUsers () {
const query = {
migration: { $ne: MIGRATION_NAME },
'auth.local.username': 'olson22',
'auth.local.username': 'SabreTest',
};
const fields = {
+391 -251
View File
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -1,12 +1,12 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.137.1",
"version": "4.138.4",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.8.7",
"@babel/preset-env": "^7.8.7",
"@babel/register": "^7.8.6",
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.0",
"@babel/register": "^7.9.0",
"@google-cloud/trace-agent": "^4.2.5",
"@slack/client": "^3.8.1",
"accepts": "^1.3.5",
@@ -14,7 +14,7 @@
"amplitude": "^3.5.0",
"apidoc": "^0.17.5",
"apn": "^2.2.0",
"aws-sdk": "^2.635.0",
"aws-sdk": "^2.648.0",
"bcrypt": "^3.0.8",
"body-parser": "^1.18.3",
"compression": "^1.7.4",
@@ -37,7 +37,7 @@
"gulp-nodemon": "^2.5.0",
"gulp.spritesmith": "^6.9.0",
"habitica-markdown": "^1.3.2",
"helmet": "^3.21.3",
"helmet": "^3.22.0",
"image-size": "^0.8.3",
"in-app-purchase": "^1.11.3",
"js2xmlparser": "^4.0.1",
@@ -46,8 +46,8 @@
"method-override": "^3.0.0",
"moment": "^2.24.0",
"moment-recur": "^1.0.7",
"mongoose": "^5.9.3",
"morgan": "^1.7.0",
"mongoose": "^5.9.6",
"morgan": "^1.10.0",
"nconf": "^0.10.0",
"node-gcm": "^1.0.2",
"pageres": "^5.1.0",
@@ -58,7 +58,7 @@
"paypal-ipn": "3.0.0",
"paypal-rest-sdk": "^1.8.1",
"ps-tree": "^1.0.0",
"regenerator-runtime": "^0.13.3",
"regenerator-runtime": "^0.13.5",
"remove-markdown": "^0.3.0",
"rimraf": "^3.0.2",
"short-uuid": "^3.0.0",
+17
View File
@@ -0,0 +1,17 @@
import * as contentLib from '../../../../website/server/libs/content';
import content from '../../../../website/common/script/content';
describe('contentLib', () => {
describe('CONTENT_CACHE_PATH', () => {
it('exports CONTENT_CACHE_PATH', () => {
expect(contentLib.CONTENT_CACHE_PATH).to.be.a.string;
});
});
describe('getLocalizedContent', () => {
it('clones, not modify, the original content data', () => {
contentLib.getLocalizedContent();
expect(typeof content.backgrounds.backgrounds062014.beach.text).to.equal('function');
});
});
});
+27
View File
@@ -3,6 +3,7 @@ import {
CustomError,
NotAuthorized,
BadRequest,
Forbidden,
InternalServerError,
NotFound,
NotificationNotFound,
@@ -113,6 +114,32 @@ describe('Custom Errors', () => {
});
});
describe('Forbidden', () => {
it('is an instance of CustomError', () => {
const forbiddenError = new Forbidden();
expect(forbiddenError).to.be.an.instanceOf(CustomError);
});
it('it returns an http code of 401', () => {
const forbiddenError = new Forbidden();
expect(forbiddenError.httpCode).to.eql(403);
});
it('returns a default message', () => {
const forbiddenError = new Forbidden();
expect(forbiddenError.message).to.eql('Access forbidden.');
});
it('allows a custom message', () => {
const forbiddenError = new Forbidden('Custom Error Message');
expect(forbiddenError.message).to.eql('Custom Error Message');
});
});
describe('InternalServerError', () => {
it('is an instance of CustomError', () => {
const internalServerError = new InternalServerError();
+65 -35
View File
@@ -30,18 +30,52 @@ describe('logger', () => {
describe('info', () => {
it('calls winston\'s info log', () => {
logger.info(1, 2, 3);
logger.info('1', 2);
expect(infoSpy).to.be.calledOnce;
expect(infoSpy).to.be.calledWith(1, 2, 3);
expect(infoSpy).to.be.calledWith('1', { extraData: 2 });
});
it('allows up to two arguments', () => {
expect(() => logger.info('1', 2, 3)).to.throw;
expect(infoSpy).to.not.be.called;
});
it('has default message', () => {
logger.info(1);
expect(infoSpy).to.be.calledOnce;
expect(infoSpy).to.be.calledWith('No message provided for log.', { extraData: 1 });
});
it('wraps non objects', () => {
logger.info('message', [1, 2]);
expect(infoSpy).to.be.calledOnce;
expect(infoSpy).to.be.calledWithMatch('message', { extraData: [1, 2] });
});
it('does not wrap objects', () => {
logger.info('message', { a: 1, b: 2 });
expect(infoSpy).to.be.calledOnce;
expect(infoSpy).to.be.calledWithMatch('message', { a: 1, b: 2 });
});
it('throws if two arguments and no message', () => {
expect(() => logger.info({ a: 1 }, { b: 2 })).to.throw;
expect(infoSpy).to.not.be.called;
});
});
describe('error', () => {
context('non-error object', () => {
it('passes through arguments if the first arg is not an error object', () => {
logger.error(1, 2, 3, 4);
expect(errorSpy).to.be.calledOnce;
expect(errorSpy).to.be.calledWith(1, 2, 3, 4);
it('allows up to two arguments', () => {
expect(() => logger.error('1', 2, 3)).to.throw;
expect(errorSpy).to.not.be.called;
});
it('handled non-error object', () => {
logger.error(1, 2);
expect(errorSpy).to.be.calledOnce;
expect(errorSpy).to.be.calledWithMatch('logger.error expects an Error instance', {
invalidErr: 1,
extraData: 2,
});
});
@@ -50,14 +84,12 @@ describe('logger', () => {
const errInstance = new Error('An error.');
logger.error(errInstance, {
data: 1,
}, 2, 3);
});
expect(errorSpy).to.be.calledOnce;
expect(errorSpy).to.be.calledWith(
errInstance.stack,
{ data: 1, fullError: errInstance },
2,
3,
);
});
@@ -68,56 +100,60 @@ describe('logger', () => {
logger.error(errInstance, {
data: 1,
fullError: anotherError,
}, 2, 3);
});
expect(errorSpy).to.be.calledOnce;
expect(errorSpy).to.be.calledWith(
errInstance.stack,
{ data: 1, fullError: anotherError },
2,
3,
);
});
it('logs the error when errorData is null', () => {
const errInstance = new Error('An error.');
logger.error(errInstance, null, 2, 3);
logger.error(errInstance, null);
expect(errorSpy).to.be.calledOnce;
expect(errorSpy).to.be.calledWith(
expect(errorSpy).to.be.calledWithMatch(
errInstance.stack,
null,
2,
3,
{ },
);
});
it('logs the error when errorData is not an object', () => {
const errInstance = new Error('An error.');
logger.error(errInstance, true, 2, 3);
logger.error(errInstance, true);
expect(errorSpy).to.be.calledOnce;
expect(errorSpy).to.be.calledWith(
expect(errorSpy).to.be.calledWithMatch(
errInstance.stack,
true,
2,
3,
{ extraData: true },
);
});
it('logs the error when errorData is a string', () => {
const errInstance = new Error('An error.');
logger.error(errInstance, 'a string');
expect(errorSpy).to.be.calledOnce;
expect(errorSpy).to.be.calledWithMatch(
errInstance.stack,
{ extraMessage: 'a string' },
);
});
it('logs the error when errorData does not include isHandledError property', () => {
const errInstance = new Error('An error.');
logger.error(errInstance, { httpCode: 400 }, 2, 3);
logger.error(errInstance, { httpCode: 400 });
expect(errorSpy).to.be.calledOnce;
expect(errorSpy).to.be.calledWith(
errInstance.stack,
{ httpCode: 400, fullError: errInstance },
2,
3,
);
});
@@ -127,14 +163,12 @@ describe('logger', () => {
logger.error(errInstance, {
isHandledError: true,
httpCode: 502,
}, 2, 3);
});
expect(errorSpy).to.be.calledOnce;
expect(errorSpy).to.be.calledWith(
errInstance.stack,
{ httpCode: 502, isHandledError: true, fullError: errInstance },
2,
3,
);
});
@@ -144,14 +178,12 @@ describe('logger', () => {
logger.error(errInstance, {
isHandledError: true,
httpCode: 403,
}, 2, 3);
});
expect(warnSpy).to.be.calledOnce;
expect(warnSpy).to.be.calledWith(
errInstance.stack,
{ httpCode: 403, isHandledError: true, fullError: errInstance },
2,
3,
);
});
@@ -160,7 +192,7 @@ describe('logger', () => {
errInstance.customField = 'Some interesting data';
logger.error(errInstance, {}, 2, 3);
logger.error(errInstance, {});
expect(errorSpy).to.be.calledOnce;
expect(errorSpy).to.be.calledWith(
@@ -170,8 +202,6 @@ describe('logger', () => {
customField: 'Some interesting data',
},
},
2,
3,
);
});
});
@@ -11,7 +11,6 @@ import { model as User } from '../../../../../../website/server/models/user';
import { model as Group } from '../../../../../../website/server/models/group';
import {
generateGroup,
sleep,
} from '../../../../../helpers/api-unit.helper';
describe('Purchasing a group plan for group', () => {
@@ -293,7 +292,7 @@ describe('Purchasing a group plan for group', () => {
});
it('sends appropriate emails when subscribed member of group must manually cancel recurring Android subscription', async () => {
const TECH_ASSISTANCE_EMAIL = nconf.get('TECH_ASSISTANCE_EMAIL');
const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL');
plan.customerId = 'random';
plan.paymentMethod = api.constants.GOOGLE_PAYMENT_METHOD;
@@ -308,26 +307,46 @@ describe('Purchasing a group plan for group', () => {
data.groupId = group._id;
await api.createSubscription(data);
await sleep(0.5);
expect(sender.sendTxn).to.have.callCount(4);
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);
expect(sender.sendTxn.args[0][1]).to.equal('admin-user-subscription-details');
expect(sender.sendTxn.args[1][0]._id).to.equal(recipient._id);
expect(sender.sendTxn.args[1][1]).to.equal('group-member-join');
expect(sender.sendTxn.args[1][2]).to.eql([
const adminUserSubscriptionDetails = sender.sendTxn.args.find(sendTxnArgs => {
const emailType = sendTxnArgs[1];
return emailType === 'admin-user-subscription-details';
});
expect(adminUserSubscriptionDetails).to.exist;
expect(adminUserSubscriptionDetails[0].email).to.equal(TECH_ASSISTANCE_EMAIL);
const groupMemberJoinOne = sender.sendTxn.args.find(sendTxnArgs => {
const emailType = sendTxnArgs[1];
const emailRecipient = sendTxnArgs[0];
return emailType === 'group-member-join' && emailRecipient._id === recipient._id;
});
expect(groupMemberJoinOne).to.exist;
expect(groupMemberJoinOne[0]._id).to.equal(recipient._id);
expect(groupMemberJoinOne[2]).to.eql([
{ name: 'LEADER', content: groupLeaderName },
{ name: 'GROUP_NAME', content: groupName },
{ name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_GOOGLE },
]);
expect(sender.sendTxn.args[2][0]._id).to.equal(group.leader);
expect(sender.sendTxn.args[2][1]).to.equal('group-member-join');
expect(sender.sendTxn.args[3][0]._id).to.equal(group.leader);
expect(sender.sendTxn.args[3][1]).to.equal('group-subscription-begins');
const groupMemberJoinTwo = sender.sendTxn.args.find(sendTxnArgs => {
const emailType = sendTxnArgs[1];
const emailRecipient = sendTxnArgs[0];
return emailType === 'group-member-join' && emailRecipient._id === group.leader;
});
expect(groupMemberJoinTwo).to.exist;
expect(groupMemberJoinTwo[0]._id).to.equal(group.leader);
const groupSubscriptionBegins = sender.sendTxn.args.find(sendTxnArgs => {
const emailType = sendTxnArgs[1];
return emailType === 'group-subscription-begins';
});
expect(groupSubscriptionBegins).to.exist;
expect(groupSubscriptionBegins[0]._id).to.equal(group.leader);
});
it('sends appropriate emails when subscribed member of group must manually cancel recurring iOS subscription', async () => {
const TECH_ASSISTANCE_EMAIL = nconf.get('TECH_ASSISTANCE_EMAIL');
const TECH_ASSISTANCE_EMAIL = nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL');
plan.customerId = 'random';
plan.paymentMethod = api.constants.IOS_PAYMENT_METHOD;
@@ -342,22 +361,43 @@ describe('Purchasing a group plan for group', () => {
data.groupId = group._id;
await api.createSubscription(data);
await sleep(0.5);
expect(sender.sendTxn).to.have.callCount(4);
expect(sender.sendTxn.args[0][0]._id).to.equal(TECH_ASSISTANCE_EMAIL);
expect(sender.sendTxn.args[0][1]).to.equal('admin-user-subscription-details');
expect(sender.sendTxn.args[1][0]._id).to.equal(recipient._id);
expect(sender.sendTxn.args[1][1]).to.equal('group-member-join');
expect(sender.sendTxn.args[1][2]).to.eql([
const adminUserSubscriptionDetails = sender.sendTxn.args.find(sendTxnArgs => {
const emailType = sendTxnArgs[1];
return emailType === 'admin-user-subscription-details';
});
expect(adminUserSubscriptionDetails).to.exist;
expect(adminUserSubscriptionDetails[0].email).to.equal(TECH_ASSISTANCE_EMAIL);
const groupMemberJoinOne = sender.sendTxn.args.find(sendTxnArgs => {
const emailType = sendTxnArgs[1];
const emailRecipient = sendTxnArgs[0];
return emailType === 'group-member-join' && emailRecipient._id === recipient._id;
});
expect(groupMemberJoinOne).to.exist;
expect(groupMemberJoinOne[0]._id).to.equal(recipient._id);
expect(groupMemberJoinOne[2]).to.eql([
{ name: 'LEADER', content: groupLeaderName },
{ name: 'GROUP_NAME', content: groupName },
{ name: 'PREVIOUS_SUBSCRIPTION_TYPE', content: EMAIL_TEMPLATE_SUBSCRIPTION_TYPE_IOS },
]);
expect(sender.sendTxn.args[2][0]._id).to.equal(group.leader);
expect(sender.sendTxn.args[2][1]).to.equal('group-member-join');
expect(sender.sendTxn.args[3][0]._id).to.equal(group.leader);
expect(sender.sendTxn.args[3][1]).to.equal('group-subscription-begins');
const groupMemberJoinTwo = sender.sendTxn.args.find(sendTxnArgs => {
const emailType = sendTxnArgs[1];
const emailRecipient = sendTxnArgs[0];
return emailType === 'group-member-join' && emailRecipient._id === group.leader;
});
expect(groupMemberJoinTwo).to.exist;
expect(groupMemberJoinTwo[0]._id).to.equal(group.leader);
const groupSubscriptionBegins = sender.sendTxn.args.find(sendTxnArgs => {
const emailType = sendTxnArgs[1];
return emailType === 'group-subscription-begins';
});
expect(groupSubscriptionBegins).to.exist;
expect(groupSubscriptionBegins[0]._id).to.equal(group.leader);
});
it('adds months to members with existing gift subscription', async () => {
+95
View File
@@ -1,4 +1,5 @@
import got from 'got';
import moment from 'moment';
import {
WebhookSender,
taskScoredWebhook,
@@ -13,6 +14,7 @@ import {
import {
generateUser,
defer,
sleep,
} from '../../../helpers/api-unit.helper';
@@ -322,6 +324,99 @@ describe('webhooks', () => {
json: true,
});
});
describe('failures', () => {
let sendWebhook;
beforeEach(async () => {
sandbox.restore();
sandbox.stub(got, 'post').returns(Promise.reject());
sendWebhook = new WebhookSender({ type: 'taskActivity' });
user.webhooks = [{
url: 'http://custom-url.com', enabled: true, type: 'taskActivity',
}];
await user.save();
expect(user.webhooks[0].failures).to.equal(0);
expect(user.webhooks[0].lastFailureAt).to.equal(undefined);
});
it('does not increase failures counter if request is successfull', async () => {
sandbox.restore();
sandbox.stub(got, 'post').returns(Promise.resolve());
const body = {};
sendWebhook.send(user, body);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
json: true,
body,
});
await sleep(0.1);
user = await User.findById(user._id).exec();
expect(user.webhooks[0].failures).to.equal(0);
expect(user.webhooks[0].lastFailureAt).to.equal(undefined);
});
it('records failures', async () => {
const body = {};
sendWebhook.send(user, body);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com', {
json: true,
body,
});
await sleep(0.1);
user = await User.findById(user._id).exec();
expect(user.webhooks[0].failures).to.equal(1);
expect((Date.now() - user.webhooks[0].lastFailureAt.getTime()) < 10000).to.be.true;
});
it('disables a webhook after 10 failures', async () => {
const times = 10;
for (let i = 0; i < times; i += 1) {
sendWebhook.send(user, {});
await sleep(0.1); // eslint-disable-line no-await-in-loop
user = await User.findById(user._id).exec(); // eslint-disable-line no-await-in-loop
}
expect(got.post).to.be.callCount(10);
expect(got.post).to.be.calledWithMatch('http://custom-url.com');
await sleep(0.1);
user = await User.findById(user._id).exec();
expect(user.webhooks[0].enabled).to.equal(false);
expect(user.webhooks[0].failures).to.equal(0);
});
it('resets failures after a month ', async () => {
const oneMonthAgo = moment().subtract(1, 'months').subtract(1, 'days').toDate();
user.webhooks[0].lastFailureAt = oneMonthAgo;
user.webhooks[0].failures = 9;
await user.save();
sendWebhook.send(user, []);
expect(got.post).to.be.calledOnce;
expect(got.post).to.be.calledWithMatch('http://custom-url.com');
await sleep(0.1);
user = await User.findById(user._id).exec();
expect(user.webhooks[0].failures).to.equal(1);
// Check that the stored date is whitin 10s from now
expect((Date.now() - user.webhooks[0].lastFailureAt.getTime()) < 10000).to.be.true;
});
});
});
describe('taskScoredWebhook', () => {
@@ -0,0 +1,94 @@
import nconf from 'nconf';
import requireAgain from 'require-again';
import {
generateRes,
generateReq,
generateNext,
} from '../../../helpers/api-unit.helper';
import { Forbidden } from '../../../../website/server/libs/errors';
import apiError from '../../../../website/server/libs/apiError';
function checkErrorThrown (next) {
expect(next).to.have.been.calledOnce;
const calledWith = next.getCall(0).args;
expect(calledWith[0].message).to.equal(apiError('ipAddressBlocked'));
expect(calledWith[0] instanceof Forbidden).to.equal(true);
}
function checkErrorNotThrown (next) {
expect(next).to.have.been.calledOnce;
const calledWith = next.getCall(0).args;
expect(typeof calledWith[0] === 'undefined').to.equal(true);
}
describe('ipBlocker middleware', () => {
const pathToIpBlocker = '../../../../website/server/middlewares/ipBlocker';
let res; let req; let next;
beforeEach(() => {
res = generateRes();
req = generateReq();
next = generateNext();
});
it('is disabled when the env var is not defined', () => {
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(undefined);
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next);
checkErrorNotThrown(next);
});
it('is disabled when the env var is an empty string', () => {
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('');
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next);
checkErrorNotThrown(next);
});
it('is disabled when the env var contains comma separated empty strings', () => {
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(' , , ');
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next);
checkErrorNotThrown(next);
});
it('does not throw when the ip does not match', () => {
req.headers['x-forwarded-for'] = '192.168.1.1';
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.2');
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next);
checkErrorNotThrown(next);
});
it('throws when a matching ip exist in x-forwarded-for', () => {
req.headers['x-forwarded-for'] = '192.168.1.1';
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1');
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next);
checkErrorThrown(next);
});
it('trims ips in x-forwarded-for', () => {
req.headers['x-forwarded-for'] = '192.168.1.1';
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(', 192.168.1.1 , 192.168.1.4, ');
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next);
checkErrorThrown(next);
});
it('works when multiple ips are passed in x-forwarded-for', () => {
req.headers['x-forwarded-for'] = '192.168.1.4';
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1, 192.168.1.4, 192.168.1.3');
const attachIpBlocker = requireAgain(pathToIpBlocker).default;
attachIpBlocker(req, res, next);
checkErrorThrown(next);
});
});
@@ -11,7 +11,9 @@ import apiError from '../../../../../website/server/libs/apiError';
describe('GET /groups', () => {
let user;
let userInGuild;
const NUMBER_OF_PUBLIC_GUILDS = 3; // 2 + the tavern
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER = 2;
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
@@ -33,14 +35,20 @@ describe('GET /groups', () => {
name: 'public guild - is member',
type: 'guild',
privacy: 'public',
summary: 'ohayou kombonwa',
description: 'oyasumi',
});
await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] });
await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
userInGuild = await generateUser({ guilds: [publicGuildUserIsMemberOf._id] });
publicGuildNotMember = await generateGroup(leader, {
name: 'public guild - is not member',
type: 'guild',
privacy: 'public',
summary: 'Natsume Soseki',
description: 'Kinnosuke no Hondana',
categories,
});
@@ -150,6 +158,35 @@ describe('GET /groups', () => {
expect(guilds.length).to.equal(0);
});
it('filters public guilds by leader role', async () => {
const guilds = await user.get('/groups?type=publicGuilds&leader=true');
expect(guilds.length).to.equal(NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER);
});
it('filters public guilds by member role', async () => {
const guilds = await userInGuild.get('/groups?type=publicGuilds&member=true');
expect(guilds.length).to.equal(1);
expect(guilds[0].name).to.have.string('is member');
});
it('filters public guilds by single-word search term', async () => {
const guilds = await user.get('/groups?type=publicGuilds&search=kom');
expect(guilds.length).to.equal(1);
expect(guilds[0].summary).to.have.string('ohayou kombonwa');
});
it('filters public guilds by single-word search term left and right-padded by spaces', async () => {
const guilds = await user.get('/groups?type=publicGuilds&search=++++ohayou+kombonwa+++++');
expect(guilds.length).to.equal(1);
expect(guilds[0].summary).to.have.string('ohayou kombonwa');
});
it('filters public guilds by two-words search term separated by multiple spaces', async () => {
const guilds = await user.get('/groups?type=publicGuilds&search=kinnosuke+++++hon');
expect(guilds.length).to.equal(1);
expect(guilds[0].description).to.have.string('Kinnosuke');
});
});
describe('public guilds pagination', () => {
@@ -55,6 +55,18 @@ describe('POST /tasks/user', () => {
});
});
it('returns an error if reward value is a negative number', async () => {
await expect(user.post('/tasks/user', {
type: 'reward',
text: 'reward with negative value',
value: -10,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'reward validation failed',
});
});
it('does not update user.tasksOrder.{taskType} when the task is not saved because invalid', async () => {
const originalHabitsOrder = (await user.get('/user')).tasksOrder.habits;
await expect(user.post('/tasks/user', {
@@ -530,5 +530,15 @@ describe('PUT /tasks/:id', () => {
expect(savedReward.value).to.eql(100);
});
it('returns an error if reward value is a negative number', async () => {
await expect(user.put(`/tasks/${reward._id}`, {
value: -10,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'reward validation failed',
});
});
});
});
@@ -81,6 +81,16 @@ describe('POST /user/webhook', () => {
expect(webhook.type).to.eql('taskActivity');
});
it('ignores protected fields', async () => {
body.failures = 3;
body.lastFailureAt = new Date();
const webhook = await user.post('/user/webhook', body);
expect(webhook.failures).to.eql(0);
expect(webhook.lastFailureAt).to.eql(undefined);
});
it('successfully adds the webhook', async () => {
expect(user.webhooks).to.eql([]);
@@ -63,6 +63,21 @@ describe('PUT /user/webhook/:id', () => {
expect(webhook.options).to.eql(options);
});
it('ignores protected fields', async () => {
const failures = 3;
const lastFailureAt = new Date();
await user.put(`/user/webhook/${webhookToUpdate.id}`, {
failures, lastFailureAt,
});
await user.sync();
const webhook = user.webhooks.find(hook => webhookToUpdate.id === hook.id);
expect(webhook.failures).to.eql(0);
expect(webhook.lastFailureAt).to.eql(undefined);
});
it('updates a webhook with empty label', async () => {
const url = 'http://a-new-url.com';
const type = 'groupChatReceived';
+718 -1263
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -18,18 +18,18 @@
"@vue/cli-plugin-router": "^4.2.3",
"@vue/cli-plugin-unit-mocha": "^4.2.3",
"@vue/cli-service": "^4.2.3",
"@storybook/addon-actions": "^5.3.14",
"@storybook/addon-knobs": "^5.3.14",
"@storybook/addon-links": "^5.3.14",
"@storybook/addon-notes": "^5.3.14",
"@storybook/vue": "^5.3.14",
"@storybook/addon-actions": "^5.3.17",
"@storybook/addon-knobs": "^5.3.17",
"@storybook/addon-links": "^5.3.17",
"@storybook/addon-notes": "^5.3.17",
"@storybook/vue": "^5.3.17",
"@vue/test-utils": "1.0.0-beta.29",
"amplitude-js": "^5.9.0",
"amplitude-js": "^5.10.0",
"axios": "^0.19.2",
"axios-progress-bar": "^1.2.0",
"babel-eslint": "^10.1.0",
"bootstrap": "^4.4.1",
"bootstrap-vue": "^2.6.1",
"bootstrap-vue": "^2.9.0",
"chai": "^4.1.2",
"core-js": "^3.6.4",
"eslint": "^6.8.0",
@@ -44,7 +44,7 @@
"lodash": "^4.17.15",
"moment": "^2.24.0",
"nconf": "^0.10.0",
"sass": "^1.26.2",
"sass": "^1.26.3",
"sass-loader": "^8.0.2",
"smartbanner.js": "^1.15.0",
"svg-inline-loader": "^0.8.2",
@@ -58,9 +58,9 @@
"vue-mugen-scroll": "^0.2.6",
"vue-router": "^3.1.6",
"vue-template-compiler": "^2.6.11",
"vue2-perfect-scrollbar": "^1.3.0",
"vue2-perfect-scrollbar": "^1.4.0",
"vuedraggable": "^2.23.1",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^4.42.0"
"webpack": "^4.42.1"
}
}
@@ -1,12 +1,18 @@
.promo_achievement_CottonCandyPink {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -328px -316px;
background-position: -952px -740px;
width: 204px;
height: 102px;
}
.promo_april_fools_2020 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -952px 0px;
width: 423px;
height: 147px;
}
.promo_armoire_backgrounds_202003 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -445px;
background-position: -340px -659px;
width: 423px;
height: 147px;
}
@@ -16,45 +22,93 @@
width: 623px;
height: 167px;
}
.promo_egg_quest {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -952px -444px;
width: 354px;
height: 147px;
}
.promo_hugabug_bundle {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -445px;
background-position: -952px -296px;
width: 420px;
height: 147px;
}
.promo_mystery_202003 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -624px -211px;
background-position: -952px -592px;
width: 282px;
height: 147px;
}
.promo_mystery_202004 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -624px -277px;
width: 282px;
height: 147px;
}
.promo_pi_day {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -593px;
background-position: -316px -316px;
width: 273px;
height: 147px;
}
.promo_seasonal_shop_spring {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -764px -659px;
width: 162px;
height: 138px;
}
.promo_spring_2019 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -430px -475px;
width: 432px;
height: 162px;
}
.promo_spring_2020 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -475px;
width: 429px;
height: 183px;
}
.promo_spring_potions_2020 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -952px -148px;
width: 423px;
height: 147px;
}
.promo_take_this {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -624px -359px;
background-position: -1235px -592px;
width: 96px;
height: 69px;
}
.scene_QuartzFox {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -870px;
width: 141px;
height: 147px;
}
.scene_dailies {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -168px;
background-position: -624px 0px;
width: 327px;
height: 276px;
}
.scene_gaining_achievement {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -624px 0px;
background-position: 0px -659px;
width: 339px;
height: 210px;
}
.scene_shanaqui {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -328px -168px;
background-position: -316px -168px;
width: 282px;
height: 147px;
}
.scene_tough_times {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -168px;
width: 315px;
height: 306px;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 140 KiB

+1 -8
View File
@@ -5,14 +5,7 @@
right: 0;
bottom: 0;
left: 0;
&:not([class*="FlyingPig"]) {
top: -28px !important;
}
}
.Pet[class*="FlyingPig"] {
top: 7px !important;
top: -28px !important;
}
.Pet.Pet-Dragon-Hydra {
@@ -2,11 +2,11 @@
// possible values are: normal, fall, habitoween, thanksgiving, winter, nye, birthday, valentines, spring, summer
// more to be added on future seasons
$npc_market_flavor: 'normal';
$npc_quests_flavor: 'normal';
$npc_seasonal_flavor: 'normal';
$npc_timetravelers_flavor: 'normal';
$npc_tavern_flavor: 'normal';
$npc_market_flavor: 'aprilfools';
$npc_quests_flavor: 'aprilfools';
$npc_seasonal_flavor: 'aprilfools';
$npc_timetravelers_flavor: 'aprilfools';
$npc_tavern_flavor: 'aprilfools';
$restingToolbarHeight: 40px;
$menuToolbarHeight: 56px;
@@ -225,30 +225,30 @@ export default {
classGear (heroClass) {
if (heroClass === 'rogue') {
return {
armor: 'armor_rogue_5',
head: 'head_rogue_5',
shield: 'shield_rogue_6',
weapon: 'weapon_rogue_6',
armor: 'armor_special_spring2020Rogue',
head: 'head_special_spring2020Rogue',
shield: 'shield_special_spring2020Rogue',
weapon: 'weapon_special_spring2020Rogue',
};
} if (heroClass === 'wizard') {
return {
armor: 'armor_wizard_5',
head: 'head_wizard_5',
weapon: 'weapon_wizard_6',
armor: 'armor_special_spring2020Mage',
head: 'head_special_spring2020Mage',
weapon: 'weapon_special_spring2020Mage',
};
} if (heroClass === 'healer') {
return {
armor: 'armor_healer_5',
head: 'head_healer_5',
shield: 'shield_healer_5',
weapon: 'weapon_healer_6',
armor: 'armor_special_spring2020Healer',
head: 'head_special_spring2020Healer',
shield: 'shield_special_spring2020Healer',
weapon: 'weapon_special_spring2020Healer',
};
}
return {
armor: 'armor_warrior_5',
head: 'head_warrior_5',
shield: 'shield_warrior_5',
weapon: 'weapon_warrior_6',
armor: 'armor_special_spring2020Warrior',
head: 'head_special_spring2020Warrior',
shield: 'shield_special_spring2020Warrior',
weapon: 'weapon_special_spring2020Warrior',
};
},
selectionBox (selectedClass, heroClass) {
+49 -3
View File
@@ -79,9 +79,8 @@
></span>
<!-- Pet-->
<span
v-if="member.items.currentPet"
class="current-pet"
:class="'Pet-' + member.items.currentPet"
:class="foolPet(member.items.currentPet)"
></span>
</template>
</div>
@@ -124,6 +123,7 @@
</style>
<script>
import includes from 'lodash/includes';
import { mapState } from '@/libs/store';
import ClassBadge from '@/components/members/classBadge';
@@ -189,7 +189,7 @@ export default {
let val = '27px';
if (!this.avatarOnly) {
if (this.member.items.currentPet) val = '24px';
// if (this.member.items.currentPet) val = '24px';
if (this.member.items.currentMount) val = '0px';
}
@@ -276,6 +276,52 @@ export default {
return !buffs.snowball && !buffs.spookySparkles && !buffs.shinySeed && !buffs.seafoam;
},
foolPet (pet) {
const SPECIAL_PETS = [
'Wolf-Veteran',
'Wolf-Cerberus',
'Dragon-Hydra',
'Turkey-Base',
'BearCub-Polar',
'MantisShrimp-Base',
'JackOLantern-Base',
'Mammoth-Base',
'Tiger-Veteran',
'Phoenix-Base',
'Turkey-Gilded',
'MagicalBee-Base',
'Lion-Veteran',
'Gryphon-RoyalPurple',
'JackOLantern-Ghost',
'Jackalope-RoyalPurple',
'Orca-Base',
'Bear-Veteran',
'Hippogriff-Hopeful',
'Fox-Veteran',
'JackOLantern-Glow',
'Gryphon-Gryphatrice',
];
const BASE_PETS = [
'Wolf',
'TigerCub',
'PandaCub',
'LionCub',
'Fox',
'FlyingPig',
'BearCub',
'Dragon',
'Cactus',
];
if (!pet) return 'Pet-Cactus-Dessert';
if (SPECIAL_PETS.indexOf(pet) !== -1) {
return 'Pet-LionCub-Dessert';
}
const species = pet.slice(0, pet.indexOf('-'));
if (includes(BASE_PETS, species)) {
return `Pet-${species}-Dessert`;
}
return 'Pet-FlyingPig-Dessert';
},
},
};
</script>
@@ -30,6 +30,7 @@
</div>
<div
v-if="hasParty"
ref="partyMembersDiv"
v-resize="1500"
class="party-members d-flex"
@resized="setPartyMembersWidth($event)"
@@ -153,6 +154,15 @@ export default {
inviteModalGroupType: undefined,
};
},
watch: {
hideHeader () {
this.$nextTick(() => {
if (this.$refs.partyMembersDiv) {
this.setPartyMembersWidth({ width: this.$refs.partyMembersDiv.clientWidth });
}
});
},
},
computed: {
...mapGetters({
user: 'user:data',
@@ -161,7 +161,10 @@ export default {
},
getPetItemClass () {
if (this.isOwned() || (this.mountOwned() && this.isHatchable())) {
return `Pet Pet-${this.item.key} ${this.item.eggKey}`;
if (this.isSpecial()) {
return 'Pet Pet-LionCub-Dessert';
}
return `${this.item.class} ${this.item.eggKey}`;
}
if (!this.isOwned() && this.isSpecial()) {
@@ -173,7 +176,7 @@ export default {
}
if (this.mountOwned()) {
return `GreyedOut Pet Pet-${this.item.key} ${this.item.eggKey}`;
return `GreyedOut ${this.item.class} ${this.item.eggKey}`;
}
// Can't hatch
@@ -833,6 +833,7 @@ export default {
},
async deleteSocialAuth (network) {
await axios.delete(`/api/v4/user/auth/social/${network.key}`);
this.user.auth[network.key] = {};
this.text(this.$t('detachedSocial', { network: network.name }));
},
async socialAuth (network) {
@@ -138,7 +138,8 @@
class="inline-edit-input checklist-item form-control"
type="text"
:placeholder="$t('newChecklistItem')"
@keydown.enter="addChecklistItem($event)"
@keypress.enter="setHasPossibilityOfIMEConversion(false)"
@keyup.enter="addChecklistItem($event)"
>
</div>
<div
@@ -1222,6 +1223,7 @@ export default {
per: 'perception',
},
calendarHighlights: { dates: [new Date()] },
hasPossibilityOfIMEConversion: true,
};
},
computed: {
@@ -1384,7 +1386,12 @@ export default {
sorting.splice(data.newIndex, 0, movingItem);
this.task.checklist = sorting;
},
setHasPossibilityOfIMEConversion (bool) {
this.hasPossibilityOfIMEConversion = bool;
},
addChecklistItem (e) {
if (e) e.preventDefault();
if (this.hasPossibilityOfIMEConversion) return;
const checkListItem = {
id: uuid.v4(),
text: this.newChecklistItem,
@@ -1394,7 +1401,7 @@ export default {
// @TODO: managing checklist separately to help with sorting on the UI
this.checklist.push(checkListItem);
this.newChecklistItem = null;
if (e) e.preventDefault();
this.setHasPossibilityOfIMEConversion(true);
},
removeChecklistItem (i) {
this.task.checklist.splice(i, 1);
+14 -2
View File
@@ -1,8 +1,10 @@
import content from '@/../../common/script/content';
const specialPets = Object.keys(content.specialPets);
const wackyPets = Object.keys(content.wackyPets);
const questPets = Object.keys(content.questPets);
const premiumPets = Object.keys(content.premiumPets);
const dropPets = Object.keys(content.pets);
function getText (textOrFunction) {
if (textOrFunction instanceof Function) {
@@ -34,10 +36,20 @@ export function isSpecial (animal) {
export function createAnimal (egg, potion, type, _content, userItems) {
const animalKey = `${egg.key}-${potion.key}`;
let fooledKey = '';
if (questPets.includes(animalKey)) {
fooledKey = 'FlyingPig-Dessert';
} else if (dropPets.includes(animalKey)
|| premiumPets.includes(animalKey)
|| wackyPets.includes(animalKey)) {
fooledKey = `${egg.key}-Dessert`;
} else {
fooledKey = animalKey;
}
return {
key: animalKey,
class: type === 'pet' ? `Pet Pet-${animalKey}` : `Mount_Icon_${animalKey}`,
class: type === 'pet' ? `Pet Pet-${fooledKey}` : `Mount_Icon_${animalKey}`,
eggKey: egg.key,
eggName: getText(egg.text),
potionKey: potion.key,
+21 -5
View File
@@ -1,4 +1,22 @@
import intersection from 'lodash/intersection';
import _ from 'lodash';
const containsAnyCi = (target, patterns) => patterns.some(el => target.match(new RegExp(el, 'i')));
const isPassedSearch = ({ name, summary, description }, search) => {
if (!search) return false;
const searchWords = _.escapeRegExp(search.trim()).split(/\s+/);
if (containsAnyCi(name, searchWords)) return true;
if (!summary) return false;
if (containsAnyCi(summary, searchWords)) return true;
if (!description) return false;
if (containsAnyCi(description, searchWords)) return true;
return false;
};
export default {
filters: {
@@ -56,9 +74,7 @@ export default {
if (group._id === this.$store.state.constants.TAVERN_ID || group._id === 'habitrpg') return false;
if (search) {
passedSearch = group.name.toLowerCase().indexOf(search.toLowerCase()) >= 0;
}
if (search) passedSearch = isPassedSearch(group, search);
if (filters.categories && filters.categories.length > 0) {
const intersectingCats = intersection(filters.categories, group.categorySlugs);
@@ -75,11 +91,11 @@ export default {
}
if (filters.guildSize && filters.guildSize.indexOf('gold_tier') !== -1) {
correctSize = group.memberCount > 1000;
correctSize = group.memberCount >= 1000;
}
if (filters.guildSize && filters.guildSize.indexOf('silver_tier') !== -1) {
correctSize = group.memberCount > 100 && group.memberCount < 1000;
correctSize = group.memberCount >= 100 && group.memberCount < 1000;
}
if (filters.guildSize && filters.guildSize.indexOf('bronze_tier') !== -1) {
@@ -0,0 +1,107 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Store from '@/libs/store';
import myGuilds from '@/components/groups/myGuilds';
import PublicGuildItem from '@/components/groups/publicGuildItem';
const localVue = createLocalVue();
localVue.use(Store);
describe('myGuilds component', () => {
let computed;
const guilds = [{
_id: '1',
type: 'guild',
name: 'Crimson Vow',
summary: 'testing',
description: 'testing',
}, {
_id: '2',
type: 'guild',
name: 'Log Horizon',
summary: 'testing',
description: 'testing',
}, {
_id: '3',
type: 'guild',
name: 'CAD Cads',
summary: '3D',
description: '3D',
}, {
_id: '4',
type: 'guild',
name: 'Santa Claus',
summary: '3d',
description: 'hohoho',
}];
const store = new Store({
state: {
user: {
data: {
_id: '999',
guilds: ['1', '2', '3', '4'],
},
},
editingGroup: {},
constants: {
TAVERN_ID: '9999',
},
},
getters: {},
actions: {
'guilds:getMyGuilds': () => guilds,
},
});
function makeWrapper (opts = {}) {
return shallowMount(myGuilds, {
data () {
return {
filter: {},
search: '',
};
},
store,
localVue,
...opts,
});
}
before(() => {
computed = {
guilds: () => guilds,
};
});
it('renders all guilds with no filter and no search', () => {
const wrapper = makeWrapper({ computed });
expect(wrapper.findAll(PublicGuildItem).length).to.equal(4);
});
it('renders guilds with name matching against a single-word search term', () => {
const search = 'vow';
const wrapper = makeWrapper({ computed });
wrapper.setData({ search });
expect(wrapper.findAll(PublicGuildItem).length).to.equal(1);
});
it('renders guilds with summary matching against a single-word search term', () => {
const search = '3d';
const wrapper = makeWrapper({ computed });
wrapper.setData({ search });
expect(wrapper.findAll(PublicGuildItem).length).to.equal(2);
});
it('renders guilds with description matching against a single-word search term', () => {
const search = 'hoho';
const wrapper = makeWrapper({ computed });
wrapper.setData({ search });
expect(wrapper.findAll(PublicGuildItem).length).to.equal(1);
});
it('renders guilds with summary matching against two search terms with space in between', () => {
const search = '3d ohayou';
const wrapper = makeWrapper({ computed });
wrapper.setData({ search });
expect(wrapper.findAll(PublicGuildItem).length).to.equal(2);
});
});
@@ -61,4 +61,172 @@ describe('Groups Utilities Mixin', () => {
})).to.equal(false);
});
});
describe('filterGuild', () => {
let testGroup;
let testGroup2;
before(() => {
testGroup = {
type: 'guild',
_id: user.guilds[0],
name: 'Crimson Vow',
summary: 'testing',
description: 'dummy 1',
leader: user.guilds[0], // test user is not guild leader
categories: [{
_id: '123',
slug: 'hobbies_occupations',
name: 'hobbies_occupations',
}],
categorySlugs: ['hobbies_occupations'],
memberCount: 1000,
};
testGroup2 = {
type: 'guild',
_id: '790',
name: 'CAD Cads',
summary: '3D',
description: 'My dummy',
leader: user._id, // test user is guild leader
categories: [{
_id: '123',
slug: 'hobbies_occupations',
name: 'hobbies_occupations',
}],
categorySlugs: ['hobbies_occupations'],
memberCount: 100,
};
});
it('returns true with no filter and no search', () => {
const filter = {};
const search = '';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
});
it('returns false with no filter and one search word not matching against any of the guild name, summary, and description', () => {
const filter = {};
const search = '3d';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(false);
});
it('returns true with no filter and one search word matched successfully against guild name', () => {
const filter = {};
const search = 'vow';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
});
it('returns true with no filter and one search word matched successfully against guild summary', () => {
const filter = {};
const search = 'test';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
});
it('returns true with no filter and one search word matched successfully against guild description', () => {
const filter = {};
const search = 'dum';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
});
it('returns true with no filter and two search words with two spaces in between matched successfully against guild name', () => {
const filter = {};
const search = 'cad test';
expect(instance.filterGuild(testGroup2, filter, search, user)).to.equal(true);
});
it('returns true with no filter and two search words with two spaces in between matched successfully against guild summary', () => {
const filter = {};
const search = 'cad 3d';
expect(instance.filterGuild(testGroup2, filter, search, user)).to.equal(true);
});
it('returns true with no filter and two search words with two spaces in between matched successfully against guild description', () => {
const filter = {};
const search = 'my dummy';
expect(instance.filterGuild(testGroup2, filter, search, user)).to.equal(true);
});
it('returns false with no search word and one filter category that does not match against any guild categories', () => {
const filter = {
categories: ['academics'],
};
const search = '';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(false);
});
it('returns true with no search word and one filter category that matches successfully against any guild categories', () => {
const filter = {
categories: ['hobbies_occupations'],
};
const search = '';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
});
it('returns false with no search word and one filter role that does not match against guild role', () => {
const filter = {
roles: ['guild_leader'],
};
const search = '';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(false);
});
it('returns true with no search word and one filter role that matches successfully against guild role', () => {
const filter = {
roles: ['member'],
};
const search = '';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
});
it('returns true with no search word and filter size silver tier that matches against a guild size of 1000, the max guild size belonging to silver tier', () => {
const filter = {
guildSize: 'gold_tier',
};
const search = '';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
});
it('returns true with no search word and filter size bronze tier that matches against a guild size of 100, the max guild size belonging to bronze tier', () => {
const filter = {
guildSize: 'silver_tier',
};
const search = '';
expect(instance.filterGuild(testGroup2, filter, search, user)).to.equal(true);
});
it('returns false with no search word and filter category that matches successfully against one guild category and filter role that does not match against guild role', () => {
const filter = {
categories: ['hobbies_occupations'],
roles: ['guild_leader'],
};
const search = '';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(false);
});
it('returns true with no search word and filter category that matches successfully against one guild category and filter role that matches successfully against guild role', () => {
const filter = {
categories: ['hobbies_occupations'],
roles: ['guild_leader'],
};
const search = '';
expect(instance.filterGuild(testGroup2, filter, search, user)).to.equal(true);
});
it('returns false with one search word that does not match against guild name and one filter category that matches successfully against guild categories', () => {
const filter = {
categories: ['hobbies_occupations'],
};
const search = 'konnichiwa';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(false);
});
it('returns true with one search word that matches against guild name and one filter role that matches successfully against guild role', () => {
const filter = {
categories: ['hobbies_occupations'],
};
const search = 'vow';
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
});
});
});
+9 -2
View File
@@ -6,7 +6,7 @@
"reachedLevel": "Dosáhl jsi úrovně <%= level %>",
"achievementLostMasterclasser": "Dokončení výprav: Série Mistra třídy",
"achievementLostMasterclasserText": "Splnil všech šestnáct výprav v sérii výprav Mistra třídy a vyřešil záhadu Ztraceného Mistra!",
"achievementLostMasterclasserModalText": "Dokončil jsi všech 16 masterclass výprav a vyřešil jsi tajemství ztracené masterclass!",
"achievementLostMasterclasserModalText": "Dokončil jsi všech šestnáct výprav Mistra třídy a vyřešil jsi tajemství Ztraceného Mistra!",
"achievementMindOverMatter": "Mysl nad hmotou",
"achievementMindOverMatterText": "Dokončil/a kamennou, slizovou a vlněnou mazlíčkovou výpravu.",
"achievementMindOverMatterModalText": "Dokončil jsi kamennou, slizovou a vlněnou mazlíčkovou výpravu!",
@@ -55,5 +55,12 @@
"achievementCreatedTask": "Vytvořte úkol",
"earnedAchievement": "Získali jste úspěch!",
"viewAchievements": "Zobrazit úspěchy",
"letsGetStarted": "Začněme!"
"letsGetStarted": "Začněme!",
"foundNewItemsCTA": "Podívej se do Tvého Inventáře a zkus zkombinovat Tvůj nový líhnoucí lektvar a vajíčko!",
"foundNewItemsExplanation": "Splnění úkolů Ti dá šanci najít předměty, jako vajíčka, líhnoucí lektvary a jídlo.",
"foundNewItems": "Našel jsi nové předměty!",
"hideAchievements": "Schovat <%= kategorie %>",
"onboardingCompleteDesc": "Získáš <strong>5 ocenění</strong> a <strong class=\"gold-amount\">100</strong> zlaťáků za dokončení seznamu.",
"onboardingProgress": "<%= percentage %>% postup",
"gettingStartedDesc": "Vytvoř si úkol, splň jej a pak se podívej na své odměny. Dostaneš <strong>5 ocenění</strong> a <strong class=“gold-amount”>100 zlaťáků</strong>, jakmile budeš hotový!"
}
+2 -1
View File
@@ -72,5 +72,6 @@
"achievementTickledPink": "Rosige Bäckchen",
"foundNewItems": "Du hast neue Gegenstände gefunden!",
"foundNewItemsCTA": "Schau in Dein Inventar und versuche, Dein neues Schlüpfelixier mit einem Ei zu kombinieren!",
"foundNewItemsExplanation": "Durch das Abschließen von Aufgaben erhältst Du die Chance Gegenstände, wie etwa Eier, Schlüpfelixiere und Futter, zu finden."
"foundNewItemsExplanation": "Durch das Abschließen von Aufgaben erhältst Du die Chance Gegenstände, wie etwa Eier, Schlüpfelixiere und Futter, zu finden.",
"achievementBugBonanza": "Kostbarer Käfer"
}
+1 -1
View File
@@ -681,7 +681,7 @@
"questRubyCollectRubyGems": "Rubine",
"questRubyCollectVenusRunes": "Venus-Runen",
"questRubyCollectAquariusRunes": "Wassermann-Tierkreis-Runen",
"questRubyText": "Die Rubinrote Lösung",
"questRubyText": "Rubinrote Reaktion",
"questRubyCompletion": "Nachdem die notwendigen Gegenstände sicher verstaut sind, eilen Sie drei zurück nach Habit City und treffen sich in @ beffymaroos Labor. \"Ausgezeichnete Arbeit!\" @beffymaroo sagt. \"Du hast die Zutaten für den Trank gesammelt!“ <br> <br> @beffymaroo kombiniert sorgfältig die Runen und Rubine zu einem leuchtend roten Trank und gießt einen Teil davon auf zwei Haustier-Eier. Wenn Sie die Ergebnisse beobachten, bemerken Sie, dass die beiden Haustiere völlig uninteressiert aneinander zu sein scheinen! <br> <br> \"Hat es nicht funktioniert?“ @Gully fragt. Aber bevor jemand antworten kann, merkt man plötzlich, dass es nicht der Trank ist, der Freundschaft und Liebe schafft, sondern die Erfahrung, gemeinsam auf ein gemeinsames Ziel hinzuarbeiten. Du kommst von der Suche weg, nachdem du neue Freunde gewonnen hast ... und einige auffällige neue Haustiere!",
"questRubyNotes": "Die normalerweise geschäftigen Gipfel der Stoïkalm-Vulkane liegen still im Schnee. \"Ich nehme an, die Wanderer und Seher halten Winterschlaf?\" @gully sagt zu dir und @Aspiring_Advocate. \"Das erleichtert uns die Suche.\" <br> <br> Wenn Sie den Gipfel erreichen, verschmilzt der kühle Wind mit dem Dampf, der aus dem Krater aufsteigt. \"Dort!\" @Aspiring_Advocate ruft aus und zeigt auf eine heiße Quelle. \"Welchen besseren Ort gibt es, um kühle Runen des Wassermanns und leidenschaftliche Runen der Venus zu finden, als wo sich Eis und Feuer treffen?“ <br> <br> Sie drei beeilen sich in Richtung der heißen Quelle. \"Laut meiner Forschung\", sagt @Aspiring_Advocate, \"wird die Kombination der Runen mit herzförmigen Rubinen einen Schlupftrank erzeugen, der Freundschaft und Liebe fördern kann!\" <br> <br> Aufgeregt von der Aussicht auf eine neue Entdeckung, Sie alle Lächeln. \"In Ordnung\", sagt @gully, \"fangen wir an zu suchen!\""
}
+1 -1
View File
@@ -203,7 +203,7 @@
"goToSettings": "Gehe zu Einstellungen",
"usernameVerifiedConfirmation": "Dein Benutzername, <%= username %>, ist bestätigt!",
"usernameNotVerified": "Bitte bestätige Deinen Benutzernamen.",
"changeUsernameDisclaimer": "Dieser Benutzername wird für Einladungen, @Erwähnungen im Chat und Nachrichten verwendet werden.",
"changeUsernameDisclaimer": "Dein Benutzername wird für Einladungen, @Erwähnungen im Chat und Nachrichten verwendet. Er muss zwischen 1 und 20 Zeichen haben, darf nur Buchstaben von a bis z, Ziffern von 0 bis 9, Bindestriche oder Unterstriche beinhalten und darf keine unpassenden Ausdrücke enthalten.",
"verifyUsernameVeteranPet": "Eines dieser Veteranen-Haustiere wartet auf Dich wenn Du die Bestätigung abgeschlossen hast!",
"subscriptionReminders": "Abonnement-Erinnerung",
"newPMNotificationTitle": "Neue Nachricht von <%= name %>",
+1 -1
View File
@@ -13,7 +13,7 @@
"showAllAchievements": "Show All <%= category %>",
"hideAchievements": "Hide <%= category %>",
"foundNewItems": "You found new items!",
"foundNewItemsExplanation": "Completing tasks gives you a chance to find items, like eggs, hatching potions, and food.",
"foundNewItemsExplanation": "Completing tasks gives you a chance to find items, like Eggs, Hatching Potions, and Pet Food.",
"foundNewItemsCTA": "Head to your Inventory and try combining your new hatching potion and egg!",
"achievementLostMasterclasser": "Quest Completionist: Masterclasser Series",
"achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!",
+1
View File
@@ -298,6 +298,7 @@
"hatchingPotionAmber": "Amber",
"hatchingPotionAurora": "Aurora",
"hatchingPotionRuby": "Ruby",
"hatchingPotionBirchBark": "Birch Bark",
"hatchingPotionNotes": "Pour this on an egg, and it will hatch as a <%= potText(locale) %> pet.",
"premiumPotionAddlNotes": "Not usable on quest pet eggs. Available for purchase until <%= date(locale) %>.",
+36
View File
@@ -350,6 +350,15 @@
"weaponSpecialWinter2020HealerText": "Clove Scepter",
"weaponSpecialWinter2020HealerNotes": "Wave it about, and its aroma will summon your friends and helpers to begin cooking and baking! Increases Intelligence by <%= int %>. Limited Edition 2019-2020 Winter Gear.",
"weaponSpecialSpring2020RogueText": "Lazurite Blade",
"weaponSpecialSpring2020RogueNotes": "You'll strike so fast it'll look even MORE blue! Increases Strength by <%= str %>. Limited Edition 2020 Spring Gear.",
"weaponSpecialSpring2020WarriorText": "Sharpened Wing",
"weaponSpecialSpring2020WarriorNotes": "Fight or flight, this wing will serve you well! Increases Strength by <%= str %>. Limited Edition 2020 Spring Gear.",
"weaponSpecialSpring2020MageText": "Raindrops",
"weaponSpecialSpring2020MageNotes": "They keep falling on your head! But you'll never stop them by complaining. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2020 Spring Gear.",
"weaponSpecialSpring2020HealerText": "Sword-Lily Staff",
"weaponSpecialSpring2020HealerNotes": "An iris is beautiful, but the leaves are like swords... don't be deceived by the flowers, this staff is tough as steel! Increases Intelligence by <%= int %>. Limited Edition 2020 Spring Gear.",
"weaponMystery201411Text": "Pitchfork of Feasting",
"weaponMystery201411Notes": "Stab your enemies or dig in to your favorite foods - this versatile pitchfork does it all! Confers no benefit. November 2014 Subscriber Item.",
"weaponMystery201502Text": "Shimmery Winged Staff of Love and Also Truth",
@@ -809,6 +818,15 @@
"armorSpecialWinter2020HealerText": "Orange Peel Gown",
"armorSpecialWinter2020HealerNotes": "An opulent gown for those with festive zest! Increases Constitution by <%= con %>. Limited Edition 2019-2020 Winter Gear.",
"armorSpecialSpring2020RogueText": "Ultramarine Armor",
"armorSpecialSpring2020RogueNotes": "The color of twilight, of a multitude of precious stones, of the deepest sea! Increases Perception by <%= per %>. Limited Edition 2020 Spring Gear.",
"armorSpecialSpring2020WarriorText": "Exoskeleton Armor",
"armorSpecialSpring2020WarriorNotes": "This rigid carapace can keep you safe from even the most crushing attacks. Increases Constitution by <%= con %>. Limited Edition 2020 Spring Gear.",
"armorSpecialSpring2020MageText": "Whirlpuddle Gown",
"armorSpecialSpring2020MageNotes": "If you can't resist stomping through the leavings of rainstorms, this armor is for you! Turn a childish impulse into a display of mystic artistry. Increases Intelligence by <%= int %>. Limited Edition 2020 Spring Gear.",
"armorSpecialSpring2020HealerText": "Protective Petals",
"armorSpecialSpring2020HealerNotes": "Wrap yourself in soft iris leaves and petals to fool enemies into underestimating your healing power. Increases Constitution by <%= con %>. Limited Edition 2020 Spring Gear.",
"armorMystery201402Text": "Messenger Robes",
"armorMystery201402Notes": "Shimmering and strong, these robes have many pockets to carry letters. Confers no benefit. February 2014 Subscriber Item.",
"armorMystery201403Text": "Forest Walker Armor",
@@ -1361,6 +1379,15 @@
"headSpecialWinter2020HealerText": "Star Anise Emblem",
"headSpecialWinter2020HealerNotes": "Please remove it from your head before attempting to brew chai or coffee with it. Increases Intelligence by <%= int %>. Limited Edition 2019-2020 Winter Gear.",
"headSpecialSpring2020RogueText": "Lapis Kabuto",
"headSpecialSpring2020RogueNotes": "So vibrant and valuable, you'll be tempted to steal it off your own head. Increases Perception by <%= per %>. Limited Edition 2020 Spring Gear.",
"headSpecialSpring2020WarriorText": "Beetle Helm",
"headSpecialSpring2020WarriorNotes": "Your enemies' blows will glance off this beetle-inspired helm! Increases Strength by <%= str %>. Limited Edition 2020 Spring Gear.",
"headSpecialSpring2020MageText": "Drip Top Cap",
"headSpecialSpring2020MageNotes": "Is the sky clear? Humidity low? Don't worry, we've got you. Moisten your magic without dampening your spirits! Increases Perception by <%= per %>. Limited Edition 2020 Spring Gear.",
"headSpecialSpring2020HealerText": "Iris Fascinator",
"headSpecialSpring2020HealerNotes": "Beguile your foes with this headpiece made of flowers! Increases Intelligence by <%= int %>. Limited Edition 2020 Spring Gear.",
"headSpecialGaymerxText": "Rainbow Warrior Helm",
"headSpecialGaymerxNotes": "In celebration of the GaymerX Conference, this special helmet is decorated with a radiant, colorful rainbow pattern! GaymerX is a game convention celebrating LGTBQ and gaming and is open to everyone.",
@@ -1814,6 +1841,11 @@
"shieldSpecialWinter2020HealerText": "Giant Cinnamon Stick",
"shieldSpecialWinter2020HealerNotes": "Do you feel you are too good for this world, too pure? Only this beauty of a spice will do. Increases Constitution by <%= con %>. Limited Edition 2019-2020 Winter Gear.",
"shieldSpecialSpring2020WarriorText": "Iridescent Shield",
"shieldSpecialSpring2020WarriorNotes": "Don't let the delicate colors fool you. This shield has got you covered! Increases Constitution by <%= con %>. Limited Edition 2020 Spring Gear.",
"shieldSpecialSpring2020HealerText": "Perfumed Shield",
"shieldSpecialSpring2020HealerNotes": "Ward off those musty old To-Dos with this sweet-smelling shield. Increases Constitution by <%= con %>. Limited Edition 2020 Spring Gear.",
"shieldMystery201601Text": "Resolution Slayer",
"shieldMystery201601Notes": "This blade can be used to parry away all distractions. Confers no benefit. January 2016 Subscriber Item.",
"shieldMystery201701Text": "Time-Freezer Shield",
@@ -1952,6 +1984,8 @@
"backMystery201912Notes": "Glide silently across sparkling snowfields and shimmering mountains with these icy wings. Confers no benefit. December 2019 Subscriber Item.",
"backMystery202001Text": "Five Tails of Fable",
"backMystery202001Notes": "These fluffy tails contain celestial power, and also a high level of cuteness! Confers no benefit. January 2020 Subscriber Item.",
"backMystery202004Text": "Mighty Monarch Wings",
"backMystery202004Notes": "Make a quick flutter to the nearest flowery meadow or migrate across the continent with these beautiful wings! Confers no benefit. April 2020 Subscriber Item.",
"backSpecialWonderconRedText": "Mighty Cape",
"backSpecialWonderconRedNotes": "Swishes with strength and beauty. Confers no benefit. Special Edition Convention Item.",
@@ -2130,6 +2164,8 @@
"headAccessoryMystery201906Notes": "Legend has it these finny ears help merfolk hear the calls and songs of all the denizens of the deep! Confers no benefit. June 2019 Subscriber Item.",
"headAccessoryMystery201908Text": "Footloose Faun Horns",
"headAccessoryMystery201908Notes": "If wearing horns floats your goat, you're in luck! Confers no benefit. August 2019 Subscriber Item.",
"headAccessoryMystery202004Text": "Mighty Monarch Antennae",
"headAccessoryMystery202004Notes": "They twitch just a bit if the scent of flowers drifts by--use them to find a pretty garden! Confers no benefit. April 2020 Subscriber Item.",
"headAccessoryMystery301405Text": "Headwear Goggles",
"headAccessoryMystery301405Notes": "\"Goggles are for your eyes,\" they said. \"Nobody wants goggles that you can only wear on your head,\" they said. Hah! You sure showed them! Confers no benefit. August 3015 Subscriber Item.",
+31 -26
View File
@@ -85,45 +85,45 @@
"scarecrowWarriorSet": "Scarecrow Warrior (Warrior)",
"stitchWitchSet": "Stitch Witch (Mage)",
"potionerSet": "Potioner (Healer)",
"battleRogueSet": "Bat-tle Rogue (Rogue)",
"battleRogueSet": "Bat-tle (Rogue)",
"springingBunnySet": "Springing Bunny (Healer)",
"grandMalkinSet": "Grand Malkin (Mage)",
"cleverDogSet": "Clever Dog (Rogue)",
"braveMouseSet": "Brave Mouse (Warrior)",
"summer2016SharkWarriorSet": "Shark Warrior (Warrior)",
"summer2016DolphinMageSet": "Dolphin Mage (Mage)",
"summer2016SeahorseHealerSet": "Seahorse Healer (Healer)",
"summer2016EelSet": "Eel Rogue (Rogue)",
"summer2016SharkWarriorSet": "Shark (Warrior)",
"summer2016DolphinMageSet": "Dolphin (Mage)",
"summer2016SeahorseHealerSet": "Seahorse (Healer)",
"summer2016EelSet": "Eel (Rogue)",
"fall2016SwampThingSet": "Swamp Thing (Warrior)",
"fall2016WickedSorcererSet": "Wicked Sorcerer (Mage)",
"fall2016GorgonHealerSet": "Gorgon Healer (Healer)",
"fall2016BlackWidowSet": "Black Widow Rogue (Rogue)",
"fall2016GorgonHealerSet": "Gorgon (Healer)",
"fall2016BlackWidowSet": "Black Widow (Rogue)",
"winter2017IceHockeySet": "Ice Hockey (Warrior)",
"winter2017WinterWolfSet": "Winter Wolf (Mage)",
"winter2017SugarPlumSet": "Sugar Plum Healer (Healer)",
"winter2017FrostyRogueSet": "Frosty Rogue (Rogue)",
"spring2017FelineWarriorSet": "Feline Warrior (Warrior)",
"winter2017SugarPlumSet": "Sugar Plum (Healer)",
"winter2017FrostyRogueSet": "Frosty (Rogue)",
"spring2017FelineWarriorSet": "Feline (Warrior)",
"spring2017CanineConjurorSet": "Canine Conjuror (Mage)",
"spring2017FloralMouseSet": "Floral Mouse (Healer)",
"spring2017SneakyBunnySet": "Sneaky Bunny (Rogue)",
"summer2017SandcastleWarriorSet": "Sandcastle Warrior (Warrior)",
"summer2017WhirlpoolMageSet": "Whirlpool Mage (Mage)",
"summer2017SandcastleWarriorSet": "Sandcastle (Warrior)",
"summer2017WhirlpoolMageSet": "Whirlpool (Mage)",
"summer2017SeashellSeahealerSet": "Seashell Seahealer (Healer)",
"summer2017SeaDragonSet": "Sea Dragon (Rogue)",
"fall2017HabitoweenSet": "Habitoween Warrior (Warrior)",
"fall2017MasqueradeSet": "Masquerade Mage (Mage)",
"fall2017HauntedHouseSet": "Haunted House Healer (Healer)",
"fall2017TrickOrTreatSet": "Trick or Treat Rogue (Rogue)",
"winter2018ConfettiSet": "Confetti Mage (Mage)",
"winter2018GiftWrappedSet": "Gift-Wrapped Warrior (Warrior)",
"winter2018MistletoeSet": "Mistletoe Healer (Healer)",
"winter2018ReindeerSet": "Reindeer Rogue (Rogue)",
"spring2018SunriseWarriorSet": "Sunrise Warrior (Warrior)",
"spring2018TulipMageSet": "Tulip Mage (Mage)",
"spring2018GarnetHealerSet": "Garnet Healer (Healer)",
"spring2018DucklingRogueSet": "Duckling Rogue (Rogue)",
"summer2018BettaFishWarriorSet": "Betta Fish Warrior (Warrior)",
"summer2018LionfishMageSet": "Lionfish Mage (Mage)",
"fall2017HabitoweenSet": "Habitoween (Warrior)",
"fall2017MasqueradeSet": "Masquerade (Mage)",
"fall2017HauntedHouseSet": "Haunted House (Healer)",
"fall2017TrickOrTreatSet": "Trick or Treat (Rogue)",
"winter2018ConfettiSet": "Confetti (Mage)",
"winter2018GiftWrappedSet": "Gift-Wrapped (Warrior)",
"winter2018MistletoeSet": "Mistletoe (Healer)",
"winter2018ReindeerSet": "Reindeer (Rogue)",
"spring2018SunriseWarriorSet": "Sunrise (Warrior)",
"spring2018TulipMageSet": "Tulip (Mage)",
"spring2018GarnetHealerSet": "Garnet (Healer)",
"spring2018DucklingRogueSet": "Duckling (Rogue)",
"summer2018BettaFishWarriorSet": "Betta Fish (Warrior)",
"summer2018LionfishMageSet": "Lionfish (Mage)",
"summer2018MerfolkMonarchSet": "Merfolk Monarch (Healer)",
"summer2018FisherRogueSet": "Fisher-Rogue (Rogue)",
"fall2018MinotaurWarriorSet": "Minotaur (Warrior)",
@@ -151,6 +151,10 @@
"winter2020CarolOfTheMageSet": "Carol of the Mage (Mage)",
"winter2020WinterSpiceSet": "Winter Spice (Healer)",
"winter2020LanternSet": "Lantern (Rogue)",
"spring2020BeetleWarriorSet": "Rhinoceros Beetle (Warrior)",
"spring2020PuddleMageSet": "Puddle (Mage)",
"spring2020IrisHealerSet": "Iris (Healer)",
"spring2020LapisLazuliRogueSet": "Lapis Lazuli (Rogue)",
"eventAvailability": "Available for purchase until <%= date(locale) %>.",
"eventAvailabilityReturning": "Available for purchase until <%= availableDate(locale) %>. This potion was last available in <%= previousDate(locale) %>.",
"dateEndMarch": "April 30",
@@ -159,6 +163,7 @@
"dateEndJune": "June 14",
"augustYYYY": "August <%= year %>",
"decemberYYYY": "December <%= year %>",
"marchYYYY": "March <%= year %>",
"dateEndJuly": "July 31",
"dateEndAugust": "August 31",
"dateEndSeptember": "September 21",
+2 -2
View File
@@ -271,7 +271,7 @@
"questGroupDilatoryDistress": "Dilatory Distress",
"questDilatoryDistress1Text": "Dilatory Distress, Part 1: Message in a Bottle",
"questDilatoryDistress1Notes": "A message in a bottle arrived from the newly rebuilt city of Dilatory! It reads: \"Dear Habiticans, we need your help once again. Our princess has disappeared and the city is under siege by some unknown watery demons! The mantis shrimps are holding the attackers at bay. Please aid us!\" To make the long journey to the sunken city, one must be able to breathe water. Fortunately, the alchemists @Benga and @hazel can make it all possible! You only have to find the proper ingredients.",
"questDilatoryDistress1Completion": "You don the the finned armor and swim to Dilatory as quickly as you can. The merfolk and their mantis shrimp allies have managed to keep the monsters outside the city for the moment, but they are losing. No sooner are you within the castle walls than the horrifying siege descends!",
"questDilatoryDistress1Completion": "You don the finned armor and swim to Dilatory as quickly as you can. The merfolk and their mantis shrimp allies have managed to keep the monsters outside the city for the moment, but they are losing. No sooner are you within the castle walls than the horrifying siege descends!",
"questDilatoryDistress1CollectFireCoral": "Fire Coral",
"questDilatoryDistress1CollectBlueFins": "Blue Fins",
"questDilatoryDistress1DropArmor": "Finned Oceanic Armor (Armor)",
@@ -661,7 +661,7 @@
"questBadgerText": "Stop Badgering Me!",
"questBadgerNotes": "Ah, winter in the Taskwoods. The softly falling snow, the branches sparkling with frost, the Flourishing Fairies… still not snoozing?<br><br>“Why are they still awake?” cries @LilithofAlfheim. “If they don't hibernate soon, they'll never have the energy for planting season.”<br><br>As you and @Willow the Witty hurry to investigate, a furry head pops up from the ground. Before you can yell, “Its the Badgering Bother!” its back in its burrow—but not before snatching up the Fairies' “Hibernate” To-Dos and dropping a giant list of pesky tasks in their place!<br><br>“No wonder the fairies aren't resting, if they're constantly being badgered like that!” @plumilla says. Can you chase off this beast and save the Taskwoods harvest this year?",
"questBadgerCompletion": "You finally drive away the the Badgering Bother and hurry into its burrow. At the end of a tunnel, you find its hoard of the faeries “Hibernate” To-Dos. The den is otherwise abandoned, except for three eggs that look ready to hatch.",
"questBadgerCompletion": "You finally drive away the Badgering Bother and hurry into its burrow. At the end of a tunnel, you find its hoard of the faeries “Hibernate” To-Dos. The den is otherwise abandoned, except for three eggs that look ready to hatch.",
"questBadgerBoss": "The Badgering Bother",
"questBadgerDropBadgerEgg": "Badger (Egg)",
"questBadgerUnlockText": "Unlocks Badger Eggs for purchase in the Market",
@@ -172,6 +172,7 @@
"mysterySet202001": "Fabled Fox Set",
"mysterySet202002": "Stylish Sweetheart Set",
"mysterySet202003": "Barbed Battler Set",
"mysterySet202004": "Mighty Monarch Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySet301703": "Peacock Steampunk Set",
@@ -69,5 +69,11 @@
"achievementRosyOutlook": "Rosy Lookout",
"achievementTickledPinkModalText": "Ye've collected all th' Cotton Candy Pink Critters!",
"achievementTickledPinkText": "'as collected all Cotton Candy Pink Critters.",
"achievementTickledPink": "Pickled Pink"
"achievementTickledPink": "Pickled Pink",
"foundNewItemsCTA": "Head t' yer inventory an' try combinin' yer new 'atchin' potion an' egg!",
"foundNewItemsExplanation": "Completin' tasks gives ye a chance ta find new items, like eggs, 'atchin' potions, an' vittles.",
"foundNewItems": "Ye found somethin' new!",
"achievementBugBonanzaModalText": "Ye've kermpleted th' Beetle, Butterfly, Snail, an' Spidey pet quests!",
"achievementBugBonanzaText": "'as kermpleted Beetle, Butterfly, Snail, an' Spidey pet quests.",
"achievementBugBonanza": "Crawly Catcher"
}
+3 -1
View File
@@ -1813,5 +1813,7 @@
"weaponSpecialWinter2020MageNotes": "Wiv practice, ye kin perject this aural magic (in waves! Like th' sea!!) any way ye like: a thoughtful hum, a festive chime, er a RED TASK O'ERBOARD ALARM. Raises yer Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2019-2020 Winter Gear.",
"weaponSpecialWinter2020MageText": "Ripplin' Waves o' Sound",
"weaponSpecialWinter2020WarriorNotes": "Avast, squirrels! Ye'll get no piece o' this! ...But iffen ye wanna hang out an' 'ave cocoa, that be cool. Raises yer Strength by <%= str %>. Limited Edition 2019-2020 Winter Gear.",
"weaponSpecialWinter2020WarriorText": "Pointy Conny-fer Cone"
"weaponSpecialWinter2020WarriorText": "Pointy Conny-fer Cone",
"weaponSpecialSpring2020RogueNotes": "Ye'll strike so fast it'll look e'en MORE blue! Raises yer Strength by <%= str %>. Limited Edition 2020 Spring Gear.",
"weaponSpecialSpring2020RogueText": "Laz-yer-rite Blade"
}
@@ -71,6 +71,9 @@
"achievementTickledPinkText": "Has collected all Cotton Candy Pink Pets.",
"achievementTickledPink": "Tickled Pink",
"foundNewItemsCTA": "Head to your Inventory and try combining your new hatching potion and egg!",
"foundNewItemsExplanation": "Completing tasks gives you a chance to find items, like eggs, hatching potions, and food.",
"foundNewItems": "You found new items!"
"foundNewItemsExplanation": "Completing tasks gives you a chance to find items, like Eggs, Hatching Potions, and Pet Food.",
"foundNewItems": "You found new items!",
"achievementBugBonanzaModalText": "You completed the Beetle, Butterfly, Snail, and Spider pet quests!",
"achievementBugBonanzaText": "Has completed Beetle, Butterfly, Snail, and Spider pet quests.",
"achievementBugBonanza": "Bug Bonanza"
}
+2 -1
View File
@@ -354,5 +354,6 @@
"questEggDolphinAdjective": "a chipper",
"questEggDolphinMountText": "Dolphin",
"questEggDolphinText": "Dolphin",
"hatchingPotionRuby": "Ruby"
"hatchingPotionRuby": "Ruby",
"hatchingPotionBirchBark": "Birch Bark"
}
+29 -1
View File
@@ -2051,5 +2051,33 @@
"armorArmoireBaseballUniformNotes": "Pinstripes never go out of style. Increases Constitution and Strength by <%= attrs %> each. Enchanted Armoire: Baseball Set (Item 2 of 4).",
"armorArmoireBaseballUniformText": "Baseball Uniform",
"weaponArmoireBaseballBatNotes": "Get a home run on those good habits! Increases Constitution by <%= con %>. Enchanted Armoire: Baseball Set (Item 3 of 4).",
"weaponArmoireBaseballBatText": "Baseball Bat"
"weaponArmoireBaseballBatText": "Baseball Bat",
"shieldSpecialSpring2020HealerNotes": "Ward off those musty old To-Dos with this sweet-smelling shield. Increases Constitution by <%= con %>. Limited Edition 2020 Spring Gear.",
"shieldSpecialSpring2020HealerText": "Perfumed Shield",
"shieldSpecialSpring2020WarriorNotes": "Don't let the delicate colors fool you. This shield has got you covered! Increases Constitution by <%= con %>. Limited Edition 2020 Spring Gear.",
"shieldSpecialSpring2020WarriorText": "Iridescent Shield",
"headSpecialSpring2020HealerNotes": "Beguile your foes with this headpiece made of flowers! Increases Intelligence by <%= int %>. Limited Edition 2020 Spring Gear.",
"headSpecialSpring2020HealerText": "Iris Fascinator",
"headSpecialSpring2020MageNotes": "Is the sky clear? Humidity low? Don't worry, we've got you. Moisten your magic without dampening your spirits! Increases Perception by <%= per %>. Limited Edition 2020 Spring Gear.",
"headSpecialSpring2020MageText": "Drip Top Cap",
"headSpecialSpring2020WarriorNotes": "Your enemies' blows will glance off this beetle-inspired helm! Increases Strength by <%= str %>. Limited Edition 2020 Spring Gear.",
"headSpecialSpring2020WarriorText": "Beetle Helm",
"headSpecialSpring2020RogueNotes": "So vibrant and valuable, you'll be tempted to steal it off your own head. Increases Perception by <%= per %>. Limited Edition 2020 Spring Gear.",
"headSpecialSpring2020RogueText": "Lapis Kabuto",
"armorSpecialSpring2020HealerNotes": "Wrap yourself in soft iris leaves and petals to fool enemies into underestimating your healing power. Increases Constitution by <%= con %>. Limited Edition 2020 Spring Gear.",
"armorSpecialSpring2020HealerText": "Protective Petals",
"armorSpecialSpring2020MageNotes": "If you can't resist stomping through the leavings of rainstorms, this armour is for you! Turn a childish impulse into a display of mystic artistry. Increases Intelligence by <%= int %>. Limited Edition 2020 Spring Gear.",
"armorSpecialSpring2020MageText": "Whirlpuddle Gown",
"armorSpecialSpring2020WarriorNotes": "This rigid carapace can keep you safe from even the most crushing attacks. Increases Constitution by <%= con %>. Limited Edition 2020 Spring Gear.",
"armorSpecialSpring2020WarriorText": "Exoskeleton Armour",
"armorSpecialSpring2020RogueNotes": "The colour of twilight, of a multitude of precious stones, of the deepest sea! Increases Perception by <%= per %>. Limited Edition 2020 Spring Gear.",
"armorSpecialSpring2020RogueText": "Ultramarine Armour",
"weaponSpecialSpring2020HealerNotes": "An iris is beautiful, but the leaves are like swords... don't be deceived by the flowers, this staff is tough as steel! Increases Intelligence by <%= int %>. Limited Edition 2020 Spring Gear.",
"weaponSpecialSpring2020HealerText": "Sword-Lily Staff",
"weaponSpecialSpring2020MageNotes": "They keep falling on your head! But you'll never stop them by complaining. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2020 Spring Gear.",
"weaponSpecialSpring2020MageText": "Raindrops",
"weaponSpecialSpring2020WarriorNotes": "Fight or flight, this wing will serve you well! Increases Strength by <%= str %>. Limited Edition 2020 Spring Gear.",
"weaponSpecialSpring2020WarriorText": "Sharpened Wing",
"weaponSpecialSpring2020RogueNotes": "You'll strike so fast it'll look even MORE blue! Increases Strength by <%= str %>. Limited Edition 2020 Spring Gear.",
"weaponSpecialSpring2020RogueText": "Lazurite Blade"
}
+32 -27
View File
@@ -85,45 +85,45 @@
"scarecrowWarriorSet": "Scarecrow Warrior (Warrior)",
"stitchWitchSet": "Stitch Witch (Mage)",
"potionerSet": "Potioner (Healer)",
"battleRogueSet": "Bat-tle Rogue (Rogue)",
"battleRogueSet": "Bat-tle (Rogue)",
"springingBunnySet": "Springing Bunny (Healer)",
"grandMalkinSet": "Grand Malkin (Mage)",
"cleverDogSet": "Clever Dog (Rogue)",
"braveMouseSet": "Brave Mouse (Warrior)",
"summer2016SharkWarriorSet": "Shark Warrior (Warrior)",
"summer2016DolphinMageSet": "Dolphin Mage (Mage)",
"summer2016SeahorseHealerSet": "Seahorse Healer (Healer)",
"summer2016EelSet": "Eel Rogue (Rogue)",
"summer2016SharkWarriorSet": "Shark (Warrior)",
"summer2016DolphinMageSet": "Dolphin (Mage)",
"summer2016SeahorseHealerSet": "Seahorse (Healer)",
"summer2016EelSet": "Eel (Rogue)",
"fall2016SwampThingSet": "Swamp Thing (Warrior)",
"fall2016WickedSorcererSet": "Wicked Sorcerer (Mage)",
"fall2016GorgonHealerSet": "Gorgon Healer (Healer)",
"fall2016BlackWidowSet": "Black Widow Rogue (Rogue)",
"fall2016GorgonHealerSet": "Gorgon (Healer)",
"fall2016BlackWidowSet": "Black Widow (Rogue)",
"winter2017IceHockeySet": "Ice Hockey (Warrior)",
"winter2017WinterWolfSet": "Winter Wolf (Mage)",
"winter2017SugarPlumSet": "Sugar Plum Healer (Healer)",
"winter2017FrostyRogueSet": "Frosty Rogue (Rogue)",
"spring2017FelineWarriorSet": "Feline Warrior (Warrior)",
"winter2017SugarPlumSet": "Sugar Plum (Healer)",
"winter2017FrostyRogueSet": "Frosty (Rogue)",
"spring2017FelineWarriorSet": "Feline (Warrior)",
"spring2017CanineConjurorSet": "Canine Conjuror (Mage)",
"spring2017FloralMouseSet": "Floral Mouse (Healer)",
"spring2017SneakyBunnySet": "Sneaky Bunny (Rogue)",
"summer2017SandcastleWarriorSet": "Sandcastle Warrior (Warrior)",
"summer2017WhirlpoolMageSet": "Whirlpool Mage (Mage)",
"summer2017SandcastleWarriorSet": "Sandcastle (Warrior)",
"summer2017WhirlpoolMageSet": "Whirlpool (Mage)",
"summer2017SeashellSeahealerSet": "Seashell Seahealer (Healer)",
"summer2017SeaDragonSet": "Sea Dragon (Rogue)",
"fall2017HabitoweenSet": "Habitoween Warrior (Warrior)",
"fall2017MasqueradeSet": "Masquerade Mage (Mage)",
"fall2017HauntedHouseSet": "Haunted House Healer (Healer)",
"fall2017TrickOrTreatSet": "Trick or Treat Rogue (Rogue)",
"winter2018ConfettiSet": "Confetti Mage (Mage)",
"winter2018GiftWrappedSet": "Gift-Wrapped Warrior (Warrior)",
"winter2018MistletoeSet": "Mistletoe Healer (Healer)",
"winter2018ReindeerSet": "Reindeer Rogue (Rogue)",
"spring2018SunriseWarriorSet": "Sunrise Warrior (Warrior)",
"spring2018TulipMageSet": "Tulip Mage (Mage)",
"spring2018GarnetHealerSet": "Garnet Healer (Healer)",
"spring2018DucklingRogueSet": "Duckling Rogue (Rogue)",
"summer2018BettaFishWarriorSet": "Betta Fish Warrior (Warrior)",
"summer2018LionfishMageSet": "Lionfish Mage (Mage)",
"fall2017HabitoweenSet": "Habitoween (Warrior)",
"fall2017MasqueradeSet": "Masquerade (Mage)",
"fall2017HauntedHouseSet": "Haunted House (Healer)",
"fall2017TrickOrTreatSet": "Trick or Treat (Rogue)",
"winter2018ConfettiSet": "Confetti (Mage)",
"winter2018GiftWrappedSet": "Gift-Wrapped (Warrior)",
"winter2018MistletoeSet": "Mistletoe (Healer)",
"winter2018ReindeerSet": "Reindeer (Rogue)",
"spring2018SunriseWarriorSet": "Sunrise (Warrior)",
"spring2018TulipMageSet": "Tulip (Mage)",
"spring2018GarnetHealerSet": "Garnet (Healer)",
"spring2018DucklingRogueSet": "Duckling (Rogue)",
"summer2018BettaFishWarriorSet": "Betta Fish (Warrior)",
"summer2018LionfishMageSet": "Lionfish (Mage)",
"summer2018MerfolkMonarchSet": "Merfolk Monarch (Healer)",
"summer2018FisherRogueSet": "Fisher-Rogue (Rogue)",
"fall2018MinotaurWarriorSet": "Minotaur (Warrior)",
@@ -173,5 +173,10 @@
"summer2019ConchHealerSet": "Conch (Healer)",
"summer2019WaterLilyMageSet": "Water Lily (Mage)",
"summer2019SeaTurtleWarriorSet": "Sea Turtle (Warrior)",
"june2018": "June 2018"
"june2018": "June 2018",
"marchYYYY": "March <%= year %>",
"spring2020LapisLazuliRogueSet": "Lapis Lazuli (Rogue)",
"spring2020IrisHealerSet": "Iris (Healer)",
"spring2020PuddleMageSet": "Puddle (Mage)",
"spring2020BeetleWarriorSet": "Rhinoceros Beetle (Warrior)"
}
@@ -231,7 +231,7 @@
"questGroupDilatoryDistress": "Dilatory Distress",
"questDilatoryDistress1Text": "Dilatory Distress, Part 1: Message in a Bottle",
"questDilatoryDistress1Notes": "A message in a bottle arrived from the newly rebuilt city of Dilatory! It reads: \"Dear Habiticans, we need your help once again. Our princess has disappeared and the city is under siege by some unknown watery demons! The mantis shrimps are holding the attackers at bay. Please aid us!\" To make the long journey to the sunken city, one must be able to breathe water. Fortunately, the alchemists @Benga and @hazel can make it all possible! You only have to find the proper ingredients.",
"questDilatoryDistress1Completion": "You don the the finned armour and swim to Dilatory as quickly as you can. The merfolk and their mantis shrimp allies have managed to keep the monsters outside the city for the moment, but they are losing. No sooner are you within the castle walls than the horrifying siege descends!",
"questDilatoryDistress1Completion": "You don the finned armor and swim to Dilatory as quickly as you can. The merfolk and their mantis shrimp allies have managed to keep the monsters outside the city for the moment, but they are losing. No sooner are you within the castle walls than the horrifying siege descends!",
"questDilatoryDistress1CollectFireCoral": "Fire Coral",
"questDilatoryDistress1CollectBlueFins": "Blue Fins",
"questDilatoryDistress1DropArmor": "Finned Oceanic Armour (Armour)",
@@ -568,7 +568,7 @@
"questPterodactylUnlockText": "Unlocks Pterodactyl Eggs for purchase in the Market",
"questBadgerText": "Stop Badgering Me!",
"questBadgerNotes": "Ah, winter in the Taskwoods. The softly falling snow, the branches sparkling with frost, the Flourishing Fairies… still not snoozing?<br><br>“Why are they still awake?” cries @LilithofAlfheim. “If they don't hibernate soon, they'll never have the energy for planting season.”<br><br>As you and @Willow the Witty hurry to investigate, a furry head pops up from the ground. Before you can yell, “Its the Badgering Bother!” its back in its burrow—but not before snatching up the Fairies' “Hibernate” To-Dos and dropping a giant list of pesky tasks in their place!<br><br>“No wonder the fairies aren't resting, if they're constantly being badgered like that!” @plumilla says. Can you chase off this beast and save the Taskwoods harvest this year?",
"questBadgerCompletion": "You finally drive away the the Badgering Bother and hurry into its burrow. At the end of a tunnel, you find its hoard of the faeries “Hibernate” To-Dos. The den is otherwise abandoned, except for three eggs that look ready to hatch.",
"questBadgerCompletion": "You finally drive away the Badgering Bother and hurry into its burrow. At the end of a tunnel, you find its hoard of the faeries “Hibernate” To-Dos. The den is otherwise abandoned, except for three eggs that look ready to hatch.",
"questBadgerBoss": "The Badgering Bother",
"questBadgerDropBadgerEgg": "Badger (Egg)",
"questBadgerUnlockText": "Unlocks Badger Eggs for purchase in the Market",
@@ -683,5 +683,5 @@
"questRubyCollectAquariusRunes": "Aquarius Zodiac Runes",
"questRubyCompletion": "With the necessary items safely packed away, the three of you rush back to Habit City and meet in @beffymaroo's lab. “Excellent work!” @beffymaroo says. “You've gathered the ingredients for the potion!”<br><br>@beffymaroo carefully combines the runes and the rubies to create a brilliant red potion and pours some of it on two pet eggs. As you observe the results, you notice that the two pets seem completely uninterested in one another!<br><br>“Did it not work?” @gully asks. But before anyone can answer, you suddenly realize that it isn't the potion that creates friendship and love, but rather it is the experience of working together toward a common goal. You come away from the quest having gained some new friends...and some flashy new pets!",
"questRubyNotes": "The normally bustling peaks of the Stoïkalm Volcanoes lie silent in the snow. “I suppose the hikers and sight-seers are hibernating?” @gully says to you and @Aspiring_Advocate. “That makes our search easier.”<br><br>As you reach the summit, the chill wind merges with the steam billowing from the crater. “There!” @Aspiring_Advocate exclaims, pointing toward a hot spring. “What better place to find cool runes of Aquarius and passionate runes of Venus than where ice and fire meet?”<br><br>The three of you hurry toward the hot spring. “According to my research,” @Aspiring_Advocate says, “combining the runes with heart-shaped rubies will create a hatching potion that can foster friendship and love!”<br><br>Excited by the prospect of a new discovery, you all smile. “All right,” @gully says, “let's start searching!”",
"questRubyText": "The Ruby Solution"
"questRubyText": "Ruby Rapport"
}
+1 -1
View File
@@ -203,7 +203,7 @@
"goToSettings": "Go to Settings",
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
"usernameNotVerified": "Please confirm your username.",
"changeUsernameDisclaimer": "This username will be used for invitations, @mentions in chat, and messaging.",
"changeUsernameDisclaimer": "Your username is used for invitations, @mentions in chat, and messaging. It must be 1 to 20 characters, containing only letters a to z, numbers 0 to 9, hyphens, or underscores, and cannot include any inappropriate terms.",
"verifyUsernameVeteranPet": "One of these Veteran Pets will be waiting for you after you've finished confirming!",
"everywhere": "Everywhere",
"onlyPrivateSpaces": "Only in private spaces",

Some files were not shown because too many files have changed in this diff Show More