From a7f0fa1e33fa7e3b453183b0ab6a5ff68242992d Mon Sep 17 00:00:00 2001 From: gintil Date: Tue, 25 Feb 2025 07:22:59 -0500 Subject: [PATCH] Delete old bot files --- services/bot/.eslintignore | 1 - services/bot/CONTRIBUTING.md | 38 - services/bot/LICENSE | 21 - services/bot/README.md | 72 - services/bot/index.js | 82 - services/bot/jest.config.js | 4 - services/bot/package-lock.json | 23571 ---------------- services/bot/package.json | 64 - services/bot/scripts/locales/create.js | 41 - services/bot/scripts/locales/verify.js | 83 - services/bot/scripts/pre_v6.js | 63 - services/bot/scripts/scheduleRun.js | 68 - services/bot/scripts/updates/6.0.0.js | 304 - services/bot/shard.js | 7 - services/bot/src/commands/add.js | 126 - services/bot/src/commands/alert.js | 97 - services/bot/src/commands/backup.js | 20 - services/bot/src/commands/clone.js | 18 - services/bot/src/commands/compare.js | 107 - services/bot/src/commands/date.js | 36 - services/bot/src/commands/dump.js | 15 - services/bot/src/commands/embed.fields.js | 44 - services/bot/src/commands/embed.js | 46 - services/bot/src/commands/faq.js | 41 - services/bot/src/commands/filters.js | 71 - services/bot/src/commands/forceexit.js | 22 - services/bot/src/commands/help.js | 60 - services/bot/src/commands/invite.js | 9 - services/bot/src/commands/list.js | 33 - services/bot/src/commands/locale.js | 65 - services/bot/src/commands/mention.filters.js | 52 - services/bot/src/commands/mention.js | 37 - services/bot/src/commands/move.js | 14 - services/bot/src/commands/options.js | 13 - services/bot/src/commands/owner/blacklist.js | 46 - .../bot/src/commands/owner/checklimits.js | 32 - services/bot/src/commands/owner/checkshard.js | 1 - services/bot/src/commands/owner/debug.js | 17 - services/bot/src/commands/owner/feedguild.js | 12 - services/bot/src/commands/owner/getguild.js | 17 - services/bot/src/commands/owner/kill.js | 5 - services/bot/src/commands/owner/listguilds.js | 34 - services/bot/src/commands/owner/pingme.js | 9 - services/bot/src/commands/owner/refreshall.js | 25 - .../src/commands/owner/refreshsupporters.js | 11 - .../bot/src/commands/owner/removeguild.js | 27 - services/bot/src/commands/owner/restore.js | 43 - services/bot/src/commands/owner/setavatar.js | 13 - .../bot/src/commands/owner/setpresence.js | 84 - services/bot/src/commands/owner/setstatus.js | 22 - .../bot/src/commands/owner/setusername.js | 15 - .../bot/src/commands/owner/unblacklist.js | 19 - services/bot/src/commands/owner/undebug.js | 19 - services/bot/src/commands/patron.js | 177 - services/bot/src/commands/prefix.js | 49 - .../bot/src/commands/prompts/clone/confirm.js | 148 - .../commands/prompts/clone/confirmSuccess.js | 32 - .../bot/src/commands/prompts/clone/index.js | 5 - .../prompts/clone/selectDestinationFeeds.js | 44 - .../prompts/clone/selectProperties.js | 68 - .../prompts/clone/selectSourceFeed.js | 40 - .../prompts/common/filterAddCategorySelect.js | 55 - .../commands/prompts/common/filterAddInput.js | 66 - .../prompts/common/filterAddInputSuccess.js | 50 - .../common/filterRemoveCategorySelect.js | 65 - .../prompts/common/filterRemoveInput.js | 67 - .../common/filterRemoveInputSuccess.js | 50 - .../bot/src/commands/prompts/common/index.js | 11 - .../commands/prompts/common/noFeedsFound.js | 20 - .../src/commands/prompts/common/selectFeed.js | 54 - .../prompts/common/selectMultipleFeeds.js | 49 - .../prompts/common/utils/LocalizedPrompt.js | 12 - .../prompts/common/utils/ThemedEmbed.js | 12 - .../common/utils/handlePaginationError.js | 22 - .../common/utils/splitMentionsByNewlines.js | 21 - .../common/utils/splitTextByNewline.js | 23 - .../src/commands/prompts/date/askFormat.js | 72 - .../src/commands/prompts/date/askLanguage.js | 80 - .../src/commands/prompts/date/askTimezone.js | 76 - .../bot/src/commands/prompts/date/index.js | 17 - .../prompts/date/selectCustomization.js | 65 - .../commands/prompts/date/successFormat.js | 37 - .../commands/prompts/date/successLanguage.js | 37 - .../src/commands/prompts/date/successReset.js | 26 - .../commands/prompts/date/successTimezone.js | 37 - .../bot/src/commands/prompts/dump/index.js | 1 - .../bot/src/commands/prompts/dump/sendFile.js | 44 - .../embed.fields/addBlankFieldSuccess.js | 37 - .../commands/prompts/embed.fields/addField.js | 66 - .../prompts/embed.fields/addFieldSuccess.js | 32 - .../commands/prompts/embed.fields/index.js | 6 - .../prompts/embed.fields/removeField.js | 64 - .../embed.fields/removeFieldSuccess.js | 29 - .../prompts/embed.fields/selectAction.js | 71 - .../bot/src/commands/prompts/embed/index.js | 6 - .../prompts/embed/removeAllEmbedsSuccess.js | 27 - .../prompts/embed/resetEmbedSuccess.js | 27 - .../src/commands/prompts/embed/selectEmbed.js | 84 - .../prompts/embed/selectProperties.js | 134 - .../src/commands/prompts/embed/setProperty.js | 113 - .../prompts/embed/setPropertySuccess.js | 49 - .../bot/src/commands/prompts/filters/index.js | 3 - .../commands/prompts/filters/listFilters.js | 52 - .../filters/removedAllFiltersSuccess.js | 26 - .../commands/prompts/filters/selectAction.js | 82 - .../prompts/filters/sendPassingArticle.js | 39 - .../bot/src/commands/prompts/list/index.js | 1 - .../src/commands/prompts/list/listFeeds.js | 213 - .../commands/prompts/mention.filters/index.js | 4 - .../prompts/mention.filters/listFilters.js | 47 - .../removeAllFiltersSuccess.js | 27 - .../prompts/mention.filters/selectAction.js | 63 - .../mention.filters/selectSubscriber.js | 60 - .../commands/prompts/mention/addSubscriber.js | 76 - .../prompts/mention/addSubscriberSuccess.js | 31 - .../bot/src/commands/prompts/mention/index.js | 7 - .../prompts/mention/listSubscribers.js | 54 - .../mention/removeAllSubscribersSuccess.js | 25 - .../prompts/mention/removeSubscriber.js | 68 - .../mention/removeSubscriberSuccess.js | 30 - .../commands/prompts/mention/selectAction.js | 55 - .../bot/src/commands/prompts/move/index.js | 2 - .../prompts/move/selectDestinationChannel.js | 108 - .../bot/src/commands/prompts/move/success.js | 34 - .../bot/src/commands/prompts/options/index.js | 3 - .../prompts/options/selectFeedWithOption.js | 90 - .../commands/prompts/options/selectOption.js | 72 - .../src/commands/prompts/options/success.js | 46 - .../bot/src/commands/prompts/remove/index.js | 2 - .../commands/prompts/remove/removeSuccess.js | 31 - .../prompts/remove/selectRemoveFeeds.js | 38 - .../bot/src/commands/prompts/runner/run.js | 53 - .../prompts/runner/util/deleteMessages.js | 19 - .../commands/prompts/split/disabledSuccess.js | 26 - .../bot/src/commands/prompts/split/enable.js | 54 - .../bot/src/commands/prompts/split/index.js | 11 - .../prompts/split/inputAppendCharacter.js | 52 - .../split/inputAppendCharacterSuccess.js | 39 - .../commands/prompts/split/inputMaxLength .js | 58 - .../prompts/split/inputMaxLengthSuccess.js | 35 - .../prompts/split/inputPrependCharacter.js | 52 - .../split/inputPrependCharacterSuccess.js | 39 - .../prompts/split/inputSplitCharacter.js | 57 - .../split/inputSplitCharacterSuccess.js | 35 - .../prompts/split/selectSplitOptions.js | 87 - .../src/commands/prompts/sub.filters/index.js | 3 - .../prompts/sub.filters/selectAction.js | 39 - .../prompts/sub.filters/selectFeed.js | 54 - .../prompts/sub.filters/sendTestArticle.js | 49 - .../commands/prompts/sub/addDirectResult.js | 34 - .../commands/prompts/sub/addRoleSuccess.js | 33 - .../bot/src/commands/prompts/sub/index.js | 6 - .../bot/src/commands/prompts/sub/inputRole.js | 76 - .../prompts/sub/listSubscribedFeeds.js | 49 - .../src/commands/prompts/sub/selectAction.js | 68 - .../src/commands/prompts/sub/selectFeed.js | 67 - .../bot/src/commands/prompts/text/index.js | 2 - .../src/commands/prompts/text/setMessage.js | 62 - .../bot/src/commands/prompts/text/success.js | 36 - .../bot/src/commands/prompts/unsub/index.js | 6 - .../commands/prompts/unsub/inputRemoveRole.js | 77 - .../prompts/unsub/removeAllSuccess.js | 23 - .../prompts/unsub/removeDirectSuccess.js | 26 - .../prompts/unsub/removeRoleSuccess.js | 26 - .../commands/prompts/unsub/selectAction.js | 112 - .../src/commands/prompts/unsub/selectFeed.js | 81 - .../bot/src/commands/prompts/webhook/index.js | 3 - .../prompts/webhook/removedSuccess.js | 25 - .../commands/prompts/webhook/selectFeed.js | 47 - .../commands/prompts/webhook/selectWebhook.js | 100 - services/bot/src/commands/refresh.js | 66 - services/bot/src/commands/remove.js | 12 - services/bot/src/commands/split.js | 52 - services/bot/src/commands/stats.js | 77 - services/bot/src/commands/sub.filters.js | 84 - services/bot/src/commands/sub.js | 30 - services/bot/src/commands/test.js | 56 - services/bot/src/commands/text.js | 14 - services/bot/src/commands/unsub.js | 34 - services/bot/src/commands/version.js | 7 - services/bot/src/commands/webhook.js | 22 - services/bot/src/config.js | 348 - services/bot/src/events/channelDelete.js | 17 - services/bot/src/events/channelUpdate.js | 27 - services/bot/src/events/guildCreate.js | 6 - services/bot/src/events/guildDelete.js | 16 - services/bot/src/events/guildUpdate.js | 15 - services/bot/src/events/message.js | 74 - services/bot/src/events/roleDelete.js | 17 - services/bot/src/initialization/index.js | 13 - .../src/initialization/populateKeyValues.js | 34 - .../src/initialization/populateSchedules.js | 53 - .../bot/src/initialization/setupCommands.js | 14 - .../bot/src/initialization/setupModels.js | 28 - .../src/initialization/setupRateLimiters.js | 29 - services/bot/src/locales/en-US.json | 613 - services/bot/src/locales/pt-BR.json | 613 - services/bot/src/locales/zh-TW.json | 612 - services/bot/src/maintenance/checkIndexes.js | 30 - services/bot/src/maintenance/checkLimits.js | 82 - .../bot/src/maintenance/checkPermissions.js | 90 - .../src/maintenance/generic/ensureIndexes.js | 30 - services/bot/src/maintenance/index.js | 91 - .../bot/src/maintenance/pruneFailRecords.js | 27 - services/bot/src/maintenance/pruneFeeds.js | 30 - .../src/maintenance/pruneFilteredFormats.js | 27 - .../bot/src/maintenance/pruneProfileAlerts.js | 64 - services/bot/src/maintenance/pruneProfiles.js | 22 - .../bot/src/maintenance/pruneSubscribers.js | 121 - services/bot/src/maintenance/pruneWebhooks.js | 176 - services/bot/src/models/Article.js | 40 - services/bot/src/models/BannedFeed.js | 20 - services/bot/src/models/Blacklist.js | 19 - services/bot/src/models/DebugFeed.js | 12 - services/bot/src/models/DeliveryRecord.js | 32 - services/bot/src/models/FailRecord.js | 22 - services/bot/src/models/Feed.js | 95 - services/bot/src/models/Feedback.js | 15 - services/bot/src/models/FilteredFormat.js | 38 - services/bot/src/models/GeneralStats.js | 20 - services/bot/src/models/KeyValue.js | 15 - services/bot/src/models/Patron.js | 23 - services/bot/src/models/Profile.js | 21 - services/bot/src/models/Rating.js | 16 - services/bot/src/models/Schedule.js | 21 - services/bot/src/models/ScheduleStats.js | 17 - services/bot/src/models/Subscriber.js | 33 - services/bot/src/models/Supporter.js | 17 - services/bot/src/models/common/Embed.js | 25 - services/bot/src/models/common/FilterBase.js | 10 - services/bot/src/models/common/Version.js | 16 - services/bot/src/models/middleware/Feed.js | 20 - .../src/models/middleware/FilteredFormat.js | 24 - .../bot/src/models/middleware/Subscriber.js | 24 - services/bot/src/structs/Article.js | 814 - services/bot/src/structs/ArticleIDResolver.js | 92 - services/bot/src/structs/ArticleMessage.js | 420 - .../src/structs/ArticleMessageRateLimiter.js | 158 - services/bot/src/structs/ArticleQueue.js | 278 - .../bot/src/structs/ArticleTestMessage.js | 9 - services/bot/src/structs/BlacklistCache.js | 35 - services/bot/src/structs/Client.js | 383 - services/bot/src/structs/ClientManager.js | 376 - services/bot/src/structs/Command.js | 392 - services/bot/src/structs/DecodedFeedParser.js | 25 - services/bot/src/structs/DeliveryPipeline.js | 256 - services/bot/src/structs/FeedData.js | 138 - services/bot/src/structs/Filter.js | 82 - services/bot/src/structs/FilterRegex.js | 24 - services/bot/src/structs/FilterResults.js | 29 - services/bot/src/structs/FlattenedJSON.js | 155 - services/bot/src/structs/Guild.js | 92 - services/bot/src/structs/GuildData.js | 175 - services/bot/src/structs/GuildSubscription.js | 100 - services/bot/src/structs/LinkLogic.js | 323 - services/bot/src/structs/NewArticle.js | 19 - services/bot/src/structs/Processor.js | 22 - services/bot/src/structs/ProcessorPool.js | 44 - .../bot/src/structs/RateLimitHitCounter.js | 28 - services/bot/src/structs/ScheduleManager.js | 332 - services/bot/src/structs/ScheduleRun.js | 601 - services/bot/src/structs/Translator.js | 143 - services/bot/src/structs/db/BannedFeed.js | 63 - services/bot/src/structs/db/Base.js | 537 - services/bot/src/structs/db/Blacklist.js | 59 - services/bot/src/structs/db/DebugFeed.js | 32 - services/bot/src/structs/db/FailRecord.js | 132 - services/bot/src/structs/db/Feed.js | 431 - services/bot/src/structs/db/FilterBase.js | 174 - services/bot/src/structs/db/FilteredFormat.js | 129 - services/bot/src/structs/db/KeyValue.js | 41 - services/bot/src/structs/db/Patron.js | 199 - services/bot/src/structs/db/Profile.js | 153 - services/bot/src/structs/db/Schedule.js | 55 - services/bot/src/structs/db/ScheduleStats.js | 59 - services/bot/src/structs/db/Subscriber.js | 72 - services/bot/src/structs/db/Supporter.js | 272 - .../src/structs/errors/ArticleMessageError.js | 12 - .../bot/src/structs/errors/FeedParserError.js | 13 - .../bot/src/structs/errors/MenuOptionError.js | 11 - .../bot/src/structs/errors/RequestError.js | 13 - .../structs/errors/http/BadRequestError.js | 12 - .../src/tests/commands/unit_compare.test.js | 32 - .../int_checkArticleIndexes.test.js | 112 - .../tests/maintenance/unit_checkLimit.test.js | 282 - .../maintenance/unit_checkPermissions.test.js | 200 - .../maintenance/unit_pruneFailRecords.test.js | 67 - .../tests/maintenance/unit_pruneFeeds.test.js | 105 - .../unit_pruneFilteredFormats.test.js | 68 - .../unit_pruneProfileAlerts.test.js | 173 - .../maintenance/unit_pruneProfiles.test.js | 52 - .../maintenance/unit_pruneSubscribers.test.js | 306 - .../maintenance/unit_pruneWebhooks.test.js | 338 - .../tests/models/int_FilteredFormat.test.js | 73 - .../tests/models/middleware/int_Feed.test.js | 39 - .../middleware/int_FilteredFormat.test.js | 56 - .../models/middleware/int_Subscriber.test.js | 57 - .../tests/models/middleware/unit_Feed.test.js | 37 - .../models/middleware/unit_Subscriber.test.js | 56 - .../tests/structs/db/__mocks__/BasicBase.js | 10 - .../src/tests/structs/db/__mocks__/Foobar.js | 16 - .../tests/structs/db/__mocks__/FoobarClass.js | 30 - .../structs/db/__mocks__/FoobarFilters.js | 12 - .../db/__mocks__/FoobarFiltersClass.js | 23 - .../tests/structs/db/__mocks__/MockModel.js | 13 - .../db/int_BannedFeed.database.test.js | 74 - .../structs/db/int_Base.database.test.js | 214 - .../structs/db/int_Base.databaseless.test.js | 130 - .../structs/db/int_Blacklist.database.test.js | 62 - .../db/int_FailRecord.database.test.js | 130 - .../structs/db/int_Feed.database.test.js | 337 - .../db/int_FilterBase.database.test.js | 73 - .../structs/db/int_KeyValue.database.test.js | 81 - .../structs/db/int_Patron.database.test.js | 80 - .../db/int_Subscriber.database.test.js | 56 - .../structs/db/int_Supporter.database.test.js | 524 - .../src/tests/structs/db/unit_Base.test.js | 926 - .../tests/structs/db/unit_Blacklist.test.js | 70 - .../tests/structs/db/unit_FailRecord.test.js | 177 - .../src/tests/structs/db/unit_Feed.test.js | 359 - .../tests/structs/db/unit_FilterBase.test.js | 364 - .../structs/db/unit_FilteredFormat.test.js | 157 - .../tests/structs/db/unit_KeyValue.test.js | 34 - .../src/tests/structs/db/unit_Patron.test.js | 233 - .../src/tests/structs/db/unit_Profile.test.js | 171 - .../tests/structs/db/unit_Schedule.test.js | 58 - .../tests/structs/db/unit_ShardStats.test.js | 29 - .../tests/structs/db/unit_Subscriber.test.js | 110 - .../tests/structs/db/unit_Supporter.test.js | 391 - .../int_ArticleMessageRateLimiter.test.js | 145 - .../tests/structs/int_ArticleQueue.test.js | 72 - .../structs/int_DeliveryPipeline.test.js | 132 - .../bot/src/tests/structs/int_Filter.test.js | 57 - .../src/tests/structs/int_GuildData.test.js | 222 - .../src/tests/structs/int_LinkLogic.test.js | 551 - .../src/tests/structs/unit_Article.test.js | 223 - .../structs/unit_ArticleIDResolver.test.js | 115 - .../tests/structs/unit_ArticleMessage.test.js | 343 - .../unit_ArticleMessageRateLimiter.test.js | 137 - .../tests/structs/unit_ArticleQueue.test.js | 111 - .../src/tests/structs/unit_Command.test.js | 527 - .../structs/unit_DeliveryPipeline.test.js | 327 - .../src/tests/structs/unit_FeedData.test.js | 252 - .../bot/src/tests/structs/unit_Filter.test.js | 129 - .../tests/structs/unit_FilterRegex.test.js | 15 - .../bot/src/tests/structs/unit_Guild.test.js | 169 - .../src/tests/structs/unit_GuildData.test.js | 406 - .../structs/unit_GuildSubscription.test.js | 194 - .../src/tests/structs/unit_LinkLogic.test.js | 362 - .../tests/structs/unit_ScheduleRun.test.js | 333 - .../src/tests/structs/unit_Translator.test.js | 89 - .../bot/src/tests/updates/int_6.0.0.test.js | 379 - .../src/tests/util/unit_FeedFetcher.test.js | 540 - .../bot/src/tests/util/unit_database.test.js | 359 - services/bot/src/tests/util/unit_ipc.test.js | 44 - services/bot/src/util/FeedFetcher.js | 317 - services/bot/src/util/config/schema.js | 138 - .../bot/src/util/config/validation/decode.js | 33 - .../bot/src/util/config/validation/locale.js | 31 - .../src/util/config/validation/timezone.js | 24 - services/bot/src/util/connectDatabase.js | 32 - services/bot/src/util/database.js | 213 - services/bot/src/util/devLevels.js | 30 - services/bot/src/util/dumpHeap.js | 19 - services/bot/src/util/ipc.js | 78 - services/bot/src/util/listeners.js | 26 - services/bot/src/util/logger/create.js | 44 - services/bot/src/util/logger/serializers.js | 57 - services/bot/src/util/pascalToSnake.js | 12 - services/bot/src/util/processor.js | 215 - .../cache-storage/cache-storage.service.ts | 12 - 371 files changed, 58176 deletions(-) delete mode 100644 services/bot/.eslintignore delete mode 100644 services/bot/CONTRIBUTING.md delete mode 100644 services/bot/LICENSE delete mode 100644 services/bot/README.md delete mode 100644 services/bot/index.js delete mode 100644 services/bot/jest.config.js delete mode 100644 services/bot/package-lock.json delete mode 100644 services/bot/package.json delete mode 100644 services/bot/scripts/locales/create.js delete mode 100644 services/bot/scripts/locales/verify.js delete mode 100644 services/bot/scripts/pre_v6.js delete mode 100644 services/bot/scripts/scheduleRun.js delete mode 100644 services/bot/scripts/updates/6.0.0.js delete mode 100644 services/bot/shard.js delete mode 100644 services/bot/src/commands/add.js delete mode 100644 services/bot/src/commands/alert.js delete mode 100644 services/bot/src/commands/backup.js delete mode 100644 services/bot/src/commands/clone.js delete mode 100644 services/bot/src/commands/compare.js delete mode 100644 services/bot/src/commands/date.js delete mode 100644 services/bot/src/commands/dump.js delete mode 100644 services/bot/src/commands/embed.fields.js delete mode 100644 services/bot/src/commands/embed.js delete mode 100644 services/bot/src/commands/faq.js delete mode 100644 services/bot/src/commands/filters.js delete mode 100644 services/bot/src/commands/forceexit.js delete mode 100644 services/bot/src/commands/help.js delete mode 100644 services/bot/src/commands/invite.js delete mode 100644 services/bot/src/commands/list.js delete mode 100644 services/bot/src/commands/locale.js delete mode 100644 services/bot/src/commands/mention.filters.js delete mode 100644 services/bot/src/commands/mention.js delete mode 100644 services/bot/src/commands/move.js delete mode 100644 services/bot/src/commands/options.js delete mode 100644 services/bot/src/commands/owner/blacklist.js delete mode 100644 services/bot/src/commands/owner/checklimits.js delete mode 100644 services/bot/src/commands/owner/checkshard.js delete mode 100644 services/bot/src/commands/owner/debug.js delete mode 100644 services/bot/src/commands/owner/feedguild.js delete mode 100644 services/bot/src/commands/owner/getguild.js delete mode 100644 services/bot/src/commands/owner/kill.js delete mode 100644 services/bot/src/commands/owner/listguilds.js delete mode 100644 services/bot/src/commands/owner/pingme.js delete mode 100644 services/bot/src/commands/owner/refreshall.js delete mode 100644 services/bot/src/commands/owner/refreshsupporters.js delete mode 100644 services/bot/src/commands/owner/removeguild.js delete mode 100644 services/bot/src/commands/owner/restore.js delete mode 100644 services/bot/src/commands/owner/setavatar.js delete mode 100644 services/bot/src/commands/owner/setpresence.js delete mode 100644 services/bot/src/commands/owner/setstatus.js delete mode 100644 services/bot/src/commands/owner/setusername.js delete mode 100644 services/bot/src/commands/owner/unblacklist.js delete mode 100644 services/bot/src/commands/owner/undebug.js delete mode 100644 services/bot/src/commands/patron.js delete mode 100644 services/bot/src/commands/prefix.js delete mode 100644 services/bot/src/commands/prompts/clone/confirm.js delete mode 100644 services/bot/src/commands/prompts/clone/confirmSuccess.js delete mode 100644 services/bot/src/commands/prompts/clone/index.js delete mode 100644 services/bot/src/commands/prompts/clone/selectDestinationFeeds.js delete mode 100644 services/bot/src/commands/prompts/clone/selectProperties.js delete mode 100644 services/bot/src/commands/prompts/clone/selectSourceFeed.js delete mode 100644 services/bot/src/commands/prompts/common/filterAddCategorySelect.js delete mode 100644 services/bot/src/commands/prompts/common/filterAddInput.js delete mode 100644 services/bot/src/commands/prompts/common/filterAddInputSuccess.js delete mode 100644 services/bot/src/commands/prompts/common/filterRemoveCategorySelect.js delete mode 100644 services/bot/src/commands/prompts/common/filterRemoveInput.js delete mode 100644 services/bot/src/commands/prompts/common/filterRemoveInputSuccess.js delete mode 100644 services/bot/src/commands/prompts/common/index.js delete mode 100644 services/bot/src/commands/prompts/common/noFeedsFound.js delete mode 100644 services/bot/src/commands/prompts/common/selectFeed.js delete mode 100644 services/bot/src/commands/prompts/common/selectMultipleFeeds.js delete mode 100644 services/bot/src/commands/prompts/common/utils/LocalizedPrompt.js delete mode 100644 services/bot/src/commands/prompts/common/utils/ThemedEmbed.js delete mode 100644 services/bot/src/commands/prompts/common/utils/handlePaginationError.js delete mode 100644 services/bot/src/commands/prompts/common/utils/splitMentionsByNewlines.js delete mode 100644 services/bot/src/commands/prompts/common/utils/splitTextByNewline.js delete mode 100644 services/bot/src/commands/prompts/date/askFormat.js delete mode 100644 services/bot/src/commands/prompts/date/askLanguage.js delete mode 100644 services/bot/src/commands/prompts/date/askTimezone.js delete mode 100644 services/bot/src/commands/prompts/date/index.js delete mode 100644 services/bot/src/commands/prompts/date/selectCustomization.js delete mode 100644 services/bot/src/commands/prompts/date/successFormat.js delete mode 100644 services/bot/src/commands/prompts/date/successLanguage.js delete mode 100644 services/bot/src/commands/prompts/date/successReset.js delete mode 100644 services/bot/src/commands/prompts/date/successTimezone.js delete mode 100644 services/bot/src/commands/prompts/dump/index.js delete mode 100644 services/bot/src/commands/prompts/dump/sendFile.js delete mode 100644 services/bot/src/commands/prompts/embed.fields/addBlankFieldSuccess.js delete mode 100644 services/bot/src/commands/prompts/embed.fields/addField.js delete mode 100644 services/bot/src/commands/prompts/embed.fields/addFieldSuccess.js delete mode 100644 services/bot/src/commands/prompts/embed.fields/index.js delete mode 100644 services/bot/src/commands/prompts/embed.fields/removeField.js delete mode 100644 services/bot/src/commands/prompts/embed.fields/removeFieldSuccess.js delete mode 100644 services/bot/src/commands/prompts/embed.fields/selectAction.js delete mode 100644 services/bot/src/commands/prompts/embed/index.js delete mode 100644 services/bot/src/commands/prompts/embed/removeAllEmbedsSuccess.js delete mode 100644 services/bot/src/commands/prompts/embed/resetEmbedSuccess.js delete mode 100644 services/bot/src/commands/prompts/embed/selectEmbed.js delete mode 100644 services/bot/src/commands/prompts/embed/selectProperties.js delete mode 100644 services/bot/src/commands/prompts/embed/setProperty.js delete mode 100644 services/bot/src/commands/prompts/embed/setPropertySuccess.js delete mode 100644 services/bot/src/commands/prompts/filters/index.js delete mode 100644 services/bot/src/commands/prompts/filters/listFilters.js delete mode 100644 services/bot/src/commands/prompts/filters/removedAllFiltersSuccess.js delete mode 100644 services/bot/src/commands/prompts/filters/selectAction.js delete mode 100644 services/bot/src/commands/prompts/filters/sendPassingArticle.js delete mode 100644 services/bot/src/commands/prompts/list/index.js delete mode 100644 services/bot/src/commands/prompts/list/listFeeds.js delete mode 100644 services/bot/src/commands/prompts/mention.filters/index.js delete mode 100644 services/bot/src/commands/prompts/mention.filters/listFilters.js delete mode 100644 services/bot/src/commands/prompts/mention.filters/removeAllFiltersSuccess.js delete mode 100644 services/bot/src/commands/prompts/mention.filters/selectAction.js delete mode 100644 services/bot/src/commands/prompts/mention.filters/selectSubscriber.js delete mode 100644 services/bot/src/commands/prompts/mention/addSubscriber.js delete mode 100644 services/bot/src/commands/prompts/mention/addSubscriberSuccess.js delete mode 100644 services/bot/src/commands/prompts/mention/index.js delete mode 100644 services/bot/src/commands/prompts/mention/listSubscribers.js delete mode 100644 services/bot/src/commands/prompts/mention/removeAllSubscribersSuccess.js delete mode 100644 services/bot/src/commands/prompts/mention/removeSubscriber.js delete mode 100644 services/bot/src/commands/prompts/mention/removeSubscriberSuccess.js delete mode 100644 services/bot/src/commands/prompts/mention/selectAction.js delete mode 100644 services/bot/src/commands/prompts/move/index.js delete mode 100644 services/bot/src/commands/prompts/move/selectDestinationChannel.js delete mode 100644 services/bot/src/commands/prompts/move/success.js delete mode 100644 services/bot/src/commands/prompts/options/index.js delete mode 100644 services/bot/src/commands/prompts/options/selectFeedWithOption.js delete mode 100644 services/bot/src/commands/prompts/options/selectOption.js delete mode 100644 services/bot/src/commands/prompts/options/success.js delete mode 100644 services/bot/src/commands/prompts/remove/index.js delete mode 100644 services/bot/src/commands/prompts/remove/removeSuccess.js delete mode 100644 services/bot/src/commands/prompts/remove/selectRemoveFeeds.js delete mode 100644 services/bot/src/commands/prompts/runner/run.js delete mode 100644 services/bot/src/commands/prompts/runner/util/deleteMessages.js delete mode 100644 services/bot/src/commands/prompts/split/disabledSuccess.js delete mode 100644 services/bot/src/commands/prompts/split/enable.js delete mode 100644 services/bot/src/commands/prompts/split/index.js delete mode 100644 services/bot/src/commands/prompts/split/inputAppendCharacter.js delete mode 100644 services/bot/src/commands/prompts/split/inputAppendCharacterSuccess.js delete mode 100644 services/bot/src/commands/prompts/split/inputMaxLength .js delete mode 100644 services/bot/src/commands/prompts/split/inputMaxLengthSuccess.js delete mode 100644 services/bot/src/commands/prompts/split/inputPrependCharacter.js delete mode 100644 services/bot/src/commands/prompts/split/inputPrependCharacterSuccess.js delete mode 100644 services/bot/src/commands/prompts/split/inputSplitCharacter.js delete mode 100644 services/bot/src/commands/prompts/split/inputSplitCharacterSuccess.js delete mode 100644 services/bot/src/commands/prompts/split/selectSplitOptions.js delete mode 100644 services/bot/src/commands/prompts/sub.filters/index.js delete mode 100644 services/bot/src/commands/prompts/sub.filters/selectAction.js delete mode 100644 services/bot/src/commands/prompts/sub.filters/selectFeed.js delete mode 100644 services/bot/src/commands/prompts/sub.filters/sendTestArticle.js delete mode 100644 services/bot/src/commands/prompts/sub/addDirectResult.js delete mode 100644 services/bot/src/commands/prompts/sub/addRoleSuccess.js delete mode 100644 services/bot/src/commands/prompts/sub/index.js delete mode 100644 services/bot/src/commands/prompts/sub/inputRole.js delete mode 100644 services/bot/src/commands/prompts/sub/listSubscribedFeeds.js delete mode 100644 services/bot/src/commands/prompts/sub/selectAction.js delete mode 100644 services/bot/src/commands/prompts/sub/selectFeed.js delete mode 100644 services/bot/src/commands/prompts/text/index.js delete mode 100644 services/bot/src/commands/prompts/text/setMessage.js delete mode 100644 services/bot/src/commands/prompts/text/success.js delete mode 100644 services/bot/src/commands/prompts/unsub/index.js delete mode 100644 services/bot/src/commands/prompts/unsub/inputRemoveRole.js delete mode 100644 services/bot/src/commands/prompts/unsub/removeAllSuccess.js delete mode 100644 services/bot/src/commands/prompts/unsub/removeDirectSuccess.js delete mode 100644 services/bot/src/commands/prompts/unsub/removeRoleSuccess.js delete mode 100644 services/bot/src/commands/prompts/unsub/selectAction.js delete mode 100644 services/bot/src/commands/prompts/unsub/selectFeed.js delete mode 100644 services/bot/src/commands/prompts/webhook/index.js delete mode 100644 services/bot/src/commands/prompts/webhook/removedSuccess.js delete mode 100644 services/bot/src/commands/prompts/webhook/selectFeed.js delete mode 100644 services/bot/src/commands/prompts/webhook/selectWebhook.js delete mode 100644 services/bot/src/commands/refresh.js delete mode 100644 services/bot/src/commands/remove.js delete mode 100644 services/bot/src/commands/split.js delete mode 100644 services/bot/src/commands/stats.js delete mode 100644 services/bot/src/commands/sub.filters.js delete mode 100644 services/bot/src/commands/sub.js delete mode 100644 services/bot/src/commands/test.js delete mode 100644 services/bot/src/commands/text.js delete mode 100644 services/bot/src/commands/unsub.js delete mode 100644 services/bot/src/commands/version.js delete mode 100644 services/bot/src/commands/webhook.js delete mode 100644 services/bot/src/config.js delete mode 100644 services/bot/src/events/channelDelete.js delete mode 100644 services/bot/src/events/channelUpdate.js delete mode 100644 services/bot/src/events/guildCreate.js delete mode 100644 services/bot/src/events/guildDelete.js delete mode 100644 services/bot/src/events/guildUpdate.js delete mode 100644 services/bot/src/events/message.js delete mode 100644 services/bot/src/events/roleDelete.js delete mode 100644 services/bot/src/initialization/index.js delete mode 100644 services/bot/src/initialization/populateKeyValues.js delete mode 100644 services/bot/src/initialization/populateSchedules.js delete mode 100644 services/bot/src/initialization/setupCommands.js delete mode 100644 services/bot/src/initialization/setupModels.js delete mode 100644 services/bot/src/initialization/setupRateLimiters.js delete mode 100644 services/bot/src/locales/en-US.json delete mode 100644 services/bot/src/locales/pt-BR.json delete mode 100644 services/bot/src/locales/zh-TW.json delete mode 100644 services/bot/src/maintenance/checkIndexes.js delete mode 100644 services/bot/src/maintenance/checkLimits.js delete mode 100644 services/bot/src/maintenance/checkPermissions.js delete mode 100644 services/bot/src/maintenance/generic/ensureIndexes.js delete mode 100644 services/bot/src/maintenance/index.js delete mode 100644 services/bot/src/maintenance/pruneFailRecords.js delete mode 100644 services/bot/src/maintenance/pruneFeeds.js delete mode 100644 services/bot/src/maintenance/pruneFilteredFormats.js delete mode 100644 services/bot/src/maintenance/pruneProfileAlerts.js delete mode 100644 services/bot/src/maintenance/pruneProfiles.js delete mode 100644 services/bot/src/maintenance/pruneSubscribers.js delete mode 100644 services/bot/src/maintenance/pruneWebhooks.js delete mode 100644 services/bot/src/models/Article.js delete mode 100644 services/bot/src/models/BannedFeed.js delete mode 100644 services/bot/src/models/Blacklist.js delete mode 100644 services/bot/src/models/DebugFeed.js delete mode 100644 services/bot/src/models/DeliveryRecord.js delete mode 100644 services/bot/src/models/FailRecord.js delete mode 100644 services/bot/src/models/Feed.js delete mode 100644 services/bot/src/models/Feedback.js delete mode 100644 services/bot/src/models/FilteredFormat.js delete mode 100644 services/bot/src/models/GeneralStats.js delete mode 100644 services/bot/src/models/KeyValue.js delete mode 100644 services/bot/src/models/Patron.js delete mode 100644 services/bot/src/models/Profile.js delete mode 100644 services/bot/src/models/Rating.js delete mode 100644 services/bot/src/models/Schedule.js delete mode 100644 services/bot/src/models/ScheduleStats.js delete mode 100644 services/bot/src/models/Subscriber.js delete mode 100644 services/bot/src/models/Supporter.js delete mode 100644 services/bot/src/models/common/Embed.js delete mode 100644 services/bot/src/models/common/FilterBase.js delete mode 100644 services/bot/src/models/common/Version.js delete mode 100644 services/bot/src/models/middleware/Feed.js delete mode 100644 services/bot/src/models/middleware/FilteredFormat.js delete mode 100644 services/bot/src/models/middleware/Subscriber.js delete mode 100644 services/bot/src/structs/Article.js delete mode 100644 services/bot/src/structs/ArticleIDResolver.js delete mode 100644 services/bot/src/structs/ArticleMessage.js delete mode 100644 services/bot/src/structs/ArticleMessageRateLimiter.js delete mode 100644 services/bot/src/structs/ArticleQueue.js delete mode 100644 services/bot/src/structs/ArticleTestMessage.js delete mode 100644 services/bot/src/structs/BlacklistCache.js delete mode 100644 services/bot/src/structs/Client.js delete mode 100644 services/bot/src/structs/ClientManager.js delete mode 100644 services/bot/src/structs/Command.js delete mode 100644 services/bot/src/structs/DecodedFeedParser.js delete mode 100644 services/bot/src/structs/DeliveryPipeline.js delete mode 100644 services/bot/src/structs/FeedData.js delete mode 100644 services/bot/src/structs/Filter.js delete mode 100644 services/bot/src/structs/FilterRegex.js delete mode 100644 services/bot/src/structs/FilterResults.js delete mode 100644 services/bot/src/structs/FlattenedJSON.js delete mode 100644 services/bot/src/structs/Guild.js delete mode 100644 services/bot/src/structs/GuildData.js delete mode 100644 services/bot/src/structs/GuildSubscription.js delete mode 100644 services/bot/src/structs/LinkLogic.js delete mode 100644 services/bot/src/structs/NewArticle.js delete mode 100644 services/bot/src/structs/Processor.js delete mode 100644 services/bot/src/structs/ProcessorPool.js delete mode 100644 services/bot/src/structs/RateLimitHitCounter.js delete mode 100644 services/bot/src/structs/ScheduleManager.js delete mode 100644 services/bot/src/structs/ScheduleRun.js delete mode 100644 services/bot/src/structs/Translator.js delete mode 100644 services/bot/src/structs/db/BannedFeed.js delete mode 100644 services/bot/src/structs/db/Base.js delete mode 100644 services/bot/src/structs/db/Blacklist.js delete mode 100644 services/bot/src/structs/db/DebugFeed.js delete mode 100644 services/bot/src/structs/db/FailRecord.js delete mode 100644 services/bot/src/structs/db/Feed.js delete mode 100644 services/bot/src/structs/db/FilterBase.js delete mode 100644 services/bot/src/structs/db/FilteredFormat.js delete mode 100644 services/bot/src/structs/db/KeyValue.js delete mode 100644 services/bot/src/structs/db/Patron.js delete mode 100644 services/bot/src/structs/db/Profile.js delete mode 100644 services/bot/src/structs/db/Schedule.js delete mode 100644 services/bot/src/structs/db/ScheduleStats.js delete mode 100644 services/bot/src/structs/db/Subscriber.js delete mode 100644 services/bot/src/structs/db/Supporter.js delete mode 100644 services/bot/src/structs/errors/ArticleMessageError.js delete mode 100644 services/bot/src/structs/errors/FeedParserError.js delete mode 100644 services/bot/src/structs/errors/MenuOptionError.js delete mode 100644 services/bot/src/structs/errors/RequestError.js delete mode 100644 services/bot/src/structs/errors/http/BadRequestError.js delete mode 100644 services/bot/src/tests/commands/unit_compare.test.js delete mode 100644 services/bot/src/tests/maintenance/int_checkArticleIndexes.test.js delete mode 100644 services/bot/src/tests/maintenance/unit_checkLimit.test.js delete mode 100644 services/bot/src/tests/maintenance/unit_checkPermissions.test.js delete mode 100644 services/bot/src/tests/maintenance/unit_pruneFailRecords.test.js delete mode 100644 services/bot/src/tests/maintenance/unit_pruneFeeds.test.js delete mode 100644 services/bot/src/tests/maintenance/unit_pruneFilteredFormats.test.js delete mode 100644 services/bot/src/tests/maintenance/unit_pruneProfileAlerts.test.js delete mode 100644 services/bot/src/tests/maintenance/unit_pruneProfiles.test.js delete mode 100644 services/bot/src/tests/maintenance/unit_pruneSubscribers.test.js delete mode 100644 services/bot/src/tests/maintenance/unit_pruneWebhooks.test.js delete mode 100644 services/bot/src/tests/models/int_FilteredFormat.test.js delete mode 100644 services/bot/src/tests/models/middleware/int_Feed.test.js delete mode 100644 services/bot/src/tests/models/middleware/int_FilteredFormat.test.js delete mode 100644 services/bot/src/tests/models/middleware/int_Subscriber.test.js delete mode 100644 services/bot/src/tests/models/middleware/unit_Feed.test.js delete mode 100644 services/bot/src/tests/models/middleware/unit_Subscriber.test.js delete mode 100644 services/bot/src/tests/structs/db/__mocks__/BasicBase.js delete mode 100644 services/bot/src/tests/structs/db/__mocks__/Foobar.js delete mode 100644 services/bot/src/tests/structs/db/__mocks__/FoobarClass.js delete mode 100644 services/bot/src/tests/structs/db/__mocks__/FoobarFilters.js delete mode 100644 services/bot/src/tests/structs/db/__mocks__/FoobarFiltersClass.js delete mode 100644 services/bot/src/tests/structs/db/__mocks__/MockModel.js delete mode 100644 services/bot/src/tests/structs/db/int_BannedFeed.database.test.js delete mode 100644 services/bot/src/tests/structs/db/int_Base.database.test.js delete mode 100644 services/bot/src/tests/structs/db/int_Base.databaseless.test.js delete mode 100644 services/bot/src/tests/structs/db/int_Blacklist.database.test.js delete mode 100644 services/bot/src/tests/structs/db/int_FailRecord.database.test.js delete mode 100644 services/bot/src/tests/structs/db/int_Feed.database.test.js delete mode 100644 services/bot/src/tests/structs/db/int_FilterBase.database.test.js delete mode 100644 services/bot/src/tests/structs/db/int_KeyValue.database.test.js delete mode 100644 services/bot/src/tests/structs/db/int_Patron.database.test.js delete mode 100644 services/bot/src/tests/structs/db/int_Subscriber.database.test.js delete mode 100644 services/bot/src/tests/structs/db/int_Supporter.database.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_Base.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_Blacklist.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_FailRecord.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_Feed.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_FilterBase.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_FilteredFormat.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_KeyValue.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_Patron.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_Profile.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_Schedule.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_ShardStats.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_Subscriber.test.js delete mode 100644 services/bot/src/tests/structs/db/unit_Supporter.test.js delete mode 100644 services/bot/src/tests/structs/int_ArticleMessageRateLimiter.test.js delete mode 100644 services/bot/src/tests/structs/int_ArticleQueue.test.js delete mode 100644 services/bot/src/tests/structs/int_DeliveryPipeline.test.js delete mode 100644 services/bot/src/tests/structs/int_Filter.test.js delete mode 100644 services/bot/src/tests/structs/int_GuildData.test.js delete mode 100644 services/bot/src/tests/structs/int_LinkLogic.test.js delete mode 100644 services/bot/src/tests/structs/unit_Article.test.js delete mode 100644 services/bot/src/tests/structs/unit_ArticleIDResolver.test.js delete mode 100644 services/bot/src/tests/structs/unit_ArticleMessage.test.js delete mode 100644 services/bot/src/tests/structs/unit_ArticleMessageRateLimiter.test.js delete mode 100644 services/bot/src/tests/structs/unit_ArticleQueue.test.js delete mode 100644 services/bot/src/tests/structs/unit_Command.test.js delete mode 100644 services/bot/src/tests/structs/unit_DeliveryPipeline.test.js delete mode 100644 services/bot/src/tests/structs/unit_FeedData.test.js delete mode 100644 services/bot/src/tests/structs/unit_Filter.test.js delete mode 100644 services/bot/src/tests/structs/unit_FilterRegex.test.js delete mode 100644 services/bot/src/tests/structs/unit_Guild.test.js delete mode 100644 services/bot/src/tests/structs/unit_GuildData.test.js delete mode 100644 services/bot/src/tests/structs/unit_GuildSubscription.test.js delete mode 100644 services/bot/src/tests/structs/unit_LinkLogic.test.js delete mode 100644 services/bot/src/tests/structs/unit_ScheduleRun.test.js delete mode 100644 services/bot/src/tests/structs/unit_Translator.test.js delete mode 100644 services/bot/src/tests/updates/int_6.0.0.test.js delete mode 100644 services/bot/src/tests/util/unit_FeedFetcher.test.js delete mode 100644 services/bot/src/tests/util/unit_database.test.js delete mode 100644 services/bot/src/tests/util/unit_ipc.test.js delete mode 100644 services/bot/src/util/FeedFetcher.js delete mode 100644 services/bot/src/util/config/schema.js delete mode 100644 services/bot/src/util/config/validation/decode.js delete mode 100644 services/bot/src/util/config/validation/locale.js delete mode 100644 services/bot/src/util/config/validation/timezone.js delete mode 100644 services/bot/src/util/connectDatabase.js delete mode 100644 services/bot/src/util/database.js delete mode 100644 services/bot/src/util/devLevels.js delete mode 100644 services/bot/src/util/dumpHeap.js delete mode 100644 services/bot/src/util/ipc.js delete mode 100644 services/bot/src/util/listeners.js delete mode 100644 services/bot/src/util/logger/create.js delete mode 100644 services/bot/src/util/logger/serializers.js delete mode 100644 services/bot/src/util/pascalToSnake.js delete mode 100644 services/bot/src/util/processor.js diff --git a/services/bot/.eslintignore b/services/bot/.eslintignore deleted file mode 100644 index 35999a204..000000000 --- a/services/bot/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -/src/web \ No newline at end of file diff --git a/services/bot/CONTRIBUTING.md b/services/bot/CONTRIBUTING.md deleted file mode 100644 index dc7181cec..000000000 --- a/services/bot/CONTRIBUTING.md +++ /dev/null @@ -1,38 +0,0 @@ -# Contributing - -**The issue tracker is only for technical support, bug reports and enhancement suggestions. -If you have a question (or any concern related to the public hosting of the bot), please ask it in the [Discord server](https://discord.gg/pudv7Rx) instead of opening an issue.** - -If you wish to contribute to the MonitoRSS, feel free to fork the repository and submit a pull request. -ESLint and StandardJS are used to enforce a consistent coding style, so having that set up in your editor of choice is a great boon to your development process. - -All development happens on dev branches. - -## Directly Work with Core Module - -1. Fork & clone this core repo -2. Create a new branch from the **dev** branch -3. Code! -4. Run `npm run eslint` to run ESLint and automatically fix problems in coding style -5. Push your work to your fork and submit a pull request (before that you may need to merge the latest from **upstream**) - - -## Work with Core Module with Clone Repo - -This will allow you to make changes and immediately testing the changes with the clone repo. - -1. Fork & clone this core repo -2. In the core repo, create a new branch from the **dev** branch -3. In the core repo, run `npm link` to set up the core dev environment in the global node modules for reference by the clone repo -4. Fork & clone the [clone repository](https://github.com/synzen/MonitoRSS-Clone), and make sure you're on the **dev** branch -5. In the clone repo, set it up by following the setup in documentation -6. In the clone repo, run `npm link monitorss` to create a symlink to the core repo in the global node modules -7. Code in the core repo! -8. In the core repo, run `npm run eslint` to run ESLint and automatically fix problems in coding style -9. In the clone repo, run the bot to test it -10. Push your work to your fork and submit a pull request (before that you may need to merge the latest from **upstream**) - -If you use an outdated version of **npm**, then you may run into a [lock file conflict](https://docs.npmjs.com/files/package-locks#resolving-lockfile-conflicts). -More info [here](https://github.com/npm/npm/issues/20434) and [here](https://github.com/npm/npm/issues/20891). - -[How do I know which version of **npm** comes with which version of **Node.js** ?](https://nodejs.org/en/download/releases/) diff --git a/services/bot/LICENSE b/services/bot/LICENSE deleted file mode 100644 index 8864d4a39..000000000 --- a/services/bot/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/services/bot/README.md b/services/bot/README.md deleted file mode 100644 index b375a235c..000000000 --- a/services/bot/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# MonitoRSS (Formerly Discord.RSS) - -This is the core repository of the MonitoRSS bot (formerly known as Discord.RSS) for development and programmatic use. For the web interface development and programmatic use, see https://github.com/synzen/MonitoRSS-Web. - -For users who want to deploy MonitoRSS for personal use, see https://github.com/synzen/MonitoRSS-Clone. - ---- - -Driven by the lack of comprehensive RSS bots available, I have decided to try my hand at creating one of my own. Designed with as much customization as possible for both users and bot hosters, while also (or should be) easy to understand. - -All documentation can be found at https://docs.monitorss.xyz. - -### Publicly Hosted Instance - -Don't want to bother hosting your own instance? Use the publicly hosted one! - -#### Website: - -https://monitorss.xyz - -#### Bot Invite: - -https://discord.com/oauth2/authorize?client_id=268478587651358721&scope=bot&permissions=19456 - -### Quick Start - -``` -npm install monitorss -``` - -```js -const MonitoRSS = require("monitorss"); - -// Some configs are mandatory - refer to documentation -const config = { - bot: { - token: "abc123", - }, - database: { - // Can be mongodb or folder URI - uri: "mongodb://localhost/rss", - }, -}; - -const settings = { - setPresence: true, - config, -}; - -const client = new MonitoRSS.ClientManager(settings); -client.start(); -``` - -For best performance, use a mongodb database.uri instead of a directory. - -### Contributing - -[Read the contribution guidelines](https://github.com/synzen/MonitoRSS/blob/master/CONTRIBUTING.md). All the latest updates are commited to the dev branch. - -### Testing - -Run `npm test` - -#### Locales - -To add or contribute to menu translations (locales): - -1. If the locale JSON doesn't exist in src/locales, create one by running `npm run locale-create` -2. Open the relevant locale file in src/locales -3. Add your translations (use the en-US.json locale as reference) -4. Verify your file(s) by running `npm run locale-verify` and make appropriate fixes. -5. Make a pull request for your changes! Please also make sure to put a screenshot of the output of this command in your PR. diff --git a/services/bot/index.js b/services/bot/index.js deleted file mode 100644 index c70b4be8e..000000000 --- a/services/bot/index.js +++ /dev/null @@ -1,82 +0,0 @@ -const connectDb = require('./src/util/connectDatabase.js') -const initialize = require('./src/initialization/index.js') -const config = require('./src/config.js') - -// Models -exports.models = { - Article: require('./src/models/Article.js'), - BannedFeed: require('./src/models/BannedFeed.js'), - Blacklist: require('./src/models/Blacklist.js'), - DeliveryRecord: require('./src/models/DeliveryRecord.js'), - FailRecord: require('./src/models/FailRecord.js'), - Feed: require('./src/models/Feed.js'), - Feedback: require('./src/models/Feedback.js'), - FilteredFormat: require('./src/models/FilteredFormat.js'), - GeneralStats: require('./src/models/GeneralStats.js'), - KeyValue: require('./src/models/KeyValue.js'), - Patron: require('./src/models/Patron.js'), - Profile: require('./src/models/Profile.js'), - Rating: require('./src/models/Rating.js'), - Schedule: require('./src/models/Schedule.js'), - ScheduleStats: require('./src/models/ScheduleStats.js'), - Subscriber: require('./src/models/Subscriber.js'), - Supporter: require('./src/models/Supporter.js') -} - -// Structures -exports.Article = require('./src/structs/Article.js') -exports.ArticleMessage = require('./src/structs/ArticleMessage.js') -exports.ArticleMessageRateLimiter = require('./src/structs/ArticleMessageRateLimiter') -exports.ArticleTestMessage = require('./src/structs/ArticleTestMessage.js') -exports.Client = require('./src/structs/Client.js') -exports.ClientManager = require('./src/structs/ClientManager.js') -exports.FeedData = require('./src/structs/FeedData.js') -exports.Guild = require('./src/structs/Guild.js') -exports.GuildData = require('./src/structs/GuildData.js') -exports.Translator = require('./src/structs/Translator.js') -exports.DeliveryPipeline = require('./src/structs/DeliveryPipeline.js') - -// Database Structures -exports.BannedFeed = require('./src/structs/db/BannedFeed.js') -exports.Blacklist = require('./src/structs/db/Blacklist.js') -exports.FailRecord = require('./src/structs/db/FailRecord.js') -exports.Feed = require('./src/structs/db/Feed.js') -exports.FilteredFormat = require('./src/structs/db/FilteredFormat.js') -exports.KeyValue = require('./src/structs/db/KeyValue.js') -exports.Patron = require('./src/structs/db/Patron.js') -exports.Profile = require('./src/structs/db/Profile.js') -exports.Schedule = require('./src/structs/db/Schedule.js') -exports.ScheduleStats = require('./src/structs/db/ScheduleStats.js') -exports.Subscriber = require('./src/structs/db/Subscriber.js') -exports.Supporter = require('./src/structs/db/Supporter.js') - -// Utils -exports.FeedFetcher = require('./src/util/FeedFetcher.js') -exports.validateConfig = require('./src/util/config/schema').validate -exports.config = config -exports.schemas = require('./src/util/config/schema.js').schemas -exports.scripts = { - runSchedule: require('./scripts/scheduleRun.js') -} -exports.migrations = { - v6: require('./scripts/pre_v6.js') -} - -// Errors -exports.errors = { - FeedParserError: require('./src/structs/errors/FeedParserError.js'), - RequestError: require('./src/structs/errors/RequestError.js'), - BadRequestError: require('./src/structs/errors/http/BadRequestError') -} - -/** - * Necessary for npm modules to use models that - * depends on the database being connected - * - * @param {string} uri - * @param {Object} options -*/ -exports.setupModels = async (uri, options) => { - const connection = await connectDb(uri, options) - await initialize.setupModels(connection) -} diff --git a/services/bot/jest.config.js b/services/bot/jest.config.js deleted file mode 100644 index d3a12850f..000000000 --- a/services/bot/jest.config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - testEnvironment: 'node', - roots: ['src/tests'] -} diff --git a/services/bot/package-lock.json b/services/bot/package-lock.json deleted file mode 100644 index 31854edfc..000000000 --- a/services/bot/package-lock.json +++ /dev/null @@ -1,23571 +0,0 @@ -{ - "name": "monitorss", - "version": "6.14.9-beta.23", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "monitorss", - "version": "6.14.9-beta.23", - "license": "MIT", - "dependencies": { - "@hapi/joi": "^17.1.1", - "@synzen/discord-rest": "^0.7.0-beta.3", - "abort-controller": "^3.0.0", - "discord.js": "^12.5.3", - "discord.js-prompts": "^2.2.1", - "feedparser": "^2.2.10", - "html-to-text": "^5.1.1", - "iconv-lite": "^0.5.2", - "moment": "^2.29.4", - "moment-timezone": "^0.5.33", - "mongoose": "^5.13.8", - "node-fetch": "^2.6.7", - "pino": "6.8.0", - "pino-pretty": "4.3.0" - }, - "devDependencies": { - "@types/jest": "^25.2.3", - "eslint": "^8.45.0", - "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-standard": "^4.1.0", - "heapdump": "^0.3.15", - "jest": "^26.6.3" - }, - "engines": { - "node": ">=12.16", - "npm": "6.x" - }, - "optionalDependencies": { - "bufferutil": "^4.0.1", - "erlpack": "github:discordapp/erlpack", - "utf-8-validate": "^5.0.2", - "zeromq": "^6.0.0-beta.6", - "zlib-sync": "^0.1.6" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", - "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.10", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.5", - "@babel/parser": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.10", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.7" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", - "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.5" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", - "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-simple-access": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/helper-validator-identifier": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.10" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", - "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.1" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", - "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.5", - "@babel/types": "^7.12.5" - } - }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", - "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", - "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.0.tgz", - "integrity": "sha512-v1JH7PeAAGBEyTQM9TqojVl+b20zXtesFKCJHu50xMxZKD1fX0TKaKHPsZfFkXfs7D1M9M6Eeqg1FkJ3a0x2dA==", - "dependencies": { - "core-js-pure": "^3.25.1", - "regenerator-runtime": "^0.13.10" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "dependencies": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - }, - "bin": { - "watch": "cli.js" - }, - "engines": { - "node": ">=0.1.95" - } - }, - "node_modules/@discordjs/collection": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", - "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==", - "deprecated": "no longer supported" - }, - "node_modules/@discordjs/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", - "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", - "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", - "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", - "engines": { - "node": ">=14" - } - }, - "node_modules/@hapi/address": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz", - "integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==", - "deprecated": "Moved to 'npm install @sideway/address'", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@hapi/bourne": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", - "integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==" - }, - "node_modules/@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==", - "deprecated": "Moved to 'npm install @sideway/formula'" - }, - "node_modules/@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - }, - "node_modules/@hapi/joi": { - "version": "17.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", - "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", - "deprecated": "Switch to 'npm install joi'", - "dependencies": { - "@hapi/address": "^4.0.1", - "@hapi/formula": "^2.0.0", - "@hapi/hoek": "^9.0.0", - "@hapi/pinpoint": "^2.0.0", - "@hapi/topo": "^5.0.0" - } - }, - "node_modules/@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==", - "deprecated": "Moved to 'npm install @sideway/pinpoint'" - }, - "node_modules/@hapi/topo": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", - "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/console/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/core/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/environment/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/environment/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/environment/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/environment/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/fake-timers/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/fake-timers/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/fake-timers/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/fake-timers/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/globals/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/globals/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/globals/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/globals/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "node-notifier": "^8.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/reporters/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-result/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/test-result/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/test-result/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-result/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/transform/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", - "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - }, - "engines": { - "node": ">= 8.3" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@synzen/discord-rest": { - "version": "0.7.0-beta.3", - "resolved": "https://registry.npmjs.org/@synzen/discord-rest/-/discord-rest-0.7.0-beta.3.tgz", - "integrity": "sha512-SJBJFVRjnAQrpuH0artx+a8T8giNI2hjSkC3yjEESuRDMa5N+hBbOhZzDE3zgFv6opKmrilLZCax4YdpP9xRiw==", - "dependencies": { - "abort-controller": "^3.0.0", - "amqplib": "^0.9.0", - "async-sema": "^3.1.1", - "dayjs": "^1.11.2", - "debug": "^4.3.1", - "express": "^4.17.1", - "nanoid": "^3.3.4", - "p-timeout": "^4.1.0", - "rascal": "^16.1.2", - "retry": "^0.13.1", - "semaphore": "^1.1.0", - "source-map-support": "^0.5.21", - "undici": "^5.10.0", - "yup": "^1.0.0-beta.4" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@types/babel__core": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", - "integrity": "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", - "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", - "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz", - "integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/bson": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.2.0.tgz", - "integrity": "sha512-ELCPqAdroMdcuxqwMgUpifQyRoTpyYCNr1V9xKyF40VsBobsj+BbWNRvwGchMgBPGqkw655ypkjj2MEF5ywVwg==", - "deprecated": "This is a stub types definition. bson provides its own type definitions, so you do not need this installed.", - "dependencies": { - "bson": "*" - } - }, - "node_modules/@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", - "integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", - "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", - "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz", - "integrity": "sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==", - "dev": true, - "dependencies": { - "jest-diff": "^25.2.1", - "pretty-format": "^25.2.1" - } - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "node_modules/@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", - "dependencies": { - "@types/bson": "*", - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "14.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz", - "integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==" - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true - }, - "node_modules/@types/prettier": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.5.tgz", - "integrity": "sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz", - "integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw==", - "dev": true - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/addressparser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", - "integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y=" - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/amqplib": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.9.1.tgz", - "integrity": "sha512-a1DP0H1LcLSMKPAnhUN2AKbVyEPqEUrUf7O+odhKGxaO+Tf0nWtuD7Zq5P9uZwZteu56OfW9EQozSCTKsAEk5w==", - "dependencies": { - "bitsyntax": "~0.1.0", - "bluebird": "^3.7.2", - "buffer-more-ints": "~1.0.0", - "readable-stream": "1.x >=1.1.9", - "url-parse": "~1.5.10" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/amqplib/node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/amqplib/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "node_modules/amqplib/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/amqplib/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - }, - "node_modules/ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "dependencies": { - "type-fest": "^0.11.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/args": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", - "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", - "dependencies": { - "camelcase": "5.0.0", - "chalk": "2.4.2", - "leven": "2.1.0", - "mri": "1.1.4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/args/node_modules/camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/args/node_modules/leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/args/node_modules/mri": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", - "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==", - "engines": { - "node": ">=4" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-indexofobject": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-indexofobject/-/array-indexofobject-0.0.1.tgz", - "integrity": "sha1-qqEo5iybPDWAlFaMIZ/2T+SJ1Co=" - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "node_modules/async-sema": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", - "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "dev": true, - "dependencies": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-jest/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/babel-jest/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bitsyntax": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", - "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", - "dependencies": { - "buffer-more-ints": "~1.0.0", - "debug": "~2.6.9", - "safe-buffer": "~5.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/bitsyntax/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/bitsyntax/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "node_modules/buffer-more-ints": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", - "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" - }, - "node_modules/bufferutil": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz", - "integrity": "sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build": "~3.7.0" - } - }, - "node_modules/builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, - "peer": true, - "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/builtins/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dev": true, - "dependencies": { - "rsvp": "^4.8.4" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", - "dev": true - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-js-pure": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.0.tgz", - "integrity": "sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "engines": { - "node": "*" - } - }, - "node_modules/dayjs": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.2.tgz", - "integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decimal.js": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", - "dev": true - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/diff-sequences": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", - "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", - "dev": true, - "engines": { - "node": ">= 8.3" - } - }, - "node_modules/discord.js": { - "version": "12.5.3", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", - "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", - "deprecated": "no longer supported", - "dependencies": { - "@discordjs/collection": "^0.1.6", - "@discordjs/form-data": "^3.0.1", - "abort-controller": "^3.0.0", - "node-fetch": "^2.6.1", - "prism-media": "^1.2.9", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.4.4" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/discord.js-prompts": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/discord.js-prompts/-/discord.js-prompts-2.2.1.tgz", - "integrity": "sha512-dRsF2JPWPDL6552Aucxg0W0/h65O5BPcFmOchHQaxBBEpOQTj3C8tb5i/sWTR6t5OQ42DBXgPa/HB4y0dRe+XA==", - "dependencies": { - "discord.js": "^12.2.0", - "prompt-anything": "^3.0.0" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "dependencies": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "node_modules/domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dependencies": { - "domelementtype": "1" - } - }, - "node_modules/domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dependencies": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "node_modules/emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - }, - "node_modules/erlpack": { - "version": "0.1.3", - "resolved": "git+ssh://git@github.com/discordapp/erlpack.git#e27db8f82892bdb9b28a0547cc394d68b5d2242d", - "integrity": "sha512-uDE+ma9xOMfF4ncJHoymBWA084DqoO8q/hBDvGx4WUxmvFQfHoCsk3BYk1D1Lei/lhNCZXLf1vkeNYVBSqrSgw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.14.0" - } - }, - "node_modules/erlpack/node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "optional": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.21.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.3.tgz", - "integrity": "sha512-ZU4miiY1j3sGPFLJ34VJXEqhpmL+HGByCinGHv4HC+Fxl2fI2Z4yR6tl0mORnDr6PA8eihWo4LmSWDbvhALckg==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.1", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz", - "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.44.0", - "@humanwhocodes/config-array": "^0.11.10", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.6.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-standard": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", - "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", - "eslint-plugin-promise": "^6.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz", - "integrity": "sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-es-x": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.1.0.tgz", - "integrity": "sha512-AhiaF31syh4CCQ+C5ccJA0VG6+kJK8+5mXKKE7Qs1xcPRg02CDPOj3mWlQxuWS/AYtg7kxrDNgW9YW3vc0Q+Mw==", - "dev": true, - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.5.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "eslint": ">=8" - } - }, - "node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-plugin-es/node_modules/regexpp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", - "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-n": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.1.tgz", - "integrity": "sha512-CDmHegJN0OF3L5cz5tATH84RPQm9kG+Yx39wIqIwPR2C0uhBGMWfbbOtetR83PQjjidA5aXMu+LEFw1jaSwvTA==", - "dev": true, - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "builtins": "^5.0.1", - "eslint-plugin-es-x": "^7.1.0", - "ignore": "^5.2.4", - "is-core-module": "^2.12.1", - "minimatch": "^3.1.2", - "resolve": "^1.22.2", - "semver": "^7.5.3" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-plugin-node/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-standard": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz", - "integrity": "sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peerDependencies": { - "eslint": ">=5.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", - "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/exec-sh": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", - "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", - "dev": true - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/expect/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/expect/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/expect/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/expect/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/expect/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/expect/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/expect/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/expect/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/express": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", - "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.0", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.10.3", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/express/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/express/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/express/node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express/node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/express/node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express/node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fast-redact": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.1.tgz", - "integrity": "sha512-kYpn4Y/valC9MdrISg47tZOpYBNoTXKgT9GYXFpHN/jYFs+lFkPoisY+LcBODdKVMY96ATzvzsWv+ES/4Kmufw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/feedparser": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/feedparser/-/feedparser-2.2.10.tgz", - "integrity": "sha512-WoAOooa61V8/xuKMi2pEtK86qQ3ZH/M72EEGdqlOTxxb3m6ve1NPvZcmPFs3wEDfcBbFLId2GqZ4YjsYi+h1xA==", - "dependencies": { - "addressparser": "^1.0.1", - "array-indexofobject": "~0.0.1", - "lodash.assign": "^4.2.0", - "lodash.get": "^4.4.2", - "lodash.has": "^4.5.2", - "lodash.uniq": "^4.5.0", - "mri": "^1.1.5", - "readable-stream": "^2.3.7", - "sax": "^1.2.4" - }, - "bin": { - "feedparser": "bin/feedparser.js" - }, - "engines": { - "node": ">= 10.18.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatstr": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", - "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formidable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", - "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", - "dependencies": { - "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forward-emitter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/forward-emitter/-/forward-emitter-0.1.1.tgz", - "integrity": "sha512-2B/uPHDqPIVRieu2XJ3RsoDaIN87uqpe3qJjRdWj31leDtDEQ2wLOoJN09UeVQGi/Z79Z5fR9su3OXabASyYqw==" - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", - "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generic-pool": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "bin": { - "he": "bin/he" - } - }, - "node_modules/heapdump": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.15.tgz", - "integrity": "sha512-n8aSFscI9r3gfhOcAECAtXFaQ1uy4QSke6bnaL+iymYZ/dWs9cqDqHM+rALfsHUwukUbxsdlECZ0pKmJdQ/4OA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "nan": "^2.13.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "engines": { - "node": ">=8" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/html-to-text": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-5.1.1.tgz", - "integrity": "sha512-Bci6bD/JIfZSvG4s0gW/9mMKwBRoe/1RWLxUME/d6WUSZCdY7T60bssf/jFf7EYXRyqU4P5xdClVqiYU0/ypdA==", - "dependencies": { - "he": "^1.2.0", - "htmlparser2": "^3.10.1", - "lodash": "^4.17.11", - "minimist": "^1.2.0" - }, - "bin": { - "html-to-text": "bin/cli.js" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dependencies": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "node_modules/htmlparser2/node_modules/readable-stream": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", - "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/individual": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", - "integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==" - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-docker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", - "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", - "dev": true, - "optional": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-changed-files/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-changed-files/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-changed-files/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-changed-files/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/jest-changed-files/node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-config/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-config/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/jest-config/node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", - "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", - "dev": true, - "dependencies": { - "chalk": "^3.0.0", - "diff-sequences": "^25.2.6", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.5.0" - }, - "engines": { - "node": ">= 8.3" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-each/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-each/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/jest-each/node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-environment-jsdom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-environment-jsdom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-node/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-environment-node/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-environment-node/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true, - "engines": { - "node": ">= 8.3" - } - }, - "node_modules/jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "fsevents": "^2.1.2" - } - }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-haste-map/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-haste-map/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-haste-map/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-jasmine2/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-jasmine2/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-jasmine2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-jasmine2/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/jest-jasmine2/node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "node_modules/jest-jasmine2/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dev": true, - "dependencies": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-leak-detector/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-leak-detector/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-leak-detector/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-leak-detector/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-leak-detector/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-leak-detector/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-leak-detector/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/jest-leak-detector/node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "node_modules/jest-leak-detector/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/jest-matcher-utils/node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-message-util/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-mock/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-mock/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-mock/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-mock/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve-dependencies/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-resolve/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/jest-resolve/node_modules/parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-resolve/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-resolve/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runner/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - }, - "bin": { - "jest-runtime": "bin/jest-runtime.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runtime/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "dev": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/jest-snapshot/node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-util/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dev": true, - "dependencies": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-watcher/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest/node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/joycon": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz", - "integrity": "sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/jsdom/node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/jsdom/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/jsdom/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "node_modules/jsdom/node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jsdom/node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/kareem": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", - "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, - "node_modules/lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "dependencies": { - "tmpl": "1.0.x" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dependencies": { - "mime-db": "1.44.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.38", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.38.tgz", - "integrity": "sha512-nMIrzGah4+oYZPflDvLZUgoVUO4fvAqHstvG3xAUnMolWncuAiLDWNnJZj6EwJGMGfb1ZcuTFE6GI3hNOVWI/Q==", - "dependencies": { - "moment": ">= 2.9.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mongodb": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", - "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", - "dependencies": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2" - }, - "engines": { - "node": ">=4" - }, - "optionalDependencies": { - "saslprep": "^1.0.0" - }, - "peerDependenciesMeta": { - "aws4": { - "optional": true - }, - "bson-ext": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "mongodb-extjson": { - "optional": true - }, - "snappy": { - "optional": true - } - } - }, - "node_modules/mongodb/node_modules/optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "dependencies": { - "require-at": "^1.0.6" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mongoose": { - "version": "5.13.20", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.20.tgz", - "integrity": "sha512-TjGFa/XnJYt+wLmn8y9ssjyO2OhBMeEBtOHb9iJM16EWu2Du6L1Q6zSiEK2ziyYQM8agb4tumNIQFzqbxId7MA==", - "dependencies": { - "@types/bson": "1.x || 4.0.x", - "@types/mongodb": "^3.5.27", - "bson": "^1.1.4", - "kareem": "2.3.2", - "mongodb": "3.7.4", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.4", - "mquery": "3.2.5", - "ms": "2.1.2", - "optional-require": "1.0.x", - "regexp-clone": "1.0.0", - "safe-buffer": "5.2.1", - "sift": "13.5.2", - "sliced": "1.0.1" - }, - "engines": { - "node": ">=4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", - "peerDependencies": { - "mongoose": "*" - } - }, - "node_modules/mongoose/node_modules/@types/bson": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", - "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/mongoose/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/mpath": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", - "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", - "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", - "dependencies": { - "bluebird": "3.5.1", - "debug": "3.1.0", - "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", - "sliced": "1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/mquery/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/mri": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.5.tgz", - "integrity": "sha512-d2RKzMD4JNyHMbnbWnznPaa8vbdlq/4pNZ3IgdaGrVbBhebBsGUUE/6qorTMYNS6TwuH3ilfOlD2bf4Igh8CKg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "devOptional": true - }, - "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-gyp-build": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz", - "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node_modules/node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/node-notifier": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", - "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", - "dev": true, - "optional": true, - "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - } - }, - "node_modules/node-notifier/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "optional": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-notifier/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "optional": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-timeout": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", - "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pino": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-6.8.0.tgz", - "integrity": "sha512-nxq+6Jr7m0cMjYFBoTRw3bco14omZ/SQCheAHz9GVwdkbUrzKhgT+gSI/ql2Mnsca0QQKgpB/ACWhjxE4JsX3Q==", - "dependencies": { - "fast-redact": "^3.0.0", - "fast-safe-stringify": "^2.0.7", - "flatstr": "^1.0.12", - "pino-std-serializers": "^2.4.2", - "quick-format-unescaped": "^4.0.1", - "sonic-boom": "^1.0.2" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-pretty": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-4.3.0.tgz", - "integrity": "sha512-uEc9SUCCGVEs0goZvyznKXBHtI1PNjGgqHviJHxOCEFEWZN6Z/IQKv5pO9gSdm/b+WfX+/dfheWhtZUyScqjlQ==", - "dependencies": { - "@hapi/bourne": "^2.0.0", - "args": "^5.0.1", - "chalk": "^4.0.0", - "dateformat": "^3.0.3", - "fast-safe-stringify": "^2.0.7", - "jmespath": "^0.15.0", - "joycon": "^2.2.5", - "pump": "^3.0.0", - "readable-stream": "^3.6.0", - "split2": "^3.1.1", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "node_modules/pino-pretty/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/pino-pretty/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/pino-pretty/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/pino-pretty/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/pino-pretty/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/pino-pretty/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pino-pretty/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pino-std-serializers": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.5.0.tgz", - "integrity": "sha512-wXqbqSrIhE58TdrxxlfLwU9eDhrzppQDvGhBEr1gYbzzM4KKo3Y63gSjiDXRKLVS2UOXdPNR2v+KnQgNrs+xUg==" - }, - "node_modules/pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "dependencies": { - "node-modules-regexp": "^1.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-format": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", - "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", - "dev": true, - "dependencies": { - "@jest/types": "^25.5.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - }, - "engines": { - "node": ">= 8.3" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/pretty-format/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/pretty-format/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/prism-media": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz", - "integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==", - "peerDependencies": { - "@discordjs/opus": "^0.5.0", - "ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0", - "node-opus": "^0.3.3", - "opusscript": "^0.0.8" - }, - "peerDependenciesMeta": { - "@discordjs/opus": { - "optional": true - }, - "ffmpeg-static": { - "optional": true - }, - "node-opus": { - "optional": true - }, - "opusscript": { - "optional": true - } - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "node_modules/prompt-anything": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prompt-anything/-/prompt-anything-3.0.0.tgz", - "integrity": "sha512-RwxpEYYfcF/H3GiTHrnEOi3RpU0v1FGoqUdszckGxsftL7+/pZmhfnRQhj+jqn20n2DgkYFZNU1CfM2dP5y15A==" - }, - "node_modules/prompts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", - "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/property-expr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", - "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz", - "integrity": "sha512-MaL/oqh02mhEo5m5J2rwsVL23Iw2PEaGVHgT2vFt8AAsr0lfvQA5dpXo9TPu0rz7tSBdUPgkbam0j/fj5ZM8yg==" - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/rascal": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/rascal/-/rascal-16.1.2.tgz", - "integrity": "sha512-hyDZ0kSvUhpf2AC9ROwjm8e8S14upFyFOse3z9hbjS+NT08wtIi2RmbZ/K7awo8+DcCAREyh2aP21L70XtOEEA==", - "dependencies": { - "async": "^3.2.4", - "debug": "^4.3.4", - "forward-emitter": "^0.1.1", - "generic-pool": "^3.8.2", - "lodash": "^4.17.21", - "lru-cache": "^7.10.1", - "safe-json-parse": "^4.0.0", - "stashback": "^2.0.1", - "superagent": "^7.1.3", - "uuid": "^8.3.2", - "xregexp": "^5.1.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "amqplib": ">=0.5.5" - } - }, - "node_modules/rascal/node_modules/lru-cache": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", - "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", - "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "node_modules/repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true, - "engines": { - "node": "6.* || >= 7.*" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rust-result": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", - "integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==", - "dependencies": { - "individual": "^2.0.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safe-json-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz", - "integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==", - "dependencies": { - "rust-result": "^1.0.0" - } - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "deprecated": "some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added", - "dev": true, - "dependencies": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "bin": { - "sane": "src/cli.js" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/sane/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/sane/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semaphore": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", - "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sift": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", - "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" - }, - "node_modules/signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sonic-boom": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", - "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", - "dependencies": { - "atomic-sleep": "^1.0.0", - "flatstr": "^1.0.12" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "dev": true - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", - "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", - "dev": true - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/split2/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/stashback": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stashback/-/stashback-2.0.1.tgz", - "integrity": "sha512-NYYD9VxxtEF4YPJjcAm1irb/2kNLG1AYgSQp0bvogzCK6JGW3iNFOILu0jmkM8YsU27SdX93VFkktiqAAHW63w==", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-length": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", - "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/superagent": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.1.5.tgz", - "integrity": "sha512-HQYyGuDRFGmZ6GNC4hq2f37KnsY9Lr0/R1marNZTgMweVDQLTLJJ6DGQ9Tj/xVVs5HEnop9EMmTbywb5P30aqw==", - "dependencies": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.3", - "debug": "^4.3.4", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.0.1", - "methods": "^1.1.2", - "mime": "^2.5.0", - "qs": "^6.10.3", - "readable-stream": "^3.6.0", - "semver": "^7.3.7" - }, - "engines": { - "node": ">=6.4.0 <13 || >=14" - } - }, - "node_modules/superagent/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/superagent/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/superagent/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", - "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true - }, - "node_modules/tiny-case": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", - "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" - }, - "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", - "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici": { - "version": "5.26.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.3.tgz", - "integrity": "sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/utf-8-validate": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.2.tgz", - "integrity": "sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build": "~3.7.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz", - "integrity": "sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "dependencies": { - "makeerror": "1.0.x" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", - "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", - "dev": true, - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^2.0.2", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "node_modules/which-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.10.tgz", - "integrity": "sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", - "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/xregexp": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.1.tgz", - "integrity": "sha512-fKXeVorD+CzWvFs7VBuKTYIW63YD1e1osxwQ8caZ6o1jg6pDAbABDG54LCIq0j5cy7PjRvGIq6sef9DYPXpncg==", - "dependencies": { - "@babel/runtime-corejs3": "^7.16.5" - } - }, - "node_modules/y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", - "dev": true - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yup": { - "version": "1.0.0-beta.4", - "resolved": "https://registry.npmjs.org/yup/-/yup-1.0.0-beta.4.tgz", - "integrity": "sha512-g5uuQH2rN+0Z4L/ix8KoYIS6v7vnrykRzM/u7ExSA0WA33vrw2YOUKShBhaueh9N95oz54slgqBQTHx7E6EHwg==", - "dependencies": { - "property-expr": "^2.0.4", - "tiny-case": "^1.0.2", - "toposort": "^2.0.2" - } - }, - "node_modules/zeromq": { - "version": "6.0.0-beta.6", - "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-6.0.0-beta.6.tgz", - "integrity": "sha512-wLf6M7pBHijl+BRltUL2VoDpgbQcOZetiX8UzycHL8CcYFxYnRrpoG5fi3UX3+Umavz1lk4/dGaQez8qiDgr/Q==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "node-gyp-build": "^4.1.0" - }, - "engines": { - "node": ">= 10.2" - } - }, - "node_modules/zeromq/node_modules/node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/zlib-sync": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/zlib-sync/-/zlib-sync-0.1.6.tgz", - "integrity": "sha512-B2XGrhlXs/izEks+705vn/aL+nWyPgnS1glyCeH8wgZdOvztRn3ZEdqclj6GoJSeA/iRSqaTlznG2KO1M/GuWg==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "nan": "^2.14.0" - } - } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true - }, - "@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", - "dev": true, - "requires": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" - } - }, - "@babel/core": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", - "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.10", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.5", - "@babel/parser": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.10", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", - "dev": true, - "requires": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", - "dev": true, - "requires": { - "@babel/types": "^7.12.7" - } - }, - "@babel/helper-module-imports": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", - "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.5" - } - }, - "@babel/helper-module-transforms": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", - "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-simple-access": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/helper-validator-identifier": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "lodash": "^4.17.19" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.10" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" - } - }, - "@babel/helper-simple-access": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", - "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.1" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true - }, - "@babel/helpers": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", - "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", - "dev": true, - "requires": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.5", - "@babel/types": "^7.12.5" - } - }, - "@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", - "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", - "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/runtime-corejs3": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.0.tgz", - "integrity": "sha512-v1JH7PeAAGBEyTQM9TqojVl+b20zXtesFKCJHu50xMxZKD1fX0TKaKHPsZfFkXfs7D1M9M6Eeqg1FkJ3a0x2dA==", - "requires": { - "core-js-pure": "^3.25.1", - "regenerator-runtime": "^0.13.10" - } - }, - "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - } - }, - "@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - } - }, - "@discordjs/collection": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", - "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" - }, - "@discordjs/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - } - } - }, - "@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", - "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@eslint/js": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", - "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", - "dev": true - }, - "@fastify/busboy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", - "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==" - }, - "@hapi/address": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz", - "integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@hapi/bourne": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", - "integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==" - }, - "@hapi/formula": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", - "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" - }, - "@hapi/hoek": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", - "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" - }, - "@hapi/joi": { - "version": "17.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", - "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", - "requires": { - "@hapi/address": "^4.0.1", - "@hapi/formula": "^2.0.0", - "@hapi/hoek": "^9.0.0", - "@hapi/pinpoint": "^2.0.0", - "@hapi/topo": "^5.0.0" - } - }, - "@hapi/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" - }, - "@hapi/topo": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", - "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true - }, - "@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "node-notifier": "^8.0.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - } - }, - "@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "dev": true, - "requires": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - } - }, - "@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", - "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@synzen/discord-rest": { - "version": "0.7.0-beta.3", - "resolved": "https://registry.npmjs.org/@synzen/discord-rest/-/discord-rest-0.7.0-beta.3.tgz", - "integrity": "sha512-SJBJFVRjnAQrpuH0artx+a8T8giNI2hjSkC3yjEESuRDMa5N+hBbOhZzDE3zgFv6opKmrilLZCax4YdpP9xRiw==", - "requires": { - "abort-controller": "^3.0.0", - "amqplib": "^0.9.0", - "async-sema": "^3.1.1", - "dayjs": "^1.11.2", - "debug": "^4.3.1", - "express": "^4.17.1", - "nanoid": "^3.3.4", - "p-timeout": "^4.1.0", - "rascal": "^16.1.2", - "retry": "^0.13.1", - "semaphore": "^1.1.0", - "source-map-support": "^0.5.21", - "undici": "^5.10.0", - "yup": "^1.0.0-beta.4" - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", - "integrity": "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", - "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", - "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz", - "integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/bson": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.2.0.tgz", - "integrity": "sha512-ELCPqAdroMdcuxqwMgUpifQyRoTpyYCNr1V9xKyF40VsBobsj+BbWNRvwGchMgBPGqkw655ypkjj2MEF5ywVwg==", - "requires": { - "bson": "*" - } - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", - "integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", - "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", - "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz", - "integrity": "sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==", - "dev": true, - "requires": { - "jest-diff": "^25.2.1", - "pretty-format": "^25.2.1" - } - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", - "requires": { - "@types/bson": "*", - "@types/node": "*" - } - }, - "@types/node": { - "version": "14.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz", - "integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==" - }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true - }, - "@types/prettier": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.5.tgz", - "integrity": "sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", - "dev": true - }, - "@types/yargs": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz", - "integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw==", - "dev": true - }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "dependencies": { - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - } - } - }, - "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "addressparser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", - "integrity": "sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y=" - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "amqplib": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.9.1.tgz", - "integrity": "sha512-a1DP0H1LcLSMKPAnhUN2AKbVyEPqEUrUf7O+odhKGxaO+Tf0nWtuD7Zq5P9uZwZteu56OfW9EQozSCTKsAEk5w==", - "requires": { - "bitsyntax": "~0.1.0", - "bluebird": "^3.7.2", - "buffer-more-ints": "~1.0.0", - "readable-stream": "1.x >=1.1.9", - "url-parse": "~1.5.10" - }, - "dependencies": { - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - } - } - }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "args": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", - "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", - "requires": { - "camelcase": "5.0.0", - "chalk": "2.4.2", - "leven": "2.1.0", - "mri": "1.1.4" - }, - "dependencies": { - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" - }, - "mri": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", - "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==" - } - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - } - }, - "array-indexofobject": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-indexofobject/-/array-indexofobject-0.0.1.tgz", - "integrity": "sha1-qqEo5iybPDWAlFaMIZ/2T+SJ1Co=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "async-sema": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", - "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true - }, - "babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "dev": true, - "requires": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bitsyntax": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", - "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", - "requires": { - "buffer-more-ints": "~1.0.0", - "debug": "~2.6.9", - "safe-buffer": "~5.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "buffer-more-ints": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", - "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" - }, - "bufferutil": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz", - "integrity": "sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==", - "optional": true, - "requires": { - "node-gyp-build": "~3.7.0" - } - }, - "builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, - "peer": true, - "requires": { - "semver": "^7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "peer": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dev": true, - "requires": { - "rsvp": "^4.8.4" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js-pure": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.0.tgz", - "integrity": "sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" - }, - "dayjs": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.2.tgz", - "integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==" - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decimal.js": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "diff-sequences": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", - "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", - "dev": true - }, - "discord.js": { - "version": "12.5.3", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", - "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", - "requires": { - "@discordjs/collection": "^0.1.6", - "@discordjs/form-data": "^3.0.1", - "abort-controller": "^3.0.0", - "node-fetch": "^2.6.1", - "prism-media": "^1.2.9", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.4.4" - } - }, - "discord.js-prompts": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/discord.js-prompts/-/discord.js-prompts-2.2.1.tgz", - "integrity": "sha512-dRsF2JPWPDL6552Aucxg0W0/h65O5BPcFmOchHQaxBBEpOQTj3C8tb5i/sWTR6t5OQ42DBXgPa/HB4y0dRe+XA==", - "requires": { - "discord.js": "^12.2.0", - "prompt-anything": "^3.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - }, - "erlpack": { - "version": "git+ssh://git@github.com/discordapp/erlpack.git#e27db8f82892bdb9b28a0547cc394d68b5d2242d", - "integrity": "sha512-uDE+ma9xOMfF4ncJHoymBWA084DqoO8q/hBDvGx4WUxmvFQfHoCsk3BYk1D1Lei/lhNCZXLf1vkeNYVBSqrSgw==", - "from": "erlpack@github:discordapp/erlpack", - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.14.0" - }, - "dependencies": { - "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "optional": true - } - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.21.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.3.tgz", - "integrity": "sha512-ZU4miiY1j3sGPFLJ34VJXEqhpmL+HGByCinGHv4HC+Fxl2fI2Z4yR6tl0mORnDr6PA8eihWo4LmSWDbvhALckg==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.1", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.10" - } - }, - "es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - } - }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "eslint": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz", - "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.44.0", - "@humanwhocodes/config-array": "^0.11.10", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.6.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "eslint-config-standard": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", - "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", - "dev": true, - "requires": {} - }, - "eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", - "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-es": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz", - "integrity": "sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng==", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "regexpp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", - "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", - "dev": true - } - } - }, - "eslint-plugin-es-x": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.1.0.tgz", - "integrity": "sha512-AhiaF31syh4CCQ+C5ccJA0VG6+kJK8+5mXKKE7Qs1xcPRg02CDPOj3mWlQxuWS/AYtg7kxrDNgW9YW3vc0Q+Mw==", - "dev": true, - "peer": true, - "requires": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.5.0" - } - }, - "eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "requires": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "eslint-plugin-n": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.1.tgz", - "integrity": "sha512-CDmHegJN0OF3L5cz5tATH84RPQm9kG+Yx39wIqIwPR2C0uhBGMWfbbOtetR83PQjjidA5aXMu+LEFw1jaSwvTA==", - "dev": true, - "peer": true, - "requires": { - "@eslint-community/eslint-utils": "^4.4.0", - "builtins": "^5.0.1", - "eslint-plugin-es-x": "^7.1.0", - "ignore": "^5.2.4", - "is-core-module": "^2.12.1", - "minimatch": "^3.1.2", - "resolve": "^1.22.2", - "semver": "^7.5.3" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "peer": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "requires": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "eslint-plugin-promise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, - "requires": {} - }, - "eslint-plugin-standard": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz", - "integrity": "sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", - "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "dev": true - }, - "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "requires": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "dependencies": { - "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true - }, - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, - "exec-sh": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", - "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", - "dev": true - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "express": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", - "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.0", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.10.3", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - } - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fast-redact": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.1.tgz", - "integrity": "sha512-kYpn4Y/valC9MdrISg47tZOpYBNoTXKgT9GYXFpHN/jYFs+lFkPoisY+LcBODdKVMY96ATzvzsWv+ES/4Kmufw==" - }, - "fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "feedparser": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/feedparser/-/feedparser-2.2.10.tgz", - "integrity": "sha512-WoAOooa61V8/xuKMi2pEtK86qQ3ZH/M72EEGdqlOTxxb3m6ve1NPvZcmPFs3wEDfcBbFLId2GqZ4YjsYi+h1xA==", - "requires": { - "addressparser": "^1.0.1", - "array-indexofobject": "~0.0.1", - "lodash.assign": "^4.2.0", - "lodash.get": "^4.4.2", - "lodash.has": "^4.5.2", - "lodash.uniq": "^4.5.0", - "mri": "^1.1.5", - "readable-stream": "^2.3.7", - "sax": "^1.2.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatstr": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", - "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "formidable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", - "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", - "requires": { - "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" - } - }, - "forward-emitter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/forward-emitter/-/forward-emitter-0.1.1.tgz", - "integrity": "sha512-2B/uPHDqPIVRieu2XJ3RsoDaIN87uqpe3qJjRdWj31leDtDEQ2wLOoJN09UeVQGi/Z79Z5fR9su3OXabASyYqw==" - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", - "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "generic-pool": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==" - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" - }, - "heapdump": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.15.tgz", - "integrity": "sha512-n8aSFscI9r3gfhOcAECAtXFaQ1uy4QSke6bnaL+iymYZ/dWs9cqDqHM+rALfsHUwukUbxsdlECZ0pKmJdQ/4OA==", - "dev": true, - "requires": { - "nan": "^2.13.2" - } - }, - "hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==" - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "html-to-text": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-5.1.1.tgz", - "integrity": "sha512-Bci6bD/JIfZSvG4s0gW/9mMKwBRoe/1RWLxUME/d6WUSZCdY7T60bssf/jFf7EYXRyqU4P5xdClVqiYU0/ypdA==", - "requires": { - "he": "^1.2.0", - "htmlparser2": "^3.10.1", - "lodash": "^4.17.11", - "minimist": "^1.2.0" - } - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", - "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "iconv-lite": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "individual": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", - "integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-docker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", - "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", - "dev": true, - "optional": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", - "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "diff-sequences": "^25.2.6", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.5.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true - }, - "jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dev": true, - "requires": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true - }, - "jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, - "jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dev": true, - "requires": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" - }, - "joycon": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz", - "integrity": "sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - } - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "kareem": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", - "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, - "lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" - }, - "moment-timezone": { - "version": "0.5.38", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.38.tgz", - "integrity": "sha512-nMIrzGah4+oYZPflDvLZUgoVUO4fvAqHstvG3xAUnMolWncuAiLDWNnJZj6EwJGMGfb1ZcuTFE6GI3hNOVWI/Q==", - "requires": { - "moment": ">= 2.9.0" - } - }, - "mongodb": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", - "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", - "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - }, - "dependencies": { - "optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "requires": { - "require-at": "^1.0.6" - } - } - } - }, - "mongoose": { - "version": "5.13.20", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.20.tgz", - "integrity": "sha512-TjGFa/XnJYt+wLmn8y9ssjyO2OhBMeEBtOHb9iJM16EWu2Du6L1Q6zSiEK2ziyYQM8agb4tumNIQFzqbxId7MA==", - "requires": { - "@types/bson": "1.x || 4.0.x", - "@types/mongodb": "^3.5.27", - "bson": "^1.1.4", - "kareem": "2.3.2", - "mongodb": "3.7.4", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.4", - "mquery": "3.2.5", - "ms": "2.1.2", - "optional-require": "1.0.x", - "regexp-clone": "1.0.0", - "safe-buffer": "5.2.1", - "sift": "13.5.2", - "sliced": "1.0.1" - }, - "dependencies": { - "@types/bson": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", - "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", - "requires": { - "@types/node": "*" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", - "requires": {} - }, - "mpath": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", - "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==" - }, - "mquery": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", - "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", - "requires": { - "bluebird": "3.5.1", - "debug": "3.1.0", - "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", - "sliced": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "mri": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.5.tgz", - "integrity": "sha512-d2RKzMD4JNyHMbnbWnznPaa8vbdlq/4pNZ3IgdaGrVbBhebBsGUUE/6qorTMYNS6TwuH3ilfOlD2bf4Igh8CKg==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "devOptional": true - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "node-gyp-build": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz", - "integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==", - "optional": true - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-notifier": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", - "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", - "dev": true, - "optional": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - }, - "dependencies": { - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "optional": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "optional": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-timeout": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", - "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "pino": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-6.8.0.tgz", - "integrity": "sha512-nxq+6Jr7m0cMjYFBoTRw3bco14omZ/SQCheAHz9GVwdkbUrzKhgT+gSI/ql2Mnsca0QQKgpB/ACWhjxE4JsX3Q==", - "requires": { - "fast-redact": "^3.0.0", - "fast-safe-stringify": "^2.0.7", - "flatstr": "^1.0.12", - "pino-std-serializers": "^2.4.2", - "quick-format-unescaped": "^4.0.1", - "sonic-boom": "^1.0.2" - } - }, - "pino-pretty": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-4.3.0.tgz", - "integrity": "sha512-uEc9SUCCGVEs0goZvyznKXBHtI1PNjGgqHviJHxOCEFEWZN6Z/IQKv5pO9gSdm/b+WfX+/dfheWhtZUyScqjlQ==", - "requires": { - "@hapi/bourne": "^2.0.0", - "args": "^5.0.1", - "chalk": "^4.0.0", - "dateformat": "^3.0.3", - "fast-safe-stringify": "^2.0.7", - "jmespath": "^0.15.0", - "joycon": "^2.2.5", - "pump": "^3.0.0", - "readable-stream": "^3.6.0", - "split2": "^3.1.1", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "pino-std-serializers": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.5.0.tgz", - "integrity": "sha512-wXqbqSrIhE58TdrxxlfLwU9eDhrzppQDvGhBEr1gYbzzM4KKo3Y63gSjiDXRKLVS2UOXdPNR2v+KnQgNrs+xUg==" - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "pretty-format": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", - "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", - "dev": true, - "requires": { - "@jest/types": "^25.5.0", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^16.12.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "prism-media": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz", - "integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==", - "requires": {} - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "prompt-anything": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prompt-anything/-/prompt-anything-3.0.0.tgz", - "integrity": "sha512-RwxpEYYfcF/H3GiTHrnEOi3RpU0v1FGoqUdszckGxsftL7+/pZmhfnRQhj+jqn20n2DgkYFZNU1CfM2dP5y15A==" - }, - "prompts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", - "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "property-expr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", - "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "quick-format-unescaped": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz", - "integrity": "sha512-MaL/oqh02mhEo5m5J2rwsVL23Iw2PEaGVHgT2vFt8AAsr0lfvQA5dpXo9TPu0rz7tSBdUPgkbam0j/fj5ZM8yg==" - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "rascal": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/rascal/-/rascal-16.1.2.tgz", - "integrity": "sha512-hyDZ0kSvUhpf2AC9ROwjm8e8S14upFyFOse3z9hbjS+NT08wtIi2RmbZ/K7awo8+DcCAREyh2aP21L70XtOEEA==", - "requires": { - "async": "^3.2.4", - "debug": "^4.3.4", - "forward-emitter": "^0.1.1", - "generic-pool": "^3.8.2", - "lodash": "^4.17.21", - "lru-cache": "^7.10.1", - "safe-json-parse": "^4.0.0", - "stashback": "^2.0.1", - "superagent": "^7.1.3", - "uuid": "^8.3.2", - "xregexp": "^5.1.0" - }, - "dependencies": { - "lru-cache": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.0.tgz", - "integrity": "sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==" - } - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "regenerator-runtime": { - "version": "0.13.10", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", - "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", - "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" - }, - "regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "requires": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rust-result": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", - "integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==", - "requires": { - "individual": "^2.0.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-json-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz", - "integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==", - "requires": { - "rust-result": "^1.0.0" - } - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "semaphore": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", - "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==" - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "sift": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", - "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "sonic-boom": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", - "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", - "requires": { - "atomic-sleep": "^1.0.0", - "flatstr": "^1.0.12" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "optional": true, - "requires": { - "memory-pager": "^1.0.2" - } - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", - "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "requires": { - "readable-stream": "^3.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "stashback": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stashback/-/stashback-2.0.1.tgz", - "integrity": "sha512-NYYD9VxxtEF4YPJjcAm1irb/2kNLG1AYgSQp0bvogzCK6JGW3iNFOILu0jmkM8YsU27SdX93VFkktiqAAHW63w==", - "requires": { - "debug": "^4.3.4" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "string-length": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", - "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - }, - "superagent": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.1.5.tgz", - "integrity": "sha512-HQYyGuDRFGmZ6GNC4hq2f37KnsY9Lr0/R1marNZTgMweVDQLTLJJ6DGQ9Tj/xVVs5HEnop9EMmTbywb5P30aqw==", - "requires": { - "component-emitter": "^1.3.0", - "cookiejar": "^2.1.3", - "debug": "^4.3.4", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^2.0.1", - "methods": "^1.1.2", - "mime": "^2.5.0", - "qs": "^6.10.3", - "readable-stream": "^3.6.0", - "semver": "^7.3.7" - }, - "dependencies": { - "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", - "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true - }, - "tiny-case": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", - "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" - }, - "tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, - "tr46": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", - "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" - } - }, - "typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - } - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "undici": { - "version": "5.26.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.3.tgz", - "integrity": "sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==", - "requires": { - "@fastify/busboy": "^2.0.0" - } - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "utf-8-validate": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.2.tgz", - "integrity": "sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==", - "optional": true, - "requires": { - "node-gyp-build": "~3.7.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "v8-to-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz", - "integrity": "sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", - "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^2.0.2", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "which-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.10.tgz", - "integrity": "sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", - "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", - "requires": {} - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "xregexp": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.1.tgz", - "integrity": "sha512-fKXeVorD+CzWvFs7VBuKTYIW63YD1e1osxwQ8caZ6o1jg6pDAbABDG54LCIq0j5cy7PjRvGIq6sef9DYPXpncg==", - "requires": { - "@babel/runtime-corejs3": "^7.16.5" - } - }, - "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - }, - "yup": { - "version": "1.0.0-beta.4", - "resolved": "https://registry.npmjs.org/yup/-/yup-1.0.0-beta.4.tgz", - "integrity": "sha512-g5uuQH2rN+0Z4L/ix8KoYIS6v7vnrykRzM/u7ExSA0WA33vrw2YOUKShBhaueh9N95oz54slgqBQTHx7E6EHwg==", - "requires": { - "property-expr": "^2.0.4", - "tiny-case": "^1.0.2", - "toposort": "^2.0.2" - } - }, - "zeromq": { - "version": "6.0.0-beta.6", - "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-6.0.0-beta.6.tgz", - "integrity": "sha512-wLf6M7pBHijl+BRltUL2VoDpgbQcOZetiX8UzycHL8CcYFxYnRrpoG5fi3UX3+Umavz1lk4/dGaQez8qiDgr/Q==", - "optional": true, - "requires": { - "node-gyp-build": "^4.1.0" - }, - "dependencies": { - "node-gyp-build": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", - "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", - "optional": true - } - } - }, - "zlib-sync": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/zlib-sync/-/zlib-sync-0.1.6.tgz", - "integrity": "sha512-B2XGrhlXs/izEks+705vn/aL+nWyPgnS1glyCeH8wgZdOvztRn3ZEdqclj6GoJSeA/iRSqaTlznG2KO1M/GuWg==", - "optional": true, - "requires": { - "nan": "^2.14.0" - } - } - } -} diff --git a/services/bot/package.json b/services/bot/package.json deleted file mode 100644 index 7c39be711..000000000 --- a/services/bot/package.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "monitorss", - "version": "6.14.9-beta.23", - "description": "Discord RSS bot with customizable feeds", - "main": "index.js", - "author": "synzen", - "license": "MIT", - "bugs": { - "url": "https://github.com/synzen/MonitoRSS/issues" - }, - "scripts": { - "test": "jest ./src/tests --detectOpenHandles --testTimeout=10000", - "test-unit": "jest ./src/tests --detectOpenHandles --testRegex=unit_", - "test-watch-unit": "jest ./src/tests --detectOpenHandles --testRegex=unit_ --watchAll", - "test-int": "jest ./src/tests --detectOpenHandles --testRegex=int_ --testTimeout=10000", - "test-watch-int": "jest ./src/tests --detectOpenHandles --testRegex=int_ --testTimeout=10000 --watchAll", - "test-watch": "jest ./src/tests --detectOpenHandles --watchAll", - "eslint": "eslint --fix ./src", - "locale-verify": "node scripts/locales/verify.js", - "locale-create": "node scripts/locales/create.js" - }, - "dependencies": { - "@hapi/joi": "^17.1.1", - "@synzen/discord-rest": "^0.7.0-beta.3", - "abort-controller": "^3.0.0", - "discord.js": "^12.5.3", - "discord.js-prompts": "^2.2.1", - "feedparser": "^2.2.10", - "html-to-text": "^5.1.1", - "iconv-lite": "^0.5.2", - "moment": "^2.29.4", - "moment-timezone": "^0.5.33", - "mongoose": "^5.13.8", - "node-fetch": "^2.6.7", - "pino": "6.8.0", - "pino-pretty": "4.3.0" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/synzen/MonitoRSS.git" - }, - "engines": { - "node": ">=12.16", - "npm": "6.x" - }, - "devDependencies": { - "@types/jest": "^25.2.3", - "eslint": "^8.45.0", - "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-standard": "^4.1.0", - "heapdump": "^0.3.15", - "jest": "^26.6.3" - }, - "optionalDependencies": { - "bufferutil": "^4.0.1", - "erlpack": "github:discordapp/erlpack", - "utf-8-validate": "^5.0.2", - "zeromq": "^6.0.0-beta.6", - "zlib-sync": "^0.1.6" - } -} diff --git a/services/bot/scripts/locales/create.js b/services/bot/scripts/locales/create.js deleted file mode 100644 index 135c208d5..000000000 --- a/services/bot/scripts/locales/create.js +++ /dev/null @@ -1,41 +0,0 @@ -const fs = require('fs') -const path = require('path') -const readline = require('readline') -const localeData = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'locales', `en-US.json`))) - -const obj = {} - -function traverse (object, reference) { - for (const key in object) { - const value = object[key] - if (typeof value === 'object') { - reference[key] = {} - traverse(value, reference[key]) - } else { - reference[key] = '' - } - } -} - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}) - -console.log('The locale name should by in the format of \x1b[1mlanguage-region\x1b[0m.') - -function prompt () { - rl.question('\nType the locale to generate a template: ', name => { - traverse(localeData, obj) - if (name.includes('_')) { - console.log('Invalid. Hyphens (-) must be used, not underscores.') - return prompt() - } - const file = path.join(__dirname, '..', '..', 'src', 'locales', `${name}.json`) - fs.writeFileSync(file, JSON.stringify(obj, null, 2)) - console.log(`Created at ${file}`) - rl.close() - }) -} - -prompt() diff --git a/services/bot/scripts/locales/verify.js b/services/bot/scripts/locales/verify.js deleted file mode 100644 index 101c02d4b..000000000 --- a/services/bot/scripts/locales/verify.js +++ /dev/null @@ -1,83 +0,0 @@ -const fs = require('fs') -const path = require('path') -const COLORS = { - BRIGHT: '\x1b[1m', - RESET: '\x1b[0m', - RED: '\x1b[31m', - GREEN: '\x1b[32m', - CYAN: '\x1b[36m' -} -const CONSTANT_CHARACTERS = ['"', '\n', '`', '\u200b'] -const referenceLocaleData = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'locales', 'en-US.json'))) -const fileNames = fs.readdirSync(path.join(__dirname, '..', '..', 'src', 'locales')) - -const stringCountsByLocale = {} -const errorStringsByLocale = {} - -function traverse (object, reference, location, locale) { - for (const key in reference) { - if (typeof reference[key] !== typeof object[key]) { - errorStringsByLocale[locale].push(`${COLORS.CYAN}${location}[${key}]${COLORS.RESET} expected ${COLORS.GREEN}${typeof reference[key]}${COLORS.RESET} but found ${COLORS.RED}${typeof object[key]}${COLORS.RESET}`) - } else if (typeof reference[key] === 'object') { - traverse(object[key], reference[key], location + `[${key}]`, locale) - } else if (typeof reference[key] === 'string' && object[key].length > 0) { - ++stringCountsByLocale[locale] - for (const character of CONSTANT_CHARACTERS) { - const referenceCharCount = (reference[key].match(new RegExp(character, 'g')) || []).length - const charCount = (object[key].match(new RegExp(character, 'g')) || []).length - if (referenceCharCount !== charCount) { - const printCharacter = character === '\n' ? '\\n' : character === '\u200b' ? '\\u200b' : character - errorStringsByLocale[locale].push(`${COLORS.CYAN}${location}[${key}]${COLORS.RESET} expected ${COLORS.GREEN}${referenceCharCount} ${COLORS.BRIGHT}${printCharacter}${COLORS.RESET} character(s)${COLORS.RESET} but found ${COLORS.RED}${charCount} ${COLORS.BRIGHT}${printCharacter}${COLORS.RESET} character(s)`) - } - } - } - } -} - -for (const fileName of fileNames) { - const localeData = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'locales', fileName))) - const locale = fileName.replace('.json', '') - errorStringsByLocale[locale] = [] - stringCountsByLocale[locale] = 0 - traverse(localeData, referenceLocaleData, locale, locale) -} - -const totalStrings = stringCountsByLocale['en-US'] -const okStrings = [] -const errorStrings = [] - -for (const locale in errorStringsByLocale) { - const strings = errorStringsByLocale[locale] - if (strings.length === 0) { - okStrings.push(`${COLORS.GREEN}√${COLORS.RESET} ${locale} ${locale === 'en-US' ? '(Reference)' : `(${((stringCountsByLocale[locale] / totalStrings) * 100).toFixed(2)}%)`}`) - continue - } - // Prettify the logs - let longestLocation = 0 - for (const line of strings) { - const parts = line.split('expected') - const location = parts[0] - if (location.length > longestLocation) { - longestLocation = location.length - } - } - - for (let i = 0; i < strings.length; ++i) { - const parts = strings[i].split('expected') - let location = parts[0] - while (location.length < longestLocation) { - location += ' ' - } - parts[0] = location - strings[i] = parts.join('expected') - } - errorStrings.push(`${COLORS.RED}X${COLORS.RESET} ${locale}\n${strings.join('\n')}`) -} - -if (okStrings.length > 0) { - console.log(okStrings.join('\n')) -} -if (errorStrings.length > 0) { - console.log(errorStrings.join('\n')) -} -console.log('\nNote that for untranslated strings, their values must be "" (an empty string). They cannot be undefined.\nEmpty string translations will fall back to using the default en-US strings.') diff --git a/services/bot/scripts/pre_v6.js b/services/bot/scripts/pre_v6.js deleted file mode 100644 index 35a11ff5a..000000000 --- a/services/bot/scripts/pre_v6.js +++ /dev/null @@ -1,63 +0,0 @@ -const fs = require('fs') -const configuration = require('../src/config.js') -const mongoose = require('mongoose') -const init = require('../src/initialization/index.js') -const v6 = require('./updates/6.0.0.js') - -const BUFFER_CONFIGS = ['sslCA', 'sslCRL', 'sslCert', 'sslKey'] - -function readFileData (config = {}) { - const buffers = {} - if (Object.keys(config).length > 0) { - for (let x = 0; x < BUFFER_CONFIGS.length; ++x) { - const name = BUFFER_CONFIGS[x] - if (config[name]) { - buffers[name] = fs.readFileSync(config[name]) - } - } - } - return buffers -} - -/** - * @param {import('mongoose').Connection} connection - */ -async function dumpCollections (connection) { - const toCheck = ['profiles', 'feeds', 'subscribers', 'filtered_formats', 'fail_records', 'supporters'] - const collections = (await connection.db - .listCollections().toArray()).map(c => c.name) - for (const name of toCheck) { - if (collections.includes(name)) { - console.log(`Dropping ${name} collection`) - await connection.collection(name).drop() - } - } -} - -/** - * @param {string} uri - * @param {Object} options - */ -async function run (config) { - const uri = config.database.uri - const options = config.database.connection - configuration.set(config) - if (uri.startsWith('mongo')) { - const parsedOptions = readFileData(options) - const connection = await mongoose.createConnection(uri, { - useCreateIndex: true, - useNewUrlParser: true, - useUnifiedTopology: true, - ...parsedOptions - }) - await init.setupModels(connection) - await dumpCollections(connection) - const failures = await v6.run(false, uri, connection) - await connection.close() - return failures - } else { - return v6.run(true, uri) - } -} - -module.exports = run diff --git a/services/bot/scripts/scheduleRun.js b/services/bot/scripts/scheduleRun.js deleted file mode 100644 index d7071af58..000000000 --- a/services/bot/scripts/scheduleRun.js +++ /dev/null @@ -1,68 +0,0 @@ -const initialize = require('../src/initialization/index.js') -const connectDatabase = require('../src/util/connectDatabase.js') -const ScheduleManager = require('../src/structs/ScheduleManager.js') -const setConfig = require('../src/config.js').set - -/** - * Returns the schedules that are only supposed to run, instead of running all of them. - * It uses process.env.SCHEDULES, where it contains comma-separated schedule names - * - * @param {import('../src/structs/db/Schedule')[]} schedules All the available schedules - * @param {string[]} whitelistNames - * @returns {string[]|null} - */ -function getWhitelistedSchedules (schedules, whitelistNames = null) { - if (!whitelistNames) { - return null - } - const arrayVal = whitelistNames - .split(',') - .map(name => schedules.find((schedule) => schedule.name === name)) - .filter((schedule) => schedule) - if (arrayVal.length === 0) { - return null - } - return arrayVal -} - -async function testScheduleRun (userConfig, runSettings = {}) { - let config = setConfig(userConfig) - config = setConfig({ - ...config - }) - const { - schedules: customSchedules, - testRuns, - whitelistNames - } = runSettings - const con = await connectDatabase(config.database.uri, config.database.connection) - console.log('Connected to database') - await initialize.setupModels(con) - const schedules = await initialize.populateSchedules(customSchedules) - const scheduleManager = new ScheduleManager() - scheduleManager.testRuns = testRuns - const whitelistedSchedules = getWhitelistedSchedules(schedules, whitelistNames) - /** - * If a whitelist exists in process.env, only run those specific schedules. - */ - if (!whitelistedSchedules) { - scheduleManager.addSchedules(schedules) - } else { - scheduleManager.addSchedules(whitelistedSchedules) - } - scheduleManager.beginTimers() - return scheduleManager -} - -module.exports = testScheduleRun - -/** - * 0. Only run certain schedules in ScheduleRun - * 1. Measure how much traffic supporter schedules make - * 2. create discriminators for failed links, and update failed links based on that - * 3. Add support for proxies - * 4. only use proxy after request failure - * - * what about failure messages? `1 - * - */ diff --git a/services/bot/scripts/updates/6.0.0.js b/services/bot/scripts/updates/6.0.0.js deleted file mode 100644 index c2d4b07ff..000000000 --- a/services/bot/scripts/updates/6.0.0.js +++ /dev/null @@ -1,304 +0,0 @@ -const fs = require('fs') -const path = require('path') -const getConfig = require('../../src/config.js').get -const mongoose = require('mongoose') -const Profile = require('../../src/structs/db/Profile.js') -const Feed = require('../../src/structs/db/Feed.js') -const FilteredFormat = require('../../src/structs/db/FilteredFormat.js') -const Subscriber = require('../../src/structs/db/Subscriber.js') -const FailRecord = require('../../src/structs/db/FailRecord.js') -const Supporter = require('../../src/structs/db/Supporter.js') -const GuildData = require('../../src/structs/GuildData.js') -const oldEmbedKeys = { - footer_text: 'footerText', - footerIconUrl: 'footerIconURL', - footer_icon_url: 'footerIconURL', - author_name: 'authorName', - authorIconUrl: 'authorIconURL', - author_icon_url: 'authorIconURL', - author_url: 'authorURL', - authorUrl: 'authorURL', - thumbnailUrl: 'thumbnailURL', - thumbnail_url: 'thumbnailURL', - imageUrl: 'imageURL', - image_url: 'imageURL' -} - -function HEXToVBColor (rrggbb) { - const bbggrr = rrggbb.substr(4, 2) + rrggbb.substr(2, 2) + rrggbb.substr(0, 2) - return parseInt(bbggrr, 16) -} - -function sanitizeFilters (target) { - const filters = target.filters - if (filters) { - for (const key in filters) { - if (key.includes('.')) { - delete filters[key] - } - } - } -} - -function getOldDate (hoursAgo) { - // https://stackoverflow.com/questions/1050720/adding-hours-to-javascript-date-object - const date = new Date() - date.setTime(date.getTime() - hoursAgo * 60 * 60 * 1000) - return date -} - -async function updateVIP (vip) { - const patreon = !vip.expireAt - const toStore = { - _id: vip.id - } - if (patreon && !vip.override) { - toStore.patron = true - toStore.guilds = vip.servers || [] - } else { - toStore.webhook = vip.allowWebhooks - toStore.guilds = vip.servers || [] - toStore.maxGuilds = vip.maxServers || 1 - toStore.maxFeeds = vip.maxFeeds - if (vip.comment) { - toStore.comment = vip.comment - } - if (vip.expireAt) { - toStore.expireAt = vip.expireAt - } - } - const supporter = new Supporter(toStore) - await supporter.save() -} - -async function updateFailRecords (doc) { - const config = getConfig() - const insert = { - _id: doc.link - } - if (doc.failed) { - insert.reason = doc.failed - insert.alerted = true - const record = new FailRecord(insert) - const oldDate = getOldDate(config.feeds.hoursUntilFail + 10) - record.failedAt = oldDate.toISOString() - await record.save() - } else { - const record = new FailRecord(insert) - await record.save() - } -} - -async function updateProfiles (guildRss) { - const data = { - feeds: [], - filteredFormats: [], - subscribers: [] - } - // Profile first - delete guildRss.version - const profile = new Profile({ - ...guildRss, - _id: guildRss.id, - alert: guildRss.sendAlertsTo || [] - }) - const profileJSON = profile.toJSON() - let populatedProfile = false - for (const key in profileJSON) { - if (key === '_id' || key === 'name') { - continue - } - const value = profileJSON[key] - if (key === 'alert') { - if (value && value.length > 0) { - populatedProfile = true - } - continue - } - if (key === 'prefix') { - if (!value || value.includes(' ')) { - // Delete prefixes with spaces - not supported - profileJSON[key] = undefined - continue - } - } - if (value !== undefined) { - populatedProfile = true - } - } - if (populatedProfile) { - data.profile = profileJSON - } - - const rssList = guildRss.sources - if (rssList) { - for (const rssName in rssList) { - // Feed - const feed = { ...rssList[rssName] } - feed.url = feed.link - feed.guild = guildRss.id - feed._id = new mongoose.Types.ObjectId().toHexString() - // Since mongoose map keys cannot have dots, remove them - sanitizeFilters(feed) - - // Format - const text = feed.message - if (text) { - feed.text = text - } - const embeds = feed.embeds - if (Array.isArray(embeds) && embeds.length > 0) { - for (const embed of embeds) { - // Replace old keys - for (const key in oldEmbedKeys) { - const newKey = oldEmbedKeys[key] - const value = embed[key] - if (value) { - if (!embed[newKey]) { - embed[newKey] = value - } - delete embed[key] - } - } - - // Convert hex strings to numbers - if (embed.color && isNaN(Number(embed.color))) { - embed.color = HEXToVBColor(embed.color) - } - - const fields = embed.fields - // Remove non-array fields - if (!Array.isArray(fields)) { - delete embed.fields - } else { - for (const field of fields) { - field.name = field.title ? field.title : '\u200b' - delete field.title - field.value = field.value ? field.value : '\u200b' - } - } - } - FilteredFormat.pruneEmbeds(embeds) - } else { - delete feed.embeds - } - - // Check titles - if (feed.checkTitles) { - feed.ncomparisons = ['title'] - } - - data.feeds.push(new Feed(feed).toJSON()) - - // Subscribers - const feedSubscribers = feed.subscribers - if (feedSubscribers && feedSubscribers.length > 0) { - for (const s of feedSubscribers) { - sanitizeFilters(s) - const subscriber = new Subscriber({ - ...s, - type: s.type !== 'role' && s.type !== 'user' ? 'role' : s.type, - feed: feed._id - }) - data.subscribers.push(subscriber.toJSON()) - } - } - } - } - - const guildData = new GuildData(data) - await guildData.restore() -} - -function formatRejections (results, dataList) { - const errors = [] - for (let i = 0; i < results.length; ++i) { - const result = results[i] - if (result.status === 'rejected') { - errors.push({ - error: result.reason.message, - data: dataList[i] - }) - } - } - return errors -} - -/** - * @param {boolean} databaseless - * @param {string} uri - * @param {import('mongoose').Connection} connection - */ -async function getProfiles (databaseless, uri, connection) { - if (databaseless) { - const names = fs.readdirSync(uri) - return names.map(n => { - const json = JSON.parse(fs.readFileSync(path.join(uri, n))) - json._id = json.id - return json - }) - } else { - return connection.collection('guilds').find({}).toArray() - } -} - -/** - * @param {boolean} databaseless - * @param {string} uri - * @param {import('mongoose').Connection} connection - */ -async function startProfiles (databaseless, uri, connection) { - console.log('Running profile migration') - const guildRssList = await getProfiles(databaseless, uri, connection) - if (guildRssList.length === 0) { - console.log('No guilds found') - return startFailRecords(databaseless, connection) - } - const promises = guildRssList.map(guildRss => updateProfiles(guildRss)) - const results = await Promise.allSettled(promises) - const rejects = formatRejections(results, guildRssList) - console.log(`Completed ${results.length} profiles`) - return rejects.concat(await startFailRecords(databaseless, connection)) -} - -/** - * @param {boolean} databaseless - * @param {import('mongoose').Connection} connection - */ -async function startFailRecords (databaseless, connection) { - if (databaseless) { - console.log('Skipping fail records migration due to databaseless') - return [] - } - console.log('Running fail records migration') - const failedLinks = await connection.collection('failed_links').find({}).toArray() - if (failedLinks.length === 0) { - console.log('No failed links found') - return startVIPs(connection) - } - const promises = failedLinks.map(failedLink => updateFailRecords(failedLink)) - const results = await Promise.allSettled(promises) - const rejects = formatRejections(results, failedLinks) - console.log(`Completed ${results.length} fail records`) - return rejects.concat(await startVIPs(connection)) -} - -/** - * - * @param {import('mongoose').Connection} connection - */ -async function startVIPs (connection) { - const vips = await connection.collection('vips').find({}).toArray() - if (vips.length === 0) { - return [] - } - const promises = vips.map(vip => updateVIP(vip)) - const results = await Promise.allSettled(promises) - const rejects = formatRejections(results, vips) - return rejects -} - -exports.updateProfiles = updateProfiles -exports.updateFailRecords = updateFailRecords -exports.updateVIP = updateVIP -exports.run = startProfiles diff --git a/services/bot/shard.js b/services/bot/shard.js deleted file mode 100644 index abc3fb9e8..000000000 --- a/services/bot/shard.js +++ /dev/null @@ -1,7 +0,0 @@ -const MonitoRSS = require('./index.js') -const passedConfig = JSON.parse(process.env.DRSS_CONFIG) -const config = require('./src/config.js').set(passedConfig) - -const client = new MonitoRSS.Client() - -client.login(config.bot.token) diff --git a/services/bot/src/commands/add.js b/services/bot/src/commands/add.js deleted file mode 100644 index 24a95d019..000000000 --- a/services/bot/src/commands/add.js +++ /dev/null @@ -1,126 +0,0 @@ -const getConfig = require('../config.js').get -const Translator = require('../structs/Translator.js') -const Profile = require('../structs/db/Profile.js') -const FailRecord = require('../structs/db/FailRecord.js') -const Feed = require('../structs/db/Feed.js') -const Guild = require('../structs/Guild.js') -const BannedFeed = require('../structs/db/BannedFeed') -const createLogger = require('../util/logger/create.js') - -module.exports = async (message) => { - const guild = new Guild(message.guild.id) - const [profile, maxFeedsAllowed] = await Promise.all([ - Profile.get(message.guild.id), - guild.getMaxFeeds() - ]) - - const feeds = await Feed.getManyBy('guild', message.guild.id) - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const translate = Translator.createLocaleTranslator(profile ? profile.locale : undefined) - if (message.content.split(' ').length === 1) { - // If there is no link after rssadd, return. - return message.channel.send(translate('commands.add.correctSyntax', { prefix })) - } - - const log = createLogger(message.guild.shard.id) - let linkList = message.content.split(' ') - linkList.shift() - linkList = linkList.map(item => item.trim()).filter(item => item).join(' ').split('>') - - const passedAddLinks = [] - const failedAddLinks = {} - const totalLinks = linkList.length - let limitExceeded = false - - let checkedSoFar = 0 - - const verifyMsg = await message.channel.send(translate('commands.add.processing')) - - // Start loop over links - for (let i = 0; i < linkList.length; ++i) { - const curLink = linkList[i] - const linkItem = curLink.split(' ') - const link = linkItem[0].trim() - - const associatedBannedFeed = await BannedFeed.findForUrl(link, message.guild.id) - - if (!link.startsWith('http')) { - failedAddLinks[link] = translate('commands.add.improperFormat') - - continue - } else if (maxFeedsAllowed !== 0 && feeds.length + checkedSoFar >= maxFeedsAllowed) { - log.info({ - guild: message.guild - }, `Unable to add feed ${link} due to limit of ${maxFeedsAllowed} feeds`) - // Only show link-specific error if it's one link since they user may be trying to add a huge number of links that exceeds the message size limit - if (totalLinks.length === 1) { - failedAddLinks[link] = translate('commands.add.limitReached', { max: maxFeedsAllowed }) - } else { - limitExceeded = true - } - - continue - } else if (associatedBannedFeed) { - console.log(associatedBannedFeed) - failedAddLinks[link] = translate('commands.add.bannedFeed', { - reason: associatedBannedFeed.reason || 'unknown' - }) - - continue - } - - for (const feed of feeds) { - if (feed.url === link && message.channel.id === feed.channel) { - failedAddLinks[link] = translate('commands.add.alreadyExists') - continue - } - } - linkItem.shift() - - try { - const newFeed = new Feed({ - url: link, - channel: message.channel.id, - guild: message.guild.id - }) - await newFeed.testAndSave(message.guild.shardID) - log.info({ - guild: message.guild - }, `Added ${link}`) - FailRecord.reset(link).catch(err => log.error(err, `Unable to reset failed status for link ${link} after rssadd`)) - passedAddLinks.push(link) - ++checkedSoFar - } catch (err) { - const channelErrMsg = err.message - log.warn({ - error: err - }, `Unable to add ${link}`) - failedAddLinks[link] = channelErrMsg - } - } - // End loop over links - - let msg = '' - if (passedAddLinks.length > 0) { - let successBox = translate('commands.add.success') + ':\n```\n' - for (const passedLink of passedAddLinks) { - successBox += `\n${passedLink}` - } - msg += successBox + '\n```\n' - } - if (Object.keys(failedAddLinks).length > 0) { - let failBox = `\n${limitExceeded ? translate('commands.add.failedLimit', { max: maxFeedsAllowed }) : ''}${translate('commands.add.failedList')}:\n\`\`\`\n` - for (const failedLink in failedAddLinks) { - failBox += `\n\n${failedLink}\n${translate('commands.add.reason')}: ${failedAddLinks[failedLink]}` - } - msg += failBox + '\n```\n' - } else if (limitExceeded) { - msg += translate('commands.add.failedLimit', { max: maxFeedsAllowed }) - } - if (passedAddLinks.length > 0) { - msg += `${translate('commands.add.successInfo', { prefix })} ${translate('generics.backupReminder', { prefix })}` - } - - await verifyMsg.edit(msg) -} diff --git a/services/bot/src/commands/alert.js b/services/bot/src/commands/alert.js deleted file mode 100644 index 1b41dbf37..000000000 --- a/services/bot/src/commands/alert.js +++ /dev/null @@ -1,97 +0,0 @@ -const getConfig = require('../config.js').get -const Translator = require('../structs/Translator.js') -const Profile = require('../structs/db/Profile.js') -const Feed = require('../structs/db/Feed.js') -const createLogger = require('../util/logger/create.js') - -module.exports = async (message, automatic) => { // automatic indicates invokation by the bot - let [profile, feeds] = await Promise.all([ - Profile.get(message.guild.id), - Feed.getManyBy('guild', message.guild.id) - ]) - const translate = Translator.createLocaleTranslator(profile ? profile.locale : undefined) - if (feeds.length === 0) { - return message.channel.send(Translator.translate('commands.alert.noFeeds')) - } - const contentArray = message.content.split(' ').map(item => item.trim()) - const guildID = message.guild.id - const guildName = message.guild.name - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - if (!profile) { - profile = new Profile({ - _id: guildID, - name: guildName - }) - } - const log = createLogger(message.guild.shard.id) - switch (contentArray[1]) { - case 'add': - case 'remove': { - const target = contentArray[2] - if (!target) { - return message.channel.send(translate('commands.alert.info', { prefix })) - } - if (target === '@everyone' || target === '@here') { - return message.channel.send(translate('commands.alert.everyoneNotAllowed')) - } - const userMention = message.mentions.users.first() - const member = userMention || await message.guild.members.fetch(target === 'me' - ? message.author.id - : target) - if (!member) { - return message.channel.send(translate('commands.alert.notFound', { - user: userMention || `\`${target}\`` - })) - } - if (contentArray[1] === 'add') { - // Add - if (profile.alert.includes(member.id)) { - return message.channel.send(translate('commands.alert.alreadyEnabled')) - } - profile.alert.push(member.id) - log.info({ - guild: message.guild - }, 'Added user to alerts', message.guild, userMention || member.user) - await member.send(translate('commands.alert.successDM', { member, guildName, guildID })) - await message.channel.send(translate('commands.alert.success', { member })) - } else { - // Remove - const removeIndex = profile.alert.indexOf(member.id) - if (removeIndex === -1) { - return message.channel.send(translate('commands.alert.removeFail')) - } - profile.alert.splice(removeIndex, 1) - log.info({ - guild: message.guild - }, 'Removed user from alerts', message.guild, userMention || member.user) - await member.send(translate('commands.alert.removedDM', { member, guildName, guildID })) - await message.channel.send(translate('commands.alert.removed', { member })) - } - await profile.save() - break - } - case 'list': { - if (profile.alert.length === 0) { - return message.channel.send(translate('commands.alert.listEmpty')) - } - let msg = translate('commands.alert.list') - for (const id of profile.alert) { - try { - const member = await message.guild.members.fetch(id) - msg += `${member}\n` - } catch (err) { - log.warn({ - id, - error: err - }, 'Failed to fetch member') - msg += `${id} (Unknown member)\n` - } - } - await message.channel.send(msg) - break - } - default: - return message.channel.send(translate('commands.alert.info', { prefix })) - } -} diff --git a/services/bot/src/commands/backup.js b/services/bot/src/commands/backup.js deleted file mode 100644 index 2ffb45299..000000000 --- a/services/bot/src/commands/backup.js +++ /dev/null @@ -1,20 +0,0 @@ -const GuildData = require('../structs/GuildData.js') -const Attachment = require('discord.js').MessageAttachment -const Translator = require('../structs/Translator.js') - -module.exports = async (message, automatic) => { // automatic indicates invokation by the bot - const guildId = message.guild.id - const guildData = await GuildData.get(guildId) - const locale = guildData.profile ? guildData.profile.locale : undefined - const translate = Translator.createLocaleTranslator(locale) - if (guildData.isEmpty() && !automatic) { - return message.channel.send(translate('commands.backup.noProfile')) - } - if (message.guild.me.permissionsIn(message.channel).has('ATTACH_FILES')) { - const data = Buffer.from(JSON.stringify(guildData.toJSON(), null, 2)) - const attachment = new Attachment(data, guildId + '.json') - await message.channel.send(attachment) - } else { - await message.channel.send(translate('commands.backup.noPermission')) - } -} diff --git a/services/bot/src/commands/clone.js b/services/bot/src/commands/clone.js deleted file mode 100644 index 55f07bf39..000000000 --- a/services/bot/src/commands/clone.js +++ /dev/null @@ -1,18 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const clonePrompts = require('./prompts/clone/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectSourceFeedNode = new PromptNode(clonePrompts.selectSourceFeed.prompt) - const selectDestinationFeedsNode = new PromptNode(clonePrompts.selectDestinationFeeds.prompt) - const selectPropertiesNode = new PromptNode(clonePrompts.selectProperties.prompt) - const confirmNode = new PromptNode(clonePrompts.confirm.prompt) - const confirmSuccessNode = new PromptNode(clonePrompts.confirmSuccess.prompt) - - selectSourceFeedNode.addChild(selectDestinationFeedsNode) - selectDestinationFeedsNode.addChild(selectPropertiesNode) - selectPropertiesNode.addChild(confirmNode) - confirmNode.addChild(confirmSuccessNode) - - await runWithFeedGuild(selectSourceFeedNode, message) -} diff --git a/services/bot/src/commands/compare.js b/services/bot/src/commands/compare.js deleted file mode 100644 index 39031fd35..000000000 --- a/services/bot/src/commands/compare.js +++ /dev/null @@ -1,107 +0,0 @@ -const Translator = require('../structs/Translator.js') -const Profile = require('../structs/db/Profile.js') -const { PromptNode } = require('discord.js-prompts') -const commonPrompts = require('./prompts/common/index.js') -const runWithFeedsProfile = require('./prompts/runner/run.js') -const getConfig = require('../config.js').get -const createLogger = require('../util/logger/create.js') - -/** - * @param {string} command - */ -function getValidInputs (command) { - const parts = command.split(' ') - // Remove the command name, such as rss.compare - parts.shift() - const cleanedParts = parts - .map(p => p.trim()) - .filter((p, index, array) => { - const exists = !!p - const isNotDupe = parts.indexOf(p) === index - const hasCorrectSymbols = p.startsWith('+') || p.startsWith('-') - return exists && isNotDupe && hasCorrectSymbols - }) - return cleanedParts -} - -/** - * @param {string} str - */ -function getInvalidInputs (str) { - const parts = str.split(' ') - parts.shift() - const cleanedParts = parts - .map(p => p.trim()) - .filter((p, index) => p && parts.indexOf(p) === index) - - return cleanedParts.filter(s => (!s.startsWith('+') && !s.startsWith('-')) || s.length < 2) -} - -module.exports = async (message, command) => { - const profile = await Profile.get(message.guild.id) - const guildLocale = profile ? profile.locale : undefined - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const translate = Translator.createLocaleTranslator(guildLocale) - const arr = message.content.split(' ') - if (arr.length === 1) { - return message.channel.send(translate('commands.compare.info', { - infoURL: 'https://docs.monitorss.xyz/advanced-bot-customizations/np-comparisons', - prefix - })) - } - const reset = arr[1].trim() === 'reset' - const list = arr[1].trim() === 'list' - const validProperties = getValidInputs(message.content) - const invalids = getInvalidInputs(message.content) - if (!reset && !list) { - if (invalids.length > 0 || validProperties.length === 0) { - const stringified = `\`${invalids.join('`,`')}\`` - return message.channel.send(translate('commands.compare.invalid', { errors: stringified })) - } - } - const selectFeedNode = new PromptNode(commonPrompts.selectFeed.prompt) - const { selectedFeed: feed } = await runWithFeedsProfile(selectFeedNode, message) - if (!feed) { - return - } - if (list) { - const nvalues = feed.ncomparisons.map(v => `-${v}`) - const pvalues = feed.pcomparisons.map(v => `+${v}`) - const values = nvalues.concat(pvalues) - if (values.length === 0) { - return message.channel.send(translate('commands.compare.listNone', { url: feed.url })) - } - const str = values.length > 0 ? `\`${values.join('`,`')}\`` : '' - return message.channel.send(translate('commands.compare.list', { values: str, url: feed.url })) - } - - const log = createLogger(message.guild.shard.id) - - if (reset) { - feed.ncomparisons = [] - await feed.save() - await message.channel.send(translate('commands.compare.reset', { url: feed.url })) - log.info({ - guild: message.guild - }, 'Comparisons have been reset') - } else { - const newPValues = validProperties - .filter((prop) => prop.startsWith('+')) - .map((s) => s.replace('+', '')) - const newNValues = validProperties - .filter((prop) => prop.startsWith('-')) - .map((s) => s.replace('-', '')) - console.log(validProperties.join('\n')) - feed.ncomparisons = newNValues - feed.pcomparisons = newPValues - await feed.save() - const str = `\`${validProperties.join('`\n`')}\`` - await message.channel.send(translate('commands.compare.success', { added: str, url: feed.url })) - log.info({ - guild: message.guild - }, `Comparisons have set ${JSON.stringify(validProperties)}`) - } -} - -module.exports.getValidInputs = getValidInputs diff --git a/services/bot/src/commands/date.js b/services/bot/src/commands/date.js deleted file mode 100644 index 8f201eacc..000000000 --- a/services/bot/src/commands/date.js +++ /dev/null @@ -1,36 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const datePrompts = require('./prompts/date/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectCustomizationNode = new PromptNode(datePrompts.selectCustomization.prompt) - - const askTimezoneCondition = data => data.selected === '1' - const askTimezoneNode = new PromptNode(datePrompts.askTimezone.prompt, askTimezoneCondition) - - const askFormatCondition = data => data.selected === '2' - const askFormatNode = new PromptNode(datePrompts.askFormat.prompt, askFormatCondition) - - const askLanguageCondition = data => data.selected === '3' - const askLanguageNode = new PromptNode(datePrompts.askLanguage.prompt, askLanguageCondition) - - const successResetNodeCondition = data => data.selected === '4' - const successResetNode = new PromptNode(datePrompts.successReset.prompt, successResetNodeCondition) - - selectCustomizationNode.setChildren([ - successResetNode, - askTimezoneNode, - askFormatNode, - askLanguageNode - ]) - - const successTimezoneNode = new PromptNode(datePrompts.successTimezone.prompt) - const successFormatNode = new PromptNode(datePrompts.successFormat.prompt) - const successLanguageNode = new PromptNode(datePrompts.successLanguage.prompt) - - askTimezoneNode.addChild(successTimezoneNode) - askFormatNode.addChild(successFormatNode) - askLanguageNode.addChild(successLanguageNode) - - await runWithFeedGuild(selectCustomizationNode, message) -} diff --git a/services/bot/src/commands/dump.js b/services/bot/src/commands/dump.js deleted file mode 100644 index f63e04081..000000000 --- a/services/bot/src/commands/dump.js +++ /dev/null @@ -1,15 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const commonPrompts = require('./prompts/common/index.js') -const dumpPrompts = require('./prompts/dump/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message, command) => { - const selectFeedNode = new PromptNode(commonPrompts.selectFeed.prompt) - const sendFileNode = new PromptNode(dumpPrompts.sendFile.prompt) - - selectFeedNode.addChild(sendFileNode) - - await runWithFeedGuild(selectFeedNode, message, { - raw: message.content.split(' ')[1] === 'original' - }) -} diff --git a/services/bot/src/commands/embed.fields.js b/services/bot/src/commands/embed.fields.js deleted file mode 100644 index 754f09270..000000000 --- a/services/bot/src/commands/embed.fields.js +++ /dev/null @@ -1,44 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const commonPrompts = require('./prompts/common/index.js') -const embedPrompts = require('./prompts/embed/index.js') -const fieldPrompts = require('./prompts/embed.fields/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectFeedNode = new PromptNode(commonPrompts.selectFeed.prompt) - - const selectEmbedNodeCondition = data => !!data.selectedFeed.webhook - const selectEmbedNode = new PromptNode(embedPrompts.selectEmbed.prompt, selectEmbedNodeCondition) - - const selectFieldActionNodeCondition = data => !data.selectedFeed.webhook - const selectFieldActionNode = new PromptNode(fieldPrompts.selectAction.prompt, selectFieldActionNodeCondition) - - const addFieldNodeCondition = data => data.selected === '1' || data.selected === '2' - const addFieldNode = new PromptNode(fieldPrompts.addField.prompt, addFieldNodeCondition) - const addFieldSuccessNode = new PromptNode(fieldPrompts.addFieldSuccess.prompt) - - const addBlankFieldSuccessNodeCondition = data => data.selected === '3' || data.selected === '4' - const addBlankFieldSuccessNode = new PromptNode(fieldPrompts.addBlankFieldSuccess.prompt, addBlankFieldSuccessNodeCondition) - - const removeFieldNodeCondition = data => data.selected === '5' - const removeFieldNode = new PromptNode(fieldPrompts.removeField.prompt, removeFieldNodeCondition) - const removeFieldSucessNode = new PromptNode(fieldPrompts.removeFieldSuccess.prompt) - - selectFeedNode.setChildren([ - selectEmbedNode, - selectFieldActionNode - ]) - selectEmbedNode.addChild(selectFieldActionNode) - - selectFieldActionNode.setChildren([ - addFieldNode, - addBlankFieldSuccessNode, - removeFieldNode - ]) - - addFieldNode.addChild(addFieldSuccessNode) - removeFieldNode.addChild(removeFieldSucessNode) - await runWithFeedGuild(selectFeedNode, message, { - targetEmbedIndex: 0 - }) -} diff --git a/services/bot/src/commands/embed.js b/services/bot/src/commands/embed.js deleted file mode 100644 index cb6e0d684..000000000 --- a/services/bot/src/commands/embed.js +++ /dev/null @@ -1,46 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const commonPrompts = require('./prompts/common/index.js') -const embedPrompts = require('./prompts/embed/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectFeedNode = new PromptNode(commonPrompts.selectFeed.prompt) - - const selectEmbedNodeCondition = data => !!data.selectedFeed.webhook - const selectEmbedNode = new PromptNode(embedPrompts.selectEmbed.prompt, selectEmbedNodeCondition) - - const removeAllEmbedsSuccessNodeCondition = data => data.targetEmbedIndex === data.selectedFeed.embeds.length + 1 - const removeAllEmbedsSuccessNode = new PromptNode(embedPrompts.removeAllEmbedsSuccess.prompt, removeAllEmbedsSuccessNodeCondition) - - const selectPropertiesNodeCondition = () => true - const selectPropertiesNode = new PromptNode(embedPrompts.selectProperties.prompt, selectPropertiesNodeCondition) - - const resetEmbedSuccessNodeCondition = data => data.reset - const resetEmbedSuccessNode = new PromptNode(embedPrompts.resetEmbedSuccess.prompt, resetEmbedSuccessNodeCondition) - - const setPropertyNodeCondition = data => data.properties.length > 0 - const setPropertyNode = new PromptNode(embedPrompts.setProperty.prompt, setPropertyNodeCondition) - - const setPropertySuccessNodeCondition = data => data.properties.length === 0 - const setPropertySuccessNode = new PromptNode(embedPrompts.setPropertySuccess.prompt, setPropertySuccessNodeCondition) - - selectFeedNode.setChildren([ - selectEmbedNode, - selectPropertiesNode - ]) - selectEmbedNode.setChildren([ - removeAllEmbedsSuccessNode, - selectPropertiesNode - ]) - selectPropertiesNode.setChildren([ - resetEmbedSuccessNode, - setPropertyNode - ]) - setPropertyNode.setChildren([ - setPropertyNode, - setPropertySuccessNode - ]) - await runWithFeedGuild(selectFeedNode, message, { - targetEmbedIndex: 0 - }) -} diff --git a/services/bot/src/commands/faq.js b/services/bot/src/commands/faq.js deleted file mode 100644 index 6dfc19424..000000000 --- a/services/bot/src/commands/faq.js +++ /dev/null @@ -1,41 +0,0 @@ -const fetch = require('node-fetch') -const Profile = require('../structs/db/Profile.js') -const Translator = require('../structs/Translator.js') -const getConfig = require('../config.js').get -const createLogger = require('../util/logger/create.js') - -module.exports = async (message) => { - const config = getConfig() - const webURL = config.webURL - if (!webURL) { - return message.channel.send('A web URL is required to be defined in config for this command.') - } - const profile = await Profile.get(message.guild.id) - const translate = Translator.createProfileTranslator(profile) - const log = createLogger(message.client.shard.ids[0]) - const split = message.content.split(' ') - let searchTerm = split.slice(1, split.length).join(' ').trim() - if (!searchTerm) { - return message.channel.send(translate('commands.faq.searchQueryRequired')) - } - if (searchTerm === '^') { - const fetched = (await message.channel.messages.fetch({ - limit: 1, - before: message.id - }, false)).first() - searchTerm = fetched.content - } - log.info(`Searching "${searchTerm}"`) - const fetchingMessage = await message.channel.send(translate('commands.faq.searching')) - const res = await fetch(`${webURL}/api/faq?search=${searchTerm}`) - if (res.status !== 200) { - throw new Error(`${res.status} status code response`) - } - const faqDocuments = await res.json() - await fetchingMessage.delete() - if (faqDocuments.length === 0) { - return message.channel.send(translate('commands.faq.noResults')) - } - const best = faqDocuments[0] - return message.channel.send(`**${best.q}**\n\n${best.a}`) -} diff --git a/services/bot/src/commands/filters.js b/services/bot/src/commands/filters.js deleted file mode 100644 index 9cc37d2b4..000000000 --- a/services/bot/src/commands/filters.js +++ /dev/null @@ -1,71 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const FeedFetcher = require('../util/FeedFetcher.js') -const Translator = require('../structs/Translator.js') -const ArticleMessage = require('../structs/ArticleMessage.js') -const createLogger = require('../util/logger/create.js') -const commonPrompts = require('./prompts/common/index.js') -const filterPrompts = require('./prompts/filters/index.js') -const runWithFeedsProfile = require('./prompts/runner/run.js') - -module.exports = async (message, command, role) => { - const selectFeedNode = new PromptNode(commonPrompts.selectFeed.prompt) - const selectActionNode = new PromptNode(filterPrompts.selectAction.prompt) - - selectFeedNode.addChild(selectActionNode) - - // Path 1 - const filterAddCategorySelectCondition = data => data.selected === '1' - const filterAddCategorySelectNode = new PromptNode(commonPrompts.filterAddCategorySelect.prompt, filterAddCategorySelectCondition) - const filterAddInputNode = new PromptNode(commonPrompts.filterAddInput.prompt) - const filterAddInputSuccessNode = new PromptNode(commonPrompts.filterAddInputSuccess.prompt) - - filterAddCategorySelectNode.addChild(filterAddInputNode) - filterAddInputNode.addChild(filterAddInputSuccessNode) - - // Path 2 - // No Filters - const noFiltersToRemoveCondition = data => data.selected === '2' && !data.selectedFeed.hasFilters() - const noFiltersToRemoveNode = new PromptNode(filterPrompts.listFilters.prompt, noFiltersToRemoveCondition) - - // Has Filters - const filterRemoveCategorySelectCondition = data => data.selected === '2' - const filterRemoveCategorySelectNode = new PromptNode(commonPrompts.filterRemoveCategorySelect.prompt, filterRemoveCategorySelectCondition) - const filterRemoveInputNode = new PromptNode(commonPrompts.filterRemoveInput.prompt) - const filterRemoveInputSuccessNode = new PromptNode(commonPrompts.filterRemoveInputSuccess.prompt) - - filterRemoveCategorySelectNode.addChild(filterRemoveInputNode) - filterRemoveInputNode.addChild(filterRemoveInputSuccessNode) - - const removedAllFiltersSuccessCondition = data => data.selected === '3' - const removedAllFiltersSuccessNode = new PromptNode(filterPrompts.removedAllFiltersSuccess.prompt, removedAllFiltersSuccessCondition) - - const listFiltersCondition = data => data.selected === '4' - const listFiltersNode = new PromptNode(filterPrompts.listFilters.prompt, listFiltersCondition) - - selectActionNode.setChildren([ - filterAddCategorySelectNode, - noFiltersToRemoveNode, - filterRemoveCategorySelectNode, - removedAllFiltersSuccessNode, - listFiltersNode - ]) - - const { selected, selectedFeed: feed, profile } = await runWithFeedsProfile(selectFeedNode, message) - const translate = Translator.createProfileTranslator(profile) - const log = createLogger(message.guild.shard.id, { - guild: message.guild, - user: message.author - }) - - if (selected === '5') { // 5 = Send passing article - const filters = feed.hasRFilters() ? feed.rfilters : feed.filters - const article = await FeedFetcher.fetchRandomArticle(feed.url, filters) - if (!article) { - return message.channel.send(translate('commands.filters.noArticlesPassed')) - } - log.info(`Sending filtered article for ${feed.url}`) - const articleMessage = await ArticleMessage.create(feed, article) - articleMessage.feed.channel = message.channel.id - await articleMessage.send(message.client) - } -} diff --git a/services/bot/src/commands/forceexit.js b/services/bot/src/commands/forceexit.js deleted file mode 100644 index 705916cc2..000000000 --- a/services/bot/src/commands/forceexit.js +++ /dev/null @@ -1,22 +0,0 @@ -const { DiscordPromptRunner } = require('discord.js-prompts') -const createLogger = require('../util/logger/create.js') - -module.exports = (message) => { - if (DiscordPromptRunner.isActiveChannel(message.channel.id)) { - DiscordPromptRunner.deleteActiveChannel(message.channel.id) - message.react('☑').catch(err => { - const log = createLogger(message.guild.shard.id) - log.warn(err, 'Unable to react checkmark for successful forceexit') - message.channel.send('Successfully cleared this channel from active status.') - .catch(err => { - const log = createLogger(message.guild.shard.id) - log.warn(err, 'forceexit') - }) - }) - } else { - message.react('❌').catch(err => { - const log = createLogger(message.guild.shard.id) - log.warn(err, 'Unable to react xmark for failed forceexit') - }) - } -} diff --git a/services/bot/src/commands/help.js b/services/bot/src/commands/help.js deleted file mode 100644 index 2d9842173..000000000 --- a/services/bot/src/commands/help.js +++ /dev/null @@ -1,60 +0,0 @@ -const Profile = require('../structs/db/Profile.js') -const Translator = require('../structs/Translator.js') -const getConfig = require('../config.js').get -const createLogger = require('../util/logger/create.js') - -module.exports = async (message, command) => { - const profile = await Profile.get(message.guild.id) - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const localeToUse = profile ? profile.locale : config.bot.locale - const translate = Translator.createLocaleTranslator(localeToUse) - const webInfo = config.webURL - ? ` ${translate('commands.help.controlPanelLink', { - url: config.webURL - })}` - : '' - let msg = `${webInfo} ${translate('commands.help.description', { prefix: config.bot.prefix })}\n\n` - const commandDescriptions = Translator.getCommandDescriptions(localeToUse) - for (const name in commandDescriptions) { - const command = commandDescriptions[name] - if (!command.description) continue - msg += `\`${prefix}${name}\` - ${command.description}` - .replace(/{{prefix}}/g, prefix) - .replace(/{{defaultPrefix}}/g, config.bot.prefix) - const args = command.args - if (args) { - msg += `\n **${translate('commands.help.arguments')}**\n` - const argsLength = Object.keys(args).length - let i = 0 - for (const arg in args) { - msg += ` \`${arg}\` - ${args[arg]}${i++ === argsLength - 1 ? '' : '\n'}` - .replace(/{{prefix}}/g, prefix) - .replace(/{{defaultPrefix}}/g, config.bot.prefix) - } - } - msg += '\n\n' - } - const helpMessage = msg + translate('commands.help.support', { - url: config.discordSupportURL || 'https://discord.gg/pudv7Rx' - }) - return message.author.send(helpMessage, { split: { prepend: '\u200b\n' } }) - .then(() => { - message.channel.send(`<@${message.author.id}>${translate('commands.help.checkDM')}`) - .catch(err => { - const log = createLogger(message.guild.shard.id) - log.warn({ - error: err, - user: message.author - }, 'Failed to send DM notification in text channel') - }) - }) - .catch(err => { - const log = createLogger(message.guild.shard.id) - log.warn({ - error: err, - user: message.author - }, 'Failed to direct message help text to user') - return message.channel.send(helpMessage, { split: { prepend: '\u200b\n' } }) - }) -} diff --git a/services/bot/src/commands/invite.js b/services/bot/src/commands/invite.js deleted file mode 100644 index 614681d65..000000000 --- a/services/bot/src/commands/invite.js +++ /dev/null @@ -1,9 +0,0 @@ -const Profile = require('../structs/db/Profile.js') -const Translator = require('../structs/Translator.js') - -module.exports = async (message, automatic) => { // automatic indicates invokation by the bot - const profile = await Profile.get(message.guild.id) - await message.channel.send(Translator.translate('commands.invite.text', profile ? profile.locale : undefined, { - id: message.client.user.id - })) -} diff --git a/services/bot/src/commands/list.js b/services/bot/src/commands/list.js deleted file mode 100644 index 97e647920..000000000 --- a/services/bot/src/commands/list.js +++ /dev/null @@ -1,33 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const listPrompts = require('./prompts/list/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -/** - * @param {import('discord.js').Message} message - */ -function getChannelMention (message) { - return message.mentions.channels.first() -} - -/** - * @param {import('discord.js').Message} message - */ -function getSearchQuery (message) { - const channelMention = getChannelMention(message) - const array = message.content.trim().split(' ').filter(s => s) - if (channelMention) { - return array.slice(2, array.length).join(' ') - } else { - return array.slice(1, array.length).join(' ') - } -} - -module.exports = async (message) => { - const selectSourceFeedNode = new PromptNode(listPrompts.listFeeds.prompt) - - await runWithFeedGuild(selectSourceFeedNode, message, { - guildID: message.guild.id, - channel: getChannelMention(message), - searchQuery: getSearchQuery(message) - }) -} diff --git a/services/bot/src/commands/locale.js b/services/bot/src/commands/locale.js deleted file mode 100644 index 29157e2f5..000000000 --- a/services/bot/src/commands/locale.js +++ /dev/null @@ -1,65 +0,0 @@ -const Translator = require('../structs/Translator.js') -const Profile = require('../structs/db/Profile.js') -const getConfig = require('../config.js').get -const createLogger = require('../util/logger/create.js') - -module.exports = async (message) => { - const locale = message.content.split(' ')[1] - const profile = await Profile.get(message.guild.id) - const guildLocale = profile ? profile.locale : undefined - const translate = Translator.createLocaleTranslator(guildLocale) - const config = getConfig() - const botLocale = config.bot.locale - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const localeList = Translator.getLocales() - - localeList.splice(localeList.indexOf(botLocale), 1) - const printLocaleList = localeList.join('`, `') - const log = createLogger(message.guild.shard.id) - - if (!locale) { - return message.channel.send(translate('commands.locale.helpText', { prefix, localeList: printLocaleList })) - } - - if (guildLocale === locale) { - return message.channel.send(translate('commands.locale.alreadySet', { locale })) - } - - if (locale !== 'reset' && !localeList.includes(locale)) { - return message.channel.send(translate('commands.locale.setNone', { prefix, locale, localeList: printLocaleList })) - } - - // Reset - if (locale === 'reset') { - if (!profile || !profile.locale) { - return message.channel.send(translate('commands.locale.resetNone')) - } - profile.locale = undefined - log.info({ - guild: message.guild - }, 'Locale reset') - await profile.save() - return message.channel.send(Translator.translate('commands.locale.resetSuccess', botLocale, { locale: botLocale })) - } - - if (botLocale === locale) { - return message.channel.send(translate('commands.locale.resetNoDefault', { locale, prefix })) - } - - if (!profile) { - const newProfile = new Profile({ - _id: message.guild.id, - name: message.guild.name, - locale - }) - await newProfile.save() - } else { - profile.locale = locale - await profile.save() - } - - log.info({ - guild: message.guild - }, `Locale changed to ${locale}`) - await message.channel.send(Translator.translate('commands.locale.setSuccess', locale, { locale })) -} diff --git a/services/bot/src/commands/mention.filters.js b/services/bot/src/commands/mention.filters.js deleted file mode 100644 index 1a27a6182..000000000 --- a/services/bot/src/commands/mention.filters.js +++ /dev/null @@ -1,52 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const commonPrompts = require('./prompts/common/index.js') -const mentionFilterPrompts = require('./prompts/mention.filters/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectFeedNode = new PromptNode(commonPrompts.selectFeed.prompt) - const selectSubscriberNode = new PromptNode(mentionFilterPrompts.selectSubscriber.prompt) - const selectActionNode = new PromptNode(mentionFilterPrompts.selectAction.prompt) - - // Path 1 - const filterAddCategorySelectCondition = data => data.selected === '1' - const filterAddCategorySelectNode = new PromptNode(commonPrompts.filterAddCategorySelect.prompt, filterAddCategorySelectCondition) - const filterAddInputNode = new PromptNode(commonPrompts.filterAddInput.prompt) - const filterAddInputSuccessNode = new PromptNode(commonPrompts.filterAddInputSuccess.prompt) - - filterAddCategorySelectNode.addChild(filterAddInputNode) - filterAddInputNode.addChild(filterAddInputSuccessNode) - - // Path 2 - // No filters to remove - const noFiltersToRemoveCondition = data => data.selected === '2' && !data.selectedSubscriber.hasFilters() - const noFiltersToRemoveNode = new PromptNode(mentionFilterPrompts.listFilters.prompt, noFiltersToRemoveCondition) - - // Input filter category and remove - const filterRemoveCategorySelectCondition = data => data.selected === '2' && data.selectedSubscriber.hasFilters() - const filterRemoveCategorySelectNode = new PromptNode(commonPrompts.filterRemoveCategorySelect.prompt, filterRemoveCategorySelectCondition) - const filterRemoveInputNode = new PromptNode(commonPrompts.filterRemoveInput.prompt) - const filterRemoveInputSuccessNode = new PromptNode(commonPrompts.filterRemoveInputSuccess.prompt) - - filterRemoveCategorySelectNode.addChild(filterRemoveInputNode) - filterRemoveInputNode.addChild(filterRemoveInputSuccessNode) - - // Path 3 - const removeAllFiltersSuccessNodeCondition = data => data.selected === '3' - const removeAllFiltersSuccessNode = new PromptNode(mentionFilterPrompts.removeAllFiltersSuccess.prompt, removeAllFiltersSuccessNodeCondition) - - const listFiltersNodeCondition = data => data.selected === '4' - const listFilterNode = new PromptNode(mentionFilterPrompts.listFilters.prompt, listFiltersNodeCondition) - - selectFeedNode.addChild(selectSubscriberNode) - selectSubscriberNode.addChild(selectActionNode) - selectActionNode.setChildren([ - filterAddCategorySelectNode, - noFiltersToRemoveNode, - filterRemoveCategorySelectNode, - removeAllFiltersSuccessNode, - listFilterNode - ]) - - await runWithFeedGuild(selectFeedNode, message) -} diff --git a/services/bot/src/commands/mention.js b/services/bot/src/commands/mention.js deleted file mode 100644 index 9b84844e5..000000000 --- a/services/bot/src/commands/mention.js +++ /dev/null @@ -1,37 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const commonPrompts = require('./prompts/common/index.js') -const mentionPrompts = require('./prompts/mention/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectFeedNode = new PromptNode(commonPrompts.selectFeed.prompt) - - const selectActionNode = new PromptNode(mentionPrompts.selectAction.prompt) - - const addSubscriberNodeCondition = data => data.selected === '1' - const addSubscriberNode = new PromptNode(mentionPrompts.addSubscriber.prompt, addSubscriberNodeCondition) - const addSubscriberSuccessNode = new PromptNode(mentionPrompts.addSubscriberSuccess.prompt) - - const removeSubscriberNodeCondition = data => data.selected === '2' - const removeSubscriberNode = new PromptNode(mentionPrompts.removeSubscriber.prompt, removeSubscriberNodeCondition) - const removeSubscriberSuccessNode = new PromptNode(mentionPrompts.removeSubscriberSuccess.prompt) - - const removeAllSubscribersSuccessNodeCondition = data => data.selected === '3' - const removeAllSubscribersSuccessNode = new PromptNode(mentionPrompts.removeAllSubscribersSuccess.prompt, removeAllSubscribersSuccessNodeCondition) - - const listSubscribersNodeCondition = data => data.selected === '4' - const listSubscribersNode = new PromptNode(mentionPrompts.listSubscribers.prompt, listSubscribersNodeCondition) - - selectFeedNode.addChild(selectActionNode) - selectActionNode.setChildren([ - addSubscriberNode, - removeSubscriberNode, - removeAllSubscribersSuccessNode, - listSubscribersNode - ]) - - addSubscriberNode.addChild(addSubscriberSuccessNode) - removeSubscriberNode.addChild(removeSubscriberSuccessNode) - - await runWithFeedGuild(selectFeedNode, message) -} diff --git a/services/bot/src/commands/move.js b/services/bot/src/commands/move.js deleted file mode 100644 index 1544943b1..000000000 --- a/services/bot/src/commands/move.js +++ /dev/null @@ -1,14 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const commonPrompts = require('./prompts/common/index.js') -const movePrompts = require('./prompts/move/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectMultipleFeedsNode = new PromptNode(commonPrompts.selectMultipleFeeds.prompt) - const selectDestinationChannelNode = new PromptNode(movePrompts.selectDestinationChannel.prompt) - const successNode = new PromptNode(movePrompts.success.prompt) - - selectMultipleFeedsNode.addChild(selectDestinationChannelNode) - selectDestinationChannelNode.addChild(successNode) - await runWithFeedGuild(selectMultipleFeedsNode, message) -} diff --git a/services/bot/src/commands/options.js b/services/bot/src/commands/options.js deleted file mode 100644 index 44b2bf32f..000000000 --- a/services/bot/src/commands/options.js +++ /dev/null @@ -1,13 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const optionsPrompts = require('./prompts/options/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectOptionNode = new PromptNode(optionsPrompts.selectOption.prompt) - const selectFeedWithOptionNode = new PromptNode(optionsPrompts.selectFeedWithOption.prompt) - const successNode = new PromptNode(optionsPrompts.success.prompt) - - selectOptionNode.addChild(selectFeedWithOptionNode) - selectFeedWithOptionNode.addChild(successNode) - await runWithFeedGuild(selectOptionNode, message) -} diff --git a/services/bot/src/commands/owner/blacklist.js b/services/bot/src/commands/owner/blacklist.js deleted file mode 100644 index 206af4501..000000000 --- a/services/bot/src/commands/owner/blacklist.js +++ /dev/null @@ -1,46 +0,0 @@ -const Blacklist = require('../../structs/db/Blacklist.js') -const createLogger = require('../../util/logger/create.js') - -module.exports = async (message) => { - const content = message.content.split(' ') - if (content.length !== 2) return - const id = content[1] - const blacklisted = await Blacklist.get(id) - if (blacklisted) { - return message.channel.send('ID is already blacklisted.') - } - const data = { - _id: id - } - const log = createLogger(message.guild.shard.id) - const guildResults = await message.client.shard.broadcastEval(`this.guilds.cache.get('${id}') ? this.guilds.cache.get('${id}').name : null`) - const matchedGuilds = guildResults.filter(g => g) - if (matchedGuilds.length > 0) { - data.type = Blacklist.TYPES.GUILD - data.name = matchedGuilds[0] - } else { - /** - * @type {import('discord.js').Client} - */ - const client = message.client - try { - const user = await client.users.fetch(id) - data.type = Blacklist.TYPES.USER - data.name = user.username - } catch (err) { - log.owner({ - error: err - }, `No guild or user found for ${id}. User fetch result:`) - return message.channel.send(`No guild or user found for id ${id}.`) - } - } - - const blacklist = new Blacklist(data) - await blacklist.save() - - log.owner({ - guild: message.guild, - user: message.author - }, `Added ${data.type} ${id} named "${data.name}" to blacklist`) - await message.channel.send(`Added ${data.type === 0 ? 'user' : 'guild'} ${id} named "${data.name}" to blacklist. Restart bot to take effect.`) -} diff --git a/services/bot/src/commands/owner/checklimits.js b/services/bot/src/commands/owner/checklimits.js deleted file mode 100644 index 6e45a15a6..000000000 --- a/services/bot/src/commands/owner/checklimits.js +++ /dev/null @@ -1,32 +0,0 @@ -const Feed = require('../../structs/db/Feed.js') -const Guild = require('../../structs/Guild.js') -const getConfig = require('../../config.js').get - -module.exports = async (message) => { - const supporterLimits = await Guild.getAllUniqueFeedLimits() - const config = getConfig() - - const illegals = [] - const guildIds = message.client.guilds.cache.keyArray() - const promises = [] - for (const id of guildIds) { - promises.push(Feed.getBy('guild', id)) - } - - /** @type {Array} */ - const results = (await Promise.all(promises)) - for (let i = 0; i < guildIds.length; ++i) { - const guildId = guildIds[i] - const guildFeeds = results[i].filter(feed => !feed.disabled) - const limit = supporterLimits.get(guildId) || config.feeds.max - if (guildFeeds.length > limit) { - illegals.push(guildId) - } - } - - if (illegals.length === 0) { - await message.channel.send('Everything looks good!') - } else { - await message.channel.send(`Illegal sources found for the following guilds: \n\`\`\`${illegals}\`\`\``) - } -} diff --git a/services/bot/src/commands/owner/checkshard.js b/services/bot/src/commands/owner/checkshard.js deleted file mode 100644 index ab5a3fa72..000000000 --- a/services/bot/src/commands/owner/checkshard.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = (message) => message.channel.send(message.guild.shardID) diff --git a/services/bot/src/commands/owner/debug.js b/services/bot/src/commands/owner/debug.js deleted file mode 100644 index f18a2b089..000000000 --- a/services/bot/src/commands/owner/debug.js +++ /dev/null @@ -1,17 +0,0 @@ -const createLogger = require('../../util/logger/create.js') -const DebugFeed = require('../../structs/db/DebugFeed') - -module.exports = async (message) => { - const log = createLogger(message.guild.shard.id) - const content = message.content.split(' ') - if (content.length !== 2) return - const feedID = content[1] - const debugFeed = new DebugFeed({ - feedId: feedID - }) - await debugFeed.save() - log.owner({ - user: message.author - }, `Added ${feedID} to debugging list for all shards.`) - await message.channel.send(`Added ${feedID} to debug`) -} diff --git a/services/bot/src/commands/owner/feedguild.js b/services/bot/src/commands/owner/feedguild.js deleted file mode 100644 index cf2ac2ae7..000000000 --- a/services/bot/src/commands/owner/feedguild.js +++ /dev/null @@ -1,12 +0,0 @@ -const Feed = require('../../structs/db/Feed.js') - -module.exports = async (message) => { - const content = message.content.split(' ') - if (content.length !== 2) return - const feedID = content[1] - const feed = await Feed.get(feedID) - if (!feed) { - return message.channel.send('Could not find any feeds with that id.') - } - return message.channel.send(`Found guild ${feed.guild}`) -} diff --git a/services/bot/src/commands/owner/getguild.js b/services/bot/src/commands/owner/getguild.js deleted file mode 100644 index 2ac34a156..000000000 --- a/services/bot/src/commands/owner/getguild.js +++ /dev/null @@ -1,17 +0,0 @@ -const Profile = require('../../structs/db/Profile.js') -const Feed = require('../../structs/db/Feed.js') -const createLogger = require('../../util/logger/create.js') - -module.exports = async (message) => { - const content = message.content.split(' ') - if (content.length !== 2) return - const guildId = content[1] - const profile = await Profile.get(guildId) - const log = createLogger(message.guild.shard.id) - const feeds = await Feed.getManyBy('guild', guildId) - log.owner({ - profile: profile ? profile.toJSON() : null, - feeds: feeds.map(feed => feed.toJSON()) - }, `Profile and Feeds output of ${guildId}`) - await message.channel.send('Check logs') -} diff --git a/services/bot/src/commands/owner/kill.js b/services/bot/src/commands/owner/kill.js deleted file mode 100644 index a30a5850a..000000000 --- a/services/bot/src/commands/owner/kill.js +++ /dev/null @@ -1,5 +0,0 @@ -const IPC = require('../../util/ipc') - -module.exports = async () => { - IPC.send(IPC.TYPES.KILL) -} diff --git a/services/bot/src/commands/owner/listguilds.js b/services/bot/src/commands/owner/listguilds.js deleted file mode 100644 index 0807e8db8..000000000 --- a/services/bot/src/commands/owner/listguilds.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = async (message) => { - const m = await message.channel.send('Generating...') - const results = await message.client.shard.broadcastEval(` - const guilds = [] - this.guilds.cache.forEach(guild => { - guilds.push({ - id: guild.id, - name: guild.name, - members: guild.memberCount, - owner: guild.owner ? { - id: guild.owner.id, - name: guild.owner.user.username - } : undefined - }) - }) - guilds - `) - let allGuilds = [] - results.forEach(item => { - allGuilds = allGuilds.concat(item) - }) - allGuilds.sort((a, b) => b.members - a.members) - let txt = '' - allGuilds.forEach(item => { - txt += `[${item.members}] (G: ${item.id}, ${item.name}) ${item.owner ? `(O: ${item.owner.id}, ${item.owner.name})` : '(O: None)'} \n` - }) - await m.delete() - await message.channel.send({ - files: [{ - attachment: Buffer.from(txt, 'utf8'), - name: 'guilds.txt' - }] - }) -} diff --git a/services/bot/src/commands/owner/pingme.js b/services/bot/src/commands/owner/pingme.js deleted file mode 100644 index 5d3801d9a..000000000 --- a/services/bot/src/commands/owner/pingme.js +++ /dev/null @@ -1,9 +0,0 @@ -const Discord = require('discord.js') - -module.exports = async (message) => { - const pong = new Discord.MessageEmbed() - .setTitle('Sending') - .setDescription('pong!') - - await message.channel.send({ embed: pong }) -} diff --git a/services/bot/src/commands/owner/refreshall.js b/services/bot/src/commands/owner/refreshall.js deleted file mode 100644 index 6e7f682ea..000000000 --- a/services/bot/src/commands/owner/refreshall.js +++ /dev/null @@ -1,25 +0,0 @@ -const FeedFetcher = require('../../util/FeedFetcher.js') -const FailRecord = require('../../structs/db/FailRecord.js') -const createLogger = require('../../util/logger/create.js') - -module.exports = async (message) => { - const content = message.content.split(' ') - if (content.length !== 2) return - const link = content[1] - - if (FailRecord.limit === 0) { - return message.channel.send('No fail limit has been set.') - } - const record = await FailRecord.get(link) - if (!record || !record.hasFailed()) { - return message.channel.send('That is not a failed link.') - } - - await FeedFetcher.fetchURL(link) - await record.delete() - const log = createLogger(message.guild.shard.id) - log.owner({ - user: message.author - }, `Link ${link} has been refreshed and will be back on cycle.`) - await message.channel.send(`Successfully refreshed <${link}>.`) -} diff --git a/services/bot/src/commands/owner/refreshsupporters.js b/services/bot/src/commands/owner/refreshsupporters.js deleted file mode 100644 index 6bdc8ec96..000000000 --- a/services/bot/src/commands/owner/refreshsupporters.js +++ /dev/null @@ -1,11 +0,0 @@ -const Patron = require('../../structs/db/Patron.js') -const createLogger = require('../../util/logger/create.js') - -module.exports = async (message) => { - await Patron.refresh() - const log = createLogger(message.guild.shard.id) - log.owner({ - user: message.author - }, 'Refreshed VIPs') - await message.channel.send('Refreshed VIPs.') -} diff --git a/services/bot/src/commands/owner/removeguild.js b/services/bot/src/commands/owner/removeguild.js deleted file mode 100644 index 37a75adc2..000000000 --- a/services/bot/src/commands/owner/removeguild.js +++ /dev/null @@ -1,27 +0,0 @@ -const createLogger = require('../../util/logger/create.js') - -module.exports = async (message) => { - const content = message.content.split(' ') - if (content.length !== 2) { - return message.channel.send('You must specify the guild ID as the first argument.') - } - const results = await message.client.shard.broadcastEval(` - const guild = this.guilds.cache.get('${content[1]}') - const obj = {} - if (guild) { - guild.leave().catch(console.log) - obj.name = guild.name - obj.id = guild.id - obj - } - `) - const removed = results.filter(kicked => kicked) - if (removed.length === 0) { - return message.channel.send('No such guild found.') - } - const log = createLogger(message.guild.shard.id) - log.owner({ - user: message.author - }, `Guild ${content[1]} (${removed[0].name}) has been removed`) - return message.channel.send(`Guild ${content[1]} (${removed[0].name}) was found - see console for whether the removal was successful.`) -} diff --git a/services/bot/src/commands/owner/restore.js b/services/bot/src/commands/owner/restore.js deleted file mode 100644 index 464c4dbab..000000000 --- a/services/bot/src/commands/owner/restore.js +++ /dev/null @@ -1,43 +0,0 @@ -const fetch = require('node-fetch') -const GuildData = require('../../structs/GuildData.js') -const createLogger = require('../../util/logger/create.js') - -async function getID (message) { - const arr = message.content.split(' ') - const id = arr[1] - if (!id) { - throw new Error('No ID found. You must pass the server ID as an argument to this command.') - } - const attachment = message.attachments.first() - const url = attachment ? attachment.url : undefined - if (!url) { - throw new Error('No attachment found') - } - const res = await fetch(url) - if (res.status !== 200) { - throw new Error('Non-200 status code: ', res.status) - } - const file = await res.json() - return file -} - -module.exports = async (message) => { - const file = await getID(message) - const guildData = new GuildData(file) - const id = guildData.id - const res = await message.client.shard.broadcastEval(` - const guild = this.guilds.cache.get('${id}'); - guild ? guild.name : null - `) - const log = createLogger(message.guild.shard.id) - for (var i = 0; i < res.length; ++i) { - if (!res[i]) continue - await guildData.restore() - log.owner({ - user: message.author - }, `Server (ID: ${id}, Name: ${res[i]}) has been restored`) - await message.channel.send(`Server (ID: ${id}, Name: ${res[i]}) has been restored.`) - return - } - await message.channel.send(`Unable to restore server, guild ${id} was not found in cache.`) -} diff --git a/services/bot/src/commands/owner/setavatar.js b/services/bot/src/commands/owner/setavatar.js deleted file mode 100644 index 0454d9371..000000000 --- a/services/bot/src/commands/owner/setavatar.js +++ /dev/null @@ -1,13 +0,0 @@ -const createLogger = require('../../util/logger/create.js') - -module.exports = async (message) => { - const content = message.content.split(' ') - if (content.length === 1) return - content.shift() - await message.client.user.setAvatar(content[0]) - const log = createLogger(message.guild.shard.id) - log.owner({ - user: message.author - }, `Changed avatar to ${content[0]}`) - await message.channel.send('Successfully changed avatar.') -} diff --git a/services/bot/src/commands/owner/setpresence.js b/services/bot/src/commands/owner/setpresence.js deleted file mode 100644 index 6b2f411b7..000000000 --- a/services/bot/src/commands/owner/setpresence.js +++ /dev/null @@ -1,84 +0,0 @@ -const Joi = require('@hapi/joi') -const getConfig = require('../../config.js').get -const createLogger = require('../../util/logger/create.js') -const schema = Joi.object({ - status: Joi.string().valid('online', 'idle', 'invisible', 'dnd'), - activity: Joi.object({ - name: Joi.string(), - type: Joi.string().valid('PLAYING', 'STREAMING', 'LISTENING', 'WATCHING', 'CUSTOM_STATUS'), - url: Joi.string().when('type', { - is: 'STREAMING', - then: Joi.string().required() - }) - }) -}) - -function getPresenceFromArgs (args) { - const status = args[0] - const activityType = args[1] - const activityName = args[1] === 'STREAMING' ? args.slice(2, args.length - 1).join(' ') : args.slice(2).join(' ') - const activityURL = args[1] === 'STREAMING' ? args[args.length - 1] : '' - const presenceData = {} - if (status) { - presenceData.status = status - } - if (activityType) { - presenceData.activity = { - type: activityType - } - if (activityName) { - presenceData.activity.name = activityName - } - if (activityURL) { - presenceData.activity.url = activityURL - } - } - return presenceData -} - -function setConfig (presenceData) { - const config = getConfig() - if (presenceData.status) { - config.bot.status = presenceData.status - } - if (presenceData.activity.type) { - config.bot.activityType = presenceData.activity.type - } - if (presenceData.activity.name) { - config.bot.activityName = presenceData.activity.name - } - if (presenceData.activity.url) { - config.bot.streamActivityURL = presenceData.activity.url - } -} - -module.exports = async (message) => { - const args = message.content.split(' ').map(s => s.trim()).filter(s => s) - args.shift() - if (args.length === 0) { - return message.channel.send('Insufficient number of arguments.') - } - const presenceData = getPresenceFromArgs(args) - const results = schema.validate(presenceData, { - abortEarly: false - }) - if (results.error) { - const str = results.error.details.map(d => d.message).join('\n') - return message.channel.send(`Invalid format. Errors:\n\n${str}`) - } - setConfig(presenceData) - const config = getConfig() - await message.client.user.setPresence(presenceData) - await message.client.shard.broadcastEval(` - const path = require('path'); - const appDir = path.dirname(require.main.filename); - const config = require(appDir + '/src/config.js').get(); - config.bot = JSON.parse(\`${JSON.stringify(config.bot)}\`) - `) - const log = createLogger(message.guild.shard.id) - log.owner({ - user: message.author, - presenceData - }, 'Set presence') - await message.channel.send('Successfully changed presence. It may be some time until it shows due to rate limiting.') -} diff --git a/services/bot/src/commands/owner/setstatus.js b/services/bot/src/commands/owner/setstatus.js deleted file mode 100644 index 506e52d93..000000000 --- a/services/bot/src/commands/owner/setstatus.js +++ /dev/null @@ -1,22 +0,0 @@ -const createLogger = require('../../util/logger/create.js') -const VALID_STATUS = ['online', 'idle', 'invisible', 'dnd'] - -function getStatus (message) { - const content = message.content.split(' ') - if (content.length === 1) return undefined - content.shift() - return content.join(' ').trim() -} - -module.exports = async (message) => { - const status = getStatus(message) - if (!VALID_STATUS.includes(status)) { - return message.channel.send(`That is not a valid status (\`${status}\`). Must be one of the following: \`${VALID_STATUS.join('`, `')}\`. `) - } - await message.client.user.setStatus(status) - const log = createLogger(message.guild.shard.id) - log.owner({ - user: message.author - }, `Changed bot status to ${status}`) - await message.channel.send(`Successfully changed the status to \`${status}\`.`) -} diff --git a/services/bot/src/commands/owner/setusername.js b/services/bot/src/commands/owner/setusername.js deleted file mode 100644 index e50d482a1..000000000 --- a/services/bot/src/commands/owner/setusername.js +++ /dev/null @@ -1,15 +0,0 @@ -const createLogger = require('../../util/logger/create.js') - -module.exports = async (message) => { - const content = message.content.split(' ') - if (content.length === 1) return - content.shift() - const username = content.join(' ') - const original = message.client.user.username - const u = await message.client.user.setUsername(username) - const log = createLogger(message.guild.shard.id) - log.owner({ - user: message.author - }, `Bot usename changed from ${original} to ${u.username}`) - await message.channel.send(`Bot username has been changed from ${original} to ${u.username}.`) -} diff --git a/services/bot/src/commands/owner/unblacklist.js b/services/bot/src/commands/owner/unblacklist.js deleted file mode 100644 index 64ccda458..000000000 --- a/services/bot/src/commands/owner/unblacklist.js +++ /dev/null @@ -1,19 +0,0 @@ -const Blacklist = require('../../structs/db/Blacklist.js') -const createLogger = require('../../util/logger/create.js') - -module.exports = async (message) => { - const content = message.content.split(' ') - if (content.length !== 2) return - const id = content[1] - const blacklisted = await Blacklist.get(id) - if (!blacklisted) { - return message.channel.send(`ID ${id} is not blacklisted.`) - } else { - await blacklisted.delete() - } - const log = createLogger(message.guild.shard.id) - log.owner({ - user: message.author - }, `Removed ${id} from blacklist`) - await message.channel.send(`Removed ${id} from blacklist. Restart bot to take effect.`) -} diff --git a/services/bot/src/commands/owner/undebug.js b/services/bot/src/commands/owner/undebug.js deleted file mode 100644 index beacaedaf..000000000 --- a/services/bot/src/commands/owner/undebug.js +++ /dev/null @@ -1,19 +0,0 @@ -const createLogger = require('../../util/logger/create.js') -const DebugFeed = require('../../structs/db/DebugFeed') - -module.exports = async (message) => { - const content = message.content.split(' ') - if (content.length !== 2) return - const feedID = content[1] - const found = await DebugFeed.getByQuery({ - feedId: feedID - }) - if (found) { - await found.delete() - } - const log = createLogger(message.guild.shard.id) - log.owner({ - user: message.author - }, `Removed ${feedID} from debug`) - await message.channel.send(`Removed ${feedID} from debug`) -} diff --git a/services/bot/src/commands/patron.js b/services/bot/src/commands/patron.js deleted file mode 100644 index f1e863edb..000000000 --- a/services/bot/src/commands/patron.js +++ /dev/null @@ -1,177 +0,0 @@ -const Profile = require('../structs/db/Profile.js') -const Feed = require('../structs/db/Feed.js') -const Supporter = require('../structs/db/Supporter.js') -const getConfig = require('../config.js').get -const createLogger = require('../util/logger/create.js') - -const helpText = profile => { - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - return `Proper usage: -\`${prefix}patron servers add \` - Add your patron backing to a server via server ID or \`this\` for this server -\`${prefix}patron servers remove \` - Remove your patron backing from a server via server ID or \`this\` for this server -\`${prefix}patron servers list\` - List servers under your patron backing, and the maximum number of servers you may have - ` -} - -async function verifyServer (bot, serverId) { - const results = (await bot.shard.broadcastEval(` - const guild = this.guilds.cache.get('${serverId}') - guild ? { name: guild.name, id: guild.id } : null - `)).filter(item => item) - - if (results.length > 0) return results[0] -} - -/** - * @param {import('discord.js').Client} bot - * @param {string} guildID - */ -async function webhookStatusCheck (bot, guildID, authorized) { - const log = createLogger(bot.shard.ids[0]) - try { - const feeds = await Feed.getManyByQuery({ - guild: guildID - }) - const saves = [] - for (const feed of feeds) { - if (authorized && feed.webhook && feed.webhook.disabled) { - feed.webhook.disabled = undefined - log.info({ - feed - }, 'Enabled webhook') - saves.push(feed.save()) - } else if (!authorized && feed.webhook && !feed.webhook.disabled) { - feed.webhook.disabled = true - log.info({ - feed - }, 'Disabled webhook') - saves.push(feed.save()) - } - } - await Promise.all(saves) - } catch (err) { - log.error(err, 'Failed to check webhook statuses after supporter server change') - } -} - -/** - * @param {import('discord.js').Client} bot - * @param {import('discord.js').Message} message - * @param {string[]} args - * @param {Supporter} supporter - * @param {string[]} supportedGuilds - * @param {Profile} profile - */ -async function switchServerArg (bot, message, args, supporter, supportedGuilds, profile) { - const maxServers = await supporter.getMaxGuilds() - const action = args.shift() // Third arg - const log = createLogger(message.guild.shard.id) - if (!action) return message.channel.send('You must specify either `add` or `remove` as your third argument.') - let server = args.shift() // Fourth arg - if (action !== 'list' && !server) return message.channel.send('You must specify the server ID, or `this` (to specify this server) as your fourth argument..') - if (server === 'this') server = message.guild.id - - if (action === 'add') { - if (supporter.guilds.length >= maxServers) { - return message.channel.send(`You cannot add any more servers for your patron status. Your maximum is ${maxServers}.`) - } - if (supporter.guilds.includes(server)) { - return message.channel.send('That server already has your patron backing.') - } - if (supportedGuilds.includes(server)) { - return message.channel.send('This server is already supported by another patron.') - } - const m = await message.channel.send(`Adding server ${server}...`) - const gotGuild = await verifyServer(bot, server) - if (!gotGuild) { - return m.edit(`Unable to add server \`${server}\`. Either it does not exist, or I am not in it.`) - } - supporter.guilds.push(server) - await supporter.save() - await m.edit(`Successfully added ${server} (${gotGuild.name})`) - log.info({ - guild: message.guild, - user: message.author - }, `Added patron server ${server} (${gotGuild.name})`) - await webhookStatusCheck(bot, message.guild.id, true) - } else if (action === 'remove') { - if (!supporter.guilds.includes(server)) { - return message.channel.send('That server does not have your patron backing.') - } - const m2 = await message.channel.send(`Removing server ${server}...`) - supporter.guilds.splice(supporter.guilds.indexOf(server), 1) - await supporter.save() - await m2.edit('Successfully removed') - log.info({ - guild: message.guild, - user: message.author - }, `Removed patron server ${server}`) - await webhookStatusCheck(bot, message.guild.id, false) - } else if (action === 'list') { - if (supporter.guilds.length === 0) { - return message.channel.send(`You have no servers under your patron backing. The maximum number of servers you may have under your patron backing is ${maxServers}.`) - } - const myGuilds = supporter.guilds - let content = `The maximum number of servers you may add your patron backing to is ${maxServers}. The following guilds are backed by your patron status:\n\n` - for (const id of myGuilds) { - const gotGuild = await verifyServer(bot, id) - content += gotGuild ? `${gotGuild.id} (${gotGuild.name})\n` : `${id} (Unknown)\n` - } - await message.channel.send(content) - } else { - await message.channel.send(`Invalid command usage. ${helpText(profile)}`) - } -} - -module.exports = async (message) => { - const [profile, supporter, supportedGuilds] = await Promise.all([ - Profile.get(message.guild.id), - Supporter.get(message.author.id), - Supporter.getValidGuilds() - ]) - const bot = message.client - - if (!supporter || !(await supporter.isValid())) { - return message.channel.send('You must be a patron to use this command.') - } - const args = message.content.toLowerCase().split(' ').map(item => item.trim()) - args.shift() // Remove prefix - if (args.length === 0) { - return message.channel.send(helpText(profile)) - } - const type = args.shift() // Second arg - if (type === 'servers') { - await switchServerArg(bot, message, args, supporter, supportedGuilds, profile) - } else { - await message.channel.send(`Invalid command usage. ${helpText(profile)}`) - } - // switch (type) { - // case 'servers': - - // break - // case 'refresh': - // if (timeLimited[message.author.id]) { - // log.command.warning('Blocked refresh due to time limit', message.author) - // return await message.channel.send(`${message.author.toString()} Please wait 5 minutes after the last use of this command before using it again.`) - // } - // timeLimited[message.author.id] = true - // setTimeout(() => delete timeLimited[message.author.id], 300000) // 5 minutes - // const m = await message.channel.send('Refreshing...') - // dbOpsVips.refresh(async err => { - // try { - // if (err) { - // log.command.error('Failed to update VIPs', message.author, err) - // return await m.edit(`Failed to refresh patrons: ` + err.message) - // } - // log.command.success(`Refreshed VIPs`, message.author) - // await m.edit(`Successfully updated patrons.`) - // } catch (err) { - // log.command.warning('rsspatron 2', err, message.author) - // } - // }) - // break - // default: - // await message.channel.send('Invalid command usage') - // } -} diff --git a/services/bot/src/commands/prefix.js b/services/bot/src/commands/prefix.js deleted file mode 100644 index 04045605c..000000000 --- a/services/bot/src/commands/prefix.js +++ /dev/null @@ -1,49 +0,0 @@ -const Translator = require('../structs/Translator.js') -const Profile = require('../structs/db/Profile.js') -const getConfig = require('../config.js').get -const createLogger = require('../util/logger/create.js') - -module.exports = async (message) => { - const prefix = message.content.split(' ')[1] - /** @type {Profile} */ - const profile = await Profile.get(message.guild.id) - const translate = Translator.createLocaleTranslator(profile ? profile.locale : undefined) - - if (!prefix) { - return message.channel.send(translate('commands.prefix.helpText')) - } - - // Reset - const config = getConfig() - if (prefix === 'reset') { - if (!profile || !profile.prefix) { - return message.channel.send(translate('commands.prefix.resetNone')) - } - await profile.setPrefixAndSave() - return message.channel.send(translate('commands.prefix.resetSuccess', { prefix: config.bot.prefix })) - } - if (prefix.length > 4 || prefix.includes(' ')) { - return message.channel.send(translate('commands.prefix.requirements')) - } - if (config.bot.prefix === prefix) { - return message.channel.send(translate('commands.prefix.cannotUseDefault')) - } - - if (!profile) { - const data = { - _id: message.guild.id, - name: message.guild.name - } - const newProfile = new Profile(data) - await newProfile.setPrefixAndSave(prefix) - } else { - await profile.setPrefixAndSave(prefix) - } - - const log = createLogger(message.guild.shard.id) - log.info({ - guild: message.guild - }, `Guild prefix updated to ${prefix}`) - - await message.channel.send(translate('commands.prefix.setSuccess', { prefix })) -} diff --git a/services/bot/src/commands/prompts/clone/confirm.js b/services/bot/src/commands/prompts/clone/confirm.js deleted file mode 100644 index 0bf70162a..000000000 --- a/services/bot/src/commands/prompts/clone/confirm.js +++ /dev/null @@ -1,148 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Subscriber = require('../../../structs/db/Subscriber.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') -const FilteredFormat = require('../../../structs/db/FilteredFormat.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - * @property {string[]} properties - * @property {import('../../../structs/db/Feed.js')} sourceFeed - * @property {import('../../../structs/db/Feed.js')[]} destinationFeeds - */ - -/** - * @param {Data} data - */ -function confirmVisual (data) { - const { profile, sourceFeed, destinationFeeds, properties } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.clone.confirm', { - link: sourceFeed.url, - cloning: properties.join('`, `'), - destinations: destinationFeeds.map(selected => selected.url).join('\n').trim() - })) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function confirmFn (message, data) { - const { profile, destinationFeeds, sourceFeed, properties } = data - const { content } = message - const translate = Translator.createProfileTranslator(profile) - if (content !== 'yes') { - throw new Rejection(translate('commands.clone.confirmError')) - } - const cloneAll = properties.includes('all') - const cloneFilters = cloneAll || properties.includes('filters') - const cloneMiscOptions = cloneAll || properties.includes('misc-options') - const cloneMessage = cloneAll || properties.includes('message') - const cloneSubscribers = cloneAll || properties.includes('subscribers') - const cloneComparisons = cloneAll || properties.includes('comparisons') - const cloneRegexops = cloneAll || properties.includes('regexops') - const cloneFilteredFormats = cloneAll || properties.includes('filtered-formats') - const cloneWebhook = cloneAll || properties.includes('webhook') - const log = createLogger(message.client.shard.ids[0]) - - const copyFromSubscribers = await sourceFeed.getSubscribers() - - for (const destinationFeed of destinationFeeds) { - let updateSelected = false - // Filters - if (cloneFilters) { - destinationFeed.filters = sourceFeed.filters - updateSelected = true - } - - // Misc Options - if (cloneMiscOptions) { - destinationFeed.checkDates = sourceFeed.checkDates - destinationFeed.formatTables = sourceFeed.formatTables - destinationFeed.imgLinksExistence = sourceFeed.imgLinksExistence - destinationFeed.imgPreviews = sourceFeed.imgPreviews - destinationFeed.toggleRoleMentions = sourceFeed.toggleRoleMentions - destinationFeed.directSubscribers = sourceFeed.directSubscribers - updateSelected = true - } - - // Format - if (cloneMessage) { - destinationFeed.text = sourceFeed.text - destinationFeed.embeds = sourceFeed.embeds - updateSelected = true - } - - // Comparisons - if (cloneComparisons) { - destinationFeed.ncomparisons = sourceFeed.ncomparisons - destinationFeed.pcomparisons = sourceFeed.pcomparisons - updateSelected = true - } - - // Regexops - if (cloneRegexops) { - destinationFeed.regexOps = sourceFeed.regexOps - updateSelected = true - } - - // Webhook - if (cloneWebhook) { - console.log(cloneWebhook, sourceFeed.webhook) - destinationFeed.webhook = sourceFeed.webhook - ? { ...sourceFeed.webhook } - : undefined - updateSelected = true - } - - if (updateSelected) { - await destinationFeed.save() - } - - // Subscribers - if (cloneSubscribers) { - // Delete the destinationFeed feed's subscribers - const subscribers = await destinationFeed.getSubscribers() - const deletions = [] - for (const subscriber of subscribers) { - deletions.push(subscriber.delete()) - } - await Promise.all(deletions) - // Save the new ones - const saves = [] - for (const copyFromSubscriber of copyFromSubscribers) { - const subscriberData = copyFromSubscriber.toJSON() - subscriberData.feed = destinationFeed._id - const newSubscriber = new Subscriber(subscriberData) - saves.push(newSubscriber.save()) - } - await Promise.all(saves) - } - - // Filtered formats - if (cloneFilteredFormats) { - const copyFromFilteredFormats = await sourceFeed.getFilteredFormats() - const filteredFormats = await destinationFeed.getFilteredFormats() - await Promise.all(filteredFormats.map(format => format.delete())) - await Promise.all(copyFromFilteredFormats.map((format) => { - const json = format.toJSON() - json.feed = destinationFeed._id - const newFormat = new FilteredFormat(json) - return newFormat.save() - })) - } - } - log.info({ - guild: message.guild, - user: message.author - }, `Properties ${properties.join(',')} for the feed ${sourceFeed.url} cloned to to ${destinationFeeds.length} feeds`) - return data -} - -const prompt = new LocalizedPrompt(confirmVisual, confirmFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/clone/confirmSuccess.js b/services/bot/src/commands/prompts/clone/confirmSuccess.js deleted file mode 100644 index ace06ebda..000000000 --- a/services/bot/src/commands/prompts/clone/confirmSuccess.js +++ /dev/null @@ -1,32 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - * @property {string[]} properties - * @property {import('../../../structs/db/Feed.js')} sourceFeed - * @property {import('../../../structs/db/Feed.js')[]} destinationFeeds - */ - -/** - * @param {Data} data - */ -function confirmSuccessVisual (data) { - const { profile, sourceFeed, destinationFeeds, properties } = data - const translate = Translator.createProfileTranslator(profile) - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - return new MessageVisual(`${translate('commands.clone.success', { - cloned: properties.join('`, `'), - link: sourceFeed.url, - destinationCount: destinationFeeds.length - })} ${translate('generics.backupReminder', { prefix })}`) -} - -const prompt = new LocalizedPrompt(confirmSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/clone/index.js b/services/bot/src/commands/prompts/clone/index.js deleted file mode 100644 index a38435e53..000000000 --- a/services/bot/src/commands/prompts/clone/index.js +++ /dev/null @@ -1,5 +0,0 @@ -exports.confirm = require('./confirm.js') -exports.confirmSuccess = require('./confirmSuccess.js') -exports.selectDestinationFeeds = require('./selectDestinationFeeds.js') -exports.selectProperties = require('./selectProperties.js') -exports.selectSourceFeed = require('./selectSourceFeed.js') diff --git a/services/bot/src/commands/prompts/clone/selectDestinationFeeds.js b/services/bot/src/commands/prompts/clone/selectDestinationFeeds.js deleted file mode 100644 index 1a7076dd2..000000000 --- a/services/bot/src/commands/prompts/clone/selectDestinationFeeds.js +++ /dev/null @@ -1,44 +0,0 @@ -const { MenuEmbed } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const commonPrompts = require('../common/index.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - * @property {import('../../../structs/db/Feed.js')} sourceFeed - */ - -/** - * @param {Data} data - */ -function selectDestinationFeedsVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - const selectFeedVisual = commonPrompts.selectFeed.visual(data) - const menu = selectFeedVisual.menu - menu.enableMultiSelect() - const embed = menu.embed - embed.setDescription(`${translate('commands.clone.copyTo')}\n\n${embed.description}\n\n${translate('structs.FeedSelector.multiSelect')}`) - return selectFeedVisual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectDestinationFeedsFn (message, data) { - const { feeds } = data - const { content } = message - const destinationFeeds = MenuEmbed.getMultiSelectOptions(content) - .map((index) => feeds[index - 1]) - return { - ...data, - destinationFeeds - } -} - -const prompt = new LocalizedPrompt(selectDestinationFeedsVisual, selectDestinationFeedsFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/clone/selectProperties.js b/services/bot/src/commands/prompts/clone/selectProperties.js deleted file mode 100644 index d280659df..000000000 --- a/services/bot/src/commands/prompts/clone/selectProperties.js +++ /dev/null @@ -1,68 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const referenceProperties = [ - 'message', - 'filters', - 'misc-options', - 'subscribers', - 'comparisons', - 'regexops', - 'filtered-formats', - 'webhook', - 'all' -] - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - * @property {import('../../../structs/db/Feed.js')} sourceFeed - * @property {import('../../../structs/db/Feed.js')[]} destinationFeeds - */ - -/** - * @param {Data} data - */ -function selectPropertiesVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - - return new MessageVisual(translate('commands.clone.inputProperties', { - properties: referenceProperties.join('`, `') - })) -} -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectDestinationFeedsFn (message, data) { - const { profile } = data - const { content } = message - const translate = Translator.createProfileTranslator(profile) - const split = content - .split(',') - .map(s => s.trim()) - .filter((value, index, self) => self.indexOf(value) === index) - const invalids = [] - for (const str of split) { - const lowercased = str.toLowerCase() - if (!referenceProperties.includes(lowercased)) { - invalids.push(str) - } - } - if (invalids.length > 0) { - throw new Rejection(translate('commands.clone.invalidProperties', { - invalid: invalids.join('`, `'), - properties: referenceProperties.join('`, `') - })) - } - return { - ...data, - properties: split.map(s => s.toLowerCase()) - } -} - -const prompt = new LocalizedPrompt(selectPropertiesVisual, selectDestinationFeedsFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/clone/selectSourceFeed.js b/services/bot/src/commands/prompts/clone/selectSourceFeed.js deleted file mode 100644 index 58cf4de87..000000000 --- a/services/bot/src/commands/prompts/clone/selectSourceFeed.js +++ /dev/null @@ -1,40 +0,0 @@ -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const commonPrompts = require('../common/index.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - */ - -/** - * @param {Data} data - */ -function selectSourceFeedVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - const selectFeedVisual = commonPrompts.selectFeed.visual(data) - const embed = selectFeedVisual.menu.embed - embed.setDescription(`${translate('commands.clone.copyFrom')}\n\n${embed.description}`) - return selectFeedVisual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectSourceFeedFn (message, data) { - const { feeds } = data - const { content } = message - const feedIndex = Number(content) - 1 - const selectedFeed = feeds[feedIndex] - return { - ...data, - sourceFeed: selectedFeed - } -} - -const prompt = new LocalizedPrompt(selectSourceFeedVisual, selectSourceFeedFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/common/filterAddCategorySelect.js b/services/bot/src/commands/prompts/common/filterAddCategorySelect.js deleted file mode 100644 index 969f2ccc4..000000000 --- a/services/bot/src/commands/prompts/common/filterAddCategorySelect.js +++ /dev/null @@ -1,55 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const ThemedEmbed = require('./utils/ThemedEmbed.js') -const LocalizedPrompt = require('./utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {import('../../../structs/db/Subscriber.js')|import('../../../structs/db/Feed.js')} target - */ - -/** - * @param {Data} data - */ -function visual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('commands.utils.filters.filtersCustomization'), - description: `**${translate('commands.utils.filters.feed')}:** ${feed.url}\n\n${translate('commands.utils.filters.categoryDescription')}` - }).addField('title', '\u200b') - .addField('description', '\u200b') - .addField('summary', '\u200b') - .addField('author', '\u200b') - .addField('tags', '\u200b') - - const visual = new MessageVisual('', { - embed - }) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function fn (message, data) { - const { profile } = data - const { content } = message - const translate = Translator.createProfileTranslator(profile) - const validInput = ['title', 'description', 'summary', 'author', 'tags'] - if (!validInput.includes(content) && !content.startsWith('raw:') && !content.startsWith('other:')) { - throw new Rejection(translate('commands.utils.filters.invalidCategory')) - } - return { - ...data, - filterCategory: content - } -} - -const prompt = new LocalizedPrompt(visual, fn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/common/filterAddInput.js b/services/bot/src/commands/prompts/common/filterAddInput.js deleted file mode 100644 index b1e180137..000000000 --- a/services/bot/src/commands/prompts/common/filterAddInput.js +++ /dev/null @@ -1,66 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('./utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} filterCategory - * @property {import('../../../structs/db/Feed.js')|import('../../../structs/db/Subscriber.js')} target - */ - -/** - * @param {Data} data - */ -function visual (data) { - const { filterCategory, profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.utils.filters.promptAdd', { - type: filterCategory - })) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function fn (message, data) { - const { target, filterCategory, selectedFeed: feed } = data - const { content } = message - const addList = content - .split('\n') - .map(item => item.trim().toLowerCase()) - .filter((item, index, self) => item && index === self.indexOf(item)) - const add = [] - const skip = [] - - for (const item of addList) { - if (target.getFilterIndex(filterCategory, item) === -1) { - add.push(item) - } else { - skip.push(item) - } - } - if (add.length > 0) { - const log = createLogger(message.client.shard.ids[0]) - log.info({ - guild: message.guild, - user: message.author, - feed: target - }, `Added filters to ${feed.url}: ${add.join('\n')}`) - await target.addFilters(filterCategory, add) - } - - return { - ...data, - addedInputFilters: add, - skippedInputFilters: skip - } -} - -const prompt = new LocalizedPrompt(visual, fn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/common/filterAddInputSuccess.js b/services/bot/src/commands/prompts/common/filterAddInputSuccess.js deleted file mode 100644 index be328c388..000000000 --- a/services/bot/src/commands/prompts/common/filterAddInputSuccess.js +++ /dev/null @@ -1,50 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('./utils/LocalizedPrompt') -const Subscriber = require('../../../structs/db/Subscriber.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} filterCategory - * @property {import('../../../structs/db/Feed.js')|import('../../../structs/db/Subscriber.js')} target - * @property {string[]} addedInputFilters - * @property {string[]} skippedInputFilters - */ - -/** - * @param {Data} data - */ -function visual (data) { - const { filterCategory, profile, addedInputFilters, skippedInputFilters, target } = data - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const translate = Translator.createProfileTranslator(profile) - let output = '' - if (addedInputFilters.length > 0) { - if (target instanceof Subscriber) { - output += `${translate('commands.utils.filters.updatedFor', { - name: target.type - })} ` - } - output = `${translate('commands.utils.filters.addSuccess')} \`${filterCategory}\`:\n\`\`\`\n\n${addedInputFilters.join('\n')}\`\`\`` - } - if (skippedInputFilters.length > 0) { - output += `\n${translate('commands.utils.filters.addFailed')}:\n\`\`\`\n\n${skippedInputFilters.join('\n')}\`\`\`` - } - if (addedInputFilters.length > 0) { - output += translate('commands.utils.filters.testFilters', { - prefix - }) - } - return new MessageVisual(`${output}\n\n${translate('generics.backupReminder', { - prefix - })}`) -} - -const prompt = new LocalizedPrompt(visual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/common/filterRemoveCategorySelect.js b/services/bot/src/commands/prompts/common/filterRemoveCategorySelect.js deleted file mode 100644 index d254d85a4..000000000 --- a/services/bot/src/commands/prompts/common/filterRemoveCategorySelect.js +++ /dev/null @@ -1,65 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const ThemedEmbed = require('./utils/ThemedEmbed.js') -const LocalizedPrompt = require('./utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {import('../../../structs/db/Subscriber.js')|import('../../../structs/db/Feed.js')} target - */ - -/** - * @param {Data} data - */ -function visual (data) { - const { profile, selectedFeed: feed, target } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('commands.utils.filters.listOfFilters'), - description: translate('commands.utils.filters.listOfFiltersDescription', { - title: feed.title, - link: feed.url - }) - }) - - const filterList = target.filters - - for (const filterCategory in filterList) { - const filters = filterList[filterCategory] - let value = '' - for (const filter of filters) { - value += `${filter}\n` - } - embed.addField(filterCategory, value, true) - } - - const visual = new MessageVisual('', { - embed - }) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function fn (message, data) { - const { profile } = data - const { content } = message - const translate = Translator.createProfileTranslator(profile) - const validInput = ['title', 'description', 'summary', 'author', 'tags'] - if (!validInput.includes(content.toLowerCase()) && !content.startsWith('raw:') && !content.startsWith('other:')) { - throw new Rejection(translate('commands.utils.filters.invalidCategory')) - } - return { - ...data, - filterCategory: content - } -} - -const prompt = new LocalizedPrompt(visual, fn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/common/filterRemoveInput.js b/services/bot/src/commands/prompts/common/filterRemoveInput.js deleted file mode 100644 index 1ba7fad5e..000000000 --- a/services/bot/src/commands/prompts/common/filterRemoveInput.js +++ /dev/null @@ -1,67 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('./utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} filterCategory - * @property {import('../../../structs/db/Feed.js')|import('../../../structs/db/Subscriber.js')} target - */ - -/** - * @param {Data} data - */ -function visual (data) { - const { filterCategory, profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.utils.filters.removeFilterConfirm', { - category: filterCategory - })) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function fn (message, data) { - const { target, filterCategory, selectedFeed: feed } = data - const { content } = message - const removeList = content - .split('\n') - .map(item => item.trim().toLowerCase()) - .filter((item, index, self) => item && index === self.indexOf(item)) - const remove = [] - const skip = [] - - for (const item of removeList) { - const index = target.getFilterIndex(filterCategory, item) - if (index === -1) { - skip.push(item) - } else { - remove.push(item) - } - } - if (remove.length > 0) { - const log = createLogger(message.client.shard.ids[0]) - log.info({ - guild: message.guild, - user: message.author, - target - }, `Removed filters from ${feed.url}: ${remove.join('\n')}`) - await target.removeFilters(filterCategory, remove) - } - - return { - ...data, - removedInputFilters: remove, - skippedInputFilters: skip - } -} - -const prompt = new LocalizedPrompt(visual, fn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/common/filterRemoveInputSuccess.js b/services/bot/src/commands/prompts/common/filterRemoveInputSuccess.js deleted file mode 100644 index 6734d27b6..000000000 --- a/services/bot/src/commands/prompts/common/filterRemoveInputSuccess.js +++ /dev/null @@ -1,50 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('./utils/LocalizedPrompt.js') -const Subscriber = require('../../../structs/db/Subscriber.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} filterCategory - * @property {import('../../../structs/db/Feed.js')|import('../../../structs/db/Subscriber.js')} target - * @property {string[]} removedInputFilters - * @property {string[]} skippedInputFilters - */ - -/** - * @param {Data} data - */ -function visual (data) { - const { filterCategory, profile, removedInputFilters, skippedInputFilters, target } = data - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const translate = Translator.createProfileTranslator(profile) - let output = '' - if (removedInputFilters.length > 0) { - if (target instanceof Subscriber) { - output += `${translate('commands.utils.filters.removeSuccessSubscriber', { - name: target.type - })} ` - } - output = `${translate('commands.utils.filters.removeSuccess')} \`${filterCategory}\`:\`\`\`\n\n${removedInputFilters.join('\n')}\`\`\`` - } - if (skippedInputFilters.length > 0) { - output += `\n\n${translate('commands.utils.filters.removeFailedNoExist')}:\n\`\`\`\n\n${skippedInputFilters.join('\n')}\`\`\`` - } - if (removedInputFilters.length > 0) { - output += translate('commands.utils.filters.testFilters', { - prefix - }) - } - return new MessageVisual(`${output}\n\n${translate('generics.backupReminder', { - prefix - })}`) -} - -const prompt = new LocalizedPrompt(visual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/common/index.js b/services/bot/src/commands/prompts/common/index.js deleted file mode 100644 index 99eb4828b..000000000 --- a/services/bot/src/commands/prompts/common/index.js +++ /dev/null @@ -1,11 +0,0 @@ -exports.noFeedsFound = require('./noFeedsFound.js') -exports.selectFeed = require('./selectFeed.js') -exports.selectMultipleFeeds = require('./selectMultipleFeeds.js') - -// Filter related -exports.filterAddCategorySelect = require('./filterAddCategorySelect.js') -exports.filterAddInput = require('./filterAddInput.js') -exports.filterAddInputSuccess = require('./filterAddInputSuccess.js') -exports.filterRemoveCategorySelect = require('./filterRemoveCategorySelect.js') -exports.filterRemoveInput = require('./filterRemoveInput.js') -exports.filterRemoveInputSuccess = require('./filterRemoveInputSuccess.js') diff --git a/services/bot/src/commands/prompts/common/noFeedsFound.js b/services/bot/src/commands/prompts/common/noFeedsFound.js deleted file mode 100644 index 22db26ba5..000000000 --- a/services/bot/src/commands/prompts/common/noFeedsFound.js +++ /dev/null @@ -1,20 +0,0 @@ -const LocalizedPrompt = require('./utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const { MessageVisual } = require('discord.js-prompts') -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - */ - -/** - * @param {Data} data - */ -function noFeedsFoundVisual (data) { - const { locale } = data.profile || {} - return new MessageVisual(Translator.translate('structs.FeedSelector.noFeeds', locale)) -} - -const prompt = new LocalizedPrompt(noFeedsFoundVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/common/selectFeed.js b/services/bot/src/commands/prompts/common/selectFeed.js deleted file mode 100644 index d0f5ce176..000000000 --- a/services/bot/src/commands/prompts/common/selectFeed.js +++ /dev/null @@ -1,54 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('./utils/ThemedEmbed.js') -const LocalizedPrompt = require('./utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const handlePaginationError = require('./utils/handlePaginationError.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - */ - -/** - * @param {Data} data - */ -function selectFeedVisual (data) { - const feeds = data.feeds - const { locale } = data.profile || {} - const translate = Translator.createLocaleTranslator(locale) - const embed = new ThemedEmbed({ - title: translate('structs.FeedSelector.feedSelectionMenu'), - description: `${translate('structs.FeedSelector.prompt')} ${translate('structs.FeedSelector.exitToCancel')} ` - }) - const menu = new MenuEmbed(embed) - .enablePagination(handlePaginationError) - - for (const feed of feeds) { - const title = feed.title.length > 200 ? feed.title.slice(0, 200) + '...' : feed.title - const url = feed.url.length > 500 ? translate('commands.list.exceeds500Characters') : feed.url - menu.addOption(title, `Channel: <#${feed.channel}>\nURL: ${url}`) - } - const visual = new MenuVisual(menu) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectFeedFn (message, data) { - const feeds = data.feeds - const index = Number(message.content) - 1 - return { - ...data, - selectedFeed: feeds[index] - } -} - -const prompt = new LocalizedPrompt(selectFeedVisual, selectFeedFn) - -exports.visual = selectFeedVisual -exports.fn = selectFeedFn -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/common/selectMultipleFeeds.js b/services/bot/src/commands/prompts/common/selectMultipleFeeds.js deleted file mode 100644 index 27f5242a5..000000000 --- a/services/bot/src/commands/prompts/common/selectMultipleFeeds.js +++ /dev/null @@ -1,49 +0,0 @@ -const { MenuEmbed, Rejection } = require('discord.js-prompts') -const LocalizedPrompt = require('./utils/LocalizedPrompt.js') -const selectFeed = require('./selectFeed.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - */ - -/** - * @param {Data} data - */ -function selectMultipleFeedsVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - const selectFeedVisual = selectFeed.visual(data) - const menu = selectFeedVisual.menu - menu.enableMultiSelect() - const embed = menu.embed - embed.setDescription(`${embed.description}\n\n${translate('structs.FeedSelector.multiSelect')}`) - return selectFeedVisual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectMultipleFeedsFn (message, data) { - const { feeds, profile } = data - const { content } = message - const selectedFeeds = MenuEmbed.getMultiSelectOptions(content) - .map((index) => feeds[index - 1]) - if (selectedFeeds.length === 0) { - const translate = Translator.createProfileTranslator(profile) - throw new Rejection(translate('structs.FeedSelector.noneSelected')) - } - return { - ...data, - selectedFeeds - } -} - -const prompt = new LocalizedPrompt(selectMultipleFeedsVisual, selectMultipleFeedsFn) - -exports.visual = selectMultipleFeedsVisual -exports.fn = selectMultipleFeedsFn -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/common/utils/LocalizedPrompt.js b/services/bot/src/commands/prompts/common/utils/LocalizedPrompt.js deleted file mode 100644 index 546b09226..000000000 --- a/services/bot/src/commands/prompts/common/utils/LocalizedPrompt.js +++ /dev/null @@ -1,12 +0,0 @@ -const { DiscordPrompt, Rejection } = require('discord.js-prompts') -const Translator = require('../../../../structs/Translator.js') - -class LocalizedPrompt extends DiscordPrompt { - static createMenuRejection (message, data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - return new Rejection(translate('structs.errors.MenuOptionError.message')) - } -} - -module.exports = LocalizedPrompt diff --git a/services/bot/src/commands/prompts/common/utils/ThemedEmbed.js b/services/bot/src/commands/prompts/common/utils/ThemedEmbed.js deleted file mode 100644 index 433e54cd8..000000000 --- a/services/bot/src/commands/prompts/common/utils/ThemedEmbed.js +++ /dev/null @@ -1,12 +0,0 @@ -const { MessageEmbed } = require('discord.js') -const getConfig = require('../../../../config.js').get - -class ThemedEmbed extends MessageEmbed { - constructor (...args) { - super(...args) - const config = getConfig() - this.setColor(config.bot.menuColor) - } -} - -module.exports = ThemedEmbed diff --git a/services/bot/src/commands/prompts/common/utils/handlePaginationError.js b/services/bot/src/commands/prompts/common/utils/handlePaginationError.js deleted file mode 100644 index 09eadfdfd..000000000 --- a/services/bot/src/commands/prompts/common/utils/handlePaginationError.js +++ /dev/null @@ -1,22 +0,0 @@ -const { MessageEmbed } = require('discord.js') -const createLogger = require('../../../../util/logger/create.js') - -/** - * @param {Error} err - * @param {import('discord.js').Message} message - */ -async function handlePaginationError (err, message) { - const log = createLogger(message.client.shard.ids[0]) - const newEmbed = new MessageEmbed(message.embeds[0]) - .setFooter(`Failed to enable pagination via message reactions (${err.message})`) - try { - await message.edit(message.content, newEmbed) - } catch (err) { - log.warn({ - error: err, - guild: message.guild - }, 'Pagination controls error') - } -} - -module.exports = handlePaginationError diff --git a/services/bot/src/commands/prompts/common/utils/splitMentionsByNewlines.js b/services/bot/src/commands/prompts/common/utils/splitMentionsByNewlines.js deleted file mode 100644 index ef70dc0a2..000000000 --- a/services/bot/src/commands/prompts/common/utils/splitMentionsByNewlines.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @param {string[]} mentionStrings - */ -function splitMentionsByNewlines (mentionStrings) { - const ordered = mentionStrings.sort((a, b) => { - return a.toLowerCase() < b.toLowerCase() ? -1 : 1 - }) - // Put 10 mentions on new lines so message splitting work properly - const outputMentionArrs = [] - for (const substring of ordered) { - const lastArray = outputMentionArrs[outputMentionArrs.length - 1] - if (!lastArray || lastArray.length === 10) { - outputMentionArrs.push([substring]) - } else { - lastArray.push(substring) - } - } - return outputMentionArrs.map(arr => arr.join(' ')).join('\n') -} - -module.exports = splitMentionsByNewlines diff --git a/services/bot/src/commands/prompts/common/utils/splitTextByNewline.js b/services/bot/src/commands/prompts/common/utils/splitTextByNewline.js deleted file mode 100644 index e45d0d011..000000000 --- a/services/bot/src/commands/prompts/common/utils/splitTextByNewline.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @param {string} str - */ -function splitTextByNewline (fullString, maxLength = 1999) { - const split = fullString.split('\n') - const strings = [] - let thisString = '' - for (const str of split) { - // Plus 1 for the newline - if (thisString.length + str.length + 1 < maxLength) { - thisString += str + '\n' - } else { - strings.push(thisString) - thisString = str + '\n' - } - } - if (thisString.length > 0) { - strings.push(thisString) - } - return strings -} - -module.exports = splitTextByNewline diff --git a/services/bot/src/commands/prompts/date/askFormat.js b/services/bot/src/commands/prompts/date/askFormat.js deleted file mode 100644 index 1df3450fa..000000000 --- a/services/bot/src/commands/prompts/date/askFormat.js +++ /dev/null @@ -1,72 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Profile = require('../../../structs/db/Profile.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function askFormatVisual (data) { - const { profile } = data - const { locale } = profile || {} - const translate = Translator.createLocaleTranslator(locale) - return new MessageVisual(translate('commands.date.promptNewDateFormat')) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function askFormatFn (message, data) { - const config = getConfig() - const { profile } = data - const { content: setting } = message - const log = createLogger(message.client.shard.ids[0]) - // Reset - if (setting === 'reset') { - if (profile) { - profile.dateFormat = undefined - await profile.save() - } - log.info({ - guild: message.guild, - user: message.author - }, 'Date format reset') - return data - } - // Not reset - const isDefault = setting === config.feeds.dateFormat - if (profile) { - profile.dateFormat = isDefault ? undefined : setting - await profile.save() - } else if (!isDefault) { - const newProfile = new Profile({ - _id: message.guild.id, - name: message.guild.name, - dateFormat: setting - }) - await newProfile.save() - } - log.info({ - guild: message.guild, - user: message.author - }, `Date format set to ${setting}`) - return { - ...data, - setting - } -} - -const prompt = new LocalizedPrompt(askFormatVisual, askFormatFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/date/askLanguage.js b/services/bot/src/commands/prompts/date/askLanguage.js deleted file mode 100644 index c8f8ab648..000000000 --- a/services/bot/src/commands/prompts/date/askLanguage.js +++ /dev/null @@ -1,80 +0,0 @@ -const moment = require('moment-timezone') -const { MessageVisual, Rejection } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Profile = require('../../../structs/db/Profile.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function askLanguageVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - const locales = moment.locales() - const localesList = locales.join(', ') - return new MessageVisual(translate('commands.date.promptNewLanguage', { localesList })) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function askLanguageFn (message, data) { - const config = getConfig() - const { profile } = data - const { content: setting } = message - const translate = Translator.createProfileTranslator(profile) - const log = createLogger(message.client.shard.ids[0]) - // Reset - if (setting === 'reset') { - if (profile) { - profile.dateLanguage = undefined - await profile.save() - } - log.info({ - guild: message.guild, - user: message.author - }, 'Date language reset') - return data - } - const locales = moment.locales() - if (!locales.includes(setting)) { - throw new Rejection(translate('commands.date.invalidLanguage', { - input: setting, - localesList: locales.join(', ') - })) - } - // Not reset - const isDefault = config.bot.dateLanguage === setting - if (profile) { - profile.dateLanguage = isDefault ? undefined : setting - await profile.save() - } else if (!isDefault) { - const newProfile = new Profile() - newProfile.dateLanguage = setting - newProfile.name = message.guild.name - await newProfile.save() - } - log.info({ - guild: message.guild, - user: message.author - }, `Date language set to ${setting}`) - return { - ...data, - setting - } -} - -const prompt = new LocalizedPrompt(askLanguageVisual, askLanguageFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/date/askTimezone.js b/services/bot/src/commands/prompts/date/askTimezone.js deleted file mode 100644 index 843cff7a0..000000000 --- a/services/bot/src/commands/prompts/date/askTimezone.js +++ /dev/null @@ -1,76 +0,0 @@ -const moment = require('moment-timezone') -const { MessageVisual, Rejection } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Profile = require('../../../structs/db/Profile.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function askTimezoneVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.date.promptNewTimezone')) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function askTimezoneFn (message, data) { - const config = getConfig() - const { profile } = data - const { content: setting } = message - const translate = Translator.createProfileTranslator(profile) - const log = createLogger(message.client.shard.ids[0]) - // Reset - if (setting === 'reset') { - if (profile) { - profile.timezone = undefined - await profile.save() - } - log.info({ - guild: message.guild, - user: message.author - }, 'Timezone reset') - return data - } - // Not reset - if (!moment.tz.zone(setting)) { - throw new Rejection(translate('commands.date.invalidTimezone', { input: setting })) - } - const isDefault = config.feeds.timezone === setting - if (profile) { - profile.timezone = isDefault ? undefined : setting - await profile.save() - } else if (!isDefault) { - const newProfile = new Profile({ - _id: message.guild.id, - name: message.guild.name, - timezone: setting - }) - await newProfile.save() - } - log.info({ - guild: message.guild, - user: message.author - }, `Timezone set to ${setting}`) - return { - ...data, - setting - } -} - -const prompt = new LocalizedPrompt(askTimezoneVisual, askTimezoneFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/date/index.js b/services/bot/src/commands/prompts/date/index.js deleted file mode 100644 index 004887cba..000000000 --- a/services/bot/src/commands/prompts/date/index.js +++ /dev/null @@ -1,17 +0,0 @@ -const selectCustomization = require('./selectCustomization') -const successReset = require('./successReset.js') -const askFormat = require('./askFormat.js') -const askLanguage = require('./askLanguage.js') -const askTimezone = require('./askTimezone.js') -const successFormat = require('./successFormat.js') -const successLanguage = require('./successLanguage.js') -const successTimezone = require('./successTimezone.js') - -exports.selectCustomization = selectCustomization -exports.successReset = successReset -exports.askFormat = askFormat -exports.askLanguage = askLanguage -exports.askTimezone = askTimezone -exports.successFormat = successFormat -exports.successLanguage = successLanguage -exports.successTimezone = successTimezone diff --git a/services/bot/src/commands/prompts/date/selectCustomization.js b/services/bot/src/commands/prompts/date/selectCustomization.js deleted file mode 100644 index a0b60655a..000000000 --- a/services/bot/src/commands/prompts/date/selectCustomization.js +++ /dev/null @@ -1,65 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - */ - -/** - * @param {Data} data - */ -function selectCustomizationVisual (data) { - const config = getConfig() - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('commands.date.selectTitle'), - description: translate('commands.date.description') - }) - const { timezone, dateFormat, dateLanguage } = profile || {} - const menu = new MenuEmbed(embed) - .addOption(translate('commands.date.optionChangeTimezone'), `${translate('generics.defaultSetting', { value: config.feeds.timezone })} ${timezone ? translate('commands.date.optionCurrentSetting', { value: timezone }) : ''}`) - .addOption(translate('commands.date.optionCustomizeFormat'), `${translate('generics.defaultSetting', { value: config.feeds.dateFormat })} ${dateFormat ? translate('commands.date.optionCurrentSetting', { value: dateFormat }) : ''}`) - .addOption(translate('commands.date.optionChangeLanguage'), `${translate('generics.defaultSetting', { value: config.feeds.dateLanguage })} ${dateLanguage ? translate('commands.date.optionCurrentSetting', { value: dateLanguage }) : ''}`) - .addOption(translate('commands.date.optionReset'), translate('commands.date.optionResetValue')) - - const visual = new MenuVisual(menu) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectCustomizationFn (message, data) { - const { profile } = data - const { content: selected } = message - const log = createLogger(message.client.shard.ids[0]) - if (selected === '4') { - log.info({ - guild: message.guild, - user: message.author - }, 'Date settings reset') - if (profile) { - profile.timezone = undefined - profile.dateFormat = undefined - profile.dateLanguage = undefined - await profile.save() - } - } - return { - ...data, - selected - } -} - -const prompt = new LocalizedPrompt(selectCustomizationVisual, selectCustomizationFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/date/successFormat.js b/services/bot/src/commands/prompts/date/successFormat.js deleted file mode 100644 index a333a99e3..000000000 --- a/services/bot/src/commands/prompts/date/successFormat.js +++ /dev/null @@ -1,37 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} selected - * @property {string} setting - */ - -/** - * @param {Data} data - */ -function successFormatVisual (data) { - const config = getConfig() - const { profile, setting } = data - const translate = Translator.createProfileTranslator(profile) - if (setting === 'reset') { - return new MessageVisual(translate('commands.date.successReset', { - name: translate('commands.date.dateFormat'), - value: config.feeds.dateFormat - })) - } else { - return new MessageVisual(translate('commands.date.successSet', { - name: translate('commands.date.dateFormat'), - value: setting - })) - } -} - -const prompt = new LocalizedPrompt(successFormatVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/date/successLanguage.js b/services/bot/src/commands/prompts/date/successLanguage.js deleted file mode 100644 index e3da02010..000000000 --- a/services/bot/src/commands/prompts/date/successLanguage.js +++ /dev/null @@ -1,37 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} selected - * @property {string} setting - */ - -/** - * @param {Data} data - */ -function successLanguageVisual (data) { - const config = getConfig() - const { profile, setting } = data - const translate = Translator.createProfileTranslator(profile) - if (setting === 'reset') { - return new MessageVisual(translate('commands.date.successReset', { - name: translate('commands.date.dateLanguage'), - value: config.feeds.dateLanguage - })) - } else { - return new MessageVisual(translate('commands.date.successSet', { - name: translate('commands.date.dateLanguage'), - value: setting - })) - } -} - -const prompt = new LocalizedPrompt(successLanguageVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/date/successReset.js b/services/bot/src/commands/prompts/date/successReset.js deleted file mode 100644 index 463cc7f10..000000000 --- a/services/bot/src/commands/prompts/date/successReset.js +++ /dev/null @@ -1,26 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function successResetVisual (data) { - const { profile } = data - const { locale } = profile || {} - const translate = Translator.createLocaleTranslator(locale) - return new MessageVisual(translate('commands.date.successResetAll')) -} - -const successResetCondition = data => data.selected === '4' -const prompt = new LocalizedPrompt(successResetVisual, undefined, successResetCondition) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/date/successTimezone.js b/services/bot/src/commands/prompts/date/successTimezone.js deleted file mode 100644 index 9b051db82..000000000 --- a/services/bot/src/commands/prompts/date/successTimezone.js +++ /dev/null @@ -1,37 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} selected - * @property {string} setting - */ - -/** - * @param {Data} data - */ -function successTimezoneVisual (data) { - const config = getConfig() - const { profile, setting } = data - const translate = Translator.createProfileTranslator(profile) - if (setting === 'reset') { - return new MessageVisual(translate('commands.date.successReset', { - name: translate('commands.date.timezone'), - value: config.feeds.timezone - })) - } else { - return new MessageVisual(translate('commands.date.successSet', { - name: translate('commands.date.timezone'), - value: setting - })) - } -} - -const prompt = new LocalizedPrompt(successTimezoneVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/dump/index.js b/services/bot/src/commands/prompts/dump/index.js deleted file mode 100644 index d2fe07ff9..000000000 --- a/services/bot/src/commands/prompts/dump/index.js +++ /dev/null @@ -1 +0,0 @@ -exports.sendFile = require('./sendFile.js') diff --git a/services/bot/src/commands/prompts/dump/sendFile.js b/services/bot/src/commands/prompts/dump/sendFile.js deleted file mode 100644 index b79d00337..000000000 --- a/services/bot/src/commands/prompts/dump/sendFile.js +++ /dev/null @@ -1,44 +0,0 @@ -const Discord = require('discord.js') -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const FeedFetcher = require('../../../util/FeedFetcher.js') -const FlattenedJSON = require('../../../structs/FlattenedJSON.js') -const URL = require('url').URL - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {boolean} raw - */ - -/** - * @param {Data} data - */ -async function sendFileVisual (data) { - const { selectedFeed: feed, raw } = data - const { articleList } = await FeedFetcher.fetchFeed(feed.url) - let textOutput = '' - const objOutput = [] - for (var articleObject of articleList) { - if (raw) { - objOutput.push(articleObject) - } else { - textOutput += new FlattenedJSON(articleObject, feed).text + '\r\n\r\n' - } - } - textOutput = textOutput.trim() - const bufferData = Buffer.from(raw ? JSON.stringify(objOutput, null, 2) : textOutput) - const domain = new URL(feed.url).hostname - const fileName = raw ? `${domain}.json` : `${domain}.txt` - return new MessageVisual('', { - files: [ - new Discord.MessageAttachment(bufferData, fileName) - ] - }) -} - -const prompt = new LocalizedPrompt(sendFileVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed.fields/addBlankFieldSuccess.js b/services/bot/src/commands/prompts/embed.fields/addBlankFieldSuccess.js deleted file mode 100644 index 6d44f3b68..000000000 --- a/services/bot/src/commands/prompts/embed.fields/addBlankFieldSuccess.js +++ /dev/null @@ -1,37 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function addBlankFieldSuccessVisual (data) { - const { profile, selected, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - let string - if (selected === '3') { - // Regular - string = translate('commands.embed.embedFieldsAddedBlank', { - link: feed.url - }) - } else if (selected === '4') { - // Inline - string = translate('commands.embed.embedFieldsAddedBlankInline', { - link: feed.url - }) - } - return new MessageVisual(string) -} - -const prompt = new LocalizedPrompt(addBlankFieldSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed.fields/addField.js b/services/bot/src/commands/prompts/embed.fields/addField.js deleted file mode 100644 index e24122bc7..000000000 --- a/services/bot/src/commands/prompts/embed.fields/addField.js +++ /dev/null @@ -1,66 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function addFieldVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.embed.embedFieldsSettingPrompt')) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function addFieldFn (message, data) { - const { profile, targetEmbedIndex, selectedFeed: feed, selected } = data - const { content } = message - const translate = Translator.createProfileTranslator(profile) - const split = content.trim().split('\n') - const name = split[0].trim() - const value = split.slice(1, split.length).join('\n').trim() - if (name.length > 256) { - throw new Rejection(translate('commands.embed.embedFieldsSettingTitleLong')) - } - if (value.length > 1024) { - throw new Rejection(translate('commands.embed.embedFieldsSettingValueLong')) - } - if (!feed.embeds[targetEmbedIndex]) { - feed.embeds[targetEmbedIndex] = { - fields: [] - } - } - const newField = { - name, - value: value || '\u200b', - inline: selected === '2' - } - feed.embeds[targetEmbedIndex].fields.push(newField) - await feed.save() - const log = createLogger(message.client.shard.ids[0]) - log.info({ - guild: message.guild, - user: message.author - }, `Embed field added. Title: '${name}', Value: '${value}'`) - return { - ...data, - newField - } -} - -const prompt = new LocalizedPrompt(addFieldVisual, addFieldFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed.fields/addFieldSuccess.js b/services/bot/src/commands/prompts/embed.fields/addFieldSuccess.js deleted file mode 100644 index 095f088fc..000000000 --- a/services/bot/src/commands/prompts/embed.fields/addFieldSuccess.js +++ /dev/null @@ -1,32 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - * @property {string} selected - * @property {Object} newField - */ - -/** - * @param {Data} data - */ -function addFieldSuccessVisual (data) { - const { profile, selected, newField, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const { name, value } = newField - return new MessageVisual(translate('commands.embed.embedFieldsAdded', { - type: selected === '2' ? ' inline' : '', - name, - value: value.length > 1500 ? value.slice(0, 1500) + '...' : value, - link: feed.url - })) -} - -const prompt = new LocalizedPrompt(addFieldSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed.fields/index.js b/services/bot/src/commands/prompts/embed.fields/index.js deleted file mode 100644 index 08539fddc..000000000 --- a/services/bot/src/commands/prompts/embed.fields/index.js +++ /dev/null @@ -1,6 +0,0 @@ -exports.addField = require('./addField.js') -exports.addFieldSuccess = require('./addFieldSuccess.js') -exports.addBlankFieldSuccess = require('./addBlankFieldSuccess.js') -exports.removeField = require('./removeField.js') -exports.removeFieldSuccess = require('./removeFieldSuccess.js') -exports.selectAction = require('./selectAction.js') diff --git a/services/bot/src/commands/prompts/embed.fields/removeField.js b/services/bot/src/commands/prompts/embed.fields/removeField.js deleted file mode 100644 index 424677126..000000000 --- a/services/bot/src/commands/prompts/embed.fields/removeField.js +++ /dev/null @@ -1,64 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function removeFieldVisual (data) { - const { profile, selectedFeed: feed, targetEmbedIndex } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed() - .setTitle(translate('commands.embed.embedFieldsOptionRemoveEmbedTitle')) - .setDescription(translate('commands.embed.embedFieldsOptionRemoveEmbedDescription')) - const menu = new MenuEmbed(embed) - - const fields = feed.embeds[targetEmbedIndex].fields - for (const field of fields) { - const inline = field.inline ? `(${translate('commands.embed.inline')})` : `(${translate('commands.embed.regular')})` - // Empty string name - if (field.name === '\u200b') { - menu.addOption(`${inline} ${translate('commands.embed.blankField')}`, '\u200b') - } else { - menu.addOption(`${inline} ${field.name}`, field.value) - } - } - return new MenuVisual(menu) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function removeFieldFn (message, data) { - const { targetEmbedIndex, selectedFeed: feed } = data - const { content } = message - const fields = feed.embeds[targetEmbedIndex].fields - const index = Number(content) - 1 - fields.splice(index, 1) - await feed.save() - const log = createLogger() - log.info({ - guild: message.guild, - user: message.author - }, `Embed[${targetEmbedIndex}] field at index ${index} deleted`) - return { - ...data, - removedFieldIndex: index - } -} - -const prompt = new LocalizedPrompt(removeFieldVisual, removeFieldFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed.fields/removeFieldSuccess.js b/services/bot/src/commands/prompts/embed.fields/removeFieldSuccess.js deleted file mode 100644 index d527ef4bb..000000000 --- a/services/bot/src/commands/prompts/embed.fields/removeFieldSuccess.js +++ /dev/null @@ -1,29 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - * @property {string} selected - * @property {number} removedFieldIndex - */ - -/** - * @param {Data} data - */ -function addFieldSuccessVisual (data) { - const { profile, removedFieldIndex, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.embed.embedFieldsRemoved', { - numbers: removedFieldIndex, - link: feed.url - })) -} - -const prompt = new LocalizedPrompt(addFieldSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed.fields/selectAction.js b/services/bot/src/commands/prompts/embed.fields/selectAction.js deleted file mode 100644 index 77b61579e..000000000 --- a/services/bot/src/commands/prompts/embed.fields/selectAction.js +++ /dev/null @@ -1,71 +0,0 @@ -const { Rejection, MenuVisual, MenuEmbed } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - */ - -/** - * @param {Data} data - */ -function selectActionVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed() - .setTitle(translate('commands.embed.embedFields')) - .setDescription(translate('commands.embed.embedFieldsDescription')) - const menu = new MenuEmbed(embed) - .addOption(translate('commands.embed.embedFieldsOptionAddRegular'), translate('commands.embed.embedFieldsOptionAddRegularDescription')) - .addOption(translate('commands.embed.embedFieldsOptionAddInline'), translate('commands.embed.embedFieldsOptionAddInlineDescription')) - .addOption(translate('commands.embed.embedFieldsOptionAddRegularBlank'), translate('commands.embed.embedFieldsOptionAddRegularBlankDescription')) - .addOption(translate('commands.embed.embedFieldsOptionAddInlineBlank'), translate('commands.embed.embedFieldsOptionAddInlineBlankDescription')) - .addOption(translate('commands.embed.embedFieldsOptionRemove'), translate('commands.embed.embedFieldsOptionRemoveDescription')) - - return new MenuVisual(menu) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectActionFn (message, data) { - const { profile, targetEmbedIndex, selectedFeed: feed } = data - const { content: selected } = message - const embeds = feed.embeds - const translate = Translator.createProfileTranslator(profile) - const newData = { - ...data, - selected - } - // Remove a field - if (selected === '5') { - const embed = embeds[targetEmbedIndex] - if (!embed || embed.fields.length === 0) { - throw new Rejection(translate('commands.embed.embedFieldsRemoveNone')) - } - return newData - } - if (!embeds[targetEmbedIndex]) { - embeds.push({ fields: [] }) - } - const embed = embeds[targetEmbedIndex] - if (selected === '4' || selected === '5') { - embed.fields.push({ - name: '\u200b', - value: '\u200b', - inline: selected === '4' - }) - await feed.save() - } - return newData -} - -const prompt = new LocalizedPrompt(selectActionVisual, selectActionFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed/index.js b/services/bot/src/commands/prompts/embed/index.js deleted file mode 100644 index 721d2ac49..000000000 --- a/services/bot/src/commands/prompts/embed/index.js +++ /dev/null @@ -1,6 +0,0 @@ -exports.removeAllEmbedsSuccess = require('./removeAllEmbedsSuccess.js') -exports.selectEmbed = require('./selectEmbed.js') -exports.selectProperties = require('./selectProperties.js') -exports.resetEmbedSuccess = require('./resetEmbedSuccess.js') -exports.setProperty = require('./setProperty.js') -exports.setPropertySuccess = require('./setPropertySuccess.js') diff --git a/services/bot/src/commands/prompts/embed/removeAllEmbedsSuccess.js b/services/bot/src/commands/prompts/embed/removeAllEmbedsSuccess.js deleted file mode 100644 index c1b582a44..000000000 --- a/services/bot/src/commands/prompts/embed/removeAllEmbedsSuccess.js +++ /dev/null @@ -1,27 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - */ - -/** - * @param {Data} data - */ -function removeAllEmbedsSuccessVisual (data) { - const { profile, selectedFeed } = data - const translate = Translator.createProfileTranslator(profile) - - return new MessageVisual(translate('commands.embed.removedAllEmbeds', { - link: selectedFeed.url - })) -} - -const prompt = new LocalizedPrompt(removeAllEmbedsSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed/resetEmbedSuccess.js b/services/bot/src/commands/prompts/embed/resetEmbedSuccess.js deleted file mode 100644 index 7168ec6ef..000000000 --- a/services/bot/src/commands/prompts/embed/resetEmbedSuccess.js +++ /dev/null @@ -1,27 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - */ - -/** - * @param {Data} data - */ -function resetEmbedSuccessVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - - return new MessageVisual(translate('commands.embed.removedEmbed', { - link: feed.url - })) -} - -const prompt = new LocalizedPrompt(resetEmbedSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed/selectEmbed.js b/services/bot/src/commands/prompts/embed/selectEmbed.js deleted file mode 100644 index 42a611e8b..000000000 --- a/services/bot/src/commands/prompts/embed/selectEmbed.js +++ /dev/null @@ -1,84 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - */ - -function listEmbedValues (embed) { - if (!embed) { - return '\u200b' - } - let val = '' - for (const prop in embed) { - if (prop !== 'fields') { - val += `**${prop}:** ${embed[prop]}\n` - } - } - return val -} - -/** - * @param {Data} data - */ -function selectPropertiesVisual (data) { - const { profile, selectedFeed: feed } = data - const embeds = feed.embeds - const translate = Translator.createProfileTranslator(profile) - const messageEmbed = new ThemedEmbed() - .setTitle(translate('commands.embed.embedSelection')) - .setDescription(translate('commands.embed.embedSelectionDescription')) - const menu = new MenuEmbed(messageEmbed) - - for (let x = 0; x < embeds.length; ++x) { - const embed = embeds[x] - const val = listEmbedValues(embed) - menu.addOption(translate('commands.embed.numberedEmbed', { - number: x + 1 - }), `${val}\u200b`) - } - - if (embeds.length < 10) { - menu.addOption(translate('commands.embed.embedSelectionOptionAdd'), translate('commands.embed.embedSelectionOptionAddDescription')) - } - - menu.addOption(translate('commands.embed.embedSelectionOptionRemoveAll'), '\u200b') - - return new MenuVisual(menu) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectPropertiesFn (message, data) { - const { selectedFeed: feed } = data - const { content } = message - const targetEmbedIndex = Number(content) - 1 - if (targetEmbedIndex === feed.embeds.length + 1) { - feed.embeds = [] - if (feed.text === '{empty}') { - feed.text = undefined - } - await feed.save() - const log = createLogger(message.client.shard.ids[0]) - log.info({ - guild: message.guild, - user: message.author - }, `Removed all embeds for ${feed.url}`) - } - return { - ...data, - targetEmbedIndex - } -} - -const prompt = new LocalizedPrompt(selectPropertiesVisual, selectPropertiesFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed/selectProperties.js b/services/bot/src/commands/prompts/embed/selectProperties.js deleted file mode 100644 index 77c3f8bac..000000000 --- a/services/bot/src/commands/prompts/embed/selectProperties.js +++ /dev/null @@ -1,134 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') -const Feed = require('../../../structs/db/Feed') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - */ - -/** - * @param {Data} data - */ -function selectPropertiesVisual (data) { - const { profile, selectedFeed: feed, targetEmbedIndex } = data - const translate = Translator.createProfileTranslator(profile) - - let currentEmbedProps = '' - const selectedEmbed = feed.embeds[targetEmbedIndex] - // Show what the user has now - if (selectedEmbed) { - for (const property in selectedEmbed) { - if (property !== 'fields') { - currentEmbedProps += `[${property}]: ${selectedEmbed[property]}\n\n` - } - } - } - if (currentEmbedProps.length === 0) { - currentEmbedProps = '```\nNo properties set.\n' - } else { - currentEmbedProps = `\`\`\`Markdown\n# ${translate('commands.embed.currentProperties')} #\n\n` + currentEmbedProps - } - const m1 = translate('commands.embed.currentPropertiesList', { link: feed.url, list: currentEmbedProps }) - - // Then list available properties - - let embedPropertiesListed = `\`\`\`Markdown\n# ${translate('commands.embed.availableProperties')} #\n\n` - embedPropertiesListed += `[Title]: ${translate('commands.embed.titleDescription')}\n\n` - embedPropertiesListed += `[Description]: ${translate('commands.embed.descriptionDescription')}\n\n` - embedPropertiesListed += `[URL]: ${translate('commands.embed.urlDescription')}\n\n` - embedPropertiesListed += `[Color]: ${translate('commands.embed.colorDescription')}\n\n` - embedPropertiesListed += `[Timestamp]: ${translate('commands.embed.timestampDescription')}\n\n` - embedPropertiesListed += `[Footer Icon URL]: ${translate('commands.embed.footerIconURLDescription')}\n\n` - embedPropertiesListed += `[Footer Text]: ${translate('commands.embed.footerTextDescription')}\n\n` - embedPropertiesListed += `[Thumbnail URL]: ${translate('commands.embed.thumbnailURLDescription')}\n\n` - embedPropertiesListed += `[Image URL]: ${translate('commands.embed.imageURLDescription')}\n\n` - embedPropertiesListed += `[Author Name]: ${translate('commands.embed.authorNameDescription')}\n\n` - embedPropertiesListed += `[Author URL]: ${translate('commands.embed.authorURLDescription')}\n\n` - embedPropertiesListed += `[Author Icon URL]: ${translate('commands.embed.authorIconURLDescription')}\n\n\`\`\`` - - const m2 = translate('commands.embed.availablePropertiesList', { list: embedPropertiesListed }) - const mFull = m1 + m2 - // const mFull = (m1 + m2).length < 1995 ? `${m1}\n${m2}` : [m1, m2] // Separate into two messages if it exceeds Discord's max length of 2000 - if (mFull.length < 1995) { - return new MessageVisual(`${m1}\n${m2}`) - } else { - return [ - new MessageVisual(m1), - new MessageVisual(m2) - ] - } -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectPropertiesFn (message, data) { - const { profile, selectedFeed: feed, targetEmbedIndex } = data - const { content } = message - const translate = Translator.createProfileTranslator(profile) - const inputs = new Map([ - ['title', 'title'], - ['description', 'description'], - ['url', 'url'], - ['color', 'color'], - ['timestamp', 'timestamp'], - ['footer icon url', 'footerIconURL'], - ['footer text', 'footerText'], - ['thumbnail url', 'thumbnailURL'], - ['image url', 'imageURL'], - ['author name', 'authorName'], - ['author url', 'authorURL'], - ['author icon url', 'authorIconURL'] - ]) - if (content === 'reset') { - feed.embeds.splice(targetEmbedIndex, 1) - await feed.save() - const log = createLogger() - log.info({ - guild: message.guild, - user: message.author - }, `Embed[${targetEmbedIndex}] deleted`) - if (feed.disabled === Feed.DISABLE_REASONS.BAD_FORMAT) { - await feed.enable() - } - return { - ...data, - reset: true - } - } - - // Clean values - const properties = content - .split(',') - .map(i => i.trim()) - .filter((val, index, self) => index === self.indexOf(val)) - // Check if invalid - const invalids = [] - for (const property of properties) { - const lowercasedProperty = property.toLowerCase() - if (!inputs.has(lowercasedProperty)) { - invalids.push(property) - } - } - // Reject if necessary - if (invalids.length > 0) { - throw new Rejection(translate('commands.embed.invalidProperties', { invalids })) - } - - const values = properties.map(p => inputs.get(p.toLowerCase())) - return { - ...data, - properties: values - } -} - -const prompt = new LocalizedPrompt(selectPropertiesVisual, selectPropertiesFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed/setProperty.js b/services/bot/src/commands/prompts/embed/setProperty.js deleted file mode 100644 index bcd8fb467..000000000 --- a/services/bot/src/commands/prompts/embed/setProperty.js +++ /dev/null @@ -1,113 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') -const Feed = require('../../../structs/db/Feed') - -const prettyNames = new Map([ - ['title', 'Title'], - ['description', 'Description'], - ['url', 'URL'], - ['color', 'Color'], - ['timestamp', 'Timestamp'], - ['footerIconURL', 'Footer Icon URL'], - ['footerText', 'Footer Text'], - ['thumbnailURL', 'Thumbnail URL'], - ['imageURL', 'Image URL'], - ['authorName', 'Author Name'], - ['authorURL', 'Author URL'], - ['authorIconURL', 'Author Icon URL'] -]) - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - * @property {string[]} properties - * @property {Object} [updatedProperties] - */ - -/** - * @param {Data} data - */ -function setPropertyVisual (data) { - const { profile, properties } = data - const translate = Translator.createProfileTranslator(profile) - - const thisPropertyKey = properties[0] - const thisPropertyName = prettyNames.get(thisPropertyKey) - if (thisPropertyKey === 'timestamp') { - return new MessageVisual(translate('commands.embed.settingPropertyTimestamp')) - } else { - return new MessageVisual(translate('commands.embed.settingProperty', { - property: thisPropertyName - })) - } -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function setPropertyFn (message, data) { - const { profile, properties, targetEmbedIndex, selectedFeed: feed, updatedProperties } = data - const { content } = message - const translate = Translator.createProfileTranslator(profile) - const thisPropertyKey = properties[0] - const thisPropertyName = prettyNames.get(thisPropertyKey) - let finalValue = content - // Validate the property - if (thisPropertyKey === 'color' && finalValue !== 'reset') { - finalValue = parseInt(content, 10) - if (isNaN(finalValue)) { - throw new Rejection(translate('commands.embed.invalidColorNumber')) - } - } - // Save - if (finalValue === 'reset' && feed.embeds[targetEmbedIndex]) { - feed.embeds[targetEmbedIndex][thisPropertyKey] = undefined - } else { - if (!feed.embeds[targetEmbedIndex]) { - feed.embeds.push({}) - } - feed.embeds[targetEmbedIndex][thisPropertyKey] = finalValue - } - feed.validate() - if (feed.embeds.length === 0 && feed.text === '{empty}') { - feed.text = undefined - } - await feed.save() - if (feed.disabled === Feed.DISABLE_REASONS.BAD_FORMAT) { - await feed.enable() - } - - // Log it - const log = createLogger() - log.info({ - guild: message.guild, - user: message.author - }, `Embed[${targetEmbedIndex}] property ${thisPropertyKey} updated to ${finalValue}`) - - // Return the data - const newData = { - ...data, - properties: properties.slice(1, properties.length) - } - if (!updatedProperties) { - newData.updatedProperties = { - [thisPropertyName]: finalValue - } - } else { - newData.updatedProperties = { - ...updatedProperties, - [thisPropertyName]: finalValue - } - } - return newData -} - -const prompt = new LocalizedPrompt(setPropertyVisual, setPropertyFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/embed/setPropertySuccess.js b/services/bot/src/commands/prompts/embed/setPropertySuccess.js deleted file mode 100644 index 06e49201e..000000000 --- a/services/bot/src/commands/prompts/embed/setPropertySuccess.js +++ /dev/null @@ -1,49 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {number} targetEmbedIndex - * @property {string[]} properties - * @property {Object} updatedProperties - */ - -/** - * @param {Data} data - */ -function setPropertyVisual (data) { - const { profile, updatedProperties, selectedFeed: feed } = data - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const translate = Translator.createProfileTranslator(profile) - let reset = '' - let updated = '' - for (const propertyName in updatedProperties) { - const setValue = updatedProperties[propertyName] - if (setValue === 'reset') { - reset += translate('commands.embed.resetSuccess', { - propName: propertyName - }) - } else { - updated += translate('commands.embed.updatedSuccess', { - propName: propertyName, - userSetting: setValue - }) - } - } - return new MessageVisual(`${translate('commands.embed.updatedInfo', { - link: feed.url, - resetList: reset, - updateList: updated, - prefix - })} ${translate('generics.backupReminder', { prefix })}`, { split: true }) -} - -const prompt = new LocalizedPrompt(setPropertyVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/filters/index.js b/services/bot/src/commands/prompts/filters/index.js deleted file mode 100644 index 0e672a74e..000000000 --- a/services/bot/src/commands/prompts/filters/index.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.selectAction = require('./selectAction.js') -exports.listFilters = require('./listFilters.js') -exports.removedAllFiltersSuccess = require('./removedAllFiltersSuccess.js') diff --git a/services/bot/src/commands/prompts/filters/listFilters.js b/services/bot/src/commands/prompts/filters/listFilters.js deleted file mode 100644 index e660cb7d8..000000000 --- a/services/bot/src/commands/prompts/filters/listFilters.js +++ /dev/null @@ -1,52 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function visual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - - if (!feed.hasFilters() && !feed.hasRFilters()) { - return new MessageVisual(translate('commands.filters.noFilters', { - link: feed.url - })) - } - - let output = translate('commands.filters.listFiltersDescription', { - title: feed.title, - link: feed.url, - channel: `<#${feed.channel}>` - }) - if (feed.hasRFilters()) { - for (const filterCat in feed.rfilters) { - output += `\n\n**${filterCat}**\n\`${feed.rfilters[filterCat]}\`` - } - } else { - for (const filterCat in feed.filters) { - output += `\n\n**${filterCat}**` - const filterContent = feed.filters[filterCat] - filterContent.forEach((filter) => { - output += `\n${filter}` - }) - } - } - - return new MessageVisual(output, { - split: true - }) -} - -const prompt = new LocalizedPrompt(visual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/filters/removedAllFiltersSuccess.js b/services/bot/src/commands/prompts/filters/removedAllFiltersSuccess.js deleted file mode 100644 index bc0a5fbd1..000000000 --- a/services/bot/src/commands/prompts/filters/removedAllFiltersSuccess.js +++ /dev/null @@ -1,26 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function removedAllFiltersSuccess (data) { - const { selectedFeed: feed, profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.filters.removedAllSuccess', { - link: feed.url - })) -} - -const prompt = new LocalizedPrompt(removedAllFiltersSuccess) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/filters/selectAction.js b/services/bot/src/commands/prompts/filters/selectAction.js deleted file mode 100644 index 7a1ed9e26..000000000 --- a/services/bot/src/commands/prompts/filters/selectAction.js +++ /dev/null @@ -1,82 +0,0 @@ -const { Rejection, MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const FailRecord = require('../../../structs/db/FailRecord.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - */ - -/** - * @param {Data} data - */ -function selectCustomizationVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('commands.filters.feedFiltersCustomization') - }) - const menu = new MenuEmbed(embed) - .addOption(translate('commands.filters.optionAddFilters'), translate('commands.filters.optionAddFiltersDescription')) - .addOption(translate('commands.filters.optionRemoveFilters'), translate('commands.filters.optionRemoveFiltersDescription')) - .addOption(translate('commands.filters.optionRemoveAllFilters'), translate('commands.filters.optionRemoveAllFiltersDescription')) - .addOption(translate('commands.filters.optionListFilters'), translate('commands.filters.optionListFiltersDescription')) - .addOption(translate('commands.filters.optionSendArticle'), translate('commands.filters.optionSendArticleDescription')) - - const visual = new MenuVisual(menu) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectCustomizationFn (message, data) { - const { profile, selectedFeed: feed } = data - const { content: selected } = message - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const log = createLogger(message.client.shard.ids[0]) - const translate = Translator.createProfileTranslator(profile) - - // 1 = add (need more input, next node), 2 = remove (need more input, next node) - if (selected === '1' || selected === '2') { - return { - ...data, - selected, - target: feed - } - } else if (selected === '3') { - await feed.removeAllFilters() - log.info({ - guild: message.guild, - user: message.author - }, `Removed all filters from ${feed.url}`) - } else if (selected === '5') { - if (!feed.hasFilters() && !feed.hasRFilters()) { - throw new Rejection(translate('commands.filters.noFiltersTryAgain', { - link: feed.url - })) - } - if (await FailRecord.hasFailed(feed.url)) { - throw new Rejection(translate('commands.filters.connectionFailureLimit', { - prefix - })) - } - } - // 4 = list (next node), 5 = send passing article (next node) - return { - ...data, - selected - } -} - -const prompt = new LocalizedPrompt(selectCustomizationVisual, selectCustomizationFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/filters/sendPassingArticle.js b/services/bot/src/commands/prompts/filters/sendPassingArticle.js deleted file mode 100644 index ba87786fd..000000000 --- a/services/bot/src/commands/prompts/filters/sendPassingArticle.js +++ /dev/null @@ -1,39 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const FailRecord = require('../../../structs/db/FailRecord.js') -const Translator = require('../../../structs/Translator.js') -const FeedFetcher = require('../../../util/FeedFetcher.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - * @property {string} selected - */ - -/** - * @param {Data} data - */ -async function visual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - if (!feed.hasFilters()) { - return new MessageVisual(translate('commands.filters.noFilters', { - link: feed.url - })) - } - if (await FailRecord.hasFailed(feed.url)) { - return new MessageVisual(translate('commands.filters.connectionFailureLimit')) - } - const filters = feed.hasRFilters() ? feed.rfilters : feed.filters - const article = await FeedFetcher.fetchRandomArticle(feed.url, filters) - if (!article) { - return new MessageVisual(translate('commands.filters.noArticlesPassed')) - } - return new MessageVisual('Sending article...') -} - -const prompt = new LocalizedPrompt(visual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/list/index.js b/services/bot/src/commands/prompts/list/index.js deleted file mode 100644 index e385d2e4a..000000000 --- a/services/bot/src/commands/prompts/list/index.js +++ /dev/null @@ -1 +0,0 @@ -exports.listFeeds = require('./listFeeds.js') diff --git a/services/bot/src/commands/prompts/list/listFeeds.js b/services/bot/src/commands/prompts/list/listFeeds.js deleted file mode 100644 index 0f9fb321e..000000000 --- a/services/bot/src/commands/prompts/list/listFeeds.js +++ /dev/null @@ -1,213 +0,0 @@ -const moment = require('moment-timezone') -const Schedule = require('../../../structs/db/Schedule.js') -const FailRecord = require('../../../structs/db/FailRecord.js') -const { MenuEmbed, MenuVisual, MessageVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed.js') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const handlePaginationError = require('../common/utils/handlePaginationError.js') -const ArticleRateLimiter = require('../../../structs/ArticleMessageRateLimiter.js') -const getConfig = require('../../../config.js').get -const Guild = require('../../../structs/Guild.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('discord.js').TextChannel} [channel] - * @property {string} [searchQuery] - * @property {string} guildID - */ - -/** - * @param {import('../../../structs/db/Feed.js')[]} feeds - * @param {string} query - */ -function queryFeeds (feeds, query) { - if (!query) { - return feeds - } - return feeds.filter(feed => { - for (const key in feed) { - const value = feed[key] - if (typeof value === 'string' && value.toLowerCase().includes(query.toLowerCase())) { - return true - } - } - - return false - }) -} - -/** - * Returns the set of channels within an array of feeds - * that is at their limit - * - * @param {import('../../../structs/db/Feed.js')[]} feeds - */ -async function getChannelsAtLimit (feeds) { - // Get all channel ids with no dupes - const channels = feeds.map(f => f.channel).filter((channel, index, array) => array.indexOf(channel) === index) - // Check if each one is at its limit - const promises = channels.map(channelId => ArticleRateLimiter.getLimiter(channelId).isAtDailyLimit()) - const limitResolves = await Promise.all(promises) - const channelsAtLimit = new Set() - // Only insert the channels at limit - limitResolves.forEach((atLimit, index) => { - const channelId = channels[index] - if (atLimit) { - channelsAtLimit.add(channelId) - } - }) - return channelsAtLimit -} - -/** - * @param {Data} data - */ -async function listFeedVisual (data) { - const { feeds, profile, guildID, channel, searchQuery } = data - const guild = new Guild(guildID) - const [supporter, subscription, schedules, supporterGuilds] = await Promise.all([ - guild.getSupporter(guildID), - guild.getSubscription(), - Schedule.getAll(), - Guild.getFastSupporterAndSubscriberGuildIds() - ]) - const unqueriedFeeds = channel ? feeds.filter(f => f.channel === channel.id) : feeds - const targetFeeds = queryFeeds(unqueriedFeeds, searchQuery) - const translate = Translator.createProfileTranslator(profile) - const channelsAtLimit = await getChannelsAtLimit(feeds) - if (feeds.length === 0) { - return new MessageVisual(translate('commands.list.noFeeds')) - } else if (targetFeeds.length === 0) { - return new MessageVisual(translate('commands.list.noFeedsChannel', { - channel: `<#${channel.id}>${channelsAtLimit.has(channel.id) ? ` (${translate('commands.list.channelLimitReached')})` : ''}` - })) - } - - const config = getConfig() - const failRecordsMap = {} - const maxFeedsAllowed = await guild.getMaxFeeds() - - // Generate the info for each feed as an array, and push into another array - const failRecords = await Promise.all(targetFeeds.map(feed => FailRecord.get(feed.url))) - const fetchedSchedules = await Promise.all(targetFeeds.map(feed => feed.determineSchedule(schedules, new Set(supporterGuilds)))) - - const supporterOrSubscriber = supporter || subscription - for (const record of failRecords) { - if (record) { - failRecordsMap[record._id] = record - } - } - let vipDetails = '' - if (supporterOrSubscriber) { - vipDetails += '**Patron Until:** ' - if (supporterOrSubscriber.expireAt) { - const expireAt = moment(supporterOrSubscriber.expireAt) - const daysLeft = Math.round(moment.duration(expireAt.diff(moment())).asDays()) - vipDetails += `${expireAt.format('D MMMM YYYY')} (${daysLeft} days)\n` - } else { - vipDetails += 'Ongoing\n' - } - } else { - vipDetails = '\n' - } - - const desc = maxFeedsAllowed === 0 - ? `${vipDetails}\u200b\n` - : `${vipDetails}**${translate('commands.list.serverLimit')}:** ${targetFeeds.length}/${maxFeedsAllowed} [+](https://www.patreon.com/monitorss)\n\n\u200b` - - const list = new ThemedEmbed() - .setDescription(desc) - - const countString = targetFeeds.length === feeds.length ? targetFeeds.length : `${targetFeeds.length}/${feeds.length} total` - - if (!channel) { - list.setAuthor(translate('commands.list.feedList') + ` (${countString})`) - } else { - list.setAuthor(translate('commands.list.feedListChannel', { - channel: channel.name - }) + ` (${countString})`) - } - - if (supporter) { - list.setFooter(`Patronage backed by ${supporter._id}`) - } - - const menu = new MenuEmbed(list) - .enablePagination(handlePaginationError) - - let someFailed = false - targetFeeds.forEach((feed, index) => { - // URL - const url = feed.url.length > 500 ? translate('commands.list.exceeds500Characters') : feed.url - - // Title - const title = feed.title - - // Channel - const atLimit = channelsAtLimit.has(feed.channel) - const channel = `<#${feed.channel}>${atLimit ? ` (${translate('commands.list.channelLimitReached')})` : ''}` - - // Status - const failRecord = failRecordsMap[feed.url] - let status = '' - if (feed.disabled) { - status = translate('commands.list.statusDisabled', { reason: feed.disabled }) - } else if (failRecord && failRecord.hasFailed()) { - status = translate('commands.list.statusFailed') - someFailed = true - } else if (failRecord && !failRecord.hasFailed()) { - // Determine hours between config spec and now, then calculate health - const hours = (new Date().getTime() - new Date(failRecord.failedAt).getTime()) / 36e5 - const health = FailRecord.cutoff === 0 ? '(100% health)' : `(${100 - Math.ceil(hours / FailRecord.cutoff * 100)}% health)` - status = translate('commands.list.statusOk', { failCount: health }) - } else { - status = translate('commands.list.statusOk', { failCount: '(100% health)' }) - } - - // Title checks - const titleChecks = feed.checkTitles === true - ? translate('commands.list.titleChecksEnabled') - : '' - - // Webhook - const webhook = feed.webhook - ? `${translate('commands.list.webhook')}: ${feed.webhook.id}\n` - : '' - - // Refresh rate - const schedule = fetchedSchedules[index] - let refreshRate = failRecord && failRecord.hasFailed() - ? 'N/A' - : schedule.refreshRateMinutes < 1 - ? `${schedule.refreshRateMinutes * 60} ${translate('commands.list.seconds')}` - : `${schedule.refreshRateMinutes} ${translate('commands.list.minutes')}` - // : translate('commands.list.unknown') - - // Patreon link - if (!supporter) { - refreshRate += ' [-](https://www.patreon.com/monitorss)' - } - - const name = `${title.length > 200 ? title.slice(0, 200) + '[...]' : title}` - const value = `${titleChecks}${status}${translate('commands.list.refreshRate')}: ${refreshRate}\n${translate('generics.channelUpper')}: ${channel}\n${webhook}${translate('commands.list.link')}: ${url}` - const number = feeds.indexOf(feed) + 1 - menu.addOption(name, value, number) - }) - - if (someFailed) { - const failAlert = translate('commands.list.failAlert', { - prefix: profile && profile.prefix ? profile.prefix : config.bot.prefix - }) - menu.embed.setDescription(`${menu.embed.description}${failAlert}\n\u200b`) - } - - return new MenuVisual(menu) -} - -const prompt = new LocalizedPrompt(listFeedVisual) - -exports.visual = listFeedVisual -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention.filters/index.js b/services/bot/src/commands/prompts/mention.filters/index.js deleted file mode 100644 index ca35eaf3e..000000000 --- a/services/bot/src/commands/prompts/mention.filters/index.js +++ /dev/null @@ -1,4 +0,0 @@ -exports.listFilters = require('./listFilters.js') -exports.removeAllFiltersSuccess = require('./removeAllFiltersSuccess.js') -exports.selectAction = require('./selectAction.js') -exports.selectSubscriber = require('./selectSubscriber.js') diff --git a/services/bot/src/commands/prompts/mention.filters/listFilters.js b/services/bot/src/commands/prompts/mention.filters/listFilters.js deleted file mode 100644 index 1c81bc4df..000000000 --- a/services/bot/src/commands/prompts/mention.filters/listFilters.js +++ /dev/null @@ -1,47 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {import('../../../structs/db/Subscriber.js')} selectedSubscriber - */ - -/** - * @param {Data} data - */ -function listFiltersVisual (data) { - const { profile, selectedSubscriber: subscriber, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const subscriberString = subscriber.type === 'role' ? `<@&${subscriber.id}>` : `<@${subscriber.id}>` - if (!subscriber.hasFilters()) { - return new MessageVisual(translate('commands.mention.filters.listNoFilters', { - link: feed.url, - subscriber: subscriberString - })) - } - - let output = translate('commands.mention.filters.listFiltersDescription', { - link: feed.url, - subscriber: subscriberString, - channel: `<#${feed.channel}>` - }) - for (const filterCat in subscriber.filters) { - output += `\n\n**${filterCat}**` - const filterContent = subscriber.filters[filterCat] - filterContent.forEach((filter) => { - output += `\n${filter}` - }) - } - - return new MessageVisual(output, { - split: true - }) -} - -const prompt = new LocalizedPrompt(listFiltersVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention.filters/removeAllFiltersSuccess.js b/services/bot/src/commands/prompts/mention.filters/removeAllFiltersSuccess.js deleted file mode 100644 index c973631f1..000000000 --- a/services/bot/src/commands/prompts/mention.filters/removeAllFiltersSuccess.js +++ /dev/null @@ -1,27 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {import('../../../structs/db/Subscriber.js')} selectedSubscriber - */ - -/** - * @param {Data} data - */ -function removeAllFiltersSuccess (data) { - const { profile, selectedFeed: feed, selectedSubscriber: subscriber } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.mention.filters.removedAllFilters', { - subscriber: subscriber.type === 'role' ? `<@&${subscriber.id}>` : `<@${subscriber.id}>`, - link: feed.url - })) -} - -const prompt = new LocalizedPrompt(removeAllFiltersSuccess) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention.filters/selectAction.js b/services/bot/src/commands/prompts/mention.filters/selectAction.js deleted file mode 100644 index 1d1d881f5..000000000 --- a/services/bot/src/commands/prompts/mention.filters/selectAction.js +++ /dev/null @@ -1,63 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {import('../../../structs/db/Subscriber.js')} selectedSubscriber - */ - -/** - * @param {Data} data - */ -function selectActionVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('commands.mention.filters.title'), - description: translate('commands.mention.filters.description', { - link: feed.url - }) - }) - const menu = new MenuEmbed(embed) - .addOption(translate('commands.mention.filters.optionAddFilter')) - .addOption(translate('commands.mention.filters.optionRemoveFilter')) - .addOption(translate('commands.mention.filters.optionRemoveAllFilters')) - .addOption(translate('commands.mention.filters.optionListFilters')) - const visual = new MenuVisual(menu) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectActionFn (message, data) { - const { content: selected, client, guild, author } = message - const { selectedSubscriber: subscriber } = data - if (selected === '3') { - await subscriber.removeAllFilters() - const log = createLogger(client.shard.ids[0]) - log.info({ - guild, - user: author - }, `Removed all filters from subscriber ${subscriber.id}`) - } - return { - ...data, - selected, - // target is for the common filter prompts - target: subscriber - } -} - -const prompt = new LocalizedPrompt(selectActionVisual, selectActionFn) - -exports.visual = selectActionVisual -exports.fn = selectActionFn -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention.filters/selectSubscriber.js b/services/bot/src/commands/prompts/mention.filters/selectSubscriber.js deleted file mode 100644 index 517e23dc9..000000000 --- a/services/bot/src/commands/prompts/mention.filters/selectSubscriber.js +++ /dev/null @@ -1,60 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Subscriber = require('../../../structs/db/Subscriber.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -async function selectSubscriberVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.mention.promptUserOrRole', { - link: feed.url, - channel: `<#${feed.channel}>` - })) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectSubscriberFn (message, data) { - const { mentions } = message - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const memberMention = mentions.members.first() - const roleMention = mentions.roles.first() - let id - if (memberMention) { - id = memberMention.id - } else if (roleMention) { - id = roleMention.id - } else { - throw new Rejection(translate('commands.mention.invalidRoleOrUser')) - } - const subscriber = await Subscriber.getByQuery({ - id, - feed: feed._id - }) - if (!subscriber) { - throw new Rejection(translate('commands.mention.notFeedSubscriber', { - link: feed.url - })) - } - return { - ...data, - selectedSubscriber: subscriber - } -} - -const prompt = new LocalizedPrompt(selectSubscriberVisual, selectSubscriberFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention/addSubscriber.js b/services/bot/src/commands/prompts/mention/addSubscriber.js deleted file mode 100644 index f88415f8b..000000000 --- a/services/bot/src/commands/prompts/mention/addSubscriber.js +++ /dev/null @@ -1,76 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Subscriber = require('../../../structs/db/Subscriber.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -async function addSubscriberVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.mention.promptUserOrRole', { - link: feed.url, - channel: `<#${feed.channel}>` - })) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function addSubscriberFn (message, data) { - const { guild, author, client, mentions } = message - const { selectedFeed: feed, profile } = data - const translate = Translator.createProfileTranslator(profile) - - const memberMention = mentions.members.first() - const roleMention = mentions.roles.first() - const subscriberData = { - feed: feed._id - } - if (memberMention) { - subscriberData.id = memberMention.id - subscriberData.type = 'user' - } else if (roleMention) { - subscriberData.id = roleMention.id - subscriberData.type = 'role' - } else { - throw new Rejection(translate('commands.mention.invalidRoleOrUser')) - } - const existingSubscriber = await Subscriber.getByQuery({ - id: subscriberData.id, - feed: feed._id - }) - if (existingSubscriber) { - throw new Rejection(translate('commands.mention.addSubscriberExists', { - type: subscriberData.type, - mention: existingSubscriber.type === 'role' ? `<@&${subscriberData.id}>` : `<@${subscriberData.id}>` - })) - } - - const subscriber = new Subscriber(subscriberData) - await subscriber.save() - const log = createLogger(client.shard.ids[0]) - log.info({ - guild, - user: author, - subscriberData - }, `Added subscriber to feed ${feed.url}`) - return { - ...data, - addedSubscriber: subscriber - } -} - -const prompt = new LocalizedPrompt(addSubscriberVisual, addSubscriberFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention/addSubscriberSuccess.js b/services/bot/src/commands/prompts/mention/addSubscriberSuccess.js deleted file mode 100644 index 6722d4382..000000000 --- a/services/bot/src/commands/prompts/mention/addSubscriberSuccess.js +++ /dev/null @@ -1,31 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {import('../../../structs/db/Subscriber.js')} addedSubscriber - */ - -/** - * @param {Data} data - */ -async function addSubscriberSuccessVisual (data) { - const { profile, addedSubscriber, selectedFeed: feed } = data - const config = getConfig() - const translate = Translator.createProfileTranslator(profile) - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - return new MessageVisual(`${translate('commands.mention.addSubscriberSuccess', { - link: feed.url, - mention: addedSubscriber.type === 'role' ? `<@&${addedSubscriber.id}>` : `<@${addedSubscriber.id}>`, - type: addedSubscriber.type === 'role' ? translate('commands.mention.role') : translate('commands.mention.user') - })} ${translate('generics.backupReminder', { prefix })}`) -} - -const prompt = new LocalizedPrompt(addSubscriberSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention/index.js b/services/bot/src/commands/prompts/mention/index.js deleted file mode 100644 index df08faea7..000000000 --- a/services/bot/src/commands/prompts/mention/index.js +++ /dev/null @@ -1,7 +0,0 @@ -exports.addSubscriber = require('./addSubscriber.js') -exports.addSubscriberSuccess = require('./addSubscriberSuccess.js') -exports.listSubscribers = require('./listSubscribers.js') -exports.removeAllSubscribersSuccess = require('./removeAllSubscribersSuccess.js') -exports.removeSubscriber = require('./removeSubscriber.js') -exports.removeSubscriberSuccess = require('./removeSubscriberSuccess.js') -exports.selectAction = require('./selectAction.js') diff --git a/services/bot/src/commands/prompts/mention/listSubscribers.js b/services/bot/src/commands/prompts/mention/listSubscribers.js deleted file mode 100644 index 686d3e41b..000000000 --- a/services/bot/src/commands/prompts/mention/listSubscribers.js +++ /dev/null @@ -1,54 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const splitMentionsByNewlines = require('../common/utils/splitMentionsByNewlines.js') -const splitTextByNewline = require('../common/utils/splitTextByNewline.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -async function listSubscribersVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const subscribers = await feed.getSubscribers() - - if (subscribers.length === 0) { - return new MessageVisual(translate('commands.mention.listSubscribersNone', { - link: feed.url, - channel: `<#${feed.channel}>` - })) - } - - let output = `${translate('commands.mention.listSubscribersDescription', { - link: feed.url, - channel: `<#${feed.channel}>` - })}\n` - const userSubscribers = subscribers.filter(s => s.type === 'user') - const roleSubscribers = subscribers.filter(s => s.type === 'role') - - const userSubscribersStrings = userSubscribers - .map(s => `<@${s.id}>`) - const roleSubscribersStrings = roleSubscribers - .map(s => `<@&${s.id}>`) - - if (userSubscribersStrings.length > 0) { - output += `\n**Users**\n${splitMentionsByNewlines(userSubscribersStrings)}` - } - if (roleSubscribersStrings.length > 0) { - output += `\n**Roles**\n${splitMentionsByNewlines(roleSubscribersStrings)}` - } - - const texts = splitTextByNewline(output) - return texts.map(text => new MessageVisual(text)) -} - -const prompt = new LocalizedPrompt(listSubscribersVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention/removeAllSubscribersSuccess.js b/services/bot/src/commands/prompts/mention/removeAllSubscribersSuccess.js deleted file mode 100644 index 635f5c448..000000000 --- a/services/bot/src/commands/prompts/mention/removeAllSubscribersSuccess.js +++ /dev/null @@ -1,25 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -async function removeAllSubscribersSuccessVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.mention.removeAllSubscribersSuccess', { - link: feed.url - })) -} - -const prompt = new LocalizedPrompt(removeAllSubscribersSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention/removeSubscriber.js b/services/bot/src/commands/prompts/mention/removeSubscriber.js deleted file mode 100644 index de86a44f6..000000000 --- a/services/bot/src/commands/prompts/mention/removeSubscriber.js +++ /dev/null @@ -1,68 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Subscriber = require('../../../structs/db/Subscriber.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -async function removeSubscriberVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.mention.promptUserOrRole', { - link: feed.url, - channel: `<#${feed.channel}>` - })) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function removeSubscriberFn (message, data) { - const { guild, author, client, mentions } = message - const { selectedFeed: feed, profile } = data - const translate = Translator.createProfileTranslator(profile) - const memberMention = mentions.members.first() - const roleMention = mentions.roles.first() - let id - if (memberMention) { - id = memberMention.id - } else if (roleMention) { - id = roleMention.id - } else { - throw new Rejection(translate('commands.mention.invalidRoleOrUser')) - } - const foundSubscriber = await Subscriber.getByQuery({ - id, - feed: feed._id - }) - if (!foundSubscriber) { - throw new Rejection(translate('commands.mention.notFeedSubscriber', { - link: feed.url - })) - } - - await foundSubscriber.delete() - const log = createLogger(client.shard.ids[0]) - log.info({ - guild, - user: author - }, `Deleted subscriber ${id} from feed ${feed.url}`) - return { - ...data, - removedSubscriber: foundSubscriber - } -} - -const prompt = new LocalizedPrompt(removeSubscriberVisual, removeSubscriberFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention/removeSubscriberSuccess.js b/services/bot/src/commands/prompts/mention/removeSubscriberSuccess.js deleted file mode 100644 index 68ba1f18d..000000000 --- a/services/bot/src/commands/prompts/mention/removeSubscriberSuccess.js +++ /dev/null @@ -1,30 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {import('../../../structs/db/Subscriber.js')} removedSubscriber - */ - -/** - * @param {Data} data - */ -async function removeSubscriberSuccessVisual (data) { - const { profile, removedSubscriber, selectedFeed: feed } = data - const config = getConfig() - const translate = Translator.createProfileTranslator(profile) - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - return new MessageVisual(`${translate('commands.mention.removeSubscriberSuccess', { - link: feed.url, - mention: removedSubscriber.type === 'role' ? `<@&${removedSubscriber.id}>` : `<@${removedSubscriber.id}>` - })} ${translate('generics.backupReminder', { prefix })}`) -} - -const prompt = new LocalizedPrompt(removeSubscriberSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/mention/selectAction.js b/services/bot/src/commands/prompts/mention/selectAction.js deleted file mode 100644 index e71e679bb..000000000 --- a/services/bot/src/commands/prompts/mention/selectAction.js +++ /dev/null @@ -1,55 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Subscriber = require('../../../structs/db/Subscriber.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -function selectActionVisual (data) { - const { profile } = data - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('commands.mention.subscriberOptions'), - description: translate('commands.mention.description', { prefix }) - }) - const menu = new MenuEmbed(embed) - .addOption(translate('commands.mention.optionAddSubscriber'), translate('commands.mention.optionAddSubscriberDescription')) - .addOption(translate('commands.mention.optionRemoveSubscriber'), translate('commands.mention.optionRemoveSubscriberDescription')) - .addOption(translate('commands.mention.optionRemoveAllSubscribers'), translate('commands.mention.optionRemoveAllSubscribersDescription')) - .addOption(translate('commands.mention.optionListSubscribers'), translate('commands.mention.optionListSubscribersDescription')) - const visual = new MenuVisual(menu) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectActionFn (message, data) { - const { content: selected } = message - const { selectedFeed: feed } = data - if (selected === '3') { - const subscribers = await Subscriber.getManyBy('feed', feed._id) - await Promise.all(subscribers.map(s => s.delete())) - } - return { - ...data, - selected - } -} - -const prompt = new LocalizedPrompt(selectActionVisual, selectActionFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/move/index.js b/services/bot/src/commands/prompts/move/index.js deleted file mode 100644 index 4cf061cd7..000000000 --- a/services/bot/src/commands/prompts/move/index.js +++ /dev/null @@ -1,2 +0,0 @@ -exports.selectDestinationChannel = require('./selectDestinationChannel.js') -exports.success = require('./success.js') diff --git a/services/bot/src/commands/prompts/move/selectDestinationChannel.js b/services/bot/src/commands/prompts/move/selectDestinationChannel.js deleted file mode 100644 index f6dd0b6d8..000000000 --- a/services/bot/src/commands/prompts/move/selectDestinationChannel.js +++ /dev/null @@ -1,108 +0,0 @@ -const FLAGS = require('discord.js').Permissions.FLAGS -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') -const MIN_PERMISSION_BOT = [ - FLAGS.VIEW_CHANNEL, - FLAGS.SEND_MESSAGES -] -const MIN_PERMISSION_USER = [ - FLAGS.VIEW_CHANNEL, - FLAGS.SEND_MESSAGES, - FLAGS.MANAGE_CHANNELS -] - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - * @property {import('../../../structs/db/Feed.js')[]} selectedFeeds - */ - -/** - * @param {Data} data - */ -function selectDestinationChannelVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.move.prompt')) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectDestinationChannelFn (message, data) { - const { profile, selectedFeeds, feeds } = data - const { content, member, guild, author } = message - const translate = Translator.createProfileTranslator(profile) - const selected = content === 'this' ? message.channel : message.mentions.channels.first() - if (!selected) { - throw new Rejection(translate('commands.move.invalidChannel')) - } - const me = guild.me - let errors = '' - if (!me.permissionsIn(selected).has(MIN_PERMISSION_BOT)) { - errors += translate('commands.move.meMissingPermission', { id: selected.id }) - } if (!member.permissionsIn(selected).has(MIN_PERMISSION_USER)) { - errors += translate('commands.move.youMissingPermission', { id: selected.id }) - } - - let feedSpecificErrors = '' - for (let i = 0; i < selectedFeeds.length; ++i) { - const selectedFeed = selectedFeeds[i] - let curErrors = '' - const hasEmbed = selectedFeed.embeds.length > 0 - const sourceChannel = guild.channels.cache.get(selectedFeed.channel) - - if (sourceChannel && selected.id === sourceChannel.id) { - curErrors += translate('commands.move.alreadyInChannel') - } else { - if (sourceChannel && !member.permissionsIn(sourceChannel).has(MIN_PERMISSION_USER)) { - errors += translate('commands.move.meMissingPermission', { id: sourceChannel.id }) - } - if (hasEmbed && !me.permissionsIn(selected).has(FLAGS.EMBED_LINKS)) { - curErrors += translate('commands.move.meMissingEmbedLinks', { id: selected.id }) - } - for (const feed of feeds) { - if (feed.channel === selected.id && feed.url === selectedFeed.url && feed._id !== selectedFeed._id) { - errors += translate('commands.move.linkAlreadyExists') - } - } - } - if (curErrors) { - feedSpecificErrors += `\n__Errors for <${selectedFeed.url}>:__${curErrors}${i === selectedFeeds.length - 1 ? '' : '\n'}` - } - } - - if (feedSpecificErrors && errors) { - errors += '\n' + feedSpecificErrors - } else if (feedSpecificErrors) { - errors += feedSpecificErrors - } - - if (errors) { - throw new Rejection(translate('commands.move.moveFailed', { errors })) - } - - const promises = [] - for (const feed of selectedFeeds) { - feed.channel = selected.id - promises.push(feed.save()) - } - await Promise.all(promises) - const log = createLogger() - log.info({ - guild, - user: author - }, `Channel for feeds ${feeds.map(f => f.url).join(',')} moved to ${selected.id} (${selected.name})`) - return { - ...data, - destinationChannel: selected - } -} - -const prompt = new LocalizedPrompt(selectDestinationChannelVisual, selectDestinationChannelFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/move/success.js b/services/bot/src/commands/prompts/move/success.js deleted file mode 100644 index 7ad16a884..000000000 --- a/services/bot/src/commands/prompts/move/success.js +++ /dev/null @@ -1,34 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - * @property {import('../../../structs/db/Feed.js')[]} selectedFeeds - * @property {import('discord.js').TextChannel} destinationChannel - */ - -/** - * @param {Data} data - */ -function successVisual (data) { - const { profile, selectedFeeds, destinationChannel } = data - const translate = Translator.createProfileTranslator(profile) - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const summary = [] - for (const feed of selectedFeeds) { - summary.push(`<${feed.url}>`) - } - return new MessageVisual(`${translate('commands.move.moveSuccess', { - summary: summary.join('\n'), - id: destinationChannel.id - })} ${translate('generics.backupReminder', { prefix })}`) -} - -const prompt = new LocalizedPrompt(successVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/options/index.js b/services/bot/src/commands/prompts/options/index.js deleted file mode 100644 index 39de44d22..000000000 --- a/services/bot/src/commands/prompts/options/index.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.selectFeedWithOption = require('./selectFeedWithOption.js') -exports.selectOption = require('./selectOption.js') -exports.success = require('./success.js') diff --git a/services/bot/src/commands/prompts/options/selectFeedWithOption.js b/services/bot/src/commands/prompts/options/selectFeedWithOption.js deleted file mode 100644 index 794be7a63..000000000 --- a/services/bot/src/commands/prompts/options/selectFeedWithOption.js +++ /dev/null @@ -1,90 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const handlePaginationError = require('../common/utils/handlePaginationError.js') -const getConfig = require('../../../config.js').get -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {string} optionKey - */ - -const boolToText = bool => bool ? 'Enabled' : 'Disabled' - -/** - * @param {Data} data - */ -function selectFeedWithOptionVisual (data) { - const { profile, optionKey, feeds } = data - const config = getConfig() - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('structs.FeedSelector.feedSelectionMenu'), - description: `${translate('structs.FeedSelector.prompt')} ${translate('structs.FeedSelector.exitToCancel')} ` - }) - const menu = new MenuEmbed(embed) - .enablePagination(handlePaginationError) - - for (const feed of feeds) { - const title = feed.title.length > 200 ? feed.title.slice(0, 200) + '...' : feed.title - const url = feed.url.length > 500 ? translate('commands.list.exceeds500Characters') : feed.url - let optionName - if (optionKey === 'imgPreviews') { - optionName = translate('commands.options.imagePreviews') - } else if (optionKey === 'imgLinksExistence') { - optionName = translate('commands.options.imageLinksExistence') - } else if (optionKey === 'checkDates') { - optionName = translate('commands.options.dateChecks') - } else if (optionKey === 'formatTables') { - optionName = translate('commands.options.tableFormatting') - } else if (optionKey === 'directSubscribers') { - optionName = translate('commands.options.directSubscribers') - } - let settingState - if (typeof feed[optionKey] === 'boolean') { - settingState = boolToText(feed[optionKey]) - } else { - settingState = boolToText(config.feeds[optionKey]) + ' (default)' - } - menu.addOption(title, `Channel: <#${feed.channel}>\nURL: ${url}\n${optionName}: ${settingState}`) - } - const visual = new MenuVisual(menu) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectFeedWithOptionFn (message, data) { - const { feeds, optionKey } = data - const config = getConfig() - const log = createLogger(message.client.shard.ids[0]) - const feed = feeds[Number(message.content) - 1] - - const globalSetting = config.feeds[optionKey] - const feedSetting = feed[optionKey] - feed[optionKey] = typeof feedSetting === 'boolean' ? !feedSetting : !globalSetting - - if (feed[optionKey] === globalSetting) { - // undefined marks it for deletion - feed[optionKey] = undefined - } - - await feed.save() - log.info({ - guild: message.guild - }, `${optionKey} set to ${feed[optionKey]} for feed ${feed.url}. ${feed[optionKey] === undefined ? 'Now following global settings.' : ''}`) - return { - ...data, - selectedFeed: feed - } -} - -const prompt = new LocalizedPrompt(selectFeedWithOptionVisual, selectFeedWithOptionFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/options/selectOption.js b/services/bot/src/commands/prompts/options/selectOption.js deleted file mode 100644 index 4141afa67..000000000 --- a/services/bot/src/commands/prompts/options/selectOption.js +++ /dev/null @@ -1,72 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - */ - -/** - * @param {Data} data - */ -function selectOptionVisual (data) { - const { locale } = data.profile || {} - const config = getConfig() - const translate = Translator.createLocaleTranslator(locale) - const embed = new ThemedEmbed({ - title: translate('commands.options.miscFeedOptions'), - description: translate('commands.options.selectOption') - }) - const ENABLED_TRANSLATED = translate('generics.enabledLower') - const DISABLED_TRANSLATED = translate('generics.disabledLower') - const menu = new MenuEmbed(embed) - .addOption(translate('commands.options.imagePreviewsToggle'), `${translate('generics.defaultSetting', { - value: config.feeds.imgPreviews === false ? DISABLED_TRANSLATED : ENABLED_TRANSLATED - })} ${translate('commands.options.imagePreviewsDescription')}`) - .addOption(translate('commands.options.imageLinksExistenceToggle'), `${translate('generics.defaultSetting', { - value: config.feeds.imgLinksExistence === false ? DISABLED_TRANSLATED : ENABLED_TRANSLATED - })} ${translate('commands.options.imageLinksExistenceDescription')}`) - .addOption(translate('commands.options.dateChecksToggle'), `${translate('generics.defaultSetting', { - value: config.feeds.checkDates === false ? DISABLED_TRANSLATED : ENABLED_TRANSLATED - })} ${translate('commands.options.dateChecksDescription', { cycleMaxAge: config.feeds.cycleMaxAge })}`) - .addOption(translate('commands.options.tableFormattingToggle'), `${translate('generics.defaultSetting', { - value: config.feeds.formatTables === false ? DISABLED_TRANSLATED : ENABLED_TRANSLATED - })} ${translate('commands.options.tableFormattingDescription')}`) - .addOption(translate('commands.options.directSubscribersToggle'), `${translate('generics.defaultSetting', { - value: config.feeds.directSubscribers === false ? DISABLED_TRANSLATED : ENABLED_TRANSLATED - })} ${translate('commands.options.directSubscribersDescription')}`) - const visual = new MenuVisual(menu) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectOptionFn (message, data) { - const { content } = message - let optionKey - if (content === '1') { - optionKey = 'imgPreviews' - } else if (content === '2') { - optionKey = 'imgLinksExistence' - } else if (content === '3') { - optionKey = 'checkDates' - } else if (content === '4') { - optionKey = 'formatTables' - } else if (content === '5') { - optionKey = 'directSubscribers' - } - return { - ...data, - optionKey - } -} - -const prompt = new LocalizedPrompt(selectOptionVisual, selectOptionFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/options/success.js b/services/bot/src/commands/prompts/options/success.js deleted file mode 100644 index 13ad1f03f..000000000 --- a/services/bot/src/commands/prompts/options/success.js +++ /dev/null @@ -1,46 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {string} optionKey - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -function successVisual (data) { - const { profile, optionKey, selectedFeed: feed } = data - const config = getConfig() - const translate = Translator.createProfileTranslator(profile) - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - - let optionName - if (optionKey === 'imgPreviews') { - optionName = translate('commands.options.imagePreviews') - } else if (optionKey === 'imgLinksExistence') { - optionName = translate('commands.options.imageLinksExistence') - } else if (optionKey === 'checkDates') { - optionName = translate('commands.options.dateChecks') - } else if (optionKey === 'formatTables') { - optionName = translate('commands.options.tableFormatting') - } else if (optionKey === 'directSubscribers') { - optionName = translate('commands.options.directSubscribers') - } - const finalState = typeof feed[optionKey] === 'boolean' ? feed[optionKey] : config.feeds[optionKey] - return new MessageVisual(`${translate('commands.options.settingChanged', { - propName: optionName, - isDefault: typeof feed[optionKey] !== 'boolean' ? ` (${translate('commands.options.defaultSetting')})` : '', - link: feed.url, - finalSetting: finalState ? translate('generics.enabledLower') : translate('generics.disabledLower') - })} ${translate('generics.backupReminder', { prefix })}`) -} - -const prompt = new LocalizedPrompt(successVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/remove/index.js b/services/bot/src/commands/prompts/remove/index.js deleted file mode 100644 index 628e72e64..000000000 --- a/services/bot/src/commands/prompts/remove/index.js +++ /dev/null @@ -1,2 +0,0 @@ -exports.removeSuccess = require('./removeSuccess.js') -exports.selectRemoveFeeds = require('./selectRemoveFeeds.js') diff --git a/services/bot/src/commands/prompts/remove/removeSuccess.js b/services/bot/src/commands/prompts/remove/removeSuccess.js deleted file mode 100644 index 4606118a5..000000000 --- a/services/bot/src/commands/prompts/remove/removeSuccess.js +++ /dev/null @@ -1,31 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - * @property {import('../../../structs/db/Feed.js')[]} selectedFeeds - */ - -/** - * @param {Data} data - */ -async function removeSuccessVisual (data) { - const { profile, selectedFeeds } = data - const config = getConfig() - const translate = Translator.createProfileTranslator(profile) - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const removed = `${translate('commands.remove.success')}\n\n**${selectedFeeds.map(f => `<${f.url}>`).join('\n')}**` - return new MessageVisual(`${removed}\n\n${translate('generics.backupReminder', { - prefix - })}`, { - split: true - }) -} - -const prompt = new LocalizedPrompt(removeSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/remove/selectRemoveFeeds.js b/services/bot/src/commands/prompts/remove/selectRemoveFeeds.js deleted file mode 100644 index faa94cc73..000000000 --- a/services/bot/src/commands/prompts/remove/selectRemoveFeeds.js +++ /dev/null @@ -1,38 +0,0 @@ -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const selectMultipleFeeds = require('../common/selectMultipleFeeds.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - */ - -/** - * @param {Data} data - */ -async function selectRemoveFeedsVisual (data) { - return selectMultipleFeeds.visual(data) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectRemoveFeedsFn (message, data) { - const newData = await selectMultipleFeeds.fn(message, data) - const { selectedFeeds } = newData - const { author, client } = message - const log = createLogger(client.shard.ids[0]) - await Promise.all(selectedFeeds.map(f => f.delete())) - log.info({ - guild: message.guild, - user: author, - selectedFeeds - }, `Removed ${selectedFeeds.length} feeds`) - return newData -} - -const prompt = new LocalizedPrompt(selectRemoveFeedsVisual, selectRemoveFeedsFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/runner/run.js b/services/bot/src/commands/prompts/runner/run.js deleted file mode 100644 index 4e5d942ad..000000000 --- a/services/bot/src/commands/prompts/runner/run.js +++ /dev/null @@ -1,53 +0,0 @@ -const { PromptNode, DiscordPromptRunner, DiscordChannel, Errors } = require('discord.js-prompts') -const Profile = require('../../../structs/db/Profile.js') -const Feed = require('../../../structs/db/Feed.js') -const Translator = require('../../../structs/Translator.js') -const noFeeds = require('../common/noFeedsFound.js') -const deleteMessages = require('./util/deleteMessages.js') - -async function getInitialData (guild) { - const [profile, feeds] = await Promise.all([ - Profile.get(guild.id), - Feed.getManyBy('guild', guild.id) - ]) - return { - profile, - feeds - } -} - -/** - * @param {import('discord-prompts').DiscordPrompt} rootNode - * @param {import('discord.js').Message} message - * @param {Object} inputData - */ -async function runWithFeedsProfile (rootNode, message, inputData = {}) { - const { author, channel, guild } = message - const initialData = { - ...await getInitialData(guild), - ...inputData - } - const noFeedsNode = new PromptNode(noFeeds.prompt, data => data.feeds.length === 0) - const runner = new DiscordPromptRunner(author, initialData) - const channelStore = new DiscordChannel(channel) - let finalData = {} - try { - finalData = await runner.runArray([ - noFeedsNode, - rootNode - ], channelStore) - } catch (err) { - const translate = Translator.createProfileTranslator(initialData.profile) - if (err instanceof Errors.UserInactivityError) { - await message.channel.send(translate('structs.MenuUtils.closedInactivity')) - } else if (err instanceof Errors.UserVoluntaryExitError) { - await message.channel.send(translate('structs.MenuUtils.closed')) - } else { - throw err - } - } - await deleteMessages(channel, channelStore.messages) - return finalData -} - -module.exports = runWithFeedsProfile diff --git a/services/bot/src/commands/prompts/runner/util/deleteMessages.js b/services/bot/src/commands/prompts/runner/util/deleteMessages.js deleted file mode 100644 index b94c3456c..000000000 --- a/services/bot/src/commands/prompts/runner/util/deleteMessages.js +++ /dev/null @@ -1,19 +0,0 @@ -const FLAGS = require('discord.js').Permissions.FLAGS -const getConfig = require('../../../../config.js').get - -/** - * @param {import('discord.js').TextChannel} channel - * @param {import('discord.js').Message[]} messages - */ -async function deleteMessages (channel, messages) { - const { guild } = channel - const permissions = channel.permissionsFor(guild.me) - const config = getConfig() - if (!config.bot.deleteMenus || !permissions.has(FLAGS.MANAGE_MESSAGES)) { - return - } - const inBetween = messages.slice(0, messages.length - 1) - await Promise.all(inBetween.map(m => m.delete())) -} - -module.exports = deleteMessages diff --git a/services/bot/src/commands/prompts/split/disabledSuccess.js b/services/bot/src/commands/prompts/split/disabledSuccess.js deleted file mode 100644 index 01eeed7b7..000000000 --- a/services/bot/src/commands/prompts/split/disabledSuccess.js +++ /dev/null @@ -1,26 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function disabledSuccessVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.split.disabledSuccess', { - link: feed.url - })) -} - -const prompt = new LocalizedPrompt(disabledSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/split/enable.js b/services/bot/src/commands/prompts/split/enable.js deleted file mode 100644 index 6449f9c2e..000000000 --- a/services/bot/src/commands/prompts/split/enable.js +++ /dev/null @@ -1,54 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -function enableVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed() - .setTitle(translate('commands.split.messageSplittingOptions')) - .setDescription(translate('commands.split.description', { - title: feed.title, - link: feed.url, - currently: translate('generics.disabledLower') - })) - const menu = new MenuEmbed(embed) - .addOption(translate('commands.split.optionEnable'), translate('commands.split.optionEnableDescription')) - - return new MenuVisual(menu) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function enableFn (message, data) { - const { client, guild, author } = message - const { selectedFeed: feed } = data - feed.split = { - enabled: true - } - await feed.save() - const log = createLogger(client.shard.ids[0]) - log.info({ - guild, - user: author - }, `Enabled message splitting for ${feed.url}`) - return data -} - -const prompt = new LocalizedPrompt(enableVisual, enableFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/split/index.js b/services/bot/src/commands/prompts/split/index.js deleted file mode 100644 index 95c2f04bb..000000000 --- a/services/bot/src/commands/prompts/split/index.js +++ /dev/null @@ -1,11 +0,0 @@ -exports.disabledSuccess = require('./disabledSuccess.js') -exports.enable = require('./enable.js') -exports.inputAppendCharacter = require('./inputAppendCharacter.js') -exports.inputAppendCharacterSuccess = require('./inputAppendCharacterSuccess.js') -exports.inputMaxLength = require('./inputMaxLength .js') -exports.inputMaxLengthSuccess = require('./inputMaxLengthSuccess.js') -exports.inputPrependCharacter = require('./inputPrependCharacter.js') -exports.inputPrependCharacterSuccess = require('./inputPrependCharacterSuccess.js') -exports.inputSplitCharacter = require('./inputSplitCharacter.js') -exports.inputSplitCharacterSuccess = require('./inputSplitCharacterSuccess.js') -exports.selectSplitOptions = require('./selectSplitOptions.js') diff --git a/services/bot/src/commands/prompts/split/inputAppendCharacter.js b/services/bot/src/commands/prompts/split/inputAppendCharacter.js deleted file mode 100644 index 1ea6c7dbc..000000000 --- a/services/bot/src/commands/prompts/split/inputAppendCharacter.js +++ /dev/null @@ -1,52 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function inputAppendCharacterVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.split.promptAppendChar')) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function inputAppendCharacterFn (message, data) { - const { client, guild, author, content } = message - const { selectedFeed: feed } = data - const log = createLogger(client.shard.ids[0]) - - if (content === 'reset') { - delete feed.split.append - await feed.save() - log.info({ - guild, - user: author - }, `Message splitting append character for ${feed.url} resetting`) - } else { - feed.split.append = content - await feed.save() - log.info({ - guild, - user: author - }, `Message splitting append character for ${feed.url} setting to ${content}`) - } - return data -} - -const prompt = new LocalizedPrompt(inputAppendCharacterVisual, inputAppendCharacterFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/split/inputAppendCharacterSuccess.js b/services/bot/src/commands/prompts/split/inputAppendCharacterSuccess.js deleted file mode 100644 index ce6785f03..000000000 --- a/services/bot/src/commands/prompts/split/inputAppendCharacterSuccess.js +++ /dev/null @@ -1,39 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -function escapeBackticks (str) { - return str.replace('`', '​`') // Replace backticks with zero-width space and backtick to escape -} - -/** - * @param {Data} data - */ -function inputAppendCharacterVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - if (!feed.split.append) { - return new MessageVisual(`${translate('commands.split.resetAppendChar', { - link: feed.url - })} ${translate('generics.backupReminder', { prefix })}`) - } else { - return new MessageVisual(`${translate('commands.split.setAppendChar', { - link: feed.url, - content: escapeBackticks(feed.split.append) - })} ${translate('generics.backupReminder', { prefix })}`) - } -} - -const prompt = new LocalizedPrompt(inputAppendCharacterVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/split/inputMaxLength .js b/services/bot/src/commands/prompts/split/inputMaxLength .js deleted file mode 100644 index af5c1f12f..000000000 --- a/services/bot/src/commands/prompts/split/inputMaxLength .js +++ /dev/null @@ -1,58 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function inputMaxLengthVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.split.promptMaxLen')) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function inputMaxLengthFn (message, data) { - const { client, guild, author, content } = message - const { selectedFeed: feed, profile } = data - const translate = Translator.createProfileTranslator(profile) - const log = createLogger(client.shard.ids[0]) - - const num = Number(content) - if (isNaN(num) || num < 500 || num > 1950) { - throw new Rejection(translate('commands.split.setInvalidMaxLen')) - } - - if (content === 'reset') { - delete feed.split.maxLength - await feed.save() - log.info({ - guild, - user: author - }, `Message splitting maxLength for ${feed.url} resetting`) - } else { - feed.split.maxLength = num - await feed.save() - log.info({ - guild, - user: author - }, `Message splitting maxLength for ${feed.url} setting to ${content}`) - } - return data -} - -const prompt = new LocalizedPrompt(inputMaxLengthVisual, inputMaxLengthFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/split/inputMaxLengthSuccess.js b/services/bot/src/commands/prompts/split/inputMaxLengthSuccess.js deleted file mode 100644 index 329a7ea2d..000000000 --- a/services/bot/src/commands/prompts/split/inputMaxLengthSuccess.js +++ /dev/null @@ -1,35 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -function inputAppendCharacterVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - if (!feed.split.maxLength) { - return new MessageVisual(`${translate('commands.split.resetMaxLen', { - link: feed.url - })} ${translate('generics.backupReminder', { prefix })}`) - } else { - return new MessageVisual(`${translate('commands.split.setMaxLen', { - link: feed.url, - num: feed.split.maxLength - })} ${translate('generics.backupReminder', { prefix })}`) - } -} - -const prompt = new LocalizedPrompt(inputAppendCharacterVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/split/inputPrependCharacter.js b/services/bot/src/commands/prompts/split/inputPrependCharacter.js deleted file mode 100644 index 2b76fac79..000000000 --- a/services/bot/src/commands/prompts/split/inputPrependCharacter.js +++ /dev/null @@ -1,52 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function inputPrependCharacterVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.split.promptPrependChar')) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function inputPrependCharacterFn (message, data) { - const { client, guild, author, content } = message - const { selectedFeed: feed } = data - const log = createLogger(client.shard.ids[0]) - - if (content === 'reset') { - delete feed.split.prepend - await feed.save() - log.info({ - guild, - user: author - }, `Message splitting prepend character for ${feed.url} resetting`) - } else { - feed.split.prepend = content - await feed.save() - log.info({ - guild, - user: author - }, `Message splitting prepend character for ${feed.url} setting to ${content}`) - } - return data -} - -const prompt = new LocalizedPrompt(inputPrependCharacterVisual, inputPrependCharacterFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/split/inputPrependCharacterSuccess.js b/services/bot/src/commands/prompts/split/inputPrependCharacterSuccess.js deleted file mode 100644 index 4991ec391..000000000 --- a/services/bot/src/commands/prompts/split/inputPrependCharacterSuccess.js +++ /dev/null @@ -1,39 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -function escapeBackticks (str) { - return str.replace('`', '​`') // Replace backticks with zero-width space and backtick to escape -} - -/** - * @param {Data} data - */ -function inputPrependCharacterVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - if (!feed.split.prepend) { - return new MessageVisual(`${translate('commands.split.resetPrependChar', { - link: feed.url - })} ${translate('generics.backupReminder', { prefix })}`) - } else { - return new MessageVisual(`${translate('commands.split.setPrependChar', { - link: feed.url, - content: escapeBackticks(feed.split.prepend) - })} ${translate('generics.backupReminder', { prefix })}`) - } -} - -const prompt = new LocalizedPrompt(inputPrependCharacterVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/split/inputSplitCharacter.js b/services/bot/src/commands/prompts/split/inputSplitCharacter.js deleted file mode 100644 index c33383768..000000000 --- a/services/bot/src/commands/prompts/split/inputSplitCharacter.js +++ /dev/null @@ -1,57 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {string} selected - */ - -/** - * @param {Data} data - */ -function inputSplitCharacterVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.split.promptSplitChar')) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function inputSplitCharacterFn (message, data) { - const { client, guild, author, content } = message - const { selectedFeed: feed, profile } = data - const translate = Translator.createProfileTranslator(profile) - const log = createLogger(client.shard.ids[0]) - - if (content === '\\n' && feed.split.char === undefined) { - throw new Rejection(translate('commands.split.setSplitCharDefault')) - } - - if (content === 'reset') { - delete feed.split.char - await feed.save() - log.info({ - guild, - user: author - }, `Message splitting split character for ${feed.url} resetting`) - } else { - feed.split.char = content - await feed.save() - log.info({ - guild, - user: author - }, `Message splitting split character for ${feed.url} setting to ${content}`) - } - return data -} - -const prompt = new LocalizedPrompt(inputSplitCharacterVisual, inputSplitCharacterFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/split/inputSplitCharacterSuccess.js b/services/bot/src/commands/prompts/split/inputSplitCharacterSuccess.js deleted file mode 100644 index fac282323..000000000 --- a/services/bot/src/commands/prompts/split/inputSplitCharacterSuccess.js +++ /dev/null @@ -1,35 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -function inputSplitCharacterVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - if (!feed.split.char) { - return new MessageVisual(`${translate('commands.split.resetSplitChar', { - link: feed.url - })} ${translate('generics.backupReminder', { prefix })}`) - } else { - return new MessageVisual(`${translate('commands.split.setSplitChar', { - link: feed.url, - content: feed.split.char - })} ${translate('generics.backupReminder', { prefix })}`) - } -} - -const prompt = new LocalizedPrompt(inputSplitCharacterVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/split/selectSplitOptions.js b/services/bot/src/commands/prompts/split/selectSplitOptions.js deleted file mode 100644 index 45d9093cc..000000000 --- a/services/bot/src/commands/prompts/split/selectSplitOptions.js +++ /dev/null @@ -1,87 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -// Replace backticks with zero-width space and backtick to escape -const escapeBackticks = (str) => str.replace('`', '​`') -const defSplitCharStr = translate => translate('commands.split.defaultIsValue', { value: `\`\\n\` (${translate('commands.split.newLine')})` }) -const defPrependCharStr = translate => translate('commands.split.defaultIsNothing') -const defAppendCharStr = defPrependCharStr -const defMaxLenStr = translate => translate('commands.split.defaultIsValue', { value: '1950' }) - -/** - * @param {Data} data - */ -function selectSplitOptionsVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed() - .setTitle(translate('commands.split.messageSplittingOptions')) - .setDescription(translate('commands.split.description', { - title: feed.title, - link: feed.url, - currently: translate('generics.enabledLower') - })) - - const splitOptions = feed.split - const currentSplitChar = splitOptions && splitOptions.char ? splitOptions.char : '' - const currentSplitPrepend = splitOptions && splitOptions.prepend ? splitOptions.prepend : '' - const currentSplitAppend = splitOptions && splitOptions.append ? splitOptions.append : '' - const currentSplitLength = splitOptions && splitOptions.maxLength ? splitOptions.maxLength : '' - - const curSplitCharStr = translate('commands.split.currentlySetTo', { - value: escapeBackticks(currentSplitChar) - }) - const curPrependCharStr = translate('commands.split.currentlySetTo', { - value: escapeBackticks(currentSplitPrepend) - }) - const curAppendCharStr = translate('commands.split.currentlySetTo', { - value: escapeBackticks(currentSplitAppend) - }) - const curMaxLenStr = translate('commands.split.currentlySetTo', { - value: currentSplitLength - }) - const menu = new MenuEmbed(embed) - .addOption(translate('commands.split.optionSetSplitChar'), `${translate('commands.split.optionSetSplitCharDescription')}${currentSplitChar ? ` ${curSplitCharStr}` : ''} ${defSplitCharStr(translate)}`) - .addOption(translate('commands.split.optionSetPrependChar'), `${translate('commands.split.optionSetPrependCharDescription')}${currentSplitPrepend ? ` ${curPrependCharStr}` : ''} ${defPrependCharStr(translate)}`) - .addOption(translate('commands.split.optionSetAppendChar'), `${translate('commands.split.optionSetAppendCharDescription')}${currentSplitAppend ? ` ${curAppendCharStr}` : ''} ${defAppendCharStr(translate)}`) - .addOption(translate('commands.split.optionSetMaxLength'), `${translate('commands.split.optionSetMaxLengthDescription')}${currentSplitLength ? ` ${curMaxLenStr}` : ''} ${defMaxLenStr(translate)}`) - .addOption(translate('commands.split.optionDisable'), '\u200b') - - return new MenuVisual(menu) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectSplitOptionsFn (message, data) { - const { client, guild, author, content: selected } = message - const { selectedFeed: feed } = data - const log = createLogger(client.shard.ids[0]) - if (selected === '5') { - feed.split = undefined - await feed.save() - log.info({ - guild, - user: author - }, `Disabled message splitting for ${feed.url}`) - } - return { - ...data, - selected - } -} - -const prompt = new LocalizedPrompt(selectSplitOptionsVisual, selectSplitOptionsFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/sub.filters/index.js b/services/bot/src/commands/prompts/sub.filters/index.js deleted file mode 100644 index 05ff0310f..000000000 --- a/services/bot/src/commands/prompts/sub.filters/index.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.selectAction = require('./selectAction.js') -exports.selectFeed = require('./selectFeed.js') -exports.sendTestArticle = require('./sendTestArticle.js') diff --git a/services/bot/src/commands/prompts/sub.filters/selectAction.js b/services/bot/src/commands/prompts/sub.filters/selectAction.js deleted file mode 100644 index a864745d8..000000000 --- a/services/bot/src/commands/prompts/sub.filters/selectAction.js +++ /dev/null @@ -1,39 +0,0 @@ -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed.js') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const mentionFilterPrompts = require('../mention.filters/index.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {import('../../../structs/db/Subscriber')} selectedSubscriber - */ - -/** - * @param {Data} data - */ -function selectActionVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('commands.mention.filters.title'), - description: translate('commands.sub.filters.description', { - link: feed.url - }) - }) - const menu = new MenuEmbed(embed) - .addOption(translate('commands.sub.filters.optionAddFilters'), translate('commands.sub.filters.optionAddFiltersDescription')) - .addOption(translate('commands.sub.filters.optionRemoveFilters'), translate('commands.sub.filters.optionRemoveFiltersDescription')) - .addOption(translate('commands.sub.filters.optionRemoveAllFilters'), translate('commands.sub.filters.optionRemoveAllFiltersDescription')) - .addOption(translate('commands.sub.filters.optionListFilters'), translate('commands.sub.filters.optionListFiltersDescription')) - .addOption(translate('commands.sub.filters.optionSendFilteredArticle'), translate('commands.sub.filters.optionSendFilteredArticleDescription')) - const visual = new MenuVisual(menu) - return visual -} - -const prompt = new LocalizedPrompt(selectActionVisual, mentionFilterPrompts.selectAction.fn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/sub.filters/selectFeed.js b/services/bot/src/commands/prompts/sub.filters/selectFeed.js deleted file mode 100644 index 89b48578f..000000000 --- a/services/bot/src/commands/prompts/sub.filters/selectFeed.js +++ /dev/null @@ -1,54 +0,0 @@ -const commonPrompts = require('../common/index.js') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Subscriber.js')[]} subscribers - */ - -/** - * @param {import('../../../structs/db/Feed.js')[]} feeds - * @param {import('../../../structs/db/Subscriber.js')[]} subscribers - */ -function getRelevantFeeds (subscribers, feeds) { - const subscribedFeedIDs = new Set(subscribers.map(s => s.feed)) - const relevantFeeds = feeds.filter(f => subscribedFeedIDs.has(f._id)) - return relevantFeeds -} - -/** - * @param {Data} data - */ -function selectFeedVisual (data) { - const { feeds, subscribers } = data - const relevantFeeds = getRelevantFeeds(subscribers, feeds) - const visual = commonPrompts.selectFeed.visual({ - ...data, - feeds: relevantFeeds - }) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectFeedFn (message, data) { - const { feeds, subscribers } = data - const index = Number(message.content) - 1 - const relevantFeeds = getRelevantFeeds(subscribers, feeds) - const selectedFeed = relevantFeeds[index] - return { - ...data, - selectedFeed, - selectedSubscriber: subscribers.find(s => s.feed === selectedFeed._id) - } -} - -const prompt = new LocalizedPrompt(selectFeedVisual, selectFeedFn) - -exports.visual = selectFeedVisual -exports.fn = selectFeedFn -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/sub.filters/sendTestArticle.js b/services/bot/src/commands/prompts/sub.filters/sendTestArticle.js deleted file mode 100644 index da9bda27d..000000000 --- a/services/bot/src/commands/prompts/sub.filters/sendTestArticle.js +++ /dev/null @@ -1,49 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const ArticleMessage = require('../../../structs/ArticleMessage.js') -const FeedFetcher = require('../../../util/FeedFetcher.js') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const Discord = require('discord.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {import('../../../structs/db/Subscriber.js')} selectedSubscriber - * @property {import('discord.js').TextChannel} channel - */ - -/** - * @param {Data} data - */ -async function sendTestArticleVisual (data) { - const { - profile, - selectedFeed: feed, - selectedSubscriber: subscriber, - channel - } = data - const filters = subscriber.hasRFilters() ? subscriber.rfilters : subscriber.filters - const article = await FeedFetcher.fetchRandomArticle(feed.url, filters) - if (!article) { - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.filters.noArticlesPassed')) - } - const articleMessage = await ArticleMessage.create(feed, article) - const articleMessageChannel = articleMessage.getChannel(channel.client) - const { text, options } = articleMessage.createTextAndOptions( - articleMessageChannel instanceof Discord.Webhook ? feed.webhook : null - ) - - return new MessageVisual(text, { - ...options, - allowedMentions: { - parse: [] - } - }) -} - -const prompt = new LocalizedPrompt(sendTestArticleVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/sub/addDirectResult.js b/services/bot/src/commands/prompts/sub/addDirectResult.js deleted file mode 100644 index 8fe9026cc..000000000 --- a/services/bot/src/commands/prompts/sub/addDirectResult.js +++ /dev/null @@ -1,34 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {boolean} newSubscriber - */ - -/** - * @param {Data} data - */ -function addDirectResult (data) { - const { newSubscriber, profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - if (newSubscriber) { - // Newly added - return new MessageVisual(translate('commands.sub.directSubscribeSuccess', { - link: feed.url - })) - } else { - // Already exists - return new MessageVisual(translate('commands.sub.directSubscribeExists', { - link: feed.url - })) - } -} - -const prompt = new LocalizedPrompt(addDirectResult) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/sub/addRoleSuccess.js b/services/bot/src/commands/prompts/sub/addRoleSuccess.js deleted file mode 100644 index ed7bf7bb0..000000000 --- a/services/bot/src/commands/prompts/sub/addRoleSuccess.js +++ /dev/null @@ -1,33 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Subscriber.js')[][]} subscribers - * @property {import('discord.js').Role} addedRole - */ - -/** - * @param {Data} data - */ -function addRoleSuccess (data) { - const { addedRole, profile, feeds, subscribers } = data - const translate = Translator.createProfileTranslator(profile) - - const subscribedFeedIDs = subscribers - .flat() - .filter(subscriber => subscriber.id === addedRole.id) - .map(s => s.feed) - - const feedURLs = subscribedFeedIDs.map(id => feeds.find(feed => feed._id === id).url) - return new MessageVisual(`${translate('commands.sub.addSuccess', { - name: `${addedRole.name}` - })}\n\n${feedURLs.map(url => `**<${url}>**`).join('\n')}`) -} - -const prompt = new LocalizedPrompt(addRoleSuccess) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/sub/index.js b/services/bot/src/commands/prompts/sub/index.js deleted file mode 100644 index 217e69d02..000000000 --- a/services/bot/src/commands/prompts/sub/index.js +++ /dev/null @@ -1,6 +0,0 @@ -exports.addDirectResult = require('./addDirectResult.js') -exports.addRoleSuccess = require('./addRoleSuccess.js') -exports.listSubscribedFeeds = require('./listSubscribedFeeds.js') -exports.inputRole = require('./inputRole.js') -exports.selectAction = require('./selectAction.js') -exports.selectFeed = require('./selectFeed.js') diff --git a/services/bot/src/commands/prompts/sub/inputRole.js b/services/bot/src/commands/prompts/sub/inputRole.js deleted file mode 100644 index 770c94c91..000000000 --- a/services/bot/src/commands/prompts/sub/inputRole.js +++ /dev/null @@ -1,76 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const splitMentionsByNewlines = require('../common/utils/splitMentionsByNewlines.js') -const splitTextByNewline = require('../common/utils/splitTextByNewline.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Subscriber.js')[][]} subscribers - */ - -/** - * @param {Data} data - */ -function inputRoleVisual (data) { - const { profile, feeds, subscribers } = data - const translate = Translator.createProfileTranslator(profile) - let output = translate('commands.sub.listInputRole') + '\n' - for (let i = 0; i < feeds.length; ++i) { - const feed = feeds[i] - const feedSubscribers = subscribers[i] - if (feedSubscribers.length === 0) { - continue - } - output += `\n**${feed.url}** (<#${feed.channel}>)\n` - const mentionStrings = feedSubscribers - .filter(s => s.type === 'role') - .map(s => `<@&${s.id}>`) - output += splitMentionsByNewlines(mentionStrings) + '\n' - } - const texts = splitTextByNewline(output) - return texts.map(text => new MessageVisual(text)) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function inputRoleFn (message, data) { - const { client, member, author, guild, content: roleName } = message - const { subscribers, profile } = data - const translate = Translator.createProfileTranslator(profile) - const mention = message.mentions.roles.first() - /** - * Input is a role name with no capitalization requirements - */ - const subscriberIDs = new Set(subscribers.flat().map(s => s.id)) - const subscriberRoles = guild.roles.cache - .filter(role => subscriberIDs.has(role.id)) - const matchedRole = subscriberRoles - .find(role => role.name.toLowerCase() === roleName.toLowerCase() || (mention && role.id === mention.id)) - if (!matchedRole) { - throw new Rejection(translate('commands.sub.invalidRole')) - } - if (member.roles.cache.has(matchedRole.id)) { - throw new Rejection(translate('commands.sub.alreadyHaveRole')) - } - await member.roles.add(matchedRole) - const log = createLogger(client.shard.ids[0]) - log.info({ - guild, - user: author, - role: matchedRole - }, 'Added subscriber role to member') - return { - ...data, - addedRole: matchedRole - } -} - -const prompt = new LocalizedPrompt(inputRoleVisual, inputRoleFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/sub/listSubscribedFeeds.js b/services/bot/src/commands/prompts/sub/listSubscribedFeeds.js deleted file mode 100644 index 12a27f9a3..000000000 --- a/services/bot/src/commands/prompts/sub/listSubscribedFeeds.js +++ /dev/null @@ -1,49 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const splitTextByNewline = require('../common/utils/splitTextByNewline.js') -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {string} selected - * @property {import('discord.js').GuildMember} member - */ - -/** - * @param {Data} data - */ -async function listSubscribedFeedsVisual (data) { - const { profile, feeds, member } = data - const translate = Translator.createProfileTranslator(profile) - const allSubscribers = await Promise.all(feeds.map(f => f.getSubscribers())) - const memberRoles = member.roles.cache - let output = '' - for (let i = 0; i < feeds.length; ++i) { - const feed = feeds[i] - const subscribers = allSubscribers[i] - const meSubscribedRoles = memberRoles.filter(r => subscribers.some(s => s.id === r.id)) - let thisFeedOutput = '' - if (meSubscribedRoles.size > 0) { - thisFeedOutput += meSubscribedRoles.map(r => `<@&${r.id}>`).join(' ') - } - const meSubscribed = subscribers.some(s => s.id === member.id) - if (meSubscribed) { - thisFeedOutput += `${thisFeedOutput ? ' ' : ''}<@${member.id}>` - } - if (thisFeedOutput) { - output += `**${feed.url}** (${thisFeedOutput})\n` - } - } - if (!output) { - return new MessageVisual(translate('commands.sub.noSubscribedFeedsList')) - } else { - output = `${translate('commands.sub.subscribedFeedsList')}\n\n${output}` - const texts = splitTextByNewline(output) - return texts.map(text => new MessageVisual(text)) - } -} - -const prompt = new LocalizedPrompt(listSubscribedFeedsVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/sub/selectAction.js b/services/bot/src/commands/prompts/sub/selectAction.js deleted file mode 100644 index 51b47fdce..000000000 --- a/services/bot/src/commands/prompts/sub/selectAction.js +++ /dev/null @@ -1,68 +0,0 @@ -const { Rejection, MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - */ - -/** - * @param {Data} data - */ -function selectActionVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('commands.sub.title'), - description: translate('commands.sub.description') - }) - const menu = new MenuEmbed(embed) - .addOption(translate('commands.sub.optionAddRole'), translate('commands.sub.optionAddRoleDescription')) - .addOption(translate('commands.sub.optionAddMe'), translate('commands.sub.optionAddMeDescription')) - .addOption(translate('commands.sub.optionSubscribedFeeds'), translate('commands.sub.optionSubscribedFeedsDescription')) - const visual = new MenuVisual(menu) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectActionFn (message, data) { - const { content: selected, member } = message - const { feeds, profile } = data - const translate = Translator.createProfileTranslator(profile) - if (selected === '1') { - const subscribers = await Promise.all(feeds.map(f => f.getSubscribers())) - if (subscribers.every(arr => arr.length === 0)) { - throw new Rejection(translate('commands.sub.noEligible')) - } - return { - ...data, - selected, - subscribers - } - } - - if (selected === '2') { - return { - ...data, - selected - } - } - - if (selected === '3') { - return { - ...data, - selected, - member - } - } -} - -const prompt = new LocalizedPrompt(selectActionVisual, selectActionFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/sub/selectFeed.js b/services/bot/src/commands/prompts/sub/selectFeed.js deleted file mode 100644 index 237540dc8..000000000 --- a/services/bot/src/commands/prompts/sub/selectFeed.js +++ /dev/null @@ -1,67 +0,0 @@ -const { Rejection } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const commonPrompts = require('../common/index.js') -const Subscriber = require('../../../structs/db/Subscriber.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - */ - -/** - * @param {Data} data - */ -function selectFeedVisual (data) { - return commonPrompts.selectFeed.visual(data) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectFeedFn (message, data) { - const newData = await commonPrompts.selectFeed.fn(message, data) - const { selectedFeed: feed, profile } = newData - const { author } = message - const feedID = feed._id - const translate = Translator.createProfileTranslator(profile) - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - - const enabled = feed.directSubscribers === undefined ? config.feeds.directSubscribers : feed.directSubscribers - if (!enabled) { - throw new Rejection(translate('commands.sub.directSubscriberDisabled', { - link: feed.url, - channel: `<#${feed.channel}>`, - prefix - })) - } - - const existingSubscriber = await Subscriber.getByQuery({ - feed: feedID, - id: author.id - }) - if (existingSubscriber) { - return { - ...newData, - newSubscriber: false - } - } - const subscriber = new Subscriber({ - feed: feedID, - id: author.id, - type: 'user' - }) - await subscriber.save() - return { - ...newData, - newSubscriber: true - } -} - -const prompt = new LocalizedPrompt(selectFeedVisual, selectFeedFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/text/index.js b/services/bot/src/commands/prompts/text/index.js deleted file mode 100644 index 9d3108f08..000000000 --- a/services/bot/src/commands/prompts/text/index.js +++ /dev/null @@ -1,2 +0,0 @@ -exports.setMessage = require('./setMessage.js') -exports.success = require('./success.js') diff --git a/services/bot/src/commands/prompts/text/setMessage.js b/services/bot/src/commands/prompts/text/setMessage.js deleted file mode 100644 index c5a71d5a7..000000000 --- a/services/bot/src/commands/prompts/text/setMessage.js +++ /dev/null @@ -1,62 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get -const createLogger = require('../../../util/logger/create.js') -const Feed = require('../../../structs/db/Feed') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {import('../../../structs/db/Profile.js')} profile - */ - -/** - * @param {Data} data - */ -function setMessageVisual (data) { - const config = getConfig() - const profile = data.profile - const { locale } = profile || {} - const { text, url } = data.selectedFeed - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - let currentMsg = '' - if (text) { - currentMsg = '```Markdown\n' + text + '```' - } else { - currentMsg = `\`\`\`Markdown\n${Translator.translate('commands.text.noSetText', locale)}\n\n\`\`\`\`\`\`\n` + config.feeds.defaultText + '```' - } - return new MessageVisual(Translator.translate('commands.text.prompt', locale, { - prefix, - currentMsg, - link: url - })) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function setMessageFn (message, data) { - const text = message.content - const selectedFeed = data.selectedFeed - const log = createLogger(message.client.shard.ids[0]) - if (text === 'reset') { - selectedFeed.text = undefined - } else { - selectedFeed.text = text - } - await selectedFeed.save() - if (selectedFeed.disabled === Feed.DISABLE_REASONS.BAD_FORMAT) { - await selectedFeed.enable() - } - log.info({ - guild: message.guild, - text - }, `Text set for ${selectedFeed.url}`) - return data -} - -const prompt = new LocalizedPrompt(setMessageVisual, setMessageFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/text/success.js b/services/bot/src/commands/prompts/text/success.js deleted file mode 100644 index 32b4f245a..000000000 --- a/services/bot/src/commands/prompts/text/success.js +++ /dev/null @@ -1,36 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const getConfig = require('../../../config.js').get - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')} selectedFeed - * @property {import('../../../structs/db/Profile.js')} profile - */ - -/** - * @param {Data} data - */ -function successVisual (data) { - const config = getConfig() - const { profile } = data - const { locale } = profile || {} - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - const { text, url } = data.selectedFeed - const defaultText = config.feeds.defaultText - const translate = Translator.createLocaleTranslator(locale) - if (text === undefined) { - return new MessageVisual(translate('commands.text.resetSuccess', { link: url }) + `\n \`\`\`Markdown\n${defaultText}\`\`\``) - } - const confirmSuccess = translate('commands.text.setSuccess', { link: url }) - const escapedText = text.replace('`', '​`') - const testReminder = translate('commands.text.reminder', { prefix }) - const backupReminder = translate('generics.backupReminder', { prefix }) - const subscriptionsReminder = text.search(/{subscriptions}/) === -1 ? translate('commands.text.noSubscriptionsPlaceholder', { prefix }) : '' - return new MessageVisual(`${confirmSuccess}\n \`\`\`Markdown\n${escapedText}\`\`\`\n${testReminder} ${backupReminder}${subscriptionsReminder}`) -} - -const prompt = new LocalizedPrompt(successVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/unsub/index.js b/services/bot/src/commands/prompts/unsub/index.js deleted file mode 100644 index 42373fb87..000000000 --- a/services/bot/src/commands/prompts/unsub/index.js +++ /dev/null @@ -1,6 +0,0 @@ -exports.inputRemoveRole = require('./inputRemoveRole.js') -exports.removeAllSuccess = require('./removeAllSuccess.js') -exports.removeDirectSuccess = require('./removeDirectSuccess.js') -exports.removeRoleSuccess = require('./removeRoleSuccess.js') -exports.selectAction = require('./selectAction.js') -exports.selectFeed = require('./selectFeed.js') diff --git a/services/bot/src/commands/prompts/unsub/inputRemoveRole.js b/services/bot/src/commands/prompts/unsub/inputRemoveRole.js deleted file mode 100644 index 44c9e0b95..000000000 --- a/services/bot/src/commands/prompts/unsub/inputRemoveRole.js +++ /dev/null @@ -1,77 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const splitMentionsByNewlines = require('../common/utils/splitMentionsByNewlines.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Subscriber.js')[][]} subscribers - * @property {import('discord.js').GuildMember} member - */ - -/** - * @param {Data} data - */ -async function inputRemoveRoleVisual (data) { - const { profile, feeds, member, subscribers } = data - const translate = Translator.createProfileTranslator(profile) - const memberRoles = member.roles.cache - let output = translate('commands.unsub.listInputRole') + '\n' - for (let i = 0; i < feeds.length; ++i) { - const feed = feeds[i] - const feedSubscribers = subscribers[i] - if (feedSubscribers.length === 0) { - continue - } - const memberSubscribedRoles = memberRoles - .filter(r => feedSubscribers.some(s => s.id === r.id)) - if (memberSubscribedRoles.size === 0) { - continue - } - output += `\n**${feed.url}** (<#${feed.channel}>)\n` - const mentionStrings = memberSubscribedRoles.map(r => `<@&${r.id}>`) - output += splitMentionsByNewlines(mentionStrings) + '\n' - } - return new MessageVisual(output, { - split: true - }) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function inputRemoveRoleFn (message, data) { - const { client, member, author, guild, content: roleName } = message - const { subscribers, profile } = data - const translate = Translator.createProfileTranslator(profile) - const mention = message.mentions.roles.first() - /** - * Input is a role name with no capitalization requirements - */ - const subscriberIDs = new Set(subscribers.flat().map(s => s.id)) - const memberSubscribedRoles = member.roles.cache - .filter(role => subscriberIDs.has(role.id)) - const memberRole = memberSubscribedRoles.find(role => role.name.toLowerCase() === roleName.toLowerCase() || (mention && role.id === mention.id)) - if (!memberRole) { - throw new Rejection(translate('commands.unsub.invalidRole')) - } - await member.roles.remove(memberRole) - const log = createLogger(client.shard.ids[0]) - log.info({ - guild, - user: author, - role: memberRole - }, 'Removed subscriber role from member') - return { - ...data, - removedRole: memberRole - } -} - -const prompt = new LocalizedPrompt(inputRemoveRoleVisual, inputRemoveRoleFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/unsub/removeAllSuccess.js b/services/bot/src/commands/prompts/unsub/removeAllSuccess.js deleted file mode 100644 index ca18accf3..000000000 --- a/services/bot/src/commands/prompts/unsub/removeAllSuccess.js +++ /dev/null @@ -1,23 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - */ - -/** - * @param {Data} data - */ -function removeDirectSuccess (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - // Newly added - return new MessageVisual(translate('commands.unsub.removeAllSuccess')) -} - -const prompt = new LocalizedPrompt(removeDirectSuccess) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/unsub/removeDirectSuccess.js b/services/bot/src/commands/prompts/unsub/removeDirectSuccess.js deleted file mode 100644 index e642b6302..000000000 --- a/services/bot/src/commands/prompts/unsub/removeDirectSuccess.js +++ /dev/null @@ -1,26 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -function removeDirectSuccess (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - // Newly added - return new MessageVisual(translate('commands.unsub.directRemoveSuccess', { - link: feed.url - })) -} - -const prompt = new LocalizedPrompt(removeDirectSuccess) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/unsub/removeRoleSuccess.js b/services/bot/src/commands/prompts/unsub/removeRoleSuccess.js deleted file mode 100644 index 3d7555006..000000000 --- a/services/bot/src/commands/prompts/unsub/removeRoleSuccess.js +++ /dev/null @@ -1,26 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Subscriber.js')[][]} subscribers - * @property {import('discord.js').Role} removedRole - */ - -/** - * @param {Data} data - */ -function removeRoleSuccess (data) { - const { removedRole, profile } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.unsub.roleRemoveSuccess', { - role: `<@&${removedRole}>` - })) -} - -const prompt = new LocalizedPrompt(removeRoleSuccess) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/unsub/selectAction.js b/services/bot/src/commands/prompts/unsub/selectAction.js deleted file mode 100644 index 7f7021732..000000000 --- a/services/bot/src/commands/prompts/unsub/selectAction.js +++ /dev/null @@ -1,112 +0,0 @@ -const { Rejection, MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - */ - -/** - * @param {Data} data - */ -function selectActionVisual (data) { - const { profile } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('commands.sub.title'), - description: translate('commands.sub.description') - }) - const menu = new MenuEmbed(embed) - .addOption(translate('commands.unsub.optionRemoveRole'), translate('commands.unsub.optionRemoveRoleDescription')) - .addOption(translate('commands.unsub.optionRemoveMyself'), translate('commands.unsub.optionRemoveMyselfDescription')) - .addOption(translate('commands.unsub.optionRemoveAll'), translate('commands.unsub.optionRemoveAllDescription')) - .addOption(translate('commands.sub.optionSubscribedFeeds'), translate('commands.sub.optionSubscribedFeedsDescription')) - const visual = new MenuVisual(menu) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectActionFn (message, data) { - const { guild, author, content: selected, client, member } = message - const { feeds, profile } = data - const translate = Translator.createProfileTranslator(profile) - const memberRoles = member.roles.cache - const log = createLogger(client.shard.ids[0], { - guild, - user: author - }) - - if (selected === '1') { - const subscribers = await Promise.all(feeds.map(f => f.getSubscribers())) - const flatSubscribers = subscribers.flat() - // Make sure some member role is a subscriber of some feed - if (!memberRoles.some(role => flatSubscribers.some(s => s.id === role.id))) { - throw new Rejection(translate('commands.unsub.noEligibleRoles')) - } - return { - ...data, - selected, - member, - subscribers - } - } - - if (selected === '2') { - const subscribers = await Promise.all(feeds.map(f => f.getSubscribers())) - if (!subscribers.flat().some(s => s.id === member.id)) { - throw new Rejection(translate('commands.unsub.noEligibleDirect')) - } - return { - ...data, - selected, - member, - subscribers - } - } - - // Remove all - if (selected === '3') { - // Remove user - const subscribers = await Promise.all(feeds.map(f => f.getSubscribers())) - /** @type {import('../../../structs/db/Subscriber.js')[]} */ - const flatSubscribers = subscribers.flat() - const meSubscribers = flatSubscribers.filter(s => s.id === member.id) - if (meSubscribers.length > 0) { - await Promise.all(meSubscribers.map(s => s.delete())) - log.info({ - meSubscribers - }, 'Removed direct subscribers') - } - // Remove roles - const removeRoles = memberRoles.filter(role => flatSubscribers.some(s => s.id === role.id)) - if (removeRoles.size > 0) { - await member.roles.remove(removeRoles) - log.info({ - removedRoles: removeRoles.map(r => `${r.name} (${r.id})`) - }, 'Removed role subscribers') - } - return { - ...data, - selected - } - } - - if (selected === '4') { - return { - ...data, - selected, - member - } - } -} - -const prompt = new LocalizedPrompt(selectActionVisual, selectActionFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/unsub/selectFeed.js b/services/bot/src/commands/prompts/unsub/selectFeed.js deleted file mode 100644 index 5277df97d..000000000 --- a/services/bot/src/commands/prompts/unsub/selectFeed.js +++ /dev/null @@ -1,81 +0,0 @@ -const mongoose = require('mongoose') -const { MenuEmbed, MenuVisual } = require('discord.js-prompts') -const ThemedEmbed = require('../common/utils/ThemedEmbed') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const handlePaginationError = require('../common/utils/handlePaginationError.js') -const Subscriber = require('../../../structs/db/Subscriber.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Subscriber.js')[][]} subscribers - * @property {import('discord.js').GuildMember} member - */ - -/** - * @param {Data} data - */ -function selectFeedVisual (data) { - const { feeds, profile, subscribers, member } = data - const translate = Translator.createProfileTranslator(profile) - const embed = new ThemedEmbed({ - title: translate('structs.FeedSelector.feedSelectionMenu'), - description: `${translate('commands.unsub.directRemoveList')} ${translate('structs.FeedSelector.prompt')} ${translate('structs.FeedSelector.exitToCancel')} ` - }) - const menu = new MenuEmbed(embed) - .enablePagination(handlePaginationError) - - /** @type {import('../../../structs/db/Subscriber.js')[]} */ - const meSubscribers = subscribers.flat().filter(s => s.id === member.id) - for (const feed of feeds) { - if (!meSubscribers.some(s => s.feed === feed._id)) { - // Ignore feeds that this member is not directly subscribed to - continue - } - const title = feed.title.length > 200 ? feed.title.slice(0, 200) + '...' : feed.title - const url = feed.url.length > 500 ? translate('commands.list.exceeds500Characters') : feed.url - menu.addOption(title, `Channel: <#${feed.channel}>\nURL: ${url}`) - } - const visual = new MenuVisual(menu) - return visual -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectFeedFn (message, data) { - const { author, client, guild, content } = message - const { feeds, subscribers, member } = data - - /** @type {import('../../../structs/db/Subscriber.js')[]} */ - const meSubscribers = subscribers.flat().filter(s => s.id === member.id) - const relevantFeeds = feeds.filter(f => meSubscribers.some(s => s.feed === f._id)) - - const selectedIndex = Number(content) - 1 - const selectedFeed = relevantFeeds[selectedIndex] - const feedID = selectedFeed._id - const existingSubscriber = await Subscriber.getByQuery({ - feed: new mongoose.Types.ObjectId(feedID), - id: author.id - }) - const log = createLogger(client.shard.ids[0]) - if (existingSubscriber) { - await existingSubscriber.delete() - log.info({ - guild, - user: author - }, `Removed direct subscriber of feed ${selectedFeed.url}`) - } - return { - selectedFeed, - newSubscriber: false - } -} - -const prompt = new LocalizedPrompt(selectFeedVisual, selectFeedFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/webhook/index.js b/services/bot/src/commands/prompts/webhook/index.js deleted file mode 100644 index 48123e280..000000000 --- a/services/bot/src/commands/prompts/webhook/index.js +++ /dev/null @@ -1,3 +0,0 @@ -exports.selectFeed = require('./selectFeed.js') -exports.removedSuccess = require('./removedSuccess.js') -exports.selectWebhook = require('./selectWebhook.js') diff --git a/services/bot/src/commands/prompts/webhook/removedSuccess.js b/services/bot/src/commands/prompts/webhook/removedSuccess.js deleted file mode 100644 index 2ad5d9a32..000000000 --- a/services/bot/src/commands/prompts/webhook/removedSuccess.js +++ /dev/null @@ -1,25 +0,0 @@ -const { MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - * @property {boolean} removed - */ - -/** - * @param {Data} data - */ -function removedSuccessVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - return new MessageVisual(translate('commands.webhook.removeSuccess', { - link: feed.url - })) -} - -const prompt = new LocalizedPrompt(removedSuccessVisual) - -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/webhook/selectFeed.js b/services/bot/src/commands/prompts/webhook/selectFeed.js deleted file mode 100644 index d330bbab8..000000000 --- a/services/bot/src/commands/prompts/webhook/selectFeed.js +++ /dev/null @@ -1,47 +0,0 @@ -const { Rejection } = require('discord.js-prompts') -const Discord = require('discord.js') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt') -const Translator = require('../../../structs/Translator.js') -const commonSelectFeed = require('../common/selectFeed.js') -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Profile.js')} [profile] - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Feed.js')} [selectedFeed] - */ - -/** - * @param {Data} data - */ -function selectFeedVisual (data) { - return commonSelectFeed.visual(data) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectFeedFn (message, data) { - const newData = await commonSelectFeed.fn(message, data) - const { selectedFeed, profile } = newData - const translate = Translator.createProfileTranslator(profile) - const channel = message.client.channels.cache.get(selectedFeed.channel) - if (!channel) { - throw new Rejection(translate('commands.webhook.missingChannel', { - channelID: selectedFeed.channel, - link: selectedFeed.url - })) - } - const manageWebhooksPerm = message.guild.me.permissionsIn(channel) - .has(Discord.Permissions.FLAGS.MANAGE_WEBHOOKS) - if (!manageWebhooksPerm) { - throw new Rejection(translate('commands.webhook.noPermission')) - } - return newData -} - -const prompt = new LocalizedPrompt(selectFeedVisual, selectFeedFn) - -exports.visual = selectFeedVisual -exports.fn = selectFeedFn -exports.prompt = prompt diff --git a/services/bot/src/commands/prompts/webhook/selectWebhook.js b/services/bot/src/commands/prompts/webhook/selectWebhook.js deleted file mode 100644 index 064737b2e..000000000 --- a/services/bot/src/commands/prompts/webhook/selectWebhook.js +++ /dev/null @@ -1,100 +0,0 @@ -const { Rejection, MessageVisual } = require('discord.js-prompts') -const LocalizedPrompt = require('../common/utils/LocalizedPrompt.js') -const Translator = require('../../../structs/Translator.js') -const createLogger = require('../../../util/logger/create.js') - -/** - * @typedef {Object} Data - * @property {import('../../../structs/db/Feed.js')[]} feeds - * @property {import('../../../structs/db/Profile.js')} profile - * @property {import('../../../structs/db/Feed.js')} selectedFeed - */ - -/** - * @param {Data} data - */ -function selectWebhookVisual (data) { - const { profile, selectedFeed: feed } = data - const translate = Translator.createProfileTranslator(profile) - const webhook = feed.webhook - return new MessageVisual(`${webhook -? translate('commands.webhook.existingFound', { - webhookMention: `<@${webhook.id}>` - }) -: ''}${translate('commands.webhook.prompt')}`) -} - -/** - * @param {import('discord.js').Message} message - * @param {Data} data - */ -async function selectWebhookFn (message, data) { - const { profile, selectedFeed: feed } = data - const { content } = message - const translate = Translator.createProfileTranslator(profile) - if (content === '{remove}') { - if (feed.webhook) { - feed.webhook = undefined - await feed.save() - } - return { - ...data, - removed: true - } - } - const nameRegex = /--name="(((?!(--name|--avatar)).)*)"/ - const avatarRegex = /--avatar="(((?!(--name|--avatar)).)*)"/ - const hookName = content.replace(nameRegex, '').replace(avatarRegex, '').trim() - const feedChannel = await message.client.channels.fetch(feed.channel) - const hooks = await feedChannel.fetchWebhooks() - const hook = hooks.find(h => h.name === hookName) - if (!hook) { - throw new Rejection(translate('commands.webhook.notFound', { name: hookName })) - } - const customNameSrch = content.match(nameRegex) - const customAvatarSrch = content.match(avatarRegex) - - const newWebhook = { - id: hook.id, - url: hook.url - } - if (customNameSrch) { - if (customNameSrch[1].length > 32 || customNameSrch[1].length < 2) { - throw new Rejection(translate('commands.webhook.tooLong')) - } - newWebhook.name = customNameSrch[1] - } - - if (customAvatarSrch) { - newWebhook.avatar = customAvatarSrch[1] - } - - feed.webhook = newWebhook - await feed.save() - const log = createLogger(message.client.shard.ids[0]) - log.info({ - guild: message.guild, - user: message.author - }, `Webhook ID ${hook.id} (${hook.name}) connected to feed ${feed.url}`) - const connected = translate('commands.webhook.connected', { - clientMention: `<@${message.client.user.id}>`, - link: feed.url - }) - if (message.channel.id === feedChannel) { - await hook.send(connected, { - username: newWebhook.name, - avatarURL: newWebhook.avatar - }) - } else { - // Some may not want messages to be delivered outside of the current channel - await message.channel.send(translate('commands.webhook.addSuccess', { - link: feed.url, - channel: feed.channel - })) - } - return data -} - -const prompt = new LocalizedPrompt(selectWebhookVisual, selectWebhookFn) - -exports.prompt = prompt diff --git a/services/bot/src/commands/refresh.js b/services/bot/src/commands/refresh.js deleted file mode 100644 index 2c18ba200..000000000 --- a/services/bot/src/commands/refresh.js +++ /dev/null @@ -1,66 +0,0 @@ -const FeedFetcher = require('../util/FeedFetcher.js') -const Translator = require('../structs/Translator.js') -const Profile = require('../structs/db/Profile.js') -const FailRecord = require('../structs/db/FailRecord.js') -const Feed = require('../structs/db/Feed.js') -const createLogger = require('../util/logger/create.js') - -module.exports = async (message) => { - const profile = await Profile.get(message.guild.id) - const feeds = await Feed.getManyBy('guild', message.guild.id) - const translate = Translator.createLocaleTranslator(profile ? profile.locale : undefined) - if (feeds.length === 0) { - return message.channel.send(translate('commands.list.noFeeds')) - } - - if (FailRecord.limit === 0) { - return message.channel.send(translate('commands.refresh.noFailLimit')) - } - - const records = [] - for (const feed of feeds) { - const failRecord = await FailRecord.get(feed.url) - if (!failRecord || !failRecord.hasFailed()) { - continue - } - records.push(failRecord) - } - if (records.length === 0) { - return message.channel.send(translate('commands.refresh.noFailedFeeds')) - } - const log = createLogger(message.guild.shard.id) - const processing = await message.channel.send(translate('commands.refresh.processing')) - - let successfulLinks = '' - let failedLinks = '' - - for (const record of records) { - const url = record._id - log.info({ - guild: message.guild - }, `Attempting to refresh ${url}`) - try { - await FeedFetcher.fetchURL(url) - await record.delete() - successfulLinks += `${url}\n` - log.info({ - guild: message.guild - }, `Refreshed ${url} and is back on cycle`) - } catch (err) { - failedLinks += `${url} (${err.message})` - log.info({ - guild: message.guild, - error: err - }, `Failed to refresh ${url} (${err.message})`) - } - } - - let reply = '' - if (successfulLinks) { - reply += translate('commands.refresh.success') + '\n```' + successfulLinks + '```\n\n' - } - if (failedLinks) { - reply += translate('commands.refresh.failed') + '\n```' + failedLinks + '```' - } - await processing.edit(reply) -} diff --git a/services/bot/src/commands/remove.js b/services/bot/src/commands/remove.js deleted file mode 100644 index b97fef041..000000000 --- a/services/bot/src/commands/remove.js +++ /dev/null @@ -1,12 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const removePrompts = require('./prompts/remove/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message, command) => { - const selectRemoveFeedsNode = new PromptNode(removePrompts.selectRemoveFeeds.prompt) - const removeSuccessNode = new PromptNode(removePrompts.removeSuccess.prompt) - - selectRemoveFeedsNode.addChild(removeSuccessNode) - - await runWithFeedGuild(selectRemoveFeedsNode, message) -} diff --git a/services/bot/src/commands/split.js b/services/bot/src/commands/split.js deleted file mode 100644 index e07bd8d90..000000000 --- a/services/bot/src/commands/split.js +++ /dev/null @@ -1,52 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const commonPrompts = require('./prompts/common/index.js') -const splitPrompts = require('./prompts/split/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectFeedNode = new PromptNode(commonPrompts.selectFeed.prompt) - - const enableNodeCondition = data => !data.selectedFeed.split - const enableNode = new PromptNode(splitPrompts.enable.prompt, enableNodeCondition) - - const selectSplitOptionsNodeCondition = data => !!data.selectedFeed.split - const selectSplitOptionsNode = new PromptNode(splitPrompts.selectSplitOptions.prompt, selectSplitOptionsNodeCondition) - - const inputSplitCharacterNodeCondition = data => data.selected === '1' - const inputSplitCharacterNode = new PromptNode(splitPrompts.inputSplitCharacter.prompt, inputSplitCharacterNodeCondition) - const inputSplitCharacterSuccessNode = new PromptNode(splitPrompts.inputSplitCharacterSuccess.prompt) - - const inputPrependCharacterNodeCondition = data => data.selected === '2' - const inputPrependCharacterNode = new PromptNode(splitPrompts.inputPrependCharacter.prompt, inputPrependCharacterNodeCondition) - const inputPrependCharacterSuccessNode = new PromptNode(splitPrompts.inputPrependCharacterSuccess.prompt) - - const inputAppendCharacterNodeCondition = data => data.selected === '3' - const inputAppendCharacterNode = new PromptNode(splitPrompts.inputAppendCharacter.prompt, inputAppendCharacterNodeCondition) - const inputAppendCharacterSuccessNode = new PromptNode(splitPrompts.inputAppendCharacterSuccess.prompt) - - const inputMaxLengthNodeCondition = data => data.selected === '4' - const inputMaxLengthNode = new PromptNode(splitPrompts.inputMaxLength.prompt, inputMaxLengthNodeCondition) - const inputMaxLengthSuccessNode = new PromptNode(splitPrompts.inputMaxLengthSuccess.prompt) - - const disabledSuccessNodeCondition = data => data.selected === '5' - const disabledSuccessNode = new PromptNode(splitPrompts.disabledSuccess.prompt, disabledSuccessNodeCondition) - - selectFeedNode.setChildren([ - enableNode, - selectSplitOptionsNode - ]) - enableNode.addChild(selectSplitOptionsNode) - selectSplitOptionsNode.setChildren([ - inputSplitCharacterNode, - inputPrependCharacterNode, - inputAppendCharacterNode, - inputMaxLengthNode, - disabledSuccessNode - ]) - inputSplitCharacterNode.addChild(inputSplitCharacterSuccessNode) - inputPrependCharacterNode.addChild(inputPrependCharacterSuccessNode) - inputAppendCharacterNode.addChild(inputAppendCharacterSuccessNode) - inputMaxLengthNode.addChild(inputMaxLengthSuccessNode) - - await runWithFeedGuild(selectFeedNode, message) -} diff --git a/services/bot/src/commands/stats.js b/services/bot/src/commands/stats.js deleted file mode 100644 index fcdffce92..000000000 --- a/services/bot/src/commands/stats.js +++ /dev/null @@ -1,77 +0,0 @@ -const moment = require('moment') -const ThemedEmbed = require('./prompts/common/utils/ThemedEmbed.js') -const GeneralStats = require('../models/GeneralStats.js') -const ScheduleStats = require('../structs/db/ScheduleStats.js') - -module.exports = async (message) => { - const [allScheduleStats, allGeneralStats] = await Promise.all([ - ScheduleStats.getAll(), - GeneralStats.Model.find().lean().exec() - ]) - const bot = message.client - const scheduleStats = allScheduleStats.find(r => r._id === 'default') - if (!scheduleStats) { - return message.channel.send('More time is needed to gather enough information. Try again later.') - } - if (scheduleStats.feeds === 0) { - return message.channel.send('No feeds found for any data.') - } - const sizeFetches = await bot.shard.fetchClientValues('guilds.cache.size') - - const aggregated = { - guilds: sizeFetches.reduce((prev, val) => prev + val, 0), - feeds: scheduleStats.feeds, - cycleTime: scheduleStats.cycleTime, - cycleFails: scheduleStats.cycleFails, - cycleURLs: scheduleStats.cycleURLs, - lastUpdated: new Date(scheduleStats.lastUpdated), - articlesSent: allGeneralStats.find(doc => doc._id === GeneralStats.TYPES.ARTICLES_SENT), - articlesBlocked: allGeneralStats.find(doc => doc._id === GeneralStats.TYPES.ARTICLES_BLOCKED) - } - - const visual = new ThemedEmbed() - .setAuthor('Basic Stats') - .setDescription(` -**Unique Feeds** - Number of unique feed links (duplicate links are not counted). -**Total Feeds** - Number of total feeds. -**Cycle Duration** - Time taken to process all the unique feeds. -**Cycle Failures** - Number of failures out of the number of unique feeds per cycle. -**Success Rate** - Rate at which unique links connect successfully per cycle. A high rate is necessary to ensure that all feeds are fetched. -**Articles Sent** - Total number of articles sent -**Articles Blocked** - Total number of articles blocked by article rate limits\n\u200b`) - - const guilds = `${aggregated.guilds}` - const feeds = `${aggregated.feeds}` - const cycleURLs = `${aggregated.cycleURLs}` - const cycleFails = `${aggregated.cycleFails}` - const cycleTime = `${aggregated.cycleTime.toFixed(2)}s` - const successRate = `${((1 - aggregated.cycleFails / aggregated.cycleURLs) * 100).toFixed(2)}%` - const articlesSent = aggregated.articlesSent ? aggregated.articlesSent.data : 0 - const articlesBlocked = aggregated.articlesBlocked ? aggregated.articlesBlocked.data : 0 - - visual - .addField('Servers', guilds, true) - .addField('Shard Count', bot.shard.count, true) - .addField('\u200b', '\u200b', true) - - let diff = moment.duration(moment().diff(moment(aggregated.lastUpdated))) - if (diff.asMinutes() < 1) { - diff = `Updated ${diff.asSeconds().toFixed(2)} seconds ago` - } else { - diff = `Updated ${diff.asMinutes().toFixed(2)} minutes ago` - } - - visual - .addField('Unique Feeds', cycleURLs, true) - .addField('Total Feeds', feeds, true) - .addField('Cycle Duration', cycleTime, true) - .addField('Cycle Failures', cycleFails, true) - .addField('Success Rate', successRate, true) - .addField('\u200b', '\u200b', true) - .addField('Articles Sent', articlesSent, true) - .addField('Articles Blocked', articlesBlocked, true) - .addField('\u200b', '\u200b', true) - .setFooter(diff) - - await message.channel.send('', visual) -} diff --git a/services/bot/src/commands/sub.filters.js b/services/bot/src/commands/sub.filters.js deleted file mode 100644 index 124f6ffb0..000000000 --- a/services/bot/src/commands/sub.filters.js +++ /dev/null @@ -1,84 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const Profile = require('../structs/db/Profile.js') -const Feed = require('../structs/db/Feed.js') -const Subscriber = require('../structs/db/Subscriber.js') -const Translator = require('../structs/Translator.js') -const commonPrompts = require('./prompts/common/index.js') -const subFilterPrompts = require('./prompts/sub.filters/index.js') -const mentionFilterPrompts = require('./prompts/mention.filters/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -/** - * @param {import('../structs/db/Feed.js')[]} feeds - * @param {import('../structs/db/Subscriber.js')[]} subscribers - * @param {string} subscriberID - */ -function subscribedToAnyFeed (feeds, subscribers) { - const feedIDs = new Set(feeds.map(f => f._id)) - const subscribersOfServer = subscribers.filter(s => feedIDs.has(s.feed)) - return subscribersOfServer.length > 0 -} - -module.exports = async (message) => { - const [feeds, subscribers, profile] = await Promise.all([ - Feed.getManyBy('guild', message.guild.id), - Subscriber.getManyBy('id', message.author.id), - Profile.get(message.guild.id) - ]) - if (!subscribedToAnyFeed(feeds, subscribers)) { - const translate = Translator.createProfileTranslator(profile) - return message.channel.send(translate('commands.sub.filters.noSubscribedFeeds')) - } - const selectFeedNode = new PromptNode(subFilterPrompts.selectFeed.prompt) - const selectActionNode = new PromptNode(subFilterPrompts.selectAction.prompt) - - // Path 1 - Add filters - const filterAddCategorySelectCondition = data => data.selected === '1' - const filterAddCategorySelectNode = new PromptNode(commonPrompts.filterAddCategorySelect.prompt, filterAddCategorySelectCondition) - const filterAddInputNode = new PromptNode(commonPrompts.filterAddInput.prompt) - const filterAddInputSuccessNode = new PromptNode(commonPrompts.filterAddInputSuccess.prompt) - - filterAddCategorySelectNode.addChild(filterAddInputNode) - filterAddInputNode.addChild(filterAddInputSuccessNode) - - // Path 2 - Removed filters - // No Filters - const noFiltersToRemoveCondition = data => data.selected === '2' && !data.selectedSubscriber.hasFilters() - const noFiltersToRemoveNode = new PromptNode(mentionFilterPrompts.listFilters.prompt, noFiltersToRemoveCondition) - - // With Filters - const filterRemoveCategorySelectCondition = data => data.selected === '2' - const filterRemoveCategorySelectNode = new PromptNode(commonPrompts.filterRemoveCategorySelect.prompt, filterRemoveCategorySelectCondition) - const filterRemoveInputNode = new PromptNode(commonPrompts.filterRemoveInput.prompt) - const filterRemoveInputSuccessNode = new PromptNode(commonPrompts.filterRemoveInputSuccess.prompt) - - filterRemoveCategorySelectNode.addChild(filterRemoveInputNode) - filterRemoveInputNode.addChild(filterRemoveInputSuccessNode) - - // Path 3 - Removed all filters - const removeAllFiltersSuccessNodeCondition = data => data.selected === '3' - const removeAllFiltersSuccessNode = new PromptNode(mentionFilterPrompts.removeAllFiltersSuccess.prompt, removeAllFiltersSuccessNodeCondition) - - // Path 4 - List filters - const listFiltersNodeCondition = data => data.selected === '4' - const listFilterNode = new PromptNode(mentionFilterPrompts.listFilters.prompt, listFiltersNodeCondition) - - // Path 5 - Send test article - const sendTestArticleNodeCondition = data => data.selected === '5' - const sendTestArticleNode = new PromptNode(subFilterPrompts.sendTestArticle.prompt, sendTestArticleNodeCondition) - - selectFeedNode.addChild(selectActionNode) - selectActionNode.setChildren([ - filterAddCategorySelectNode, - noFiltersToRemoveNode, - filterRemoveCategorySelectNode, - removeAllFiltersSuccessNode, - listFilterNode, - sendTestArticleNode - ]) - - await runWithFeedGuild(selectFeedNode, message, { - subscribers, - channel: message.channel - }) -} diff --git a/services/bot/src/commands/sub.js b/services/bot/src/commands/sub.js deleted file mode 100644 index 9f9f37935..000000000 --- a/services/bot/src/commands/sub.js +++ /dev/null @@ -1,30 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const subPrompts = require('./prompts/sub/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectActionNode = new PromptNode(subPrompts.selectAction.prompt) - - const inputRoleNodeCondition = data => data.selected === '1' - const inputRoleNode = new PromptNode(subPrompts.inputRole.prompt, inputRoleNodeCondition) - const addRoleSuccess = new PromptNode(subPrompts.addRoleSuccess.prompt) - - inputRoleNode.addChild(addRoleSuccess) - - const selectFeedNodeCondition = data => data.selected === '2' - const selectFeedNode = new PromptNode(subPrompts.selectFeed.prompt, selectFeedNodeCondition) - const addDirectResultNode = new PromptNode(subPrompts.addDirectResult.prompt) - - const listSubscribedFeedsNodeCondition = data => data.selected === '3' - const listSubscribedFeedsNode = new PromptNode(subPrompts.listSubscribedFeeds.prompt, listSubscribedFeedsNodeCondition) - - selectFeedNode.addChild(addDirectResultNode) - - selectActionNode.setChildren([ - inputRoleNode, - selectFeedNode, - listSubscribedFeedsNode - ]) - - await runWithFeedGuild(selectActionNode, message) -} diff --git a/services/bot/src/commands/test.js b/services/bot/src/commands/test.js deleted file mode 100644 index ddaacbf49..000000000 --- a/services/bot/src/commands/test.js +++ /dev/null @@ -1,56 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const commonPrompts = require('./prompts/common/index.js') -const Article = require('../structs/Article.js') -const FeedFetcher = require('../util/FeedFetcher.js') -const ArticleTestMessage = require('../structs/ArticleTestMessage.js') -const Translator = require('../structs/Translator.js') -const Profile = require('../structs/db/Profile.js') -const FailRecord = require('../structs/db/FailRecord.js') -const FeedData = require('../structs/FeedData.js') -const runWithFeedGuild = require('./prompts/runner/run.js') -const getConfig = require('../config.js').get - -module.exports = async (message) => { - const split = message.content.split(' ') - const simple = split.includes('simple') - const latest = split.includes('latest') - const profile = await Profile.get(message.guild.id) - const translate = Translator.createProfileTranslator(profile) - const selectFeedNode = new PromptNode(commonPrompts.selectFeed.prompt) - const data = await runWithFeedGuild(selectFeedNode, message) - const { selectedFeed: feed } = data - if (!feed) { - return - } - const config = getConfig() - const prefix = profile && profile.prefix ? profile.prefix : config.bot.prefix - if (await FailRecord.hasFailed(feed.url)) { - return message.channel.send(translate('commands.test.failed', { - prefix - })) - } - const grabMsg = await message.channel.send(translate('commands.test.grabbingRandom')) - const article = latest ? await FeedFetcher.fetchLatestArticle(feed.url) : await FeedFetcher.fetchRandomArticle(feed.url) - if (!article) { - const string = latest ? translate('commands.test.noValidLatest') : translate('commands.test.noArticles') - return message.channel.send(string) - } - const feedData = await FeedData.ofFeed(feed) - - if (!simple) { - const parsedArticle = new Article(article, feedData) - const testText = parsedArticle.createTestText() - await message.channel.send(testText, { - split: { - prepend: '```md\n', - append: '```' - } - }) - } - - const articleMessage = new ArticleTestMessage(article, feedData) - articleMessage.feed.channel = message.channel.id - - await articleMessage.send(message.client) - await grabMsg.delete() -} diff --git a/services/bot/src/commands/text.js b/services/bot/src/commands/text.js deleted file mode 100644 index 6a0a0c097..000000000 --- a/services/bot/src/commands/text.js +++ /dev/null @@ -1,14 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const commonPrompts = require('./prompts/common/index.js') -const messagePrompts = require('./prompts/text/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectFeedNode = new PromptNode(commonPrompts.selectFeed.prompt) - const setMessageNode = new PromptNode(messagePrompts.setMessage.prompt) - const successNode = new PromptNode(messagePrompts.success.prompt) - - selectFeedNode.addChild(setMessageNode) - setMessageNode.addChild(successNode) - await runWithFeedGuild(selectFeedNode, message) -} diff --git a/services/bot/src/commands/unsub.js b/services/bot/src/commands/unsub.js deleted file mode 100644 index 6461834f3..000000000 --- a/services/bot/src/commands/unsub.js +++ /dev/null @@ -1,34 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const subPrompts = require('./prompts/sub/index.js') -const unsubPrompts = require('./prompts/unsub/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') - -module.exports = async (message) => { - const selectActionNode = new PromptNode(unsubPrompts.selectAction.prompt) - - const inputRemoveRoleNodeCondition = data => data.selected === '1' - const inputRemoveRoleNode = new PromptNode(unsubPrompts.inputRemoveRole.prompt, inputRemoveRoleNodeCondition) - const removeRoleSuccessNode = new PromptNode(unsubPrompts.removeRoleSuccess.prompt) - - inputRemoveRoleNode.addChild(removeRoleSuccessNode) - - const selectFeedNodeCondition = data => data.selected === '2' - const selectFeedNode = new PromptNode(unsubPrompts.selectFeed.prompt, selectFeedNodeCondition) - const removeDirectSuccessNode = new PromptNode(unsubPrompts.removeDirectSuccess.prompt) - - selectFeedNode.addChild(removeDirectSuccessNode) - - const removeAllSuccessNodeCondition = data => data.selected === '3' - const removeAllSuccessNode = new PromptNode(unsubPrompts.removeAllSuccess.prompt, removeAllSuccessNodeCondition) - - const listSubscribedFeedsNodeCondition = data => data.selected === '4' - const listSubscribedFeedsNode = new PromptNode(subPrompts.listSubscribedFeeds.prompt, listSubscribedFeedsNodeCondition) - - selectActionNode.setChildren([ - inputRemoveRoleNode, - selectFeedNode, - removeAllSuccessNode, - listSubscribedFeedsNode - ]) - await runWithFeedGuild(selectActionNode, message) -} diff --git a/services/bot/src/commands/version.js b/services/bot/src/commands/version.js deleted file mode 100644 index 044f0d401..000000000 --- a/services/bot/src/commands/version.js +++ /dev/null @@ -1,7 +0,0 @@ -const fs = require('fs') -const path = require('path') -const packageJSON = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'))) - -module.exports = async (message, command) => { - await message.channel.send(packageJSON.version) -} diff --git a/services/bot/src/commands/webhook.js b/services/bot/src/commands/webhook.js deleted file mode 100644 index d22805b53..000000000 --- a/services/bot/src/commands/webhook.js +++ /dev/null @@ -1,22 +0,0 @@ -const { PromptNode } = require('discord.js-prompts') -const webhookPrompts = require('./prompts/webhook/index.js') -const runWithFeedGuild = require('./prompts/runner/run.js') -const Guild = require('../structs/Guild.js') -const Supporter = require('../structs/db/Supporter.js') - -module.exports = async (message) => { - const guild = new Guild(message.guild.id) - const supporter = await guild.hasSupporterOrSubscriber() - if (Supporter.enabled && !supporter) { - message.channel.send('You must be a patron to add webhooks. See for more details.') - return - } - const selectFeedNode = new PromptNode(webhookPrompts.selectFeed.prompt) - const selectWebhookNode = new PromptNode(webhookPrompts.selectWebhook.prompt) - const removedSuccessNodeCondition = data => data.removed === true - const removedSuccessNode = new PromptNode(webhookPrompts.removedSuccess.prompt, removedSuccessNodeCondition) - - selectFeedNode.addChild(selectWebhookNode) - selectWebhookNode.addChild(removedSuccessNode) - await runWithFeedGuild(selectFeedNode, message) -} diff --git a/services/bot/src/config.js b/services/bot/src/config.js deleted file mode 100644 index 0d06cdd0c..000000000 --- a/services/bot/src/config.js +++ /dev/null @@ -1,348 +0,0 @@ -const moment = require('moment-timezone') -const schema = require('./util/config/schema.js') -const config = schema.defaults - -function envArray (name) { - const value = process.env[name] - if (!value) { - return null - } - return value.split(',').map(s => s.trim()) -} - -function resolveBoolValue (envName, originalValue, overrideValue) { - return process.env[envName] !== undefined - ? process.env[envName] === 'true' - : overrideValue !== undefined - ? overrideValue - : originalValue -} - -function resolveStringValue (envName, originalValue, overrideValue) { - return process.env[envName] || overrideValue || originalValue -} - -exports.set = (override, skipValidation) => { - // APIS - if (!override.apis) { - override.apis = { - pledge: {}, - discordHttpGateway: {} - } - } - const apis = config.apis - - // APIS - Pledge - if (!override.apis.pledge) { - override.apis.pledge = {} - } - const apisPledge = apis.pledge - const apisPledgeOverride = override.apis.pledge - apis.pledge.enabled = resolveBoolValue( - 'DRSS_APIS_PLEDGE_ENABLED', - apisPledge.enabled, - apisPledgeOverride.enabled - ) - apis.pledge.url = process.env.DRSS_APIS_PLEDGE_URL || apisPledgeOverride.url || apisPledge.url - apis.pledge.accessToken = process.env.DRSS_APIS_PLEDGE_ACCESSTOKEN || apisPledgeOverride.accessToken || apisPledge.accessToken - - // APIS - Discord HTTP Gateway - if (!override.apis.discordHttpGateway) { - override.apis.discordHttpGateway = {} - } - const apisDiscordHttpGateway = apis.discordHttpGateway - const apisDiscordHttpGatewayOverride = override.apis.discordHttpGateway - apis.discordHttpGateway.enabled = resolveBoolValue( - 'DRSS_APIS_DISCORDHTTPGATEWAY_ENABLED', - apisDiscordHttpGateway.enabled, - apisDiscordHttpGatewayOverride.enabled - ) - apis.discordHttpGateway.redisUri = resolveStringValue( - 'DRSS_APIS_DISCORDHTTPGATEWAY_REDISURI', - apisDiscordHttpGatewayOverride.redisUri, - apisDiscordHttpGateway.redisUri - ) - apis.discordHttpGateway.rabbitmqUri = resolveStringValue( - 'DRSS_APIS_DISCORDHTTPGATEWAY_RABBITMQURI', - apisDiscordHttpGatewayOverride.rabbitmqUri, - apisDiscordHttpGateway.rabbitmqUri - ) - - // LOG - if (!override.log) { - override.log = {} - } - const log = config.log - const logOverride = override.log - log.level = process.env.DRSS_LOG_LEVEL || logOverride.level || log.level - log.destination = process.env.DRSS_LOG_DESTINATION || logOverride.destination || log.destination - log.linkErrs = process.env.DRSS_LOG_LINKERRS !== undefined - ? process.env.DRSS_LOG_LINKERRS === 'true' - : logOverride.linkErrs !== undefined - ? logOverride.linkErrs - : log.linkErrs - log.unfiltered = process.env.DRSS_LOG_UNFILTERED !== undefined - ? process.env.DRSS_LOG_UNFILTERED === 'true' - : logOverride.unfiltered !== undefined - ? logOverride.unfiltered - : log.unfiltered - log.failedFeeds = process.env.DRSS_LOG_FAILEDFEEDS !== undefined - ? process.env.DRSS_LOG_FAILEDFEEDS === 'true' - : logOverride.failedFeeds !== undefined - ? logOverride.failedFeeds - : log.failedFeeds - log.rateLimitHits = process.env.DRSS_LOG_RATELIMITHITS !== undefined - ? process.env.DRSS_LOG_RATELIMITHITS === 'true' - : logOverride.rateLimitHits !== undefined - ? logOverride.rateLimitHits - : log.rateLimitHits - - // BOT - if (!override.bot) { - override.bot = {} - } - const bot = config.bot - const botOverride = override.bot - bot.clientId = process.env.DRSS_BOT_CLIENTID || botOverride.clientId || bot.clientId - bot.token = process.env.DRSS_BOT_TOKEN || botOverride.token || bot.token - bot.locale = process.env.DRSS_BOT_LOCALE || botOverride.locale || bot.locale - bot.enableCommands = process.env.DRSS_BOT_ENABLECOMMANDS !== undefined - ? process.env.DRSS_BOT_ENABLECOMMANDS === 'true' - : botOverride.enableCommands !== undefined - ? botOverride.enableCommands - : bot.enableCommands - bot.prefix = process.env.DRSS_BOT_PREFIX || botOverride.prefix || bot.prefix - bot.status = process.env.DRSS_BOT_STATUS || botOverride.status || bot.status - bot.activityType = process.env.DRSS_BOT_ACTIVITYTYPE || botOverride.activityType || bot.activityType - bot.activityName = process.env.DRSS_BOT_ACTIVITYNAME || botOverride.activityName || bot.activityName - bot.streamActivityURL = process.env.DRSS_BOT_STREAMACTIVITYURL || botOverride.streamActivityURL || bot.streamActivityURL - bot.ownerIDs = envArray('DRSS_BOT_OWNERIDS') || botOverride.ownerIDs || bot.ownerIDs - bot.menuColor = process.env.DRSS_BOT_MENUCOLOR !== undefined - ? Number(process.env.DRSS_BOT_MENUCOLOR) - : botOverride.menuColor !== undefined - ? botOverride.menuColor - : bot.menuColor - bot.deleteMenus = process.env.DRSS_BOT_DELETEMENUS !== undefined - ? process.env.DRSS_BOT_DELETEMENUS === 'true' - : botOverride.deleteMenus !== undefined - ? botOverride.deleteMenus - : bot.deleteMenus - bot.runSchedulesOnStart = process.env.DRSS_BOT_RUNSCHEDULESONSTART !== undefined - ? process.env.RUNSCHEDULESONSTART === 'true' - : botOverride.runSchedulesOnStart !== undefined - ? botOverride.runSchedulesOnStart - : bot.runSchedulesOnStart - bot.exitOnSocketIssues = process.env.DRSS_BOT_EXITONSOCKETISSUES !== undefined - ? process.env.DRSS_BOT_EXITONSOCKETISSUES === 'true' - : botOverride.exitOnSocketIssues !== undefined - ? botOverride.exitOnSocketIssues - : bot.exitOnSocketIssues - bot.exitOnDatabaseDisconnect = process.env.DRSS_BOT_EXITONDATABASEDISCONNECT !== undefined - ? process.env.DRSS_BOT_EXITONDATABASEDISCONNECT === 'true' - : botOverride.exitOnDatabaseDisconnect !== undefined - ? botOverride.exitOnDatabaseDisconnect - : bot.exitOnDatabaseDisconnect - bot.exitOnExcessRateLimits = process.env.DRSS_BOT_EXITONEXCESSRATELIMITS !== undefined - ? process.env.DRSS_BOT_EXITONEXCESSRATELIMITS === 'true' - : botOverride.exitOnExcessRateLimits !== undefined - ? botOverride.exitOnExcessRateLimits - : bot.exitOnExcessRateLimits - bot.userAgent = process.env.DRSS_BOT_USERAGENT || botOverride.userAgent || bot.userAgent - bot.feedParseTimeoutMs = process.env.DRSS_BOT_FEEDPARSETIMEOUTMS !== undefined - ? Number(process.env.DRSS_BOT_FEEDPARSETIMEOUTMS) - : botOverride.feedParseTimeoutMs !== undefined - ? botOverride.feedParseTimeoutMs - : bot.feedParseTimeoutMs - bot.feedRequestTimeoutMs = process.env.DRSS_BOT_FEEDREQUESTTIMEOUTMS !== undefined - ? Number(process.env.DRSS_BOT_FEEDREQUESTTIMEOUTMS) - : botOverride.feedRequestTimeoutMs !== undefined - ? botOverride.feedRequestTimeoutMs - : bot.feedRequestTimeoutMs - - // DATABASE - if (!override.database) { - override.database = {} - } - const database = config.database - const databaseOverride = override.database - database.uri = process.env.MONGODB_URI || process.env.DRSS_DATABASE_URI || databaseOverride.uri || database.uri - database.redis = process.env.REDIS_URL || process.env.DRSS_DATABASE_REDIS || databaseOverride.redis || database.redis - database.connection = databaseOverride.connection || database.connection - database.articlesExpire = process.env.DRSS_DATABASE_ARTICLESEXPIRE !== undefined - ? Number(process.env.DRSS_DATABASE_ARTICLESEXPIRE) - : databaseOverride.articlesExpire !== undefined - ? databaseOverride.articlesExpire - : database.articlesExpire - database.deliveryRecordsExpire = process.env.DRSS_DATABASE_DELIVERYRECORDSEXPIRE !== undefined - ? Number(process.env.DRSS_DATABASE_DELIVERYRECORDSEXPIRE) - : databaseOverride.deliveryRecordsExpire !== undefined - ? databaseOverride.deliveryRecordsExpire - : database.deliveryRecordsExpire - - // FEEDS - if (!override.feeds) { - override.feeds = {} - } - const feeds = config.feeds - const feedsOverride = override.feeds - feeds.refreshRateMinutes = process.env.DRSS_FEEDS_REFRESHRATEMINUTES !== undefined - ? Number(process.env.DRSS_FEEDS_REFRESHRATEMINUTES) - : feedsOverride.refreshRateMinutes !== undefined - ? feedsOverride.refreshRateMinutes - : feeds.refreshRateMinutes - feeds.articleDequeueRate = process.env.DRSS_FEEDS_ARTICLEDEQUEUERATE !== undefined - ? Number(process.env.DRSS_FEEDS_ARTICLEDEQUEUERATE) - : feedsOverride.articleDequeueRate !== undefined - ? feedsOverride.articleDequeueRate - : feeds.articleDequeueRate - feeds.articleRateLimit = process.env.DRSS_FEEDS_ARTICLERATELIMIT !== undefined - ? Number(process.env.DRSS_FEEDS_ARTICLERATELIMIT) - : feedsOverride.articleRateLimit !== undefined - ? feedsOverride.articleRateLimit - : feeds.articleRateLimit - feeds.articleDailyChannelLimit = process.env.DRSS_FEEDS_ARTICLEDAILYCHANNELLIMIT !== undefined - ? Number(process.env.DRSS_FEEDS_ARTICLEDAILYCHANNELLIMIT) - : feedsOverride.articleDailyChannelLimit !== undefined - ? feedsOverride.articleDailyChannelLimit - : feeds.articleDailyChannelLimit - feeds.timezone = process.env.DRSS_FEEDS_TIMEZONE || feedsOverride.timezone || feeds.timezone - feeds.dateFormat = process.env.DRSS_FEEDS_DATEFORMAT || feedsOverride.dateFormat || feeds.dateFormat - feeds.dateLanguage = process.env.DRSS_FEEDS_DATELANGUAGE || feedsOverride.dateLanguage || feeds.dateLanguage - feeds.dateLanguageList = envArray('DRSS_FEEDS_DATELANGUAGELIST') || feedsOverride.dateLanguageList || feeds.dateLanguageList - feeds.dateFallback = process.env.DRSS_FEEDS_DATEFALLBACK !== undefined - ? process.env.DRSS_FEEDS_DATEFALLBACK === 'true' - : feedsOverride.dateFallback !== undefined - ? feedsOverride.dateFallback - : feeds.dateFallback - feeds.timeFallback = process.env.DRSS_FEEDS_TIMEFALLBACK !== undefined - ? process.env.DRSS_FEEDS_TIMEFALLBACK === 'true' - : feedsOverride.timeFallback !== undefined - ? feedsOverride.timeFallback - : feeds.timeFallback - feeds.max = process.env.DRSS_FEEDS_MAX !== undefined - ? Number(process.env.DRSS_FEEDS_MAX) - : feedsOverride.max !== undefined - ? feedsOverride.max - : feeds.max - feeds.hoursUntilFail = process.env.DRSS_FEEDS_HOURSUNTILFAIL !== undefined - ? Number(process.env.DRSS_FEEDS_HOURSUNTILFAIL) - : feedsOverride.hoursUntilFail !== undefined - ? feedsOverride.hoursUntilFail - : feeds.hoursUntilFail - feeds.notifyFail = process.env.DRSS_FEEDS_NOTIFYFAIL !== undefined - ? process.env.DRSS_FEEDS_NOTIFYFAIL === 'true' - : feedsOverride.notifyFail !== undefined - ? feedsOverride.notifyFail - : feeds.notifyFail - feeds.sendFirstCycle = process.env.DRSS_FEEDS_SENDFIRSTCYCLE !== undefined - ? process.env.DRSS_FEEDS_SENDFIRSTCYCLE === 'true' - : feedsOverride.sendFirstCycle !== undefined - ? feedsOverride.sendFirstCycle - : feeds.sendFirstCycle - feeds.cycleMaxAge = process.env.DRSS_FEEDS_CYCLEMAXAGE !== undefined - ? Number(process.env.DRSS_FEEDS_CYCLEMAXAGE) - : feedsOverride.cycleMaxAge !== undefined - ? feedsOverride.cycleMaxAge - : feeds.cycleMaxAge - feeds.defaultText = process.env.DRSS_FEEDS_DEFAULTTEXT !== undefined - ? process.env.DRSS_FEEDS_DEFAULTTEXT.replace(/\\n/g, '\n') - : feedsOverride.defaultText || feeds.defaultText - feeds.imgPreviews = process.env.DRSS_FEEDS_IMGPREVIEWS !== undefined - ? process.env.DRSS_FEEDS_IMGPREVIEWS === 'true' - : feedsOverride.imgPreviews !== undefined - ? feedsOverride.imgPreviews - : feeds.imgPreviews - feeds.imgLinksExistence = process.env.DRSS_FEEDS_IMGLINKSEXISTENCE !== undefined - ? process.env.DRSS_FEEDS_IMGLINKSEXISTENCE === 'true' - : feedsOverride.imgLinksExistence !== undefined - ? feedsOverride.imgLinksExistence - : feeds.imgLinksExistence - feeds.checkDates = process.env.DRSS_FEEDS_CHECKDATES !== undefined - ? process.env.DRSS_FEEDS_CHECKDATES === 'true' - : feedsOverride.checkDates !== undefined - ? feedsOverride.checkDates - : feeds.checkDates - feeds.formatTables = process.env.DRSS_FEEDS_FORMATTABLES !== undefined - ? process.env.DRSS_FEEDS_FORMATTABLES === 'true' - : feedsOverride.formatTables !== undefined - ? feedsOverride.formatTables - : feeds.formatTables - feeds.directSubscribers = process.env.DRSS_FEEDS_DIRECTSUBSCRIBERS !== undefined - ? process.env.DRSS_FEEDS_DIRECTSUBSCRIBERS === 'true' - : feedsOverride.directSubscribers !== undefined - ? feedsOverride.directSubscribers - : feeds.directSubscribers - feeds.decode = feedsOverride.decode || feeds.decode - - // ADVANCED - if (!override.advanced) { - override.advanced = {} - } - const advanced = config.advanced - const advancedOverride = override.advanced - advanced.shards = process.env.DRSS_ADVANCED_SHARDS !== undefined - ? Number(process.env.DRSS_ADVANCED_SHARDS) - : advancedOverride.shards !== undefined - ? advancedOverride.shards - : advanced.shards - advanced.batchSize = process.env.DRSS_ADVANCED_BATCHSIZE !== undefined - ? Number(process.env.DRSS_ADVANCED_BATCHSIZE) - : advancedOverride.batchSize !== undefined - ? advancedOverride.batchSize - : advanced.batchSize - advanced.parallelBatches = process.env.DRSS_ADVANCED_PARALLELBATCHES !== undefined - ? Number(process.env.DRSS_ADVANCED_PARALLELBATCHES) - : advancedOverride.parallelBatches !== undefined - ? advancedOverride.parallelBatches - : advanced.parallelBatches - advanced.parallelRuns = process.env.DRSS_ADVANCED_PARALLELRUNS !== undefined - ? Number(process.env.DRSS_ADVANCED_PARALLELRUNS) - : advancedOverride.parallelRuns !== undefined - ? advancedOverride.parallelRuns - : advanced.parallelRuns - - // Web URL - config.webURL = process.env.DRSS_WEBURL || override.webURL || config.webURL - config.discordSupportURL = process.env.DRSS_DISCORDSUPPORTURL || override.discordSupportURL || config.discordSupportURL - - // Disale feed cycles - config.disableFeedCycles = resolveBoolValue( - 'DRSS_DISABLEFEEDCYCLES', - config.disableFeedCycles, - override.disableFeedCycles - ) - - // Other private ones - config.dev = process.env.DRSS_DEV !== undefined - ? Number(process.env.DRSS_DEV) - : override.dev !== undefined - ? override.dev - : config.dev - config._vipRestricted = process.env.DRSS__VIPRESTRICTED !== undefined - ? Number(process.env.DRSS__VIPRESTRICTED) - : override._vipRestricted !== undefined - ? override._vipRestricted - : config._vipRestricted - config._vip = process.env.DRSS__VIP !== undefined - ? Number(process.env.DRSS__VIP) - : override._vip !== undefined - ? override._vip - : config._vip - config._vipRefreshRateMinutes = process.env.DRSS__vipRefreshRateMinutes !== undefined - ? Number(process.env.DRSS__vipRefreshRateMinutes) - : override._vipRefreshRateMinutes !== undefined - ? override._vipRefreshRateMinutes - : config._vipRefreshRateMinutes - - if (process.env.NODE_ENV !== 'test') { - moment.locale(config.feeds.dateLanguage) - // .validate can throw a TypeError - if (!skipValidation) { - schema.validate(config) - } - } - - return exports.get() -} - -exports.get = () => config diff --git a/services/bot/src/events/channelDelete.js b/services/bot/src/events/channelDelete.js deleted file mode 100644 index 0ac9494ee..000000000 --- a/services/bot/src/events/channelDelete.js +++ /dev/null @@ -1,17 +0,0 @@ -const Discord = require('discord.js') -const Feed = require('../structs/db/Feed.js') -const createLogger = require('../util/logger/create.js') - -module.exports = async channel => { - if (!(channel instanceof Discord.GuildChannel)) { - return - } - const feeds = await Feed.getManyBy('channel', channel.id) - feeds.forEach(feed => { - feed.delete() - .catch(err => { - const log = createLogger(channel.guild.shard.id) - log.error(err, 'Failed to delete feed after channel deletion') - }) - }) -} diff --git a/services/bot/src/events/channelUpdate.js b/services/bot/src/events/channelUpdate.js deleted file mode 100644 index 115b56bcd..000000000 --- a/services/bot/src/events/channelUpdate.js +++ /dev/null @@ -1,27 +0,0 @@ -const Discord = require('discord.js') -const Feed = require('../structs/db/Feed.js') -const createLogger = require('../util/logger/create.js') -const maintenance = require('../maintenance/index.js') - -module.exports = async (oldChannel, newChannel) => { - if (!(newChannel instanceof Discord.GuildChannel) || !(oldChannel instanceof Discord.GuildChannel)) { - return - } - const client = newChannel.client - const log = createLogger(oldChannel.guild.shard.id) - const deleted = newChannel.guild.deleted || newChannel.deleted || !client.channels.cache.has(newChannel.id) - try { - const feeds = await Feed.getManyBy('channel', newChannel.id) - for (const feed of feeds) { - if (deleted) { - feed.delete() - .catch(err => log.error(err, 'Failed to delete due to deleted channel')) - } else { - maintenance.checkPermissions.feed(feed, client) - .catch(err => log.error(err, `Failed to check permissions of feed ${feed._id} after channel update`)) - } - } - } catch (err) { - log.error(err, 'Failed to check feeds in channelUpdate') - } -} diff --git a/services/bot/src/events/guildCreate.js b/services/bot/src/events/guildCreate.js deleted file mode 100644 index 11c72814b..000000000 --- a/services/bot/src/events/guildCreate.js +++ /dev/null @@ -1,6 +0,0 @@ -const createLogger = require('../util/logger/create.js') - -module.exports = guild => { - const log = createLogger(guild.shard.id) - log.info({ guild }, `Guild (Members: ${guild.memberCount}) has been added`) -} diff --git a/services/bot/src/events/guildDelete.js b/services/bot/src/events/guildDelete.js deleted file mode 100644 index afb913146..000000000 --- a/services/bot/src/events/guildDelete.js +++ /dev/null @@ -1,16 +0,0 @@ -const GuildData = require('../structs/GuildData.js') -const createLogger = require('../util/logger/create.js') - -module.exports = async guild => { - const log = createLogger(guild.shard.id) - log.info({ guild }, `Guild (Members: ${guild.memberCount}) has been removed`) - try { - const guildData = await GuildData.get(guild.id) - await guildData.delete() - } catch (err) { - log.warn({ - error: err, - guild - }, 'Failed to remove data of guild') - } -} diff --git a/services/bot/src/events/guildUpdate.js b/services/bot/src/events/guildUpdate.js deleted file mode 100644 index 5bcd6cb9a..000000000 --- a/services/bot/src/events/guildUpdate.js +++ /dev/null @@ -1,15 +0,0 @@ -const Profile = require('../structs/db/Profile.js') -const createLogger = require('../util/logger/create.js') - -module.exports = async (oldGuild, newGuild) => { - try { - const profile = await Profile.get(newGuild.id) - if (profile && profile.name !== newGuild.name) { - profile.name = newGuild.name - await profile.save() - } - } catch (err) { - const log = createLogger(oldGuild.shard.id) - log.warn(err, 'Could not update guild after name change event') - } -} diff --git a/services/bot/src/events/message.js b/services/bot/src/events/message.js deleted file mode 100644 index 12b56f11a..000000000 --- a/services/bot/src/events/message.js +++ /dev/null @@ -1,74 +0,0 @@ -const Command = require('../structs/Command.js') -const createLogger = require('../util/logger/create.js') -const config = require('../config') - -/** - * Handle discord messages from ws - * @param {import('discord.js').Message} message - Discord message - */ -async function handler (message) { - const { guild, author, channel, client, member } = message - const log = createLogger(client.shard.ids[0], { - message, - guild, - channel, - user: author - }) - if (Command.shouldIgnore(message, log)) { - return - } - // Check command validity - const command = Command.tryGetCommand(message, log) - if (!command) { - return log.trace('No valid command found') - } - try { - // Check member - const memberPerms = command.getMemberPermission() - log.debug({ - requiredPerms: Command.getPermissionNames(memberPerms) - }, 'Checking member permissions') - const hasMemberPermission = command.hasMemberPermission(message) - if (!hasMemberPermission) { - const missingPerms = command.getMissingChannelPermissions(memberPerms, member, channel) - const requiredPerms = await command.notifyMissingMemberPerms(message, missingPerms) - return log.info(`Member permissions ${requiredPerms} missing for command ${command.name}`) - } - // If commands are disabled, ignore if it's not an owner - if (!Command.enabled && !Command.isOwnerID(author.id)) { - return log.info(`Command ${command.name} disabled, only owners allowed`) - } - - if (!Command.isOwnerID(author.id) && await Command.blockIfNotSupporter(message)) { - const visitUrl = config.get().apis.pledge.url.replace('/api', '') - return await message.channel.send( - `Sorry, only supporters have access to this bot. To become a supporter, visit ${visitUrl}` - ) - } - - // Check bot - const botPerms = command.getBotPermissions() - log.debug({ - requiredPerms: Command.getPermissionNames(botPerms) - }, 'Checking bot permissions') - const hasBotPermission = command.hasBotPermission(message) - if (!hasBotPermission) { - const missingPerms = command.getMissingChannelPermissions(botPerms, guild.me, channel) - const requiredPerms = await command.notifyMissingBotPerms(message, missingPerms) - return log.info(`Bot permissions ${requiredPerms} missing for command ${command.name}`) - } - // Run - log.info(`Used ${command.name}`) - await command.run(message) - } catch (err) { - if (err.code !== 50013) { - log.error(err, 'Message listener error (not 50013)') - message.channel.send(err.message) - .catch(err => log.error(err, 'Failed to send error message to channel')) - } else { - log.warn(err, 'Message listener (50013 permission error)') - } - } -} - -module.exports = handler diff --git a/services/bot/src/events/roleDelete.js b/services/bot/src/events/roleDelete.js deleted file mode 100644 index adedb6be2..000000000 --- a/services/bot/src/events/roleDelete.js +++ /dev/null @@ -1,17 +0,0 @@ -const Subscriber = require('../structs/db/Subscriber.js') -const createLogger = require('../util/logger/create.js') - -module.exports = async role => { - const log = createLogger(role.guild.shard.id) - try { - const subscribers = await Subscriber.getManyBy('id', role.id) - subscribers.forEach(subscriber => { - subscriber.delete() - .catch(err => { - log.error(err, 'Failed to delete subscriber after role deletion') - }) - }) - } catch (err) { - log.error(err, 'Role could not be removed from config by guild role deletion') - } -} diff --git a/services/bot/src/initialization/index.js b/services/bot/src/initialization/index.js deleted file mode 100644 index 850269f65..000000000 --- a/services/bot/src/initialization/index.js +++ /dev/null @@ -1,13 +0,0 @@ -const populateKeyValues = require('./populateKeyValues.js') -const populateSchedules = require('./populateSchedules') -const setupCommands = require('./setupCommands.js') -const setupModels = require('./setupModels.js') -const setupRateLimiters = require('./setupRateLimiters.js') - -module.exports = { - populateKeyValues, - populateSchedules, - setupCommands, - setupModels, - setupRateLimiters -} diff --git a/services/bot/src/initialization/populateKeyValues.js b/services/bot/src/initialization/populateKeyValues.js deleted file mode 100644 index f95b74885..000000000 --- a/services/bot/src/initialization/populateKeyValues.js +++ /dev/null @@ -1,34 +0,0 @@ -const Supporter = require('../structs/db/Supporter.js') -const KeyValue = require('../structs/db/KeyValue.js') -const getConfig = require('../config').get - -/** - * Stores the feeds config for use by the control panel - * that is an external process - */ -async function populateKeyValues () { - const config = getConfig() - await KeyValue.deleteAll() - const feedConfigData = { - _id: KeyValue.keys.FEED_CONFIG, - value: { - ...config.feeds, - decode: {} - } - } - const supporterConfigData = { - _id: KeyValue.keys.SUPPORTER_CONFIG, - value: { - [Supporter.keys.ENABLED]: config[Supporter.keys.ENABLED], - [Supporter.keys.REFRESH_RATE]: config[Supporter.keys.REFRESH_RATE] - } - } - const feedsConfig = new KeyValue(feedConfigData) - const supporterConfig = new KeyValue(supporterConfigData) - await Promise.all([ - feedsConfig.save(), - supporterConfig.save() - ]) -} - -module.exports = populateKeyValues diff --git a/services/bot/src/initialization/populateSchedules.js b/services/bot/src/initialization/populateSchedules.js deleted file mode 100644 index b499d38b9..000000000 --- a/services/bot/src/initialization/populateSchedules.js +++ /dev/null @@ -1,53 +0,0 @@ -const Supporter = require('../structs/db/Supporter.js') -const Schedule = require('../structs/db/Schedule.js') -const getConfig = require('../config.js').get - -/** - * Create schedules for feeds to be assigned to - * @param {Object>} customSchedules - * @returns {Schedule[]} - */ -async function populateSchedules (customSchedules = {}) { - const config = getConfig() - await Schedule.deleteAll() - const schedules = [] - const defaultSchedule = new Schedule({ - name: 'default', - refreshRateMinutes: config.feeds.refreshRateMinutes - }) - - schedules.push(defaultSchedule) - - for (const name in customSchedules) { - if (name === 'example') { - continue - } - const schedule = customSchedules[name] - const { refreshRateMinutes } = schedule - const custom = new Schedule({ - name, - refreshRateMinutes, - keywords: schedule.keywords || [], - feeds: schedule.feeds || [] - }) - schedules.push(custom) - } - - if (Supporter.enabled) { - const supporterRefreshRate = Supporter.schedule.refreshRateMinutes - if (!supporterRefreshRate || config.feeds.refreshRateMinutes === supporterRefreshRate) { - throw new Error('Missing valid supporter refresh rate') - } - const supporterSchedule = new Schedule({ - name: Supporter.schedule.name, - refreshRateMinutes: supporterRefreshRate, - // Determined at runtime - keywords: [] - }) - schedules.push(supporterSchedule) - } - await Promise.all(schedules.map(s => s.save())) - return schedules -} - -module.exports = populateSchedules diff --git a/services/bot/src/initialization/setupCommands.js b/services/bot/src/initialization/setupCommands.js deleted file mode 100644 index fe7269a47..000000000 --- a/services/bot/src/initialization/setupCommands.js +++ /dev/null @@ -1,14 +0,0 @@ -const Profile = require('../structs/db/Profile.js') -const Command = require('../structs/Command.js') - -async function setupCommands (disableCommands) { - await Profile.populatePrefixes() - await Command.initialize() - if (disableCommands) { - Command.disable() - } else { - Command.enable() - } -} - -module.exports = setupCommands diff --git a/services/bot/src/initialization/setupModels.js b/services/bot/src/initialization/setupModels.js deleted file mode 100644 index 56bef7c4a..000000000 --- a/services/bot/src/initialization/setupModels.js +++ /dev/null @@ -1,28 +0,0 @@ -const fs = require('fs') -const path = require('path') -const mongoose = require('mongoose') -const pascalToSnake = require('../util/pascalToSnake.js') - -/** - * Sets up all the mongoose models - * - * @param {import('mongoose').Connection} connection - */ -async function setupModels (connection) { - if (!connection) { - connection = mongoose - } - const modelsPath = path.join(__dirname, '..', 'models') - const contents = await fs.promises.readdir(modelsPath, 'utf-8') - const files = contents.filter(name => name.endsWith('.js')) - for (const name of files) { - const required = require(`../models/${name}`) - const modelName = pascalToSnake(name).replace('.js', '') - if (required.setupHooks) { - required.setupHooks(connection) - } - required.Model = connection.model(modelName, required.schema) - } -} - -module.exports = setupModels diff --git a/services/bot/src/initialization/setupRateLimiters.js b/services/bot/src/initialization/setupRateLimiters.js deleted file mode 100644 index 472f8bdca..000000000 --- a/services/bot/src/initialization/setupRateLimiters.js +++ /dev/null @@ -1,29 +0,0 @@ -const Feed = require('../structs/db/Feed.js') -const ArticleMessageRateLimiter = require('../structs/ArticleMessageRateLimiter.js') -const Guild = require('../structs/Guild.js') - -/** - * @param {import('discord.js').Client} bot - */ -async function setupRateLimiters (bot) { - const guilds = bot.guilds.cache.keyArray() - const feeds = await Feed.getManyByQuery({ - guild: { - $in: guilds - } - }) - const supporterGuilds = await Guild.getFastSupporterAndSubscriberGuildIds() - for (var i = feeds.length - 1; i >= 0; --i) { - const feed = feeds[i] - const channel = bot.channels.cache.get(feed.channel) - if (!channel) { - continue - } - const isSupporter = supporterGuilds.has(channel.guild.id) - if (!ArticleMessageRateLimiter.hasLimiter(feed.channel)) { - ArticleMessageRateLimiter.create(feed.channel, isSupporter) - } - } -} - -module.exports = setupRateLimiters diff --git a/services/bot/src/locales/en-US.json b/services/bot/src/locales/en-US.json deleted file mode 100644 index 3c194989c..000000000 --- a/services/bot/src/locales/en-US.json +++ /dev/null @@ -1,613 +0,0 @@ -{ - "generics": { - "defaultSetting": "Default is `{{value}}`.", - "backupReminder": "After completely setting up, it is recommended that you use {{prefix}}backup to have a personal backup of your settings.", - "channelUpper": "Channel", - "channelLower": "channel", - "enabledUpper": "Enabled", - "enabledLower": "enabled", - "disabledUpper": "Disabled", - "disabledLower": "disabled" - }, - "commandDescriptions": { - "add": { - "description": "Add an RSS feed to the channel this command is used in. Multiple feeds can be added by separation with `>`.", - "args": { - "": "Feed URL." - } - }, - "alert": { - "description": "Set up user direct messaging for feed warnings/failures.", - "args": { - "add ": "Add a user to direct message.", - "remove ": "Remove a user from direct messaging.", - "list": "List the current users enabled for direct messaging." - } - }, - "backup": { - "description": "Send server profile as a JSON attachment for personal backups." - }, - "clone": { - "action": "Feed Settings Cloning", - "description": "Clone a feed's settings to other feed(s)." - }, - "compare": { - "action": "Set comparisons to article checks", - "description": "Add additional positive/negative comparisons on articles for greater control over which articles get sent to your server, such as blocking or re-sending them.", - "args": { - "list": "List the current comparisons.", - "(+|-)": "The positive or negative article properties to add for comparison. More than one can be added." - } - }, - "date": { - "description": "Open a menu to customize how dates are displayed." - }, - "dump": { - "action": "Raw Placeholders Dump", - "args": { - "original": "Output the original, untrimmed JSON form instead." - } - }, - "embed": { - "action": "Embed Customization", - "description": "Open a menu to customize a feed's embed message. This will replace the normal embed Discord usually sends when a link is posted." - }, - "embed.fields": { - "action": "Embed Fields Customization", - "description": "Open a menu to customize fields for a feed's embed message. This will replace the normal embed Discord usually sends when a link is posted." - }, - "filters": { - "action": "Feed Filter Addition/Removal", - "description": "Open a menu to add or remove global filters from a feed. Messages that do not have any of the words in any of your filters won't be sent to your Discord." - }, - "invite": { - "description": "Send the invite links for this bot." - }, - "list": { - "description": "List all active feeds in server.", - "args": { - "[] []": "Mention a channel to only show feeds in that channel, and/or add a search query." - } - }, - "locale": { - "description": "Change the language of commands (if the desired language is supported)", - "args": { - "": "The desired locale" - } - }, - "mention": { - "action": "Subscriber Customization", - "description": "Open a menu to add global/filtered subscriptions for roles/users to feeds." - }, - "mention.filters": { - "description": "Open a menu to manage filters for subscribers so they only get mentioned when its filters pass for any particular delivered article." - }, - "move": { - "description": "Open a menu to move a feed into another channel.", - "action": "Feed Channel Transfer" - }, - "options": { - "description": "Open a menu for miscellaneous feed options." - }, - "prefix": { - "action": "Commands Prefix Change", - "description": "Change the prefix used for commands from the default ({{defaultPrefix}}).", - "args": { - "": "The prefix to use.", - "reset": "Reset prefix back to default ({{defaultPrefix}})." - } - }, - "refresh": { - "action": "Refresh Feed", - "description": "Open a menu to restore the feed link back onto the regular cycle after removal due to consecutively surpassing the fail limit." - }, - "remove": { - "action": "Feed Removal", - "description": "Open a menu to delete a feed from the channel." - }, - "split": { - "action": "Message Splitting Customization", - "description": "Open a menu to customize message splitting settings." - }, - "stats": { - "description": "Show simple stats on bot performance and size." - }, - "sub": { - "description": "Open a menu to add a role with an active feed subscription to the user. Usable by anyone in server, enabled/disabled by \"Manage Roles\" permission. Roles *must* be below the bot's role in role order in role settings." - }, - "sub.filters": { - "description": "Open a menu to add filters to yourself (or test them) as a direct subscriber for a particular feed." - }, - "test": { - "args": { - "[simple] [latest]": "Add `simple` to omit test results. Add `latest` to try and send the latest article." - }, - "action": "Feed Delivery Test", - "description": "Opens a menu to send a test message for a random article in a feed, along with the available properties and placeholders for various customizations. You may add `simple` as a parameter to exclude the test details." - }, - "text": { - "action": "Text Customization", - "description": "Open a menu to customize a feed's text message." - }, - "unsub": { - "description": "Open a menu similar to `sub`, except to remove a role. Any role beneath the bot's role order will be removeable." - }, - "version": { - "description": "Show version of the bot." - }, - "webhook": { - "action": "Webhook Connection", - "description": "Open a menu to connect a webhook to a feed to send messages instead." - } - }, - "commands": { - "add": { - "correctSyntax": "The correct syntax is {{prefix}}add https://www.some_url_here.com. Multiple links can be added at once, separated by `>`.", - "improperFormat": "Invalid/improperly-formatted link.", - "processing": "Processing...", - "bannedFeed": "This feed is banned (reason: {{reason}}).", - "limitReached": "Maximum feed limit of {{max}} reached.", - "alreadyExists": "Already exists for this channel.", - "success": "The following feeds have been successfully added to **this channel**", - "successInfo": "Articles will be automatically delivered once new articles are found.", - "failedLimit": "Feed(s) not listed here could not be added due to the feed limit ({{max}}).", - "failedList": "The following feed(s) could not be added", - "reason": "Reason" - }, - "alert": { - "noFeeds": "You cannot set up user alerts if you have not added any feeds.", - "info": "This command adds users for direct messaging (instead of posting such alerts to the feed's channel by default) when there are any warnings or alerts concerning feeds, such as feed limit changes or feed failures. **The user you're adding or removing will be notified.** The correct syntaxes are:\n\n`{{prefix}}alert add ` - Add a user to alerted.\n`{{prefix}}alert remove ` - Remove a user currently receiving alerts\n`{{prefix}}alert list` - Show all users currently receiving alerts", - "notFound": "The user {{user}} was not found in this server.", - "everyoneNotAllowed": "`@everyone` and `@here` are not allowed. Users must be added individually.", - "alreadyEnabled": "That user is already enabled for direct messaging feed warnings/failures in this server.", - "successDM": "At the request of {{member}}, you will now be notified of any warnings/failures of feeds in the server `{{guildName}}` (ID `{{guildID}}`).", - "success": "Successfully enabled user {{member}} for direct messaging feed warnings/failures. The user has been notified of this change.", - "removedDM": "At the request of {{member}}, you will no longer be notified of any warnings or failures of feeds in the server `{{guildName}}` (ID `{{guildID}}`).", - "removed": "Successfully removed user {{member}} from direct messaging feed warnings/failures. The user has been notified of this change.", - "removeFail": "You cannot remove a user that is not currently enabled for feed warning/failure direct messaging alerts.", - "listEmpty": "There are currently no users that will be notified through direct messaging when there are feed warnings/failures in this server.", - "list": "The current list of users below will be notified through direct messaging when there are feed warnings/failures in this server:\n\n" - }, - "backup": { - "noProfile": "This server does not have a profile.", - "noPermission": "Unable to send backup due to missing `Attach Files` permission." - }, - "clone": { - "copyFrom": "Select the source to copy from.", - "copyTo": "Select the destination(s) to copy to.", - "inputProperties": "Input properties to clone, separated by commas. The properties available for cloning are `{{properties}}`. To clone all these properties, you can just use `all`.\n\nFor example, to clone a feed's filters and message, type `filters,message`.", - "invalidProperties": "Invalid properties found: `{{invalid}}`\n\nValid properties available for cloning are `{{properties}}`.", - "confirm": "The following settings for the feed <{{link}}>\n\n`{{cloning}}`\n\nare about to be cloned into the following feeds:\n```{{destinations}}```\nNote that if a property does not exist in the source feed, the same property in the destination feed(s) will be **deleted**.To confirm this, type `yes`. Otherwise, type `exit` to cancel.", - "confirmError": "You must confirm by typing `yes`, or cancel by typing `exit`. Try again.", - "success": "The following settings\n\n`{{cloned}}`\n\nfor the feed <{{link}}> have been successfully cloned into {{destinationCount}} feed(s)." - }, - "compare": { - "info": "For greater control over what articles get sent to your server, you can add additional comparisons on article properties. There are \"positive\" and \"negative\" comparisons. Each property must be prepended with `+` for positive or `-` for negative.\n\nAn example would be `{{prefix}}compare -title`. **This overwrites the current comparison settings.** For more information on what these comparisons mean, please refer to {{infoURL}}.", - "list": "The current comparisons are currently set for the feed <{{url}}>:\n\n{{values}}", - "listNone": "There are no comparisons set for the feed <{{url}}>.", - "invalid": "Invalid inputs were found. All comparisons must be prepended with `+` or `-`. The invalid inputs were:\n\n{{errors}}", - "onlyTitle": "Only the negative comparison `-title` is supported at this time.", - "reset": "Comparisons have been successfully reset for the feed <{{url}}>.", - "success": "The following comparisons have been set for the feed <{{url}}>:\n\n{{added}}" - }, - "date": { - "selectTitle": "Date Customizations", - "dateLanguage": "Date language", - "dateFormat": "Date format", - "timezone": "Timezone", - "noFeeds": "You cannot customize the date placeholder if you have not added any feeds.", - "description": "\u200b\nPlease select an option to customize the {date} placeholder by typing its number, or type `exit` to cancel.\u200b\n\u200b\n", - "optionCurrentSetting": "Your current setting is `{{value}}`.", - "optionChangeTimezone": "Change Timezone", - "optionCustomizeFormat": "Customize Format", - "optionChangeLanguage": "Change Language", - "optionReset": "Reset", - "optionResetValue": "Reset all of the above back to default.", - "successResetAll": "All date customizations have been reset back to default.", - "successReset": "{{name}} has been reset to the default: `{{value}}`.", - "successSet": "{{name}} has been successfully updated to `{{value}}`.", - "promptNewLanguage": "Type the abbreviation for a new language now, `reset` to reset back to default, or `exit` to cancel. The available list of languages supported at this time are (separated by commas):\n\n{{localesList}}", - "promptNewDateFormat": "Type your new date format now, `reset` to reset back to default, or `exit` to cancel. See on how to format a date.", - "promptNewTimezone": "Type your new timezone now, `reset` to reset back to default, or `exit` to cancel. See for a list of timezones under the TZ column.", - "invalidLanguage": "`{{input}}` is not a supported language abbreviation. The available languages are:\n\n{{localesList}}\n\nTry again, or type `exit` to cancel.", - "invalidTimezone": "`{{input}}` is not a valid timezone. See for more information. Valid timezones are in the `TZ` column. Try again, or type `exit` to cancel." - }, - "dump": { - "generatingDump": "Generating dump...", - "generatedDump": "Dump has been generated. Attempting to attach file, see below." - }, - "embed": { - "numberedEmbed": "Embed #{{number}}", - "embedSelection": "Embed Selection", - "embedSelectionDescription": "The properties for each embed is shown below. Select one of the embeds by typing the number next to it, or type `exit` to cancel.\n\u200b", - "embedSelectionOptionAdd": "Add a new embed", - "embedSelectionOptionAddDescription": "A maximum of 10 embeds may be added.\n\u200b", - "embedSelectionOptionRemoveAll": "Remove all embeds", - "currentProperties": "Current Properties", - "currentPropertiesList": "The current embed properties for {{link}} are: \n{{list}}```\n", - "availableProperties": "Available Properties", - "availablePropertiesList": "The list of embed properties that can be set are:\n{{list}}\nType the embed property (for example, `color` or `message`) you want to set/reset, or multiple properties by separation with commas (for example, `color, message`). Type `reset` to remove all properties, or type `exit` to cancel.", - "invalidColorNumber": "The color must be an **number**. See . Try again, or type `exit` to cancel.", - "invalidColorRange": "The color must be a number between 0 and 16777215. Try again, or type `exit` to cancel.", - "invalidProperties": "The following properties {{invalids}} are invalid. Try again, or type `exit` to cancel.", - "removedAllEmbeds": "Successfully removed all embeds from feed <{{link}}>.", - "removedEmbed": "Embed has been disabled, and all properties have been removed for <{{link}}>.", - "resetSuccess": "☑ **{{propName}}** has been reset\n", - "updatedSuccess": "☑ **{{propName}}** updated to \n```\n{{userSetting}}\n```\n", - "updatedInfo": "Settings updated for <{{link}}>:\n\n{{resetList}}{{updateList}}\nYou may use `{{prefix}}test` or `{{prefix}}test simple` to see your new embed format.", - "title": "Title", - "titleDescription": "Title under Author Title\nAccepts placeholders", - "description": "Description", - "descriptionDescription": "Main message\nAccepts placeholders", - "url": "URL", - "urlDescription": "Clicking on the Title/Thumbnail will lead to this URL\nMUST be a link. Set to the article's url by default", - "color": "Color", - "colorDescription": "Sidebar color\nMUST be an integer color between 0 and 16777215. See https://www.shodor.org/stella2java/rgbint.html", - "timestamp": "Timestamp", - "timestampDescription": "Date that is visually localized to every user\nIf an invalid timestamp date is detected, timestamp will not be shown", - "footerIconURL": "Footer Icon URL", - "footerIconURLDescription": "Icon to the left of Footer Text\nMUST be a link to an image. If Footer Text is unspecified, the Footer Icon will be hidden\nAccepts placeholders", - "footerText": "Footer Text", - "footerTextDescription": "Bottom-most text\nAccepts placeholders", - "thumbnailURL": "Thumbnail Image URL", - "thumbnailURLDescription": "Image on the right side\nMUST be a link to an image, OR an image placeholder", - "imageURL": "Image URL", - "imageURLDescription": "Image on the bottom\nMUST be a link to an image, OR an image placeholder", - "authorName": "Author Name", - "authorNameDescription": "Name at the top\nAccepts placeholders", - "authorURL": "Author URL", - "authorURLDescription": "Clicking on the Author Name will lead to this URL\nMUST be a link", - "authorIconURL": "Author Icon URL", - "authorIconURLDescription": "Icon to the left of Author Name\nMUST be a link to an image. If Author Name is unspecified, the Author Icon will be hidden", - "settingProperty": "You are now customizing the **{{property}}**. Type your input now. To reset the property, type `reset`.", - "settingPropertyTimestamp": "You are now customizing the **timestamp**. Type your input now.\n\nTo set the timestamp to the time the article is sent to Discord, type `now`.\nTo set the timestamp to the time the article was published (if available in the feed), type `article`.\nFor a custom timestamp, type the text representation of a custom date (see ).\n\nTo reset the property, type `reset`.", - "settingPropertyTimestampError": "That is not a valid setting. It must be either `now`, `article`, or a valid text representation of a date. Try again.", - "inline": "Inline", - "regular": "Regular", - "blankField": "Blank Field", - "embedFields": "Embed Fields", - "embedFieldsRemoveNone": "There are no embed fields to remove for this feed. Try again, or type `exit` to cancel.", - "embedFieldsRemoved": "The Field(s) numbered {{numbers}} have been removed from the embed for the feed <{{link}}>.", - "embedFieldsDescription": "\u200b\nSelect whether to add or remove a field from this feed's embed. For an example of what a field looks like, see https://i.imgur.com/WSHwmyB.png. Type `exit` to cancel.\n\u200b", - "embedFieldsOptionRemove": "Remove a Field", - "embedFieldsOptionRemoveDescription": "Remove a Field if it exists.", - "embedFieldsOptionAddRegular": "Add a regular Field", - "embedFieldsOptionAddRegularDescription": "Fields will be stacked on top of each other.", - "embedFieldsOptionAddInline": "Add an inline Field", - "embedFieldsOptionAddInlineDescription": "Fields will be placed beside each other whenever possible instead of being stacked.", - "embedFieldsOptionAddRegularBlank": "Add a regular blank Field", - "embedFieldsOptionAddRegularBlankDescription": "Contains no title or description. This is used to take up empty space.", - "embedFieldsOptionAddInlineBlank": "Add an inline blank Field", - "embedFieldsOptionAddInlineBlankDescription": "A Blank Field, but inline.", - "embedFieldsOptionRemoveEmbedTitle": "Embed Fields Removal", - "embedFieldsOptionRemoveEmbedDescription": "\u200b\nYour Fields are listed below, ordered by when they were added. Type the Field's number to remove it, or type multiple Field numbers separateed by commas (`,`). Type `exit` to cancel.\n\u200b", - "embedFieldsAddedBlank": "An blank Field has been added to the embed for the feed <{{link}}>.", - "embedFieldsAddedBlankInline": "An inline blank Field has been added to the embed for the feed <{{link}}>.", - "embedFieldsSettingPrompt": "Set your Field settings now. The **first line will be the Field title**, and **any new lines after the first will be the Field description**. If there is no content after the first line, then it will be an empty description. Type `exit` to cancel.", - "embedFieldsSettingTitleLong": "Titles cannot exceed 256 characters. Try again, or type `exit` to cancel.", - "embedFieldsSettingValueLong": "Field values cannot exceed 1024 characters. Try again, or type `exit` to cancel.", - "embedFieldsAdded": "A new{{type}} Field has been added to the embed with the following details:\n\n**Title**\n```\n{{name}}\n```\n**Value**\n```\n{{value}}\n```\nfor the feed {{link}}." - }, - "faq": { - "searchQueryRequired": "A search query is required.", - "searching": "Searching...", - "noResults": "No results found 🙁" - }, - "filters": { - "selectFeedDescription": "**Feed Title:** {{title}}\n**Feed Link:** {{link}}\n\nSelect an option by typing its number, or type *exit* to cancel. Only messages that contain any of the words defined in these feed filters will be sent to Discord.\u200b\n\u200b\n", - "feedFiltersCustomization": "Feed Filters Customization", - "optionAddFilters": "Add feed filter(s)", - "optionAddFiltersDescription": "Add new filter(s) to a specific category in a feed.", - "optionRemoveFilters": "Remove feed filter(s)", - "optionRemoveFiltersDescription": "Remove existing filter(s), if any.", - "optionRemoveAllFilters": "Remove all feed filter(s)", - "optionRemoveAllFiltersDescription": "Remove all filter(s), if any.", - "optionListFilters": "List existing filter(s)", - "optionListFiltersDescription": "List all filters in all categories, if any.", - "optionSendArticle": "Send passing article", - "optionSendArticleDescription": "Send a randomly chosen article that passes current filter(s) if they exist.", - "removedAllSuccess": "All feed filters have been successfully removed from <{{link}}>.", - "noFilters": "There are no filters assigned to {{link}}.", - "noFiltersTryAgain": "There are no filters assigned to {{link}}. Select another option, or type `exit` to cancel.", - "listFiltersDescription": "Below are the filter categories with their words/phrases under each for the feed <{{link}}> in the channel {{channel}}.", - "connectionFailureLimit": "Unable to send article since failure limit has been reached (see {{prefix}}list for more information). Select another option, or type `exit` to cancel.", - "noArticlesPassed": "No articles were able to pass this feed's filters." - }, - "help": { - "controlPanelLink": "Be sure to check out your control panel at {{url}} for easy feed management!", - "description": "Arguments for commands are added after the command. For example: `{{prefix}}test simple`.\n\n**Argument Definitions**\n`a` = Directly add `a`\n`[a]` = Optionally add `a`\n`(a|b)` = Directly input `a` OR `b`\n`` = Replace `a` with relevant content", - "arguments": "Arguments:", - "support": "\nSupport can be found at {{url}}", - "checkDM": ", Check your direct messages!" - }, - "invite": { - "text": "Invite with Role - \n\nInvite without Role - " - }, - "list": { - "noFeeds": "There are no feeds.", - "noFeedsChannel": "There are no feeds in the channel {{channel}}.", - "channelLimitReached": "Daily article limit reached", - "titleChecksEnabled": "Title Checks: Enabled\n", - "statusDisabled": "Status: DISABLED ({{reason}})\n", - "statusFailed": "Status: FAILED\n", - "statusOk": "Status: OK {{failCount}}\n", - "serverLimit": "Server Limit", - "failAlert": "**Attention!** Feeds that have reached connection failure limit have been detected. They will no longer be retried. Please either remove, or use **{{prefix}}refresh** to try and reset its status.", - "feedList": "Feed List", - "feedListChannel": "Feed List in Channel #{{channel}}", - "refreshRate": "Refresh Rate", - "webhook": "Webhook", - "link": "Link", - "exceeds500Characters": "*Exceeds 500 characters*", - "seconds": "seconds", - "minutes": "minutes", - "unknown": "unknown" - }, - "locale": { - "helpText": "To change the interface language of commands, type the locale name after `{{prefix}}locale`. Locales are added through volunteer-sourced translations. Supported locales are:\n\n`{{localeList}}`", - "resetNone": "This server already has no locale set.", - "resetNoDefault": "`{{locale}}` is the default locale. Use `{{prefix}}locale reset` to restore the default instead.", - "resetSuccess": "Locale has been successfully reset back to `{{locale}}`.", - "setSuccess": "Locale has successfully been changed to `{{locale}}`.", - "setNone": "`{{locale}}` is not a supported locale. Supported locales are:\n\n`{{localeList}}`", - "alreadySet": "`{{locale}}` is already the set locale." - }, - "mention": { - "role": "role", - "user": "user", - "noFeeds": "Cannot add mention customizations without any active feeds.", - "subscriptionsList": "Subscriptions List", - "description": "Adding a subscription for a user or role will automatically add their mentions in the `{subscribers}` placeholder. If the subscriber is a role, then the role will be added to the list of eligible roles for role mention toggling, and also for the commands {{prefix}}sub/unsub.\n\nSelect an option by typing its number, or type `exit` to cancel.\u200b\n\u200b\n", - "subscriberOptions": "Subscriber Options", - "optionAddSubscriber": "Add the subscriber to the feed", - "optionAddSubscriberDescription": "Mentions a role/user for all delivered articles of this feed.", - "optionRemoveSubscriber": "Remove the subscriber from the feed", - "optionRemoveSubscriberDescription": "Remove the subscribed role/user from the feed", - "optionRemoveAllSubscribers": "Remove all subscribers of the feed", - "optionRemoveAllSubscribersDescription": "Remove all role/user subscribers from the feed", - "optionListSubscribers": "List all subscribers", - "optionListSubscribersDescription": "List all subscribers.", - "promptUserOrRole": "Mention a role/user that is subscribed to the feed <{{link}}> in channel {{channel}}.", - "listSubscribersDescription": "Below are the subscribers of the feed <{{link}}> in channel {{channel}}.", - "listSubscribersNone": "There are no subscribers for the feed <{{link}}> in channel {{channel}}.", - "removeAllSubscribersSuccess": "All subscribers have been successfully removed from feed <{{link}}>.", - "addSubscriberExists": "Unable to add subscriber, {{type}} {{mention}} is already subscribed to this feed.", - "addSubscriberSuccess": "Subscriber {{type}} {{mention}} successfully added to feed <{{link}}>.", - "notFeedSubscriber": "This role or user is not subscribed to the feed <{{link}}>. Try again, or type `exit` to cancel.", - "removeSubscriberSuccess": "Successfully removed the subscriber {{mention}} from the feed <{{link}}>.", - "removeAnySubscriberNone": "There are no subscribers to remove from the feed <{{link}}>.", - "invalidRoleOrUser": "That is not a valid role or user. Try again, or type `exit` to cancel.", - "filters": { - "title": "Subscriber Filters Customization", - "description": "**Feed URL:** {{link}}\n\nAdding filters to a subscriber will cause it to only get mentioned within the `{suscribers}` placeholder of an article if its filters pass the article.", - "notSubscriber": "This role or user is not a subscriber. Try again, or type `exit` to cancel.", - "optionAddFilter": "Add filter(s) to the subscriber", - "optionRemoveFilter": "Remove filter(s) from the subscriber", - "optionRemoveAllFilters": "Remove all filters from the subscriber", - "optionListFilters": "List all filters of the subscriber", - "listFiltersDescription": "Below are the filter categories with their words/phrases under each for the feed <{{link}}> within the channel {{channel}} for the subscriber {{subscriber}}.", - "listNoFilters": "There are no filters assigned to {{subscriber}} for the feed <{{link}}>.", - "removedAllFilters": "Successfully removed all filters from subscriber {{subscriber}} for feed <{{link}}>" - } - }, - "text": { - "prompt": "The current text for {{link}} is: \n{{currentMsg}}\nType your new customized text now, type `reset` to use the default text, or type `exit` to cancel. \n\nRemember that you can use the placeholders `{title}`, `{description}`, `{link}`, and etc. `{empty}` will create an empty message, but only if an embed is used. Regular formatting such as **bold** and etc. is also available. To find other placeholders, type `exit` then `{{prefix}}test`.\n\n", - "noEmpty": "You cannot have empty text if there is no embed used for this feed. Try again.", - "noSetText": "None has been set. Currently using default text below:", - "resetSuccess": "Text has been reset and using default text for feed {{link}}:", - "setSuccess": "Text recorded for feed <{{link}}>:", - "noSubscriptionsPlaceholder": "Note that because there is no `{subscriptions}`, whatever subscriptions you add through {{prefix}}mention will *not* appear in this feed's article text.", - "reminder": "You may use `{{prefix}}test` to see your new text format." - }, - "move": { - "prompt": "Mention the channel to move the feed(s) to, or type `this` for this channel.", - "invalidChannel": "That is not a valid channel. Try again, or type `exit` to cancel.", - "alreadyInChannel": "\nThe feed is already in that channel.", - "linkAlreadyExists": "\nA feed with this link already exists in that channel.", - "meMissingPermission": "\nI am missing **Read Messages** or **Send Messages** permission in <#{{id}}>.", - "meMissingEmbedLinks": "\nI am missing **Embed Links** permission in the <#{{id}}>. To bypass this permission, you can reset this feed's embed via the embed command.", - "youMissingPermission": "\nYou are missing **Read Messages**, **Send Messages**, or **Manage Channel** permission in <#{{id}}>.", - "moveFailed": "Unable to move channel for the following reasons:\n{{errors}}\n\nTry again, or type `exit` to cancel.", - "moveSuccess": "The channel for the following feed(s):\n\n{{summary}}\n\nhave been successfully moved to <#{{id}}>." - }, - "options": { - "miscFeedOptions": "Miscellaneous Feed Options", - "selectOption": "\u200b\nPlease select an option by typing its number, or type `exit` to cancel.\u200b\n\u200b\n", - "onlyIfNecessary": "Only enable this if necessary!", - "titleChecks": "Title Checks", - "titleChecksToggle": "Toggle Title Checks for a feed", - "titleChecksDescription": "Title checks will ensure no article with the same title as a previous one will be sent for a specific feed.", - "imagePreviews": "Image Link Previews", - "imagePreviewsToggle": "Toggle Image Link Previews for a feed's placeholders", - "imagePreviewsDescription": "Toggle automatic Discord image link embedded previews for image links found inside placeholders such as {description}.", - "imageLinksExistence": "Image Links Existence", - "imageLinksExistenceToggle": "Toggle Image Links Existence for a feed's placeholders", - "imageLinksExistenceDescription": "Remove image links found inside placeholders such as {description}. If disabled, all image `src` links in such placeholders will be removed.", - "dateChecks": "Date Checks", - "dateChecksToggle": "Toggle Date Checks for a feed", - "dateChecksDescription": "Date checking ensures that articles that are {{cycleMaxAge}} day(s) old or has invalid/no pubdates are't sent.", - "tableFormatting": "Table Formatting", - "tableFormattingToggle": "Toggle Table Formatting for a feed", - "tableFormattingDescription": "If table formatting is enabled, they should be enclosed in code blocks to ensure uniform spacing.", - "directSubscribers": "Direct Subscriptions", - "directSubscribersToggle": "Toggle direct subscriptions for a feed", - "directSubscribersDescription": "Allows users to directly add themselves as a subscriber to the feed `{subscribers}` placeholder. This is an alternative to letting users add roles to themselves.", - "settingChanged": "{{propName}} has been {{finalSetting}} for <{{link}}>{{isDefault}}.", - "defaultSetting": "default setting" - }, - "prefix": { - "helpText": "You must specify a prefix 4 characters or less as the first argument to set a custom prefix, or `reset` to reset the prefix to default if a custom prefix is already set.", - "resetNone": "You have no custom prefix to reset.", - "resetSuccess": "Commands prefix has been reset back to the default ({{prefix}}).", - "requirements": "Commands prefix length must be less than 5 characters with no spaces.", - "cannotUseDefault": "Cannot use this commands prefix because it is already the default prefix.", - "setSuccess": "Successfully changed commands prefix to \"{{prefix}}\"." - }, - "refresh": { - "noFailLimit": "No failure limit has been set.", - "noFailedFeeds": "There are no failed feeds to refresh.", - "processing": "Processing requests for refresh. This may take a while. Please wait...", - "success": "The following links have successfully been refreshed:", - "failed": "The following links failed to refresh:" - }, - "remove": { - "removing": "Removing...", - "success": "Successfully removed the following link(s):" - }, - "split": { - "messageSplittingOptions": "Message Splitting Options", - "description": "**Feed Title:** {{title}}\n**Feed Link:** {{link}}\n\nMessage splitting for this feed is currently {{currently}}. Select an option by typing its number, or type `exit` to cancel.\u200b\n\u200b\n", - "enabledDescription": "**Feed Title:** {{title}}\n**Feed Link:** {{link}}\n\n**Message Splitting is now enabled for this feed.**\n\nYou may customize message splitting further by selecting one of the options below by typing its number, or type `exit` to leave as is. It is recommended to leave at the default settings.\u200b\n\u200b\n", - "optionEnable": "Enable Message Splitting", - "optionEnableDescription": "Message splitting splits a message that exceeds the Discord character limit into multiple messages instead.", - "optionDisable": "Disable Message Splitting", - "optionSetSplitChar": "Set split character", - "optionSetSplitCharDescription": "Specify the character that the message should split according to.", - "optionSetPrependChar": "Set prepend character", - "optionSetPrependCharDescription": "Specify the character that every message except the first should be prepended (added before) with.", - "optionSetAppendChar": "Set append character", - "optionSetAppendCharDescription": "Specify the character that every message except the last should be appended (added after) with.", - "optionSetMaxLength": "Set max length", - "optionSetMaxLengthDescription": "Specify the maximum length a single message should have.", - "currentlySetTo": "Currently set to `{{value}}`.", - "defaultIsValue": "Default is {{value}}", - "defaultIsNothing": "Default is nothing.", - "newLine": "new line", - "promptSplitChar": "Type a split character now, `reset` to reset, or `exit` to cancel.", - "promptPrependChar": "Type a prepend character now, `reset` to reset, or `exit` to cancel.", - "promptAppendChar": "Type an append character now, `reset` to reset, or `exit` to cancel.", - "promptMaxLen": "Type the max length a single message should have now, `reset` to reset, or `exit` to cancel. **Must be a number >= 500 and <= 1950.**", - "setSplitCharDefault": "That is already the default character. Try again, or type `exit` to cancel and leave it at default.", - "setSplitChar": "The split character for the feed <{{link}}> has been set to `{{content}}`.", - "setPrependChar": "The prepend character for the feed <{{link}}> has been set to `{{content}}`.", - "setAppendChar": "The append character for the feed <{{link}}> has been set to `{{content}}`.", - "setMaxLen": "The max length for a single message for the feed <{{link}}> has been set to `{{num}}`.", - "setInvalidMaxLen": "That is not a valid number >= 500 and <= 1950. Try again.", - "resetSplitChar": "The split character for the feed <{{link}}> has been reset to `\\n`.", - "resetPrependChar": "The prepend character for the feed <{{link}}> has been reset to be nothing.", - "resetAppendChar": "The append character for the feed <{{link}}> has been reset to be nothing.", - "resetMaxLen": "The max length for a single message for the feed <{{link}}> has been reset to be `1950`.", - "disabledSuccess": "Message splitting is now disabled for feed <{{link}}>." - }, - "test": { - "failed": "Unable to grab a random article because this feed's current status is failed. See {{prefix}}list for more information.", - "grabbingRandom": "Grabbing a random feed article...", - "noArticles": "There are no articles in this feed to send.", - "noValidLatest": "There are either no articles in this feed, or there are articles with invalid dates and the latest one cannot be reliably determined." - }, - "webhook": { - "existingFound": "An existing webhook was found ({{webhookMention}}). You may type `{remove}` to disconnect the existing webhook, or continue and your new setting will overwrite the existing one.\n\n", - "prompt": "Type the name of the webhook in this channel you wish to use (case sensitive), or type `exit` to cancel.\n\nTo use a different name or avatar url of the webhook when articles are sent for this particular feed, add parameters `--name=\"my new name here\"` or `--avatar=\"http://website.com/image.jpg\"`. Placeholders are supported.", - "notFound": "No such webhook named \"{{name}}\" found for this channel. Try again, or type `exit` to cancel.", - "tooLong": "Webhook name must be between 2 and 32 characters. Try again, or type `exit` to cancel.", - "noPermission": "I must have Manage Webhooks permission in this channel in order to work. Try again, or type `exit` to cancel.", - "removeSuccess": "Successfully removed webhook from the feed <{{link}}>.", - "addSuccess": "Successfully connected the webhook to the feed <{{link}}>. Note that test articles will be sent in <#{{channel}}> via your webhook.", - "connected": "I am now connected to {{clientMention}}, and will send feed articles for <{{link}}>!", - "missingChannel": "The channel <#{{channelID}}> for the feed <{{link}}> is missing. Try again, or type `exit` to cancel." - }, - "sub": { - "title": "Self-Subscription Addition", - "description": "You may be mentioned whenever a new article comes in for a particular feed by selecting an option below. ", - "optionAddRole": "I want to add a mentioned role to myself", - "optionAddRoleDescription": "You may add a role that is subscribed to a feed so that you'll be mentioned via role.", - "optionAddMe": "I want to be directly mentioned for a feed", - "optionAddMeDescription": "You may directly add yourself as a subscriber to a feed so that you'll be directly mentioned. **This is only possible if this feature is enabled for this server.**", - "optionSubscribedFeeds": "List my subscribed feeds", - "optionSubscribedFeedsDescription": "Show all the feeds that you are currently being mentioned in (whether they're role mentions or direct mentions)", - "subscribedFeedsList": "Below is the list of feeds that you are subscribed to.", - "noSubscribedFeedsList": "You are not being mentioned in any feeds.", - "noEligible": "There are either no feeds with subscribed roles, or no eligible subscribed roles that can be self-added.", - "invalidRole": "That is an invalid role. Try again, or type `exit` to cancel.", - "listInputRole": "Below is the list of feeds, their channels, and eligible roles that you may add to yourself. Type the role name or mention to add the role to yourself, or type `exit` to cancel.", - "addSuccess": "You now have the role `{{name}}`, subscribed to:", - "directSubscribeExists": "Unable to directly subscribe to feed <{{link}}>. You are already subscribed to it.", - "directSubscribeSuccess": "You are now directly subscribed to the feed <{{link}}>.", - "alreadyHaveRole": "You already have this role. Try another role name, or type `exit` to cancel.", - "directSubscriberDisabled": "The feed <{{link}}> in {{channel}} does not allow direct subscribers. It must be enabled in the `{{prefix}}options` command. Try another feed, or type `exit` to cancel.", - "filters": { - "description": "Manage filters for your subscription. You may use filters to determine what articles within the feed {{link}} you want to be mentioned in.", - "noSubscribedFeeds": "You are not directly subscribed to any feeds.", - "optionAddFilters": "Add filter(s)", - "optionAddFiltersDescription": "Add new filter(s) to myself so I only get mentioned in certain articles.", - "optionRemoveFilters": "Remove filter(s)", - "optionRemoveFiltersDescription": "Remove existing filter(s) from myself, if any.", - "optionRemoveAllFilters": "Remove all filter(s)", - "optionRemoveAllFiltersDescription": "Remove all filter(s) from myself, if any.", - "optionListFilters": "List existing filter(s)", - "optionListFiltersDescription": "List my filters in all categories, if any.", - "optionSendFilteredArticle": "Send a filtered article that mentions you", - "optionSendFilteredArticleDescription": "Send a random article that will mention you, taking your filters (if they exist) into account. **Mentioned users/roles will not be pinged for this test.**" - } - }, - "unsub": { - "optionRemoveRole": "I want to remove a mentioned role from myself", - "optionRemoveRoleDescription": "Remove a role from yourself that is currently mentioned within a feed", - "optionRemoveMyself": "I want to remove a mention of myself from a feed", - "optionRemoveMyselfDescription": "Remove a direct mention of yourself within a feed", - "optionRemoveAll": "I want to remove all mentions of me from all feeds", - "optionRemoveAllDescription": "Remove all direct mentions and mentioned roles within feeds that include you", - "noEligibleRoles": "There are no eligible roles to be removed from you. Select a different option, or type `exit` to cancel.", - "noEligibleDirect": "You are not directly mentioned in any feeds. Select a different option, or type `exit` to cancel.", - "invalidRole": "That is not a valid role to remove. Try again, or type `exit` to cancel.", - "title": "Self-Subscription Removal", - "directRemoveList": "Below are all the feeds you are directly mentioned in.", - "listInputRole": "Below is the list of feeds, their channels, and eligible roles that you may remove from yourself. Type the role name or mention to remove it, or type `exit` to cancel.", - "roleRemoveSuccess": "You no longer have the role {{role}}.", - "directRemoveSuccess": "You will now no longer be directly mentioned for the feed <{{link}}>.", - "removeAllSuccess": "You will now no longer be directly mentioned or mentioned through roles for any feed." - }, - "utils": { - "filters": { - "title": "Title", - "dscription": "Description", - "summary": "Summary", - "author": "Author", - "tags": "Tags", - "feed": "Feed", - "role": "Role", - "user": "User", - "filtersCustomization": "Filters Customization", - "categoryDescription": "Below is the list of filter categories you may add filters to. Type the filter category for which you would like you add a filter to, or type `exit` to cancel. To type a filter category that's not listed here but is in the raw dump, start it with `raw:`. For other unlisted filter categories, start it with `other:`.\u200b\n\u200b\n", - "invalidCategory": "That is not a valid filter category. Try again, or type `exit` to cancel.", - "promptAdd": "Type the filter word/phrase you would like to add in the category `{{type}}` by typing it, type multiple word/phrases on different lines to add more than one, or type `exit` to cancel. The following can be added in front of a search term to change its behavior:\n\n\n`~` - Broad filter modifier to trigger even if the term is found embedded inside words/phrases.\n`!` - NOT filter modifier to do the opposite of a normal search term. Can be added in front of any term, including one with broad filter mod.\n`\\` - Escape symbol added before modifiers to interpret them as regular characters and not modifiers.\n\n\nFilters will be applied as **case insensitive** to feeds. Because of this, all input will be converted to be lowercase.", - "addSuccess": "The following filter(s) have been successfully added for the filter category", - "addFailed": "The following filter(s) could not be added because they already exist", - "updatedFor": "Subscription updated for {{name}}.", - "testFiltersSubscriber": "\nYou may test your filters on random articles via `{{prefix}}test` and see what articles will mention this subscriber.", - "testFilters": "\nYou may test random articles with `{{prefix}}test` to see what articles pass your filters, or specifically send filtered articles with `{{prefix}}filters` option 5.", - "removeFilterConfirm": "Confirm the filter word/phrase you would like to remove in the category `{{category}}` by typing one or multiple word/phrases separated by new lines (case sensitive).", - "removeFilterInvalid": "That is not a valid filter to remove from `{{category}}`. Try again, or type `exit` to cancel.", - "removeSuccess": "The following filter(s) have been successfully removed from the filter category", - "removeSuccessSubscriber": "Subscription updated for `{{name}}`.", - "removeFailedNoExist": "The following filter(s) were unable to be deleted because they do not exist", - "removeNone": "There are no filters to remove for <{{link}}>.", - "listOfFilters": "List of Assigned Filters", - "listOfFiltersDescription": "**Feed Title:** {{title}}\n**Feed Link:** {{link}}\n\nBelow are the filter categories with their words/phrases under each. Type the filter category for which you would like you remove a filter from, or type `exit` to cancel.\u200b\n\u200b\n", - "regexExists": "Error: Filters cannot be modified through commands when a regex filter string exists for this filter category ({{category}}). Manually remove the regex string to be able to edit this filter category through commands again." - } - } - }, - "structs": { - "MenuUtils": { - "permissionWarning": "WARNING: Missing \"Add Reactions\" or \"Read Message History\" permissions in this channel. Because this menu has more than {{maxPerPage}} options, it will not function properly without the right permissions", - "closedInactivity": "I have closed the menu due to inactivity.", - "closed": "Menu closed." - }, - "FeedSelector": { - "noFeeds": "This server does not have any feeds.", - "noFeedsInChannel": "There are no feeds assigned to this channel.", - "feedSelectionMenu": "Feed Selection Menu", - "action": "Action", - "prompt": "Choose a feed to from this channel by typing the number to execute your requested action on.", - "multiSelect": "You may select multiple feeds by separation with commas (for example `1,3,8`), or with a range (for example `1-3,5,7-9`).", - "noneSelected": "No valid feeds were selected. To select a feed, the number next to the feed (for example `1`). To select multiple feeds, separate the numberas by commas (for example `1,3,8`), or with a range (for example `1-3,5,7-9`). Try again, or type `exit` to cancel.", - "exitToCancel": "Type `exit` to cancel." - }, - "errors": { - "MenuOptionError": { - "message": "That is not a valid choice. Try again, or type `exit` to cancel." - } - } - } -} diff --git a/services/bot/src/locales/pt-BR.json b/services/bot/src/locales/pt-BR.json deleted file mode 100644 index 4e6749827..000000000 --- a/services/bot/src/locales/pt-BR.json +++ /dev/null @@ -1,613 +0,0 @@ -{ - "generics": { - "defaultSetting": "Padrão é `{{value}}`.", - "backupReminder": "Depois de configurado, é recomendado que você use {{prefix}}backup para ter um backup pessoal de suas configurações.", - "channelUpper": "Canal", - "channelLower": "canal", - "enabledUpper": "Ativado", - "enabledLower": "ativado", - "disabledUpper": "Desativado", - "disabledLower": "desativado" - }, - "commandDescriptions": { - "add": { - "description": "Adicione um feed ao canal com a mensagem padrão. Multiplos feeds podem ser adicionados separando-os por `>`", - "args": { - "": "Feed Link." - } - }, - "compare": { - "action": "", - "description": "", - "args": { - "list": "", - "(+|-)": "" - } - }, - "remove": { - "action": "Remover feed", - "description": "Abre um menu para deletar um feed de um canal." - }, - "list": { - "description": "Mostrar todos os feeds ativos neste servidor.", - "args": { - "[] []": "" - } - }, - "text": { - "action": "Customizar texto", - "description": "Abre um menu para customizar a texto do feed." - }, - "embed": { - "action": "Customizar mensagem da embed", - "description": "Abre um menu para customizar a mensagem da embed. Isso irá substituir a embed padrão que o Discord utiliza quando um link é postado." - }, - "embed.fields": { - "action": "", - "description": "" - }, - "filters": { - "action": "Adicionar ou remover filtros adicionados ao feed", - "description": "Abre um menu para adicionar ou remover filtros globais de um feed. Mensagens que não possuam nenhuma das palavras em nenhum de seus filtros não serão enviadas pelo bot." - }, - "date": { - "description": "Abre um menu para customizar como a data é mostrada." - }, - "mention": { - "action": "Customizar menção", - "description": "Abre um menu para adicionar menções filtradas/global para cargos/usuarios em um feed" - }, - "mention.filters": { - "description": "" - }, - "test": { - "args": { - "[simple] [latest]": "" - }, - "action": "testar feed", - "description": "Abre um menu para enviar uma mensagem de teste para um artigo aleatório em um feed, juntamente com as propriedades e espaços reservados disponíveis para várias personalizações. Você pode adicionar `simple` como um parâmetro para excluir os detalhes do teste." - }, - "sub": { - "description": "Abre um menu para adicionar um cargo com uma menção filtrada ao usuário. Usável por qualquer um no servidor, ativável/desativável pela permissão \"Gerenciar Cargos\". Os cargo precisam estar abaixo do cargo do bot na configuração de cargos." - }, - "sub.filters": { - "description": "" - }, - "unsub": { - "description": "Abre um menu semelhante ao `sub`, mas será para remover o cargo. Qualquer cargo abaixo do cargo do bot será removível." - }, - "refresh": { - "action": "Atualize um feed", - "description": "Abre um menu para restaurar o feed de volta ao ciclo normal após a desativação, devido o feed ter ficado passando consecutivamente do limite de falha." - }, - "options": { - "description": "Abre um menu para editar diversas opções de feed." - }, - "split": { - "action": "Personalização de divisão de mensagens.", - "description": "Abre um menu para personalizar as configurações de divisão de mensagens." - }, - "move": { - "description": "Abre um menu para mover o feed de um canal para o outro", - "action": "Transferir feed entre canais" - }, - "clone": { - "action": "Clonar configurações do feed.", - "description": "Clona as configurações de um feed para outro." - }, - "backup": { - "description": "Envia o perfil do servidor como um anexo JSON para backups pessoais" - }, - "dump": { - "action": "Raw Placeholders Dump", - "args": { - "original": "Output the original, untrimmed JSON form instead." - } - }, - "stats": { - "description": "Mostra um simples status do bot, como desempenho e feeds ativos." - }, - "webhook": { - "action": "Conectar webhook", - "description": "Abre um menu para conectar um webhook a um feed, e fazer com que o envie ao invés do bot." - }, - "prefix": { - "action": "Alteração de prefixo de comandos", - "description": "Altere o prefixo usado para os comandos do padrão ({{prefix}}).", - "args": { - "": "O prefixo a usar", - "reset": "Reseta o prefix para o padrão ({{prefix}})." - } - }, - "alert": { - "description": "Configurar mensagem direta a um usuário para caso um feed falhar", - "args": { - "add ": "Adicionar um usuário para direcionar a mensagem direta.", - "remove ": "Remover um usuário da mensagem direta.", - "list": "Listar usuarios ativos" - } - }, - "locale": { - "description": "Muda a linguagem dos comandos (se a linguagem desejada for suportada)", - "args": { - "": "Linguagem desejada" - } - }, - "invite": { - "description": "Envia o convite link para adicionar o bot." - }, - "version": { - "description": "Mostra a versão do bot." - } - }, - "commands": { - "add": { - "correctSyntax": "A sintaxe correta é {{prefix}}add https://www.alguma_url_aqui.com | multiplos links podem ser adicionados, separando-os por `>`.", - "improperFormat": "inválido/feed mal formatado.", - "processing": "processando...", - "limitReached": "limite máximo de feed alcançado {{max}}.", - "alreadyExists": "Já existe neste canal.", - "success": "O seguinte feed foi adicionado com sucesso a **este canal**", - "successInfo": "Artigos serão automaticamente enviados assim que encontrados.", - "failedLimit": "Feed(s) não listados aqui não puderam ser adicionados devido ao limite de feed ({{max}}).", - "failedList": "Não foi possível adicionar o seguinte feed(s)", - "reason": "Motivo." - }, - "alert": { - "noFeeds": "Você não pode criar um alerta, porque você não adicionou nenhum feed.", - "info": "Esse comando adiciona usuários para mensagens diretas (em vez de postar esses alertas no canal do feed por padrão) quando há avisos ou alertas sobre feeds, como alterações no limite de feed ou falhas no feed. **O usuário que você está adicionando ou removendo será notificado**. A sintaxe correta é:\n\n`{{prefix}}alert add ` - Adiciona um usuário a ser notificado.\n`{{prefix}}alert remove ` - Remove um usuário que está sendo notificado\n`{{prefix}}alert list` - Mostra os usuarios que estão sendo notificados", - "notFound": "O usuário {{user}} não foi encontrado neste servidor.", - "everyoneNotAllowed": "", - "alreadyEnabled": "Este usuário já está sendo notificado neste servidor", - "successDM": "A pedido do usuário {{member}}, você será notificado quando houver qualquer aviso/falhas em feeds neste servidor `{{guildName}}` (ID `{{guildID}}`).", - "success": "Adicionado com sucesso o usuário {{member}} a ser alertado em caso de avisos/falhas. O usuário foi notificado desta mudança.", - "removedDM": "A pedido do usuário {{member}}, Você não será notificado em caso de avisos/falha em feeds neste servidor`{{guildName}}` (ID `{{guildID}}`).", - "removed": "usuário {{member}} removido com sucesso, ele não será mais notificado em avisos/falhas. O usuário foi notificado sobre esta mudança.", - "removeFail": "Você não pode remover um usuário que já está sendo notificado em avisos/falha em feeds.", - "listEmpty": "Atualmente não há nenhum usuário sendo notificado quando houver falhas ou alertas neste servidor.", - "list": "A lista de usuarios abaixo serão notificados em caso de falhas/alertas neste servidor:\n\n" - }, - "backup": { - "noProfile": "Este servidor não tem um Perfil.", - "noPermission": "Não foi possível enviar o backup devido falta de permissão `Anexar arquivos`" - }, - "clone": { - "copyFrom": "Selecione a fonte para copiar de.", - "copyTo": "Selecione o destino pra copiar para.", - "inputProperties": "Entrar propriedades para clonar, separadas por vírgulas. As propriedades disponíveis para clonagem são `{{properties}}`. Para clonar todas estas propriedades, Você pode simplesmente utilizar `all`.\n\nPor exemplo, para clonar a mensagem de um feed e a filtros, digite `filters,message`.", - "invalidProperties": "Propriedades inválidas encontrada: `{{invalid}}`\n\nPropriedades disponíveis para clonagem são `{{properties}}`.", - "confirm": "As seguintes configurações para o feed <{{link}}>\n\n`{{cloning}}`\n\nestão prestes a ser clonadas nos seguintes feeds:\n```{{destinations}}```\nObserve que se uma propriedade não existir no feed de origem, a mesma propriedade no(s) feed(s) de destino será **deletada**.Para confirmar isso, digite `yes`. Caso contrário, digite `exit` para cancelar.", - "confirmError": "Você precisa confirmar digitando `yes`, ou cancelar digitando `exit`. tente novamente.", - "success": "As seguintes configurações\n\n`{{cloned}}`\n\npara o feed <{{link}}> foram clonadas com sucesso em {{destinationCount}} feed(s)." - }, - "compare": { - "info": "", - "list": "", - "listNone": "", - "invalid": "", - "onlyTitle": "", - "reset": "", - "success": "" - }, - "date": { - "dateLanguage": "Idioma da data", - "dateFormat": "Formato de data", - "timezone": "Fuso horário", - "noFeeds": "Você não pode personalizar o espaço reservado para a data se você não adicionou nenhum feed", - "selectTitle": "", - "description": "\u200b\nPor favor, selecione uma opção para personalizar o campo {date} digitando o numero, ou digite `exit` para cancelar.\u200b\n\u200b\n", - "optionCurrentSetting": "Sua configuração atual é `{{value}}`.", - "optionChangeTimezone": "Mudar Fuso horário", - "optionCustomizeFormat": "Customizar formato", - "optionChangeLanguage": "Mudar linguagem", - "optionReset": "resetar", - "optionResetValue": "Resetar todas as configurações acima ao padrão.", - "successResetAll": "Todas as personalizações de data foram resetadas de volta ao padrão.", - "successReset": "{{name}} foi redefinido para o padrão: `{{value}}`.", - "successSet": "{{name}} foi atualizado com sucesso para `{{value}}`.", - "promptNewLanguage": "Digite a abreviação de um novo idioma agora, `reset` para voltar ao padrão, ou` exit` para cancelar. A lista disponível de idiomas suportados neste momento é (separada por vírgulas):\n\n{{localesList}}", - "promptNewDateFormat": "Digite seu novo formato de data agora, use `reset` para resetar de volta ao padrão, ou `exit` para cancelar. veja em como formatar uma data.", - "promptNewTimezone": "Digite seu novo fuso horário agora, `reset` para voltar ao padrão, ou `exit` para cancelar. veja para uma lista de fusos horários na coluna TZ.", - "invalidLanguage": "`{{input}}` não é uma abreviação de idioma suportada. Os idiomas disponíveis são:\n\n{{localesList}}\n\nTente novamente, ou digite `exit` para cancelar.", - "invalidTimezone": "`{{input}}` não é um fuso horário válido. Veja para mais informações, fusos horários válidos estão na coluna `TZ`. tente novamente, ou digite `exit` para cancelar." - }, - "dump": { - "generatingDump": "Gerando dump...", - "generatedDump": "Dump foi gerado, tentando anexar arquivo, veja abaixo." - }, - "embed": { - "numberedEmbed": "Embed #{{number}}", - "embedSelection": "Seleção de embed", - "embedSelectionOptionRemoveAll": "", - "removedAllEmbeds": "", - "embedSelectionDescription": "As propriedades de cada embed são mostradas abaixo. Selecione uma das embeds digitando o número ao lado, ou digite `exit` para cancelar.\n\u200b", - "embedSelectionOptionAdd": "adicionar uma nova embed", - "embedSelectionOptionAddDescription": "No máximo 10 embeds podem ser adicionadas.\n\u200b", - "currentProperties": "Propriedades atuais", - "currentPropertiesList": "As propriedades atuais incorporadas para {{link}} são: \n{{list}}```\n", - "availableProperties": "Propriedades disponíveis", - "availablePropertiesList": "A lista de propriedades embeds que podem ser definidas são:\n{{list}}\nDigite a propriedade embed (por exemplo, `color` ou `message`) que você deseja definir/resetar, ou várias propriedades por separação por vírgulas (por exemplo, `color, message`). Digite `reset` para remover todas as propriedades ou digite `exit` para cancelar.", - "invalidColorNumber": "A cor deve ser um **número**. Veja Tente novamente, ou digite `exit` para cancelar.", - "invalidColorRange": "A cor precisa ser um numero entre 0 e 16777215. Tente novamente, ou digite `exit` para cancelar.", - "invalidProperties": "As seguintes propriedades {{invalids}} são inválidas. Tente novamente, ou digite `exit` para cancelar.", - "removedEmbed": "A embed foi desativada e todas as propriedades foram removidas para <{{link}}>.", - "resetSuccess": "☑ **{{propName}}** foi resetada\n", - "updatedSuccess": "☑ **{{propName}}** Atualizado para \n```\n{{userSetting}}\n```\n", - "updatedInfo": "Configurações atualizadas para <{{link}}>:\n\n{{resetList}}{{updateList}}\nVocê pode utilizar `{{prefix}}test` ou `{{prefix}}test simple` para ver seu novo formato de embed.", - "title": "Título", - "titleDescription": "Título sob título do autor\nAceita campo reservado", - "description": "Descrição", - "descriptionDescription": "Mensagem principal\nAceita campo reservado", - "url": "URL", - "urlDescription": "Clicando no título/imagem irá levá-lo para este link\nPRECISA ser um link. Definido como URL do artigo por padrão", - "color": "Cor", - "colorDescription": "A cor da barra lateral\nDEVE ser uma cor entre 0 e 16777215. Veja https://www.shodor.org/stella2java/rgbint.html", - "timestamp": "Data e Hora", - "timestampDescription": "Data localizada visualmente para todos os usuários\nSe a data e hora for inválida, ela não será exibido", - "footerIconURL": "URL do ícone de rodapé", - "footerIconURLDescription": "O ícone à esquerda do texto do rodapé\nDEVE ser um link para uma imagem. Se o texto do rodapé não for especificado, o ícone do rodapé ficará oculto\nAceita campos reservado", - "footerText": "Texto do rodapé", - "footerTextDescription": "Texto mais abaixo\nAceita campo reservado", - "thumbnailURL": "URL da imagem, Miniatura", - "thumbnailURLDescription": "A imagem à direita\nDEVE ser um link para uma imagem OU um espaço reservado para a imagem", - "imageURL": "Imagem URL", - "imageURLDescription": "Imagem na parte inferior\nDEVE ser um link para uma imagem OU um espaço reservado para a imagem", - "authorName": "Nome do autor", - "authorNameDescription": "Nome no topo\nAceita campo reservado", - "authorURL": "Autor URL", - "authorURLDescription": "Clicar no nome do autor levará a este URL\nPRECISA ser um link", - "authorIconURL": "Icone do Autor URL", - "authorIconURLDescription": "O ícone à esquerda do Nome do Autor\nPRECISA ser um link para uma imagem, Se o nome do autor não for especificado, o icone do autor será escondido", - "settingProperty": "Você está personalizando a **{{property}}**. Digite sua entrada agora. Para redefinir a propriedade, digite `reset`.", - "settingPropertyTimestamp": "Agora você está personalizando o **timestamp** Digite sua entrada agora.\n\nPara definir o registro de data e hora para o momento em que o artigo é enviado para Discord, digite `now`.\nPara definir o registro de data e hora para o momento em que o artigo foi publicado (se disponível no feed), digite` article`.\nPara uma data e hora personalizada, digite a representação de texto de uma data personalizada (consulte ).\n\nPara redefinir a propriedade, digite `reset`.", - "settingPropertyTimestampError": "Essa não é uma configuração válida. Deve ser `now`, `article` ou uma representação de texto válida de uma data. Tente novamente.", - "inline": "Na linha", - "regular": "Regular", - "blankField": "Campo em branco", - "embedFields": "embedar campos", - "embedFieldsRemoveNone": "Não há campos embedados a serem removidos para este feed. Tente novamente, ou digite `exit` para cancelar.", - "embedFieldsRemoved": "O campo(s) numerado {{numbers}} foram removidos da embed para o feed <{{link}}>", - "embedFieldsDescription": "\u200b\nSelecione se deseja adicionar ou remover um campo da embed deste feed. Para um exemplo de como um campo se parece, consulte https://i.imgur.com/WSHwmyB.png. Digite `exit` para cancelar.\n\u200b", - "embedFieldsOptionRemove": "Remover um campo", - "embedFieldsOptionRemoveDescription": "Remova um campo, se existir.", - "embedFieldsOptionAddRegular": "Adicione um campo regular", - "embedFieldsOptionAddRegularDescription": "Os campos serão empilhados em cima uns dos outros.", - "embedFieldsOptionAddInline": "Adicionar um campo in-line", - "embedFieldsOptionAddInlineDescription": "Os campos serão colocados um ao lado do outro sempre que possível, em vez de serem empilhados.", - "embedFieldsOptionAddRegularBlank": "Adicionar um campo em branco regular", - "embedFieldsOptionAddRegularBlankDescription": "Não contém título ou descrição. Isso é usado para ocupar espaço vazio.", - "embedFieldsOptionAddInlineBlank": "Adicionar um campo em branco inline", - "embedFieldsOptionAddInlineBlankDescription": "Um campo em branco, mas em linha.", - "embedFieldsOptionRemoveEmbedTitle": "Remoção de campos embedados", - "embedFieldsOptionRemoveEmbedDescription": "\u200b\nOs campos estão listados abaixo, ordenados por quando foram adicionados. Digite o número do campo para removê-lo ou digite vários números de campo separados por vírgulas (`,`). Digite `exit` para cancelar.\n\u200b", - "embedFieldsAddedBlank": "Um campo em branco foi adicionado a embed do feed <{{link}}>.", - "embedFieldsAddedBlankInline": "Um campo em branco inline foi adicionado ao embed do feed <{{link}}>", - "embedFieldsSettingPrompt": "Defina suas configurações de campo agora. A **primeira linha será o título do campo** e **todas as novas linhas após a primeira serão a descrição do campo**. Se não houver conteúdo após a primeira linha, será uma descrição vazia. Digite `exit` para cancelar.", - "embedFieldsSettingTitleLong": "Títulos não podem exceder 256 caracteres. Tente novamente, ou digite `exit` para cancelar.", - "embedFieldsSettingValueLong": "Valores de campo não podem exceder 1024 caracteres. Tente novamente, ou digite `exit` para cancelar.", - "embedFieldsAdded": "Um novo campo {{type}} foi adicionado á embed com os seguintes detalhes: \n\n**Título**\n```\n{{name}}\n```\n**Valor**\n```\n{{value}}\n```\npara o feed {{link}}." - }, - "faq": { - "searchQueryRequired": "", - "searching": "", - "noResults": "" - }, - "filters": { - "selectFeedDescription": "**Feed Título:** {{title}}\n**Feed Link:** {{link}}\n\nSeleciona uma opção digitando o numero mostrado, ou digite *exit* para cancelar. Somente as mensagens que contiverem qualquer uma das palavras definidas nesses filtros de feed serão enviadas para Discord.\u200b\n\u200b\n", - "feedFiltersCustomization": "Customização de filtros de feed", - "optionAddFilters": "Adicionar filtro(s) de feed", - "optionAddFiltersDescription": "Adicionar novo(s) filtro(s) a uma categoria específica em um feed.", - "optionRemoveFilters": "Remover o(s) filtro(s) de feed", - "optionRemoveFiltersDescription": "Remover o(s) filtro(s) existente(s), se houver.", - "optionRemoveAllFilters": "Remover todos os filtros de feed", - "optionRemoveAllFiltersDescription": "Remover todos os filtros, se houver.", - "optionListFilters": "Listar filtro(s) existente(s)", - "optionListFiltersDescription": "Listar todos os filtros em todas as categorias, se houver.", - "optionSendArticle": "Enviar artigo aprovado", - "optionSendArticleDescription": "Envie um artigo escolhido aleatoriamente que passe o(s) filtro(s) atual(s), se existirem.", - "removedAllSuccess": "Todos os filtros de feed foram removidos com sucesso de <{{link}}>.", - "noFilters": "Não há filtros atribuídos a {{link}}.", - "listFiltersDescription": "", - "noArticlesPassed": "Nenhum artigo conseguiu passar nos filtros deste feed.", - "noFiltersTryAgain": "", - "connectionFailureLimit": "" - }, - "help": { - "controlPanelLink": "Não deixe de conferir seu painel de controle em {{url}} para facilitar o gerenciamento de feeds!", - "description": "", - "arguments": "Argumentos:", - "support": "\nSuporte pode ser encontrado em https://discord.gg/pudv7Rx", - "checkDM": ", Verifique suas mensagens diretas! 😉" - }, - "invite": { - "text": "Convite com cargo - \n\nConvite sem cargo - " - }, - "list": { - "noFeeds": "Não há feeds", - "noFeedsChannel": "", - "channelLimitReached": "", - "titleChecksEnabled": "Verificações de título: ativada\n", - "statusDisabled": "Status: DESATIVADO ({{reason}})\n", - "statusFailed": "Status: FALHADO\n", - "statusOk": "Status: OK {{failCount}}\n", - "serverLimit": "Limite do servidor", - "failAlert": "", - "feedList": "Lista de feeds", - "feedListChannel": "Lista de feeds no canal #{{channel}}", - "refreshRate": "Taxa de verificação", - "webhook": "Webhook", - "link": "Link", - "exceeds500Characters": "*Excede 500 caracteres*", - "seconds": "segundos", - "minutes": "minutos", - "unknown": "desconhecido" - }, - "locale": { - "helpText": "Para mudar a interface de linguagem de comandos, digite o nome do local depois de `{{prefix}}locale`. linguagens suportadas são:\n\n`{{localeList}}`", - "resetNone": "Este servidor não possui uma linguagem setada", - "resetNoDefault": "", - "resetSuccess": "Local de idioma foi redefinido com sucesso de volta para `{{locale}}`.", - "setSuccess": "Linguagem foi alterada com sucesso para `{{locale}}`.", - "setNone": "`{{locale}}` não é uma linguagem suportada. Linguagens suportadas atualmente são:\n\n`{{localeList}}`", - "alreadySet": "" - }, - "mention": { - "role": "cargo", - "user": "usuário", - "noFeeds": "Não é possível adicionar personalizações de menção sem nenhum feed ativo.", - "subscriptionsList": "Lista de inscrição", - "description": "Adicionando a inscrição para um usuário ou cargo irá automaticamente adicionar a menção no campo `{subscribers}`. Se a menção for em um cargo, então o cargo irá ser adicionado na lista de cargos elegíveis para a alternancia de menção (mencionável para não mencionável ou vice-versa) e também para os comandos {{prefix}}sub/unsub.\n\nSelecione uma opção digitando o numero, ou digite `exit` para cancelar.\u200b\n\u200b\n", - "subscriberOptions": "Opções de menção", - "optionAddSubscriber": "", - "optionRemoveSubscriber": "", - "optionRemoveSubscriberDescription": "", - "optionRemoveAllSubscribersDescription": "", - "promptUserOrRole": "", - "listSubscribersDescription": "", - "listSubscribersNone": "", - "removeAllSubscribersSuccess": "", - "notFeedSubscriber": "", - "removeSubscriberSuccess": "", - "optionAddSubscriberDescription": "Menções para um cargo/usuário para todos os arquivos enviados deste feed.", - "optionRemoveAllSubscribers": "Remover todas as menções para um Cargo ou Usuário", - "optionListSubscribers": "Listar todas as menções", - "optionListSubscribersDescription": "Listar todas as menções", - "addSubscriberExists": "Não é possível adicionar a inscrição. {{type}} {{mention}} já está inscrito neste feed.", - "addSubscriberSuccess": "Inscrição {{type}} {{mention}} adicionada com sucesso ao feed <{{link}}>.", - "removeAnySubscriberNone": "Não há inscritos para remover no feed <{{link}}>.", - "invalidRoleOrUser": "Esse não é um cargo válido ou usuário válido. Tente novamente, ou digite `exit` para cancelar.", - "filters": { - "title": "", - "description": "", - "noSubscriber": "", - "optionRemoveFilter": "", - "optionAddFilter": "Adicionar filtro(s)", - "optionRemoveAllFilters": "", - "optionListFilters": "", - "listFiltersDescription": "", - "listNoFilters": "", - "removedAllFilters": "", - "notSubscriber": "" - } - }, - "text": { - "prompt": "A texto atual para {{link}} é: \n{{currentMsg}}\nDigite sua nova texto personalizada agora, digite `reset` para usar a texto padrão, ou digite` exit` para cancelar. \n\nLembre-se que você pode usar campos como `{title}`, `{description}`, `{link}`, e etc. `{empty}` irá criar uma mensagem vazia, but apenas se uma embed for usada. formato regular como **negrito** e etc. também está disponível. para encontrar outros campos, digite `exit` e então `{{prefix}}test`.\n\n", - "noEmpty": "Você não pode ter uma texto vazia se não houver uma embed sendo usada para este feed. Tente novamente.", - "noSetText": "Nenhuma texto foi definida. Atualmente usando a texto padrão abaixo:", - "resetSuccess": "A texto foi redefinida e usando a texto padrão para o feed {{link}}:", - "setSuccess": "Texto registrada para o feed <{{link}}>:", - "noSubscriptionsPlaceholder": "Note que se não houver `{subscriptions}`, ou qualquer inscrição que você adicionou pelo {{prefix}}mention *não* irá aparecer na texto dos artigos deste feed.", - "reminder": "Você pode usar o `{{prefix}}test` para ver seu novo formato de texto." - }, - "move": { - "prompt": "Mencione o canal para mover o(s) feed(s) para, ou digite `this` para este canal.", - "invalidChannel": "Esse não é um canal válido. Tente novamente ou digite `exit` para cancelar.", - "alreadyInChannel": "\nO feed já está neste canal.", - "linkAlreadyExists": "\nUm feed com esse link já existe nesse canal.", - "meMissingPermission": "\nEstou sem a permissão **Ler mensagens** ou **Enviar mensagens** em <#{{id}}>.", - "meMissingEmbedLinks": "\nEstou sem a permissão **Inserir links** no canal <#{{id}}>. Para contornar essa permissão, você pode resetar a embed deste feed através do comando embed.", - "youMissingPermission": "\nEstá faltando a permissão **Ler mensagens**, **Enviar mensagens** ou **Gerenciar canal** em <#{{id}}>.", - "moveFailed": "Não é possível mover o canal pelas seguintes razões:\n{{errors}}\n\nTente novamente, ou digite `exit` para cancelar.", - "moveSuccess": "O canal para o(s) seguinte(s) feed(s):\n\n{{summary}}\n\nforam movidos com sucesso para <#{{id}}>" - }, - "options": { - "miscFeedOptions": "Diversas opções de Feed", - "selectOption": "\u200b\nPor favor selecione uma opção digitando o numero, ou digite `exit` para cancelar.\u200b\n\u200b\n", - "onlyIfNecessary": "Apenas ative se isto for necessário!", - "titleChecks": "Checar títulos", - "titleChecksToggle": "Alternar verificar título há um feed", - "titleChecksDescription": "As verificações de título garantirão que nenhum artigo com o mesmo título de um anterior será enviado para um feed específico.", - "imagePreviews": "Preview da imagem link", - "imagePreviewsToggle": "Alternar a preview da imagem de um feed", - "imagePreviewsDescription": "Alternar preview de uma imagem de um feed encontrado em um campo, como por exemplo em: {description}", - "imageLinksExistence": "Existência de links de imagem", - "imageLinksExistenceToggle": "Alternar links de imagem existência de espaços reservados de um feed", - "imageLinksExistenceDescription": "Remova links de imagem encontrados dentro de espaços reservados, como {description}. Se desabilitado, todos os links `src` da imagem em tais espaços reservados serão removidos.", - "dateChecks": "Checar data", - "dateChecksToggle": "Alterna a checagem da data de um feed", - "dateChecksDescription": "A verificação de datas garante que os artigos que são {{cycleMaxAge}} dia(s) antigos ou tenham pubdates inválidos/não publicados não serão enviados.", - "tableFormatting": "Formatação de Tabelas", - "tableFormattingToggle": "Alternar a formatação da tabela para um feed", - "tableFormattingDescription": "Se a formatação da tabela estiver ativada, eles devem ser colocados em blocos de código para garantir um espaçamento uniforme.", - "settingChanged": "{{propName}} foi {{finalSetting}} para <{{link}}>{{isDefault}}.", - "defaultSetting": "configuração padrão", - "directSubscribers": "", - "directSubscribersToggle": "", - "directSubscribersDescription": "" - }, - "prefix": { - "helpText": "Você deve especificar um prefixo de 4 caracteres ou menos como o primeiro argumento para definir um prefixo personalizado ou `reset` para redefinir o prefixo para padrão se um prefixo personalizado já estiver definido.", - "resetNone": "Você não tem prefixo personalizado para resetar.", - "resetSuccess": "O prefixo de comandos foi redefinido de volta ao padrão ({{prefix}}).", - "requirements": "O comprimento do prefixo de comandos deve ser menor que 5 caracteres sem espaços.", - "cannotUseDefault": "Não é possível usar este prefixo de comandos porque ele já é o prefixo padrão.", - "setSuccess": "Comando alterado com sucesso prefixo para \"{{prefixo}}\"." - }, - "refresh": { - "noFailLimit": "Nenhum limite de falha foi definido.", - "noFailedFeeds": "Não há feeds com falha para atualizar.", - "processing": "Processando solicitações para atualização. Isso pode demorar um pouco. Por favor, espere...", - "success": "Os seguintes links foram atualizados com sucesso:", - "failed": "Os seguintes links não foram atualizados:" - }, - "remove": { - "removing": "Removendo...", - "success": "Removeu com sucesso o seguinte link(s):" - }, - "split": { - "messageSplittingOptions": "Opções de divisão de mensagens", - "description": "**Feed Título:** {{title}}\n**Feed Link:** {{link}}\n\nA divisão de mensagens para este feed é atualmente {{currently}}. Selecione uma opção digitando o numero, ou digite `exit` para cancelar.\u200b\n\u200b\n", - "enabledDescription": "**Feed Título:** {{title}}\n**Feed Link:** {{link}}\n\n**Agora, a divisão de mensagens está ativada para este feed.**\n\nVocê pode personalizar ainda mais a divisão de mensagens selecionando uma das opções abaixo digitando seu número ou digite `exit` para deixar as configurações padrão.\u200b\n\u200b\n", - "optionEnable": "Ativar divisão de mensagens", - "optionEnableDescription": "A divisão de mensagens divide uma mensagem que excede o limite de caracteres Discord em várias mensagens.", - "optionDisable": "Desativar divisão de mensagens", - "optionSetSplitChar": "Definir caractere dividido", - "optionSetSplitCharDescription": "Especifique o caractere que a mensagem deve dividir de acordo com.", - "optionSetPrependChar": "Definir caractere pré-definido", - "optionSetPrependCharDescription": "Especifique o caractere que todas as mensagens, exceto a primeira, devem ser incluídas (adicionadas antes) com", - "optionSetAppendChar": "Definir o caractere acrescentado", - "optionSetAppendCharDescription": "Especifique o caractere que todas as mensagens, exceto a última, devem ser anexadas (adicionadas após) com", - "optionSetMaxLength": "Definir comprimento máximo", - "optionSetMaxLengthDescription": "Especifique o comprimento máximo que uma única mensagem deve ter.", - "currentlySetTo": "Atualmente configurado para `{{value}}`.", - "defaultIsValue": "Padrão é {{value}}", - "defaultIsNothing": "Padrão é nada", - "newLine": "nova linha", - "promptSplitChar": "Digite um caractere de divisão agora, `reset` para redefinir ou `exit` para cancelar.", - "promptPrependChar": "Digite um caractere pré-definido agora, `reset` para redefinir ou` sair` para cancelar.", - "promptAppendChar": "Digite um caractere anexado agora, `reset` para redefinir ou `exit` para cancelar.", - "promptMaxLen": "Digite o tamanho máximo que uma única mensagem deve ter agora, `reset` para resetar, ou `exit` para cancelar. **Deve ser um número> = 500 e <= 1950.**", - "setSplitCharDefault": "Esse já é o caractere padrão. Tente novamente ou digite `exit` para cancelar e deixar por padrão.", - "setSplitChar": "O caractere de divisão do feed <{{link}}> foi definido como `{{content}}`.", - "setPrependChar": "O caractere anterior ao feed <{{link}}> foi definido como `{{content}}`.", - "setAppendChar": "O caractere acrescentado ao feed <{{link}}> foi definido como `{{content}}`.", - "setMaxLen": "O tamanho máximo de uma única mensagem para o feed <{{link}}> foi definido como `{{num}}`.", - "setInvalidMaxLen": "Esse não é um número válido > = 500 e <= 1950. Tente novamente.", - "resetSplitChar": "O caractere de divisão do feed <{{link}}> foi redefinido como `\\n`.", - "resetPrependChar": "O caractere anterior ao feed <{{link}}> foi redefinido para não ser nada.", - "resetAppendChar": "O caractere acrescentado ao feed <{{link}}> foi redefinido para não ser nada.", - "resetMaxLen": "O tamanho máximo de uma única mensagem para o feed <{{link}}> foi redefinido como `1950`.", - "disabledSuccess": "A divisão de mensagens agora está desativada para o feed <{{link}}>." - }, - "test": { - "failed": "", - "grabbingRandom": "Buscando um artigo de feed aleatório ...", - "noArticles": "Não há artigos neste feed para enviar.", - "noValidLatest": "" - }, - "webhook": { - "existingFound": "Um webhook existente foi encontrado ({{webhookMention}}). Você pode digitar `{remove}` para desconectar o webhook existente ou continuar, e sua nova configuração sobrescreverá a existente.\n\n", - "prompt": "Digite o nome do webhook neste canal que você deseja usar (com distinção entre maiúsculas e minúsculas) ou digite `exit` para cancelar.\n\nPara usar um nome ou URL de avatar diferente do webhook quando os artigos são enviados para este feed específico, adicione parâmetros `--name=\"my new name here\"` or `--avatar=\"http://website.com/image.jpg\"`. Espaços reservados são suportados.", - "notFound": "Nenhum webhook chamado \"{{name}}\" foi encontrado para este canal. Tente novamente ou digite `exit` para cancelar.", - "tooLong": "O nome do Webhook deve ter entre 2 e 32 caracteres. Tente novamente ou digite `exit` para cancelar.", - "noPermission": "Eu preciso ter a permissão Gerenciar Webhooks neste canal para funcionar. Tente novamente ou digite `exit` para cancelar.", - "removeSuccess": "Webhook removido com sucesso do feed <{{link}}>.", - "addSuccess": "", - "connected": "Agora estou conectado a {{clientMention}} e vou enviar artigos de feed para <{{link}}>!", - "missingChannel": "" - }, - "sub": { - "title": "cargo auto-adicionável", - "invalidRole": "Esse não é um cargo válida para adicionar. Para ver a lista completa de cargos que podem ser adicionadas, digite `{{prefix}}sub`.", - "addSuccess": "Agora você tem o cargo `{{name}}`, inscrito em:", - "description": "", - "optionAddRole": "", - "optionAddRoleDescription": "", - "optionAddMe": "", - "optionAddMeDescription": "", - "optionSubscribedFeeds": "", - "optionSubscribedFeedsDescription": "", - "subscribedFeedsList": "", - "noSubscribedFeedsList": "", - "noEligible": "", - "listInputRole": "", - "directSubscribeExists": "", - "directSubscribeSuccess": "", - "alreadyHaveRole": "", - "directSubscriberDisabled": "", - "filters": { - "description": "", - "noSubscribedFeeds": "", - "optionAddFilters": "", - "optionAddFiltersDescription": "", - "optionRemoveFilters": "", - "optionRemoveFiltersDescription": "", - "optionRemoveAllFilters": "", - "optionRemoveAllFiltersDescription": "", - "optionListFilters": "", - "optionListFiltersDescription": "", - "optionSendFilteredArticle": "", - "optionSendFilteredArticleDescription": "" - } - }, - "unsub": { - "title": "Remoção de auto-inscrição", - "roleRemoveSuccess": "Você não tem mais o cargo {{role}}.", - "optionRemoveRole": "", - "optionRemoveRoleDescription": "", - "optionRemoveMyself": "", - "optionRemoveMyselfDescription": "", - "optionRemoveAll": "", - "optionRemoveAllDescription": "", - "noEligibleRoles": "", - "noEligibleDirect": "", - "invalidRole": "", - "directRemoveList": "", - "listInputRole": "", - "directRemoveSuccess": "", - "removeAllSuccess": "" - }, - "utils": { - "filters": { - "title": "Título", - "dscription": "Descrição", - "summary": "Resumo", - "author": "Autor", - "tags": "Tags", - "feed": "Feeds", - "role": "Cargo", - "user": "Usuário", - "filtersCustomization": "Personalização de Filtros", - "categoryDescription": "", - "invalidCategory": "Essa não é uma categoria de filtro válida. Tente novamente ou digite `exit` para cancelar.", - "promptAdd": "", - "addSuccess": "Os seguintes filtros foram adicionados com sucesso para a categoria de filtro", - "addFailed": "Os seguintes filtros não puderam ser adicionados porque já existem", - "updatedFor": "Inscrição atualizada para {{name}}.", - "testFiltersSubscriber": "\nVocê pode testar seus filtros em artigos aleatórios via `{{prefixo}}test` e ver quais artigos mencionarão esta inscrição.", - "testFilters": "\nVocê pode testar artigos aleatórios com `{{prefixo}}test` para ver quais artigos passam seus filtros, ou especificamente enviar artigos filtrados com a opção` {{prefixo}}filters` opção 5.", - "removeFilterConfirm": "Confirme a palavra/frase de filtro que você gostaria de remover na categoria `{{category}}` digitando uma ou várias palavras/frases separadas por novas linhas (diferenciando maiúsculas de minúsculas).", - "removeFilterInvalid": "Esse não é um filtro válido para remover de `{{category}}`. Tente novamente ou digite `exit` para cancelar.", - "removeSuccess": "Os seguintes filtros foram removidos com sucesso da categoria de filtro", - "removeSuccessSubscriber": "as seguintes inscrições foram atualizada para o `{{name}}`.", - "removeFailedNoExist": "Os seguintes filtros não puderam ser excluídos porque não existem", - "removeNone": "Não há filtros para remover de <{{link}}>", - "listOfFilters": "Lista de filtros atribuídos", - "listOfFiltersDescription": "**Feed Título:** {{title}}\n**Feed Link:** {{link}}\n\nAbaixo estão as categorias de filtro com suas palavras/frases em cada um. Digite a categoria de filtro da qual você deseja remover um filtro ou digite `exit` para cancelar.\u200b\n\u200b\n", - "regexExists": "" - } - } - }, - "structs": { - "MenuUtils": { - "permissionWarning": "AVISO: Faltam as permissões \"Adicionar Reações\" ou \"Ler Histórico de Mensagens \" neste canal. Como esse menu tem mais de {{maxPerPage}} opções, ele não funcionará corretamente sem as devidas permissões", - "closedInactivity": "Eu fechei o menu devido a inatividade.", - "closed": "Menu fechado." - }, - "FeedSelector": { - "noFeeds": "Este servidor não possui nenhum feed.", - "noFeedsInChannel": "Não há nenhum feed adicionado a este canal.", - "feedSelectionMenu": "Menu de seleção de feed", - "action": "Action", - "prompt": "Escolha um feed nesse canal digitando o número para executar a ação solicitada.", - "multiSelect": "Você pode selecionar vários feeds por separação com vírgulas (por exemplo `1,3,8`) ou com um intervalo (por exemplo `1-3,5,7-9`).", - "noneSelected": "", - "exitToCancel": "digite `exit` para cancelar." - }, - "errors": { - "MenuOptionError": { - "message": "Esta não é uma escolha válida. tente novamente, ou digite `exit` para cancelar." - } - } - } -} diff --git a/services/bot/src/locales/zh-TW.json b/services/bot/src/locales/zh-TW.json deleted file mode 100644 index 8ae2a6df9..000000000 --- a/services/bot/src/locales/zh-TW.json +++ /dev/null @@ -1,612 +0,0 @@ -{ - "generics": { - "defaultSetting": "預設值為`{{value}}`。", - "backupReminder": "在完全設定好之後,建議您使用 {{prefix}}backup 以備份個人設定。", - "channelUpper": "頻道", - "channelLower": "頻道", - "enabledUpper": "已啟用", - "enabledLower": "已啟用", - "disabledUpper": "已停用", - "disabledLower": "已停用" - }, - "commandDescriptions": { - "add": { - "description": "新增RSS feed至使用此指令的頻道。多個feed可透過使用`>`分隔同時新增。", - "args": { - "": "feed網址。" - } - }, - "alert": { - "description": "設定用於傳送feed警告/失敗的使用者私訊。", - "args": { - "add ": "新增要傳送私訊的使用者。", - "remove ": "移除要傳送私訊的使用者。", - "list": "已啟用私人訊息的使用者清單。" - } - }, - "backup": { - "description": "將伺服器設定檔以JSON附件傳送以作為個人備份。" - }, - "clone": { - "action": "複製feed設定", - "description": "將feed設定複製至其他feed。" - }, - "compare": { - "action": "", - "description": "", - "args": { - "list": "", - "(+|-)": "" - } - }, - "date": { - "description": "開啟選單以自訂如何顯示日期。" - }, - "dump": { - "action": "", - "args": { - "original": "輸出原始JSON格式。" - } - }, - "embed": { - "action": "自訂嵌入式訊息", - "description": "開啟用於自訂feed嵌入式訊息的選單。這將會在發佈連結時取代Discord平常傳送的一般嵌入式訊息。" - }, - "embed.fields": { - "action": "自訂嵌入field", - "description": "" - }, - "filters": { - "action": "增加/移除feed篩選條件", - "description": "" - }, - "invite": { - "description": "傳送此機器人的邀請連結。" - }, - "list": { - "description": "列出本伺服器所有可用的feed。", - "args": { - "[] []": "" - } - }, - "locale": { - "description": "變更指令語言(如支援該語言)", - "args": { - "": "希望使用之語言" - } - }, - "mention": { - "action": "自訂訂閱", - "description": "開啟選單以為身分組/使用者新增全域/過濾後之feed訂閱。" - }, - "mention.filters": { - "description": "" - }, - "move": { - "description": "開啟選單以將feed移動至另一個頻道。", - "action": "轉移feed頻道" - }, - "options": { - "description": "開啟選單以設定其他feed選項。" - }, - "prefix": { - "action": "變更指令前綴", - "description": "將用於指令的前綴從預設值({{defaultPrefix}})變更。", - "args": { - "": "要使用的前綴。", - "reset": "將前綴重設為預設值({{defaultPrefix}})。" - } - }, - "refresh": { - "action": "重新整理feed", - "description": "" - }, - "remove": { - "action": "移除feed", - "description": "開啟選單以從頻道刪除feed。" - }, - "split": { - "action": "", - "description": "" - }, - "stats": { - "description": "" - }, - "sub": { - "description": "" - }, - "sub.filters": { - "description": "" - }, - "test": { - "args": { - "[simple] [latest]": "" - }, - "action": "測試feed傳送", - "description": "" - }, - "text": { - "action": "自訂文字", - "description": "開啟選單以自訂feed的文字訊息。" - }, - "unsub": { - "description": "" - }, - "version": { - "description": "顯示機器人版本。" - }, - "webhook": { - "action": "連接webhook", - "description": "" - } - }, - "commands": { - "add": { - "correctSyntax": "正確的語法為 {{prefix}}add https://example.com 。多個連結可透過使用`>`分隔同時新增。", - "improperFormat": "", - "processing": "處理中……", - "limitReached": "", - "alreadyExists": "已存在於本頻道。", - "success": "已成功將以下feed加入**本頻道**", - "successInfo": "", - "failedLimit": "", - "failedList": "無法加入以下feed", - "reason": "原因" - }, - "alert": { - "noFeeds": "", - "info": "", - "notFound": "未在此伺服器找到使用者 {{user}} 。", - "everyoneNotAllowed": "", - "alreadyEnabled": "", - "successDM": "", - "success": "", - "removedDM": "", - "removed": "", - "removeFail": "", - "listEmpty": "", - "list": "" - }, - "backup": { - "noProfile": "本伺服器尚無設定檔。", - "noPermission": "由於缺少附加檔案(`Attach Files`)權限,無法傳送備份。" - }, - "clone": { - "copyFrom": "", - "copyTo": "", - "inputProperties": "", - "invalidProperties": "", - "confirm": "", - "confirmError": "", - "success": "" - }, - "compare": { - "info": "", - "list": "", - "listNone": "", - "invalid": "", - "onlyTitle": "", - "reset": "", - "success": "" - }, - "date": { - "selectTitle": "自訂日期", - "dateLanguage": "日期語言", - "dateFormat": "日期格式", - "timezone": "時區", - "noFeeds": "", - "description": "", - "optionCurrentSetting": "目前的設定值為`{{value}}`。", - "optionChangeTimezone": "變更時區", - "optionCustomizeFormat": "自訂格式", - "optionChangeLanguage": "變更語言", - "optionReset": "重設", - "optionResetValue": "", - "successResetAll": "已將所有日期自訂值重設為預設值。", - "successReset": "{{name}}已重設為預設值:`{{value}}`。", - "successSet": "{{name}}已成功變更為`{{value}}`。", - "promptNewLanguage": "", - "promptNewDateFormat": "", - "promptNewTimezone": "", - "invalidLanguage": "", - "invalidTimezone": "" - }, - "dump": { - "generatingDump": "", - "generatedDump": "" - }, - "embed": { - "numberedEmbed": "", - "embedSelection": "", - "embedSelectionDescription": "", - "embedSelectionOptionAdd": "", - "embedSelectionOptionAddDescription": "", - "embedSelectionOptionRemoveAll": "", - "currentProperties": "目前屬性", - "currentPropertiesList": "", - "availableProperties": "可用屬性", - "availablePropertiesList": "", - "invalidColorNumber": "", - "invalidColorRange": "顏色代碼須為0到16777215之間的數值。請重試或輸入`exit`取消。", - "invalidProperties": "以下屬性{{invalids}}無效。請重試或輸入`exit`取消。", - "removedAllEmbeds": "", - "removedEmbed": "", - "resetSuccess": "☑ 已重設**{{propName}}**\n", - "updatedSuccess": "☑ **{{propName}}**已更新至\n```\n{{userSetting}}\n```\n", - "updatedInfo": "", - "title": "標題", - "titleDescription": "", - "description": "描述", - "descriptionDescription": "", - "url": "URL", - "urlDescription": "", - "color": "顏色", - "colorDescription": "", - "timestamp": "時間戳記", - "timestampDescription": "", - "footerIconURL": "底部圖示URL", - "footerIconURLDescription": "", - "footerText": "底部文字", - "footerTextDescription": "", - "thumbnailURL": "縮圖URL", - "thumbnailURLDescription": "", - "imageURL": "圖片URL", - "imageURLDescription": "", - "authorName": "", - "authorNameDescription": "", - "authorURL": "", - "authorURLDescription": "", - "authorIconURL": "", - "authorIconURLDescription": "", - "settingProperty": "", - "settingPropertyTimestamp": "", - "settingPropertyTimestampError": "", - "inline": "行內", - "regular": "", - "blankField": "", - "embedFields": "", - "embedFieldsRemoveNone": "", - "embedFieldsRemoved": "", - "embedFieldsDescription": "", - "embedFieldsOptionRemove": "", - "embedFieldsOptionRemoveDescription": "", - "embedFieldsOptionAddRegular": "", - "embedFieldsOptionAddRegularDescription": "", - "embedFieldsOptionAddInline": "", - "embedFieldsOptionAddInlineDescription": "", - "embedFieldsOptionAddRegularBlank": "", - "embedFieldsOptionAddRegularBlankDescription": "", - "embedFieldsOptionAddInlineBlank": "", - "embedFieldsOptionAddInlineBlankDescription": "", - "embedFieldsOptionRemoveEmbedTitle": "", - "embedFieldsOptionRemoveEmbedDescription": "", - "embedFieldsAddedBlank": "", - "embedFieldsAddedBlankInline": "", - "embedFieldsSettingPrompt": "", - "embedFieldsSettingTitleLong": "", - "embedFieldsSettingValueLong": "", - "embedFieldsAdded": "" - }, - "faq": { - "searchQueryRequired": "", - "searching": "搜尋中……", - "noResults": "查無結果 🙁" - }, - "filters": { - "selectFeedDescription": "", - "feedFiltersCustomization": "自訂feed過濾器", - "optionAddFilters": "新增feed過濾器", - "optionAddFiltersDescription": "", - "optionRemoveFilters": "移除feed過濾器", - "optionRemoveFiltersDescription": "", - "optionRemoveAllFilters": "移除所有feed過濾器", - "optionRemoveAllFiltersDescription": "", - "optionListFilters": "列出現有的feed過濾器", - "optionListFiltersDescription": "", - "optionSendArticle": "", - "optionSendArticleDescription": "", - "removedAllSuccess": "", - "noFilters": "", - "noFiltersTryAgain": "", - "listFiltersDescription": "", - "connectionFailureLimit": "", - "noArticlesPassed": "" - }, - "help": { - "controlPanelLink": "", - "description": "", - "arguments": "引數:", - "support": "", - "checkDM": " ,請檢查您的私訊!" - }, - "invite": { - "text": "" - }, - "list": { - "noFeeds": "暫無feed。", - "noFeedsChannel": "頻道 {{channel}} 沒有任何feed。", - "channelLimitReached": "", - "titleChecksEnabled": "檢查標題:已啟用\n", - "statusDisabled": "狀態:已停用({{reason}})\n", - "statusFailed": "狀態:失敗\n", - "statusOk": "狀態:正常 {{failCount}}\n", - "serverLimit": "伺服器限制", - "failAlert": "", - "feedList": "feed清單", - "feedListChannel": "", - "refreshRate": "重新整理頻率", - "webhook": "webhook", - "link": "連結", - "exceeds500Characters": "*已達到500字元*", - "seconds": "秒", - "minutes": "分", - "unknown": "未知" - }, - "locale": { - "helpText": "", - "resetNone": "", - "resetNoDefault": "", - "resetSuccess": "", - "setSuccess": "", - "setNone": "", - "alreadySet": "" - }, - "mention": { - "role": "身分組", - "user": "使用者", - "noFeeds": "", - "subscriptionsList": "訂閱清單", - "description": "", - "subscriberOptions": "訂閱選項", - "optionAddSubscriber": "", - "optionAddSubscriberDescription": "", - "optionRemoveSubscriber": "", - "optionRemoveSubscriberDescription": "", - "optionRemoveAllSubscribers": "", - "optionRemoveAllSubscribersDescription": "", - "optionListSubscribers": "", - "optionListSubscribersDescription": "", - "promptUserOrRole": "", - "listSubscribersDescription": "", - "listSubscribersNone": "", - "removeAllSubscribersSuccess": "", - "addSubscriberExists": "", - "addSubscriberSuccess": "", - "notFeedSubscriber": "", - "removeSubscriberSuccess": "", - "removeAnySubscriberNone": "", - "invalidRoleOrUser": "", - "filters": { - "title": "", - "description": "", - "notSubscriber": "", - "optionAddFilter": "", - "optionRemoveFilter": "", - "optionRemoveAllFilters": "", - "optionListFilters": "", - "listFiltersDescription": "", - "listNoFilters": "", - "removedAllFilters": "" - } - }, - "text": { - "prompt": "", - "noEmpty": "", - "noSetText": "", - "resetSuccess": "", - "setSuccess": "", - "noSubscriptionsPlaceholder": "", - "reminder": "" - }, - "move": { - "prompt": "", - "invalidChannel": "", - "alreadyInChannel": "", - "linkAlreadyExists": "", - "meMissingPermission": "", - "meMissingEmbedLinks": "", - "youMissingPermission": "", - "moveFailed": "", - "moveSuccess": "" - }, - "options": { - "miscFeedOptions": "其他feed選項", - "selectOption": "", - "onlyIfNecessary": "", - "titleChecks": "檢查標題", - "titleChecksToggle": "", - "titleChecksDescription": "", - "imagePreviews": "圖片連結預覽", - "imagePreviewsToggle": "", - "imagePreviewsDescription": "", - "imageLinksExistence": "", - "imageLinksExistenceToggle": "", - "imageLinksExistenceDescription": "", - "dateChecks": "檢查日期", - "dateChecksToggle": "", - "dateChecksDescription": "", - "tableFormatting": "Table Formatting", - "tableFormattingToggle": "", - "tableFormattingDescription": "", - "directSubscribers": "", - "directSubscribersToggle": "", - "directSubscribersDescription": "", - "settingChanged": "", - "defaultSetting": "預設設定" - }, - "prefix": { - "helpText": "", - "resetNone": "", - "resetSuccess": "指令前綴已重設為預設值({{prefix}})。", - "requirements": "", - "cannotUseDefault": "", - "setSuccess": "已成功將指令前綴變更為 \"{{prefix}}\"。" - }, - "refresh": { - "noFailLimit": "", - "noFailedFeeds": "", - "processing": "", - "success": "", - "failed": "" - }, - "remove": { - "removing": "移除中……", - "success": "已成功移除以下連結:" - }, - "split": { - "messageSplittingOptions": "", - "description": "", - "enabledDescription": "", - "optionEnable": "", - "optionEnableDescription": "", - "optionDisable": "", - "optionSetSplitChar": "", - "optionSetSplitCharDescription": "", - "optionSetPrependChar": "", - "optionSetPrependCharDescription": "", - "optionSetAppendChar": "", - "optionSetAppendCharDescription": "", - "optionSetMaxLength": "", - "optionSetMaxLengthDescription": "", - "currentlySetTo": "目前設定值為`{{value}}`。", - "defaultIsValue": "預設值為{{value}}", - "defaultIsNothing": "", - "newLine": "", - "promptSplitChar": "", - "promptPrependChar": "", - "promptAppendChar": "", - "promptMaxLen": "", - "setSplitCharDefault": "", - "setSplitChar": "", - "setPrependChar": "", - "setAppendChar": "", - "setMaxLen": "", - "setInvalidMaxLen": "", - "resetSplitChar": "", - "resetPrependChar": "", - "resetAppendChar": "", - "resetMaxLen": "", - "disabledSuccess": "" - }, - "test": { - "failed": "", - "grabbingRandom": "", - "noArticles": "", - "noValidLatest": "" - }, - "webhook": { - "existingFound": "", - "prompt": "", - "notFound": "", - "tooLong": "", - "noPermission": "", - "removeSuccess": "", - "addSuccess": "", - "connected": "", - "missingChannel": "" - }, - "sub": { - "title": "", - "description": "", - "optionAddRole": "", - "optionAddRoleDescription": "", - "optionAddMe": "", - "optionAddMeDescription": "", - "optionSubscribedFeeds": "", - "optionSubscribedFeedsDescription": "", - "subscribedFeedsList": "", - "noSubscribedFeedsList": "", - "noEligible": "", - "invalidRole": "", - "listInputRole": "", - "addSuccess": "", - "directSubscribeExists": "", - "directSubscribeSuccess": "", - "alreadyHaveRole": "", - "directSubscriberDisabled": "", - "filters": { - "description": "", - "noSubscribedFeeds": "", - "optionAddFilters": "新增過濾器", - "optionAddFiltersDescription": "", - "optionRemoveFilters": "移除過濾器", - "optionRemoveFiltersDescription": "", - "optionRemoveAllFilters": "移除所有過濾器", - "optionRemoveAllFiltersDescription": "", - "optionListFilters": "列出現有過濾器", - "optionListFiltersDescription": "", - "optionSendFilteredArticle": "", - "optionSendFilteredArticleDescription": "" - } - }, - "unsub": { - "optionRemoveRole": "", - "optionRemoveRoleDescription": "", - "optionRemoveMyself": "", - "optionRemoveMyselfDescription": "", - "optionRemoveAll": "", - "optionRemoveAllDescription": "", - "noEligibleRoles": "", - "noEligibleDirect": "", - "invalidRole": "要移除的身分組無效。請重試或輸入`exit`取消。", - "title": "", - "directRemoveList": "", - "listInputRole": "", - "roleRemoveSuccess": "", - "directRemoveSuccess": "", - "removeAllSuccess": "" - }, - "utils": { - "filters": { - "title": "標題", - "dscription": "描述", - "summary": "摘要", - "author": "", - "tags": "標籤", - "feed": "feed", - "role": "身分組", - "user": "使用者", - "filtersCustomization": "自訂過濾器", - "categoryDescription": "", - "invalidCategory": "過濾器分類無效。請重試或輸入`exit`取消。", - "promptAdd": "", - "addSuccess": "", - "addFailed": "", - "updatedFor": "已更新{{name}}的訂閱。", - "testFiltersSubscriber": "", - "testFilters": "", - "removeFilterConfirm": "", - "removeFilterInvalid": "", - "removeSuccess": "", - "removeSuccessSubscriber": "已更新`{{name}}`的訂閱。", - "removeFailedNoExist": "以下過濾器由於不存在無法刪除", - "removeNone": "", - "listOfFilters": "", - "listOfFiltersDescription": "", - "regexExists": "" - } - } - }, - "structs": { - "MenuUtils": { - "permissionWarning": "", - "closedInactivity": "", - "closed": "已關閉選單。" - }, - "FeedSelector": { - "noFeeds": "此伺服器沒有任何feed。", - "noFeedsInChannel": "", - "feedSelectionMenu": "", - "action": "操作", - "prompt": "", - "multiSelect": "", - "noneSelected": "", - "exitToCancel": "輸入`exit`取消。" - }, - "errors": { - "MenuOptionError": { - "message": "選擇無效。請重試或輸入`exit`取消。" - } - } - } -} diff --git a/services/bot/src/maintenance/checkIndexes.js b/services/bot/src/maintenance/checkIndexes.js deleted file mode 100644 index e86fd1933..000000000 --- a/services/bot/src/maintenance/checkIndexes.js +++ /dev/null @@ -1,30 +0,0 @@ -const Article = require('../models/Article.js') -const DeliveryRecord = require('../models/DeliveryRecord.js') -const ensureIndexes = require('./generic/ensureIndexes.js') -const configuration = require('../config.js') -const INDEX_NAME = 'addedAt_1' - -async function checkArticleIndexes () { - const config = configuration.get() - await ensureIndexes(Article.Model, INDEX_NAME, config.database.articlesExpire) -} - -async function checkDeliveryRecordsIndexes () { - const config = configuration.get() - await ensureIndexes(DeliveryRecord.Model, INDEX_NAME, config.database.deliveryRecordsExpire) -} - -async function checkIndexes () { - const config = configuration.get() - if (!config.database.uri.startsWith('mongo')) { - return - } - await checkArticleIndexes() - await checkDeliveryRecordsIndexes() -} - -module.exports = { - checkArticleIndexes, - checkDeliveryRecordsIndexes, - checkIndexes -} diff --git a/services/bot/src/maintenance/checkLimits.js b/services/bot/src/maintenance/checkLimits.js deleted file mode 100644 index 1cdee03ab..000000000 --- a/services/bot/src/maintenance/checkLimits.js +++ /dev/null @@ -1,82 +0,0 @@ -const Guild = require('../structs/Guild.js') -const createLogger = require('../util/logger/create.js') -const getConfig = require('../config.js').get -const log = createLogger() - -/** - * Enable or disable feeds for guilds past their limit - * @param {import('../structs/db/Feed.js')[]} feeds - * @returns {Object} object - * @returns {import('../structs/db/Feed.js')[]} object.enabled - Number of enabled feeds - * @returns {import('../structs/db/Feed.js')[]} object.disabled - Number of disabled feeds - */ -async function checkLimits (feeds) { - const supporterLimits = await Guild.getAllUniqueFeedLimits() - const config = getConfig() - const feedCounts = new Map() - const enabled = [] - const disabled = [] - const length = feeds.length - for (var i = 0; i < length; ++i) { - const feed = feeds[i] - const guild = feed.guild - const supporterLimit = supporterLimits.get(guild) - const guildLimit = supporterLimit !== undefined - ? supporterLimit - : config._vipRestricted === true - ? -1 // If vip restricted, every non-supporter should have 0 feeds - : config.feeds.max - let feedCount = feedCounts.get(guild) || 0 - - // Ignore all unrelated disabled feeds and don't count them - if (feed.disabled && feed.disabled !== 'Exceeded feed limit') { - continue - } - - // If the limit is 0 and the feed exceeded limit, enable it - if (guildLimit === 0) { - if (feed.disabled) { - log.info(`Enabling disabled feed ${feed._id} of guild ${feed.guild} due to no set limit`) - enabled.push(feed.enable()) - } - continue - } - - /** - * Two cases: - * 1. Disabled feed whose reason is exceeded feed limit - * 2. Enabled feed - */ - - if (feed.disabled) { - if (feedCount < guildLimit) { - // Enable it - feedCount = feedCount + 1 - feedCounts.set(guild, feedCount) - log.info(`Enabling disabled feed ${feed._id} of guild ${feed.guild} due to limit change`) - enabled.push(feed.enable()) - } - // Otherwise, don't count it - } else { - if (feedCount + 1 > guildLimit) { - // Disable it if adding the current one goes over limit - log.info(`Disabling enabled feed ${feed._id} of guild ${feed.guild} due to limit change`) - disabled.push(feed.disable('Exceeded feed limit')) - } else { - // Otherwise, just count it - feedCount = feedCount + 1 - feedCounts.set(guild, feedCount) - } - } - } - const results = await Promise.all([ - Promise.all(disabled), - Promise.all(enabled) - ]) - return { - disabled: results[0], - enabled: results[1] - } -} - -exports.limits = checkLimits diff --git a/services/bot/src/maintenance/checkPermissions.js b/services/bot/src/maintenance/checkPermissions.js deleted file mode 100644 index 6e3ef8dda..000000000 --- a/services/bot/src/maintenance/checkPermissions.js +++ /dev/null @@ -1,90 +0,0 @@ -const FLAGS = require('discord.js').Permissions.FLAGS -const createLogger = require('../util/logger/create.js') - -/** - * Precondition: The feed's guild belongs to the bot, or the - * shard if it is sharded. Webhooks have been pruned. - * - * @param {import('../../structs/db/Feed.js')} feed - The feed - * @param {import('discord.js').Client} bot - * @returns {Promise} - The feed's disabled status - */ -async function feed (feed, bot) { - const log = createLogger(bot.shard.ids[0]) - if (feed.disabled && !feed.disabled.startsWith('Missing permissions')) { - // The feed is disabled for a separate reason - skip all checks - return true - } - if (feed.webhook) { - return false - } - const channel = bot.channels.cache.get(feed.channel) - const guild = channel.guild - const permissions = guild.me.permissionsIn(channel) - const allowView = permissions.has(FLAGS.VIEW_CHANNEL) - const allowSendMessages = permissions.has(FLAGS.SEND_MESSAGES) - const allowEmbedLinks = feed.embeds.length === 0 ? true : permissions.has(FLAGS.EMBED_LINKS) - if (!allowSendMessages || !allowEmbedLinks || !allowView) { - const reasons = [] - if (!allowSendMessages) { - reasons.push('SEND_MESSAGES') - } - if (!allowEmbedLinks) { - reasons.push('EMBED_LINKS') - } - if (!allowView) { - reasons.push('VIEW_CHANNEL') - } - const reason = `Missing permissions ${reasons.join(', ')}` - if (!feed.disabled) { - // ipc.sendUserAlert(channel.id, `The feed <${feed.url}> has been disabled in channel <#${channel.id}>: ${reason}`) - log.info({ - guild, - channel - }, `Disabling feed ${feed._id} (${reason})`) - await feed.disable(reason) - } else if (feed.disabled.startsWith('Missing permissions') && feed.disabled !== reason) { - log.info({ - guild, - channel - }, `Updating disabled feed ${feed._id} (${reason})`) - await feed.disable(reason) - } - return true - } else if (feed.disabled && feed.disabled.startsWith('Missing permissions')) { - log.info({ - guild, - channel - }, `Enabling feed ${feed._id} for found permissions`) - // ipc.sendUserAlert(channel.id, `The feed <${feed.url}> has been enabled in channel <#${channel.id}> due to found permissions.`) - await feed.enable() - return false - } - return !!feed.disabled -} - -/** - * Checks the permissions of all feeds. - * @param {import('discord.js').Client} bot - * @param {import('../structs/db/Feed.js')[]} feeds - */ -async function feeds (bot, feeds) { - const length = feeds.length - const promises = [] - for (var i = length - 1; i >= 0; --i) { - const feed = feeds[i] - const channelID = feed.channel - const hasChannel = bot.channels.cache.has(channelID) - - // Skip channels that don't belong to this shard - if (!hasChannel) { - continue - } - - promises.push(exports.feed(feed, bot)) - } - await Promise.all(promises) -} - -exports.feed = feed -exports.feeds = feeds diff --git a/services/bot/src/maintenance/generic/ensureIndexes.js b/services/bot/src/maintenance/generic/ensureIndexes.js deleted file mode 100644 index c9ef96dee..000000000 --- a/services/bot/src/maintenance/generic/ensureIndexes.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @param {import('mongoose').Model} Model - * @param {number} daysUntilExpiration - */ -async function checkIndexes (Model, indexName, daysUntilExpiration) { - const names = (await Model.db.db.listCollections().toArray()) - .map((collectionDetail) => collectionDetail.name) - if (!names.includes(Model.collection.name)) { - return - } - const indexes = await Model.collection.indexes() - const foundIndex = indexes.find(idx => idx.name === indexName) - if (daysUntilExpiration <= 0) { - if (foundIndex) { - await Model.collection.dropIndex(indexName) - } - } else { - if (foundIndex && foundIndex.expireAfterSeconds !== 86400 * daysUntilExpiration) { - await Model.collection.dropIndex(indexName) - } - Model.schema.index({ - addedAt: 1 - }, { - expires: daysUntilExpiration * 86400 - }) - await Model.ensureIndexes() - } -} - -module.exports = checkIndexes diff --git a/services/bot/src/maintenance/index.js b/services/bot/src/maintenance/index.js deleted file mode 100644 index c0cb9c191..000000000 --- a/services/bot/src/maintenance/index.js +++ /dev/null @@ -1,91 +0,0 @@ -const pruneProfiles = require('./pruneProfiles.js') -const pruneProfileAlerts = require('./pruneProfileAlerts.js') -const pruneFeeds = require('./pruneFeeds.js') -const pruneFilteredFormats = require('./pruneFilteredFormats.js') -const pruneFailRecords = require('./pruneFailRecords.js') -const pruneSubscribers = require('./pruneSubscribers.js') -const checkLimits = require('./checkLimits.js') -const checkPermissions = require('./checkPermissions.js') -const checkIndexes = require('./checkIndexes.js') -const ScheduleStats = require('../structs/db/ScheduleStats.js') -const Supporter = require('../structs/db/Supporter.js') -const Patron = require('../structs/db/Patron.js') -const Feed = require('../structs/db/Feed.js') -const createLogger = require('../util/logger/create.js') - -/** - * @param {Map} guildIdsByShard - * @param {Map} channelIdsByShard - * @param {import('discord.js').Client} bot - */ -async function prunePreInit (guildIdsByShard, channelIdsByShard) { - const feeds = await Feed.getAllByPagination() - await Promise.all([ - checkIndexes.checkIndexes(), - ScheduleStats.deleteAll(), - pruneProfiles(guildIdsByShard) - ]) - // await pruneFeeds(feeds, guildIdsByShard, channelIdsByShard) - await Promise.all([ - pruneFilteredFormats(feeds), - pruneFailRecords(feeds) - ]) -} - -/** - * @param {import('discord.js').Client} bot - * @param {import('@synzen/discord-rest').RESTProducer} restProducer - */ -async function pruneWithBot (bot, restProducer) { - const log = createLogger(bot.shard.ids[0]) - const guilds = bot.guilds.cache.keyArray() - log.debug('Pruning with bot. Fetching feeds') - const feeds = await Feed.getManyByQuery({ - guild: { - $in: guilds - } - }) - log.debug(`Fetched ${feeds.length} feeds for pruning`) - // await Promise.all([ - // pruneSubscribers.pruneSubscribers(bot, feeds, restProducer), - // pruneProfileAlerts(bot, restProducer), - // pruneWebhooks.pruneWebhooks(bot, feeds, restProducer) - // ]) - // await checkPermissions.feeds(bot, feeds) -} - -/** - * @param {Map} guildIdsByShard - */ -async function prunePostInit (guildIdsByShard) { -} - -function cycleFunctions () { - const log = createLogger() - if (Supporter.enabled) { - Patron.refresh() - .then(() => log.info('Patron check finished')) - .catch(err => log.error(err, 'Failed to refresh patrons on timer')) - } -} - -async function cycle () { - cycleFunctions() - // Every 10 minutes run these functions - return setInterval(cycleFunctions, 600000) -} - -module.exports = { - pruneWithBot, - prunePreInit, - prunePostInit, - pruneProfiles, - pruneProfileAlerts, - pruneFeeds, - pruneFilteredFormats, - pruneFailRecords, - pruneSubscribers, - checkLimits, - checkPermissions, - cycle -} diff --git a/services/bot/src/maintenance/pruneFailRecords.js b/services/bot/src/maintenance/pruneFailRecords.js deleted file mode 100644 index 802ebb65e..000000000 --- a/services/bot/src/maintenance/pruneFailRecords.js +++ /dev/null @@ -1,27 +0,0 @@ -const FailRecord = require('../structs/db/FailRecord.js') - -/** - * Remove all fail records with URLS that no feed has - * @param {import('../structs/db/Feed.js')[]} feeds - * @returns {number} - */ -async function pruneFailRecords (feeds) { - const records = await FailRecord.getAll() - const feedsLength = feeds.length - const activeURLs = new Set() - for (var i = feedsLength - 1; i >= 0; --i) { - activeURLs.add(feeds[i].url) - } - const deletions = [] - const recordsLength = records.length - for (var j = recordsLength - 1; j >= 0; --j) { - const record = records[j] - if (!activeURLs.has(record._id)) { - deletions.push(record.delete()) - } - } - await Promise.all(deletions) - return deletions.length -} - -module.exports = pruneFailRecords diff --git a/services/bot/src/maintenance/pruneFeeds.js b/services/bot/src/maintenance/pruneFeeds.js deleted file mode 100644 index 28c14bbbc..000000000 --- a/services/bot/src/maintenance/pruneFeeds.js +++ /dev/null @@ -1,30 +0,0 @@ -const log = require('../util/logger/create.js')('-') - -/** - * Remove all feeds whose guild doesn't exist - * - * Removing feeds will also delete their formats/subscribers - * according to the Feed.prototype.delete implementation - * @param {import('../structs/db/Feed.js')[]} feeds - * @param {Map} guildIdsByShard - * @param {Map} channelIdsByShard - * @returns {number} - */ -async function pruneFeeds (feeds, guildIdsByShard, channelIdsByShard) { - const deletions = [] - const length = feeds.length - for (var i = length - 1; i >= 0; --i) { - const feed = feeds[i] - const hasGuild = guildIdsByShard.has(feed.guild) - const hasChannel = channelIdsByShard.has(feed.channel) - if (!hasGuild || !hasChannel) { - log.info(`Removing feed ${feed._id} (hasGuild: ${hasGuild}, hasChannel: ${hasChannel})`) - deletions.push(feed.delete()) - feeds.splice(i, 1) - } - } - await Promise.all(deletions) - return deletions.length -} - -module.exports = pruneFeeds diff --git a/services/bot/src/maintenance/pruneFilteredFormats.js b/services/bot/src/maintenance/pruneFilteredFormats.js deleted file mode 100644 index 247b66f9d..000000000 --- a/services/bot/src/maintenance/pruneFilteredFormats.js +++ /dev/null @@ -1,27 +0,0 @@ -const FilteredFormat = require('../structs/db/FilteredFormat.js') - -/** - * Remove all formats with missing feeds - * @param {import('../structs/db/Feed.js')[]} feeds - * @returns {number} - */ -async function pruneFormats (feeds) { - const filteredFormats = await FilteredFormat.getAll() - const feedIDs = new Set() - const feedsLength = feeds.length - for (var i = feedsLength - 1; i >= 0; --i) { - feedIDs.add(feeds[i]._id) - } - const deletions = [] - const filteredFormatsLength = filteredFormats.length - for (var j = filteredFormatsLength - 1; j >= 0; --j) { - const format = filteredFormats[j] - if (!feedIDs.has(format.feed)) { - deletions.push(format.delete()) - } - } - await Promise.all(deletions) - return deletions.length -} - -module.exports = pruneFormats diff --git a/services/bot/src/maintenance/pruneProfileAlerts.js b/services/bot/src/maintenance/pruneProfileAlerts.js deleted file mode 100644 index e0caed64f..000000000 --- a/services/bot/src/maintenance/pruneProfileAlerts.js +++ /dev/null @@ -1,64 +0,0 @@ -const Profile = require('../structs/db/Profile.js') -const createLogger = require('../util/logger/create.js') -// https://discord.com/developers/docs/topics/opcodes-and-status-codes -const DELETE_CODES = new Set([10007, 10013, 50035]) - -/** - * @param {import('discord.js').Client} bot - * @param {import('@synzen/discord-rest').RESTProducer|null} restProducer - * @returns {number} - */ -async function pruneProfileAlerts (bot, restProducer) { - const log = createLogger(bot.shard.ids[0]) - /** @type {Profile[]} */ - const profiles = await Profile.getAll() - const promises = profiles.map(profile => (async () => { - let updated = false - const guildID = profile._id - const guild = bot.guilds.cache.get(guildID) - if (!guild) { - return - } - const userAlerts = profile.alert - for (var i = userAlerts.length - 1; i >= 0; --i) { - const memberID = userAlerts[i] - if (!(/^\d+$/.test(memberID))) { - // Has non-digits - log.info(`Deleting invalid alert user "${memberID}"`, guild) - userAlerts.splice(i, 1) - updated = true - continue - } - try { - if (!restProducer) { - await guild.members.fetch(memberID) - } else { - const res = await restProducer.fetch(`https://discord.com/api/guilds/${guild.id}/members/${memberID}`, { - method: 'GET' - }) - if (!String(res.status).startsWith('2')) { - const error = new Error(`Bad status code (${res.status})`) - error.code = res.body.code - throw error - } - } - } catch (err) { - // Either unknown member, user, or invalid ID - if (DELETE_CODES.has(err.code)) { - log.info(`Deleting missing alert user "${memberID}"`, guild) - userAlerts.splice(i, 1) - updated = true - } else { - throw err - } - } - } - if (updated) { - await profile.save() - } - })()) - - await Promise.all(promises) -} - -module.exports = pruneProfileAlerts diff --git a/services/bot/src/maintenance/pruneProfiles.js b/services/bot/src/maintenance/pruneProfiles.js deleted file mode 100644 index d96ec9e15..000000000 --- a/services/bot/src/maintenance/pruneProfiles.js +++ /dev/null @@ -1,22 +0,0 @@ -const Profile = require('../structs/db/Profile.js') - -/** - * Remove all guilds no longer with the bot - * @param {Map} guildIdsByShard - * @returns {number} - */ -async function pruneProfiles (guildIdsByShard) { - const profiles = await Profile.getAll() - const deletions = [] - const length = profiles.length - for (var i = 0; i < length; ++i) { - const profile = profiles[i] - if (!guildIdsByShard.has(profile._id)) { - deletions.push(profile.delete()) - } - } - await Promise.all(deletions) - return deletions.length -} - -module.exports = pruneProfiles diff --git a/services/bot/src/maintenance/pruneSubscribers.js b/services/bot/src/maintenance/pruneSubscribers.js deleted file mode 100644 index e865de19b..000000000 --- a/services/bot/src/maintenance/pruneSubscribers.js +++ /dev/null @@ -1,121 +0,0 @@ -const Subscriber = require('../structs/db/Subscriber.js') -const createLogger = require('../util/logger/create.js') -const MISSING_CODES = new Set([10011, 10013, 10007]) - -/** - * @typedef {Object} SubscriberDetails - * @property {import('discord.js').Guild} guild - * @property {import('../structs/db/Subscriber.js')} subscriber - */ - -/** - * @param {import('discord.js').Client} bot - * @param {SubscriberDetails[]} subscribersDetails - * @param {import('@synzen/discord-rest').RESTProducer|null} restProducer - */ -async function fetchSubscribers (subscribersDetails, restProducer) { - const idsToFetch = new Set() - for (var i = subscribersDetails.length - 1; i >= 0; --i) { - const { subscriber } = subscribersDetails[i] - idsToFetch.add(subscriber.id) - } - const ids = [] - const fetches = [] - for (var j = subscribersDetails.length - 1; j >= 0; --j) { - const { subscriber, guild } = subscribersDetails[j] - if (!idsToFetch.has(subscriber.id)) { - continue - } - ids.push(subscriber.id) - if (!restProducer) { - fetches.push(guild.members.fetch(subscriber.id)) - } else { - fetches.push(restProducer.fetch(`https://discord.com/api/guilds/${guild.id}/members/${subscriber.id}`, { - method: 'GET' - }) - .then((res) => { - if (!String(res.status).startsWith('2')) { - const error = new Error(`Bad status code (${res.status})`) - error.code = res.body.code - throw error - } - return res.body - })) - } - idsToFetch.delete(subscriber.id) - } - const results = await Promise.allSettled(fetches) - const returnData = new Map() - for (var k = results.length - 1; k >= 0; --k) { - const id = ids[k] - const result = results[k] - returnData.set(id, result) - } - return returnData -} - -/** - * Precondition: Feeds have been pruned, and thus no feeds - * with missing guilds remain. The bot is also sharded. - * - * 1. Remove all subscribers whose feed doesn't exist - * 2. Remove all subscribers that don't exist in Discord - * @param {import('discord.js').Client} bot - * @param {import('../structs/db/Feed.js')[]} feeds - * @param {import('@synzen/discord-rest').RESTProducer|null} restProducer - * @returns {number} - */ -async function pruneSubscribers (bot, feeds, restProducer) { - const log = createLogger(bot.shard.ids[0]) - const subscribers = await Subscriber.getAll() - /** @type {Map} */ - const feedsById = new Map() - for (var i = feeds.length - 1; i >= 0; --i) { - const feed = feeds[i] - feedsById.set(feed._id, feed) - } - const deletions = [] - const relevantSubscribers = [] - for (var j = subscribers.length - 1; j >= 0; --j) { - const subscriber = subscribers[j] - const feed = feedsById.get(subscriber.feed) - if (!feed) { - continue - } - const guild = bot.guilds.cache.get(feed.guild) - if (subscriber.type === Subscriber.TYPES.ROLE) { - if (!guild.roles.cache.has(subscriber.id)) { - log.info(`Deleting missing role subscriber ${subscriber._id} of feed ${subscriber.feed}`) - deletions.push(subscriber.delete()) - } - } else if (subscriber.type === Subscriber.TYPES.USER) { - relevantSubscribers.push({ - guild, - subscriber - }) - } else { - log.info(`Deleting invalid ${subscriber.type} subscriber ${subscriber._id} of feed ${feed._id} of guild ${feed.guild}`) - deletions.push(subscriber.delete()) - } - } - const results = await exports.fetchSubscribers(relevantSubscribers, restProducer) - const brokenSubscribers = new Set() - results.forEach((result, subscriberID) => { - if (result.status === 'rejected' && MISSING_CODES.has(result.reason.code)) { - brokenSubscribers.add(subscriberID) - } - }) - for (var k = subscribers.length - 1; k >= 0; --k) { - const subscriber = subscribers[k] - if (brokenSubscribers.has(subscriber.id)) { - log.info(`Deleting missing user subscriber ${subscriber._id} of feed ${subscriber.feed}`) - deletions.push(subscriber.delete()) - } - } - - await Promise.all(deletions) - return deletions.length -} - -exports.fetchSubscribers = fetchSubscribers -exports.pruneSubscribers = pruneSubscribers diff --git a/services/bot/src/maintenance/pruneWebhooks.js b/services/bot/src/maintenance/pruneWebhooks.js deleted file mode 100644 index 5d3efd3b8..000000000 --- a/services/bot/src/maintenance/pruneWebhooks.js +++ /dev/null @@ -1,176 +0,0 @@ -const Guild = require('../structs/Guild.js') -const Supporter = require('../structs/db/Supporter.js') -const createLogger = require('../util/logger/create.js') - -/** - * @param {import('discord.js').Client} bot - * @param {import('../structs/db/Feed.js')[]} feeds - */ -function getRelevantFeeds (bot, feeds) { - const relevantFeeds = [] - const feedsLength = feeds.length - for (var i = 0; i < feedsLength; ++i) { - const feed = feeds[i] - if (!feed.webhook) { - continue - } - const channel = bot.channels.cache.get(feed.channel) - if (!channel) { - continue - } - relevantFeeds.push(feed) - } - return relevantFeeds -} - -/** - * @param {import('discord.js').Client} bot - * @param {import('../structs/db/Feed.js')[]} relevantFeeds - * @param {import('@synzen/discord-rest').RESTProducer|null} restProducer - */ -async function fetchChannelWebhooks (bot, relevantFeeds, restProducer) { - const feedsLength = relevantFeeds.length - const channelsToFetch = [] - for (var i = 0; i < feedsLength; ++i) { - const feed = relevantFeeds[i] - const channel = bot.channels.cache.get(feed.channel) - if (!channelsToFetch.includes(channel)) { - channelsToFetch.push(channel) - } - } - const results = await Promise.allSettled(channelsToFetch.map(async c => { - if (!restProducer) { - return c.fetchWebhooks() - } - const { status, body } = await restProducer.fetch(`https://discord.com/api/channels/${c.id}/webhooks`, { - method: 'GET' - }) - if (!String(status).startsWith('2')) { - // Add code field to maintain compatibility with discord.js error handling - const error = new Error(`Bad status code (${status})`) - error.code = body.code - throw error - } - const webhooksMap = new Map() - body.forEach(webhook => webhooksMap.set(webhook.id, webhook)) - return webhooksMap - })) - const map = new Map() - for (var j = 0; j < results.length; ++j) { - const channel = channelsToFetch[j] - const fetchResult = results[j] - map.set(channel.id, fetchResult) - } - return map -} - -/** - * @param {import('discord.js').Client} bot - * @param {import('../structs/db/Feed.js')} feed - * @param {Object} webhookFetchResult - */ -function getRemoveReason (bot, feed, webhookFetchResult) { - const { status, value: webhooks, reason } = webhookFetchResult - const log = createLogger(bot.shard.ids[0]) - const channel = bot.channels.cache.get(feed.channel) - let removeReason = '' - const webhookID = feed.webhook.id - if (status === 'fulfilled') { - if (!webhooks.get(webhookID)) { - removeReason = `Removing missing webhook from feed ${feed._id}` - } - } else { - const err = reason - if (err.code === 50013) { - removeReason = `Removing unpermitted webhook from feed ${feed._id}` - } else { - log.warn({ - guild: channel.guild, - channel, - error: err - }, `Unable to check webhook (request error, code ${err.code})`) - } - } - return removeReason -} - -async function getDisableReason (bot, feed) { - const channel = bot.channels.cache.get(feed.channel) - let disableReason = '' - const guild = new Guild(channel.guild.id) - if (Supporter.enabled && !(await guild.hasSupporterOrSubscriber())) { - disableReason = `Disabling unauthorized supporter webhook from feed ${feed._id}` - } - return disableReason -} - -/** - * Precondition: The bot is sharded and no guilds - * with missing channels remain. - * - * Remove all webhooks from feeds that don't exist - * @param {import('discord.js').Client} bot - * @param {import('../structs/db/Feed.js')[]} feeds - * @param {import('@synzen/discord-rest').RESTProducer|null} restProducer - * @returns {number} - */ -async function pruneWebhooks (bot, feeds, restProducer) { - const updates = [] - const log = createLogger(bot.shard.ids[0]) - const relevantFeeds = exports.getRelevantFeeds(bot, feeds) - const webhookFetchData = await exports.fetchChannelWebhooks(bot, relevantFeeds, restProducer) - - // Parse the fetch results - const relevantFeedsLength = relevantFeeds.length - const removeReasons = [] - const disableReasonsFetches = [] - for (var j = 0; j < relevantFeedsLength; ++j) { - const feed = relevantFeeds[j] - const webhookFetchResult = webhookFetchData.get(feed.channel) - const removeReason = exports.getRemoveReason(bot, feed, webhookFetchResult) - removeReasons.push(removeReason) - if (removeReason) { - disableReasonsFetches.push('') - } else { - disableReasonsFetches.push(exports.getDisableReason(bot, feed)) - } - } - const disableReasons = await Promise.all(disableReasonsFetches) - for (var k = 0; k < relevantFeedsLength; ++k) { - const feed = relevantFeeds[k] - const removeReason = removeReasons[k] - const disableReason = disableReasons[k] - const channel = bot.channels.cache.get(feed.channel) - if (removeReason) { - log.info({ - guild: channel.guild, - channel - }, removeReason) - feed.webhook = undefined - updates.push(feed.save()) - } else { - if (disableReason && !feed.webhook.disabled) { - feed.webhook.disabled = true - updates.push(feed.save()) - log.info({ - guild: channel.guild, - channel - }, disableReason) - } else if (!disableReason && feed.webhook.disabled) { - feed.webhook.disabled = undefined - updates.push(feed.save()) - log.info({ - guild: channel.guild, - channel - }, 'Enabling webhook, found authorization') - } - } - } - await Promise.all(updates) -} - -exports.getRelevantFeeds = getRelevantFeeds -exports.fetchChannelWebhooks = fetchChannelWebhooks -exports.getDisableReason = getDisableReason -exports.getRemoveReason = getRemoveReason -exports.pruneWebhooks = pruneWebhooks diff --git a/services/bot/src/models/Article.js b/services/bot/src/models/Article.js deleted file mode 100644 index 8b2ab2a1c..000000000 --- a/services/bot/src/models/Article.js +++ /dev/null @@ -1,40 +0,0 @@ -const mongoose = require('mongoose') -const Version = require('./common/Version.js') - -const schema = new mongoose.Schema({ - id: { - type: String, - required: true - }, - feedURL: { - required: true, - type: String - }, - scheduleName: { - required: true, - type: String - }, - date: { - type: Date, - default: Date.now - }, - properties: { - type: Map, - of: String - } -}) - -schema.add(Version) - -schema.index({ - scheduleName: 1 -}) - -schema.index({ - feedURL: 1, - scheduleName: 1 -}) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/BannedFeed.js b/services/bot/src/models/BannedFeed.js deleted file mode 100644 index b867adad8..000000000 --- a/services/bot/src/models/BannedFeed.js +++ /dev/null @@ -1,20 +0,0 @@ -const mongoose = require('mongoose') -const Version = require('./common/Version.js') - -const schema = new mongoose.Schema({ - url: { - type: String, - required: true - }, - reason: String, - guildIds: { - type: [String], - default: [] - } -}) - -schema.add(Version) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/Blacklist.js b/services/bot/src/models/Blacklist.js deleted file mode 100644 index 8ed34e557..000000000 --- a/services/bot/src/models/Blacklist.js +++ /dev/null @@ -1,19 +0,0 @@ -const mongoose = require('mongoose') -const Version = require('./common/Version.js') - -const schema = new mongoose.Schema({ - _id: { - type: String - }, - type: { - type: Number, - required: true - }, - name: String -}) - -schema.add(Version) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/DebugFeed.js b/services/bot/src/models/DebugFeed.js deleted file mode 100644 index 4bad40983..000000000 --- a/services/bot/src/models/DebugFeed.js +++ /dev/null @@ -1,12 +0,0 @@ -const mongoose = require('mongoose') -const Version = require('./common/Version.js') - -const schema = new mongoose.Schema({ - feedId: String -}) - -schema.add(Version) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/DeliveryRecord.js b/services/bot/src/models/DeliveryRecord.js deleted file mode 100644 index 198616b99..000000000 --- a/services/bot/src/models/DeliveryRecord.js +++ /dev/null @@ -1,32 +0,0 @@ -const mongoose = require('mongoose') -const Version = require('./common/Version.js') - -const schema = new mongoose.Schema({ - articleID: { - type: String, - required: true - }, - feedURL: { - type: String, - required: true - }, - channel: { - type: String, - required: true - }, - delivered: { - type: Boolean, - required: true - }, - comment: String -}) - -schema.add(Version) - -schema.index({ - channel: 1 -}) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/FailRecord.js b/services/bot/src/models/FailRecord.js deleted file mode 100644 index a990f8afe..000000000 --- a/services/bot/src/models/FailRecord.js +++ /dev/null @@ -1,22 +0,0 @@ -const mongoose = require('mongoose') -const Version = require('./common/Version.js') - -const schema = new mongoose.Schema({ - _id: String, - reason: String, - failedAt: { - type: Date, - required: true, - default: Date.now - }, - alerted: { - type: Boolean, - default: false - } -}) - -schema.add(Version) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/Feed.js b/services/bot/src/models/Feed.js deleted file mode 100644 index 465184d8f..000000000 --- a/services/bot/src/models/Feed.js +++ /dev/null @@ -1,95 +0,0 @@ -const mongoose = require('mongoose') -const feedMiddleware = require('./middleware/Feed.js') -const FilterBase = require('./common/FilterBase.js') -const Version = require('./common/Version.js') -const Embed = require('./common/Embed.js') - -const regexOpSchema = new mongoose.Schema({ - name: { - type: String, - required: true - }, - search: { - regex: { - type: String, - required: true - }, - flags: String, - match: Number, - group: Number - }, - fallbackValue: String, - replacement: String, - replacementDirect: String -}) - -const schema = new mongoose.Schema({ - articleMaxAge: { - type: Number, - required: false, - min: 1 - }, - title: { - type: String, - required: true - }, - url: { - type: String, - required: true - }, - guild: { - type: String, - required: true - }, - channel: { - type: String, - required: true - }, - webhook: { - id: String, - name: String, - avatar: String, - disabled: Boolean, - url: String - }, - split: { - enabled: Boolean, - char: String, - prepend: String, - append: String, - maxLength: Number - }, - text: String, - embeds: [Embed], - disabled: String, - checkTitles: Boolean, - checkDates: Boolean, - imgPreviews: Boolean, - imgLinksExistence: Boolean, - formatTables: Boolean, - directSubscribers: Boolean, - ncomparisons: [String], - pcomparisons: [String], - regexOps: { - type: Map, - of: [regexOpSchema] - } -}) - -schema.add(Version) -schema.add(FilterBase) - -schema.index({ - guild: 1 -}) - -schema.index({ - url: 1 -}) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null -exports.setupHooks = (connection) => { - schema.pre('validate', feedMiddleware.validate(connection)) -} diff --git a/services/bot/src/models/Feedback.js b/services/bot/src/models/Feedback.js deleted file mode 100644 index e03f28412..000000000 --- a/services/bot/src/models/Feedback.js +++ /dev/null @@ -1,15 +0,0 @@ -const mongoose = require('mongoose') -const Version = require('./common/Version.js') - -const schema = new mongoose.Schema({ - type: String, - userID: String, - username: String, - content: String -}) - -schema.add(Version) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/FilteredFormat.js b/services/bot/src/models/FilteredFormat.js deleted file mode 100644 index f697720fd..000000000 --- a/services/bot/src/models/FilteredFormat.js +++ /dev/null @@ -1,38 +0,0 @@ -const mongoose = require('mongoose') -const FilterBase = require('./common/FilterBase.js') -const Embed = require('./common/Embed.js') -const Version = require('./common/Version.js') -const filteredFormatMiddleware = require('./middleware/FilteredFormat.js') - -/** - * Override the feed key, removing the unique constraint - */ -const schema = new mongoose.Schema({ - text: { - type: String, - default: undefined - }, - embeds: { - type: [Embed], - default: undefined - }, - feed: { - type: mongoose.Types.ObjectId, - required: true - }, - priority: Number -}) - -schema.add(Version) -schema.add(FilterBase) - -schema.index({ - feed: 1 -}) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null -exports.setupHooks = (connection) => { - schema.pre('validate', filteredFormatMiddleware.validate(connection)) -} diff --git a/services/bot/src/models/GeneralStats.js b/services/bot/src/models/GeneralStats.js deleted file mode 100644 index 1ddf53188..000000000 --- a/services/bot/src/models/GeneralStats.js +++ /dev/null @@ -1,20 +0,0 @@ -const mongoose = require('mongoose') -const Version = require('./common/Version.js') - -const schema = new mongoose.Schema({ - _id: { - type: String, - required: true - }, - data: mongoose.Schema.Types.Mixed -}) - -schema.add(Version) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null -exports.TYPES = { - ARTICLES_SENT: 'articlesSent', - ARTICLES_BLOCKED: 'articlesBlocked' -} diff --git a/services/bot/src/models/KeyValue.js b/services/bot/src/models/KeyValue.js deleted file mode 100644 index 8a76ae4f3..000000000 --- a/services/bot/src/models/KeyValue.js +++ /dev/null @@ -1,15 +0,0 @@ -const mongoose = require('mongoose') -const Version = require('./common/Version.js') - -const schema = new mongoose.Schema({ - _id: String, - value: mongoose.Schema.Types.Mixed -}, { - minimize: false -}) - -schema.add(Version) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/Patron.js b/services/bot/src/models/Patron.js deleted file mode 100644 index 9fd621bd8..000000000 --- a/services/bot/src/models/Patron.js +++ /dev/null @@ -1,23 +0,0 @@ -const mongoose = require('mongoose') - -const schema = new mongoose.Schema({ - _id: String, - statusOverride: String, - status: String, - lastCharge: Date, - pledgeLifetime: { - type: Number, - required: true - }, - pledge: { - type: Number, - required: true - }, - name: String, - discord: String, - email: String -}) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/Profile.js b/services/bot/src/models/Profile.js deleted file mode 100644 index 438c7cb8b..000000000 --- a/services/bot/src/models/Profile.js +++ /dev/null @@ -1,21 +0,0 @@ -const mongoose = require('mongoose') -const Version = require('./common/Version.js') - -const schema = new mongoose.Schema({ - _id: String, - name: String, - alert: { - type: [String] - }, - dateFormat: String, - dateLanguage: String, - timezone: String, - prefix: String, - locale: String -}) - -schema.add(Version) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/Rating.js b/services/bot/src/models/Rating.js deleted file mode 100644 index 49cae79c3..000000000 --- a/services/bot/src/models/Rating.js +++ /dev/null @@ -1,16 +0,0 @@ -const mongoose = require('mongoose') - -const schema = mongoose.Schema({ - type: String, - userId: String, - username: String, - rating: Number, - date: { - type: Date, - default: Date.now - } -}) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/Schedule.js b/services/bot/src/models/Schedule.js deleted file mode 100644 index 8f2f6a2ad..000000000 --- a/services/bot/src/models/Schedule.js +++ /dev/null @@ -1,21 +0,0 @@ -const mongoose = require('mongoose') - -const schema = new mongoose.Schema({ - name: { - type: String, - unique: true - }, - refreshRateMinutes: Number, - keywords: { - type: [String], - default: [] - }, - feeds: { - type: [mongoose.Types.ObjectId], - default: [] - } -}) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/ScheduleStats.js b/services/bot/src/models/ScheduleStats.js deleted file mode 100644 index 056b23890..000000000 --- a/services/bot/src/models/ScheduleStats.js +++ /dev/null @@ -1,17 +0,0 @@ -const mongoose = require('mongoose') - -const schema = new mongoose.Schema({ - _id: String, - feeds: Number, - cycleTime: Number, - cycleFails: Number, - cycleURLs: Number, - lastUpdated: { - type: Date, - default: Date.now - } -}) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/Subscriber.js b/services/bot/src/models/Subscriber.js deleted file mode 100644 index b28f35cdf..000000000 --- a/services/bot/src/models/Subscriber.js +++ /dev/null @@ -1,33 +0,0 @@ -const mongoose = require('mongoose') -const FilterBase = require('./common/FilterBase.js') -const Version = require('./common/Version.js') -const subscriberMiddleware = require('./middleware/Subscriber.js') - -const schema = new mongoose.Schema({ - feed: { - type: mongoose.Types.ObjectId, - required: true - }, - id: { - type: String, - required: true - }, - type: { - type: String, - required: true - } -}) - -schema.add(Version) -schema.add(FilterBase) - -schema.index({ - feed: 1 -}) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null -exports.setupHooks = (connection) => { - schema.pre('validate', subscriberMiddleware.validate(connection)) -} diff --git a/services/bot/src/models/Supporter.js b/services/bot/src/models/Supporter.js deleted file mode 100644 index 84a74853e..000000000 --- a/services/bot/src/models/Supporter.js +++ /dev/null @@ -1,17 +0,0 @@ -const mongoose = require('mongoose') - -const schema = new mongoose.Schema({ - _id: String, - patron: Boolean, - webhook: Boolean, - maxGuilds: Number, - maxFeeds: Number, - guilds: [String], - expireAt: Date, - comment: String, - slowRate: Boolean -}) - -exports.schema = schema -/** @type {import('mongoose').Model} */ -exports.Model = null diff --git a/services/bot/src/models/common/Embed.js b/services/bot/src/models/common/Embed.js deleted file mode 100644 index 012d67052..000000000 --- a/services/bot/src/models/common/Embed.js +++ /dev/null @@ -1,25 +0,0 @@ -const fieldSchema = { - _id: false, - name: String, - value: String, - inline: Boolean -} - -const embedSchema = { - _id: false, - title: String, - description: String, - url: String, - color: Number, - footerText: String, - footerIconURL: String, - authorName: String, - authorIconURL: String, - authorURL: String, - thumbnailURL: String, - imageURL: String, - timestamp: String, - fields: [fieldSchema] -} - -module.exports = embedSchema diff --git a/services/bot/src/models/common/FilterBase.js b/services/bot/src/models/common/FilterBase.js deleted file mode 100644 index 1f9eb3a98..000000000 --- a/services/bot/src/models/common/FilterBase.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - filters: { - type: Map, - of: [String] - }, - rfilters: { - type: Map, - of: String - } -} diff --git a/services/bot/src/models/common/Version.js b/services/bot/src/models/common/Version.js deleted file mode 100644 index a5ab2bfc6..000000000 --- a/services/bot/src/models/common/Version.js +++ /dev/null @@ -1,16 +0,0 @@ -const fs = require('fs') -const path = require('path') -const packagePath = path.join(__dirname, '..', '..', '..', 'package.json') -const packageContents = fs.readFileSync(packagePath) -const version = JSON.parse(packageContents).version - -module.exports = { - addedAt: { - type: Date, - default: Date.now - }, - version: { - type: String, - default: version - } -} diff --git a/services/bot/src/models/middleware/Feed.js b/services/bot/src/models/middleware/Feed.js deleted file mode 100644 index 5e5965b5d..000000000 --- a/services/bot/src/models/middleware/Feed.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @param {import('mongoose').Connection} connection - */ -function validate (connection) { - /** - * @this import('mongoose').MongooseDocument - */ - async function run () { - const current = await connection.model('feed').findById(this._id).exec() - // If current doesn't exist, then it's a new feed - if (current && current.guild !== this.guild) { - throw new Error('Guild cannot be changed') - } - } - return run -} - -module.exports = { - validate -} diff --git a/services/bot/src/models/middleware/FilteredFormat.js b/services/bot/src/models/middleware/FilteredFormat.js deleted file mode 100644 index a92c3d486..000000000 --- a/services/bot/src/models/middleware/FilteredFormat.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @param {import('mongoose').Connection} connection - */ -function validate (connection) { - /** - * @this import('mongoose').MongooseDocument - */ - async function run () { - const feed = await connection.model('feed').findById(this.feed).exec() - if (!feed) { - throw new Error(`FilteredFormat's specified feed ${this.feed} was not found`) - } - const current = await connection.model('filtered_format').findById(this._id).exec() - // If current doesn't exist, then it's a new subscriber - if (current && current.feed !== this.feed) { - throw new Error('Feed cannot be changed') - } - } - return run -} - -module.exports = { - validate -} diff --git a/services/bot/src/models/middleware/Subscriber.js b/services/bot/src/models/middleware/Subscriber.js deleted file mode 100644 index 22af8d163..000000000 --- a/services/bot/src/models/middleware/Subscriber.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @param {import('mongoose').Connection} connection - */ -function validate (connection) { - /** - * @this import('mongoose').MongooseDocument - */ - async function run () { - const feed = await connection.model('feed').findById(this.feed).exec() - if (!feed) { - throw new Error(`Subscriber's specified feed ${this.feed} was not found`) - } - const current = await connection.model('subscriber').findById(this._id).exec() - // If current doesn't exist, then it's a new subscriber - if (current && !current.feed.equals(this.feed)) { - throw new Error('Feed cannot be changed') - } - } - return run -} - -module.exports = { - validate -} diff --git a/services/bot/src/structs/Article.js b/services/bot/src/structs/Article.js deleted file mode 100644 index c82b34610..000000000 --- a/services/bot/src/structs/Article.js +++ /dev/null @@ -1,814 +0,0 @@ -const moment = require('moment-timezone') -const htmlConvert = require('html-to-text') -const htmlDecoder = require('html-to-text/lib/formatter.js').text -const FlattenedJSON = require('./FlattenedJSON.js') -const FilterResults = require('./FilterResults.js') -const Filter = require('./Filter.js') -const FilterRegex = require('./FilterRegex.js') -const getConfig = require('../config.js').get -const VALID_PH_IMGS = ['title', 'description', 'summary'] -const VALID_PH_ANCHORS = ['title', 'description', 'summary'] -const BASE_REGEX_PHS = ['title', 'author', 'summary', 'description', 'guid', 'date', 'link'] - -function dateHasNoTime (date) { // Determine if the time is T00:00:00.000Z - const timeParts = [date.getUTCHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()] - for (var x in timeParts) { - if (timeParts[x] !== 0) return false - } - return true -} - -function setCurrentTime (momentObj) { - const now = new Date() - return momentObj.hours(now.getHours()).minutes(now.getMinutes()).seconds(now.getSeconds()).millisecond(now.getMilliseconds()) -} - -// To avoid stack call exceeded -function checkObjType (item, results) { - if (Object.prototype.toString.call(item) === '[object Object]') { - return () => findImages(item, results) - } else if (typeof item === 'string' && item.match(/\.(jpg|jpeg|png|gif|bmp|webp|php)$/i) && !results.includes(item) && results.length < 9) { - if (item.startsWith('//')) item = 'http:' + item - results.push(item.replace(/\s/g, '%20')) - } -} - -// Used to find images in any object values of the article -function findImages (obj, results) { - for (var key in obj) { - let value = checkObjType(obj[key], results) - while (typeof value === 'function') { - value = value() - } - } -} - -function escapeRegExp (str) { - return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&') -} - -function regexReplace (string, searchOptions, replacement, replacementDirect, fallbackValue) { - if (typeof searchOptions !== 'object') throw new TypeError(`Expected RegexOp search key to have an object value, found ${typeof searchOptions} instead`) - if (replacementDirect !== undefined) return string.replace(new RegExp(searchOptions.regex, searchOptions.flags), replacementDirect) // Allow direct input into the search function, and ignore "match" and "group" in the regexOp.search - const flags = !searchOptions.flags ? 'g' : searchOptions.flags.includes('g') ? searchOptions.flags : searchOptions.flags + 'g' // Global flag must be included to prevent infinite loop during .exec - const matchIndex = searchOptions.match !== undefined ? parseInt(searchOptions.match, 10) : undefined - const groupNum = searchOptions.group !== undefined ? parseInt(searchOptions.group, 10) : undefined - const regExp = new RegExp(searchOptions.regex, flags) - const matches = [] - let match - do { // Find everything that matches the search regex query and push it to matches. - match = regExp.exec(string) - if (match) matches.push(match) - } while (match) - - if (matches.length === 0) { - return fallbackValue != null ? fallbackValue : string - } else { - const mi = matches[matchIndex || 0] - if (!mi) return string - else match = mi[groupNum || 0] - } - - if (replacement !== undefined) { - if (matchIndex === undefined && groupNum === undefined) { // If no match or group is defined, replace every full match of the search in the original string - for (var x in matches) { - const exp = new RegExp(escapeRegExp(matches[x][0]), flags) - string = string.replace(exp, replacement) - } - } else if (matchIndex && groupNum === undefined) { // If no group number is defined, use the full match of this particular match number in the original string - const exp = new RegExp(escapeRegExp(matches[matchIndex][0]), flags) - string = string.replace(exp, replacement) - } else if (matchIndex === undefined && groupNum) { - const exp = new RegExp(escapeRegExp(matches[0][groupNum]), flags) - string = string.replace(exp, replacement) - } else { - const exp = new RegExp(escapeRegExp(matches[matchIndex][groupNum]), flags) - string = string.replace(exp, replacement) - } - } else string = match - - return string -} - -function evalRegexConfig (feed, text, placeholderName) { - const customPlaceholders = {} - - if (Array.isArray(feed.regexOps[placeholderName])) { // Eval regex if specified - if (Array.isArray(feed.regexOps.disabled) && feed.regexOps.disabled.length > 0) { // .disabled can be an array of disabled placeholders, or just a boolean to disable everything - for (var y in feed.regexOps.disabled) { // Looping through strings of placeholders - if (feed.regexOps.disabled[y] === placeholderName) return null - } - } - - const phRegexOps = feed.regexOps[placeholderName] - for (var regexOpIndex in phRegexOps) { // Looping through each regexOp for a placeholder - const regexOp = phRegexOps[regexOpIndex] - if (regexOp.disabled === true || typeof regexOp.name !== 'string') continue - if (!customPlaceholders[regexOp.name]) customPlaceholders[regexOp.name] = text // Initialize with a value if it doesn't exist - const clone = Object.assign({}, customPlaceholders) - const replacement = regexReplace(clone[regexOp.name], regexOp.search, regexOp.replacement, regexOp.replacementDirect, regexOp.fallbackValue) - customPlaceholders[regexOp.name] = replacement === clone[regexOp.name] && regexOp.emptyOnNoMatch === true ? '\u200b' : replacement - } - } else return null - return customPlaceholders -} - -function cleanup (feed, text, imgSrcs, anchorLinks) { - if (!text) return '' - const config = getConfig() - text = htmlDecoder({ data: text }, {}).replace(/\*/gi, '') - .replace(/<(strong|b)>(.*?)<\/(strong|b)>/gi, '**$2**') // Bolded markdown - .replace(/<(em|i)>(.*?)<(\/(em|i))>/gi, '*$2*') // Italicized markdown - .replace(/<(u)>(.*?)<(\/(u))>/gi, '__$2__') // Underlined markdown - - text = htmlConvert.fromString(text, { - tables: (feed.formatTables !== undefined && typeof feed.formatTables === 'boolean' ? feed.formatTables : config.feeds.formatTables) === true ? true : [], - wordwrap: null, - ignoreHref: true, - noLinkBrackets: true, - format: { - image: node => { - const isStr = typeof node.attribs.src === 'string' - let link = isStr ? node.attribs.src.trim().replace(/\s/g, '%20') : node.attribs.src - if (isStr && link.startsWith('//')) link = 'http:' + link - else if (isStr && !link.startsWith('http://') && !link.startsWith('https://')) link = 'http://' + link - - if (Array.isArray(imgSrcs) && imgSrcs.length < 9 && isStr && link) { - imgSrcs.push(link) - } - - let exist = true - const globalExistOption = config.feeds.imgLinksExistence - exist = globalExistOption - const specificExistOption = feed.imgLinksExistence - exist = typeof specificExistOption !== 'boolean' ? exist : specificExistOption - if (!exist) return '' - - let image = '' - const globalPreviewOption = config.feeds.imgPreviews - image = globalPreviewOption ? link : `<${link}>` - const specificPreviewOption = feed.imgPreviews - image = typeof specificPreviewOption !== 'boolean' ? image : specificPreviewOption === true ? link : `<${link}>` - - return image - }, - anchor: (node, fn, options) => { - const orig = fn(node.children, options) - if (!Array.isArray(anchorLinks)) return orig - const href = node.attribs.href ? node.attribs.href.trim() : '' - if (anchorLinks.length < 5 && href) anchorLinks.push(href) - return orig - }, - blockquote: (node, fn, options) => { - const orig = fn(node.children, options).trim() - return '> ' + orig.replace(/(?:\n)/g, '\n> ') + '\n' - } - } - }) - - text = text.replace(/\n\s*\n\s*\n/g, '\n\n') // Replace triple line breaks with double - .replace(/@/g, '@' + String.fromCharCode(8203)) // Sanitize mentions with zero-width character "\u200b", does not affect subscribed roles or modify anything outside the scope of sanitizing Discord mentions in the raw RSS feed content - const arr = text.split('\n') - for (var q = 0; q < arr.length; ++q) arr[q] = arr[q].replace(/\s+$/, '') // Remove trailing spaces - return arr.join('\n').trim() -} - -module.exports = class Article { - constructor (raw, feedData) { - const feed = feedData.feed - const profile = feedData.profile || {} - this.id = raw._id || null - this.feed = feed - this.profile = profile - this.raw = raw - this.reddit = raw.meta.link && raw.meta.link.includes('www.reddit.com') - this.youtube = !!(raw.guid && raw.guid.startsWith('yt:video') && raw['media:group'] && raw['media:group']['media:description'] && raw['media:group']['media:description']['#']) - this.enabledRegex = typeof feed.regexOps === 'object' && feed.regexOps.disabled !== true - this.placeholdersForRegex = BASE_REGEX_PHS.slice() - this.privatePlaceholders = ['id', 'fullDescription', 'fullSummary', 'fullTitle', 'fullDate'] - this.placeholders = [] - this.meta = raw.meta - this.guid = raw.guid - // Author - this.author = raw.author ? cleanup(feed, raw.author) : '' - if (this.author) this.placeholders.push('author') - - // Link - this.link = raw.link ? raw.link.split(' ')[0].trim() : '' // Sometimes HTML is appended at the end of links for some reason - if (this.link) this.placeholders.push('link') - if (this.reddit && this.link.startsWith('/r/')) this.link = 'https://www.reddit.com' + this.link - - // Title - this.titleImages = [] - this.titleAnchors = [] - this.fullTitle = cleanup(feed, raw.title, this.titleImages, this.titleAnchors) - this.title = this.fullTitle.length > 150 ? `${this.fullTitle.slice(0, 150)}...` : this.fullTitle - if (this.title) this.placeholders.push('title') - for (var titleImgNum in this.titleImages) { - const term = `title:image${parseInt(titleImgNum, 10) + 1}` - this.placeholders.push(term) - this[term] = this.titleImages[titleImgNum] - if (this.enabledRegex) this.placeholdersForRegex.push(term) - } - for (var titleAnchorNum in this.titleAnchors) { - const term = `title:anchor${parseInt(titleAnchorNum, 10) + 1}` - this.placeholders.push(term) - this[term] = this.titleAnchors[titleAnchorNum] - if (this.enabledRegex) this.placeholdersForRegex.push(term) - } - - // guid - Raw exposure, no cleanup. Not meant for use by most feeds. - this.guid = raw.guid ? raw.guid : '' - if (this.guid) this.placeholders.push('guid') - - // Date - this.fullDate = raw.pubdate - this.date = this.formatDate(this.fullDate, this.profile.timezone) - if (this.date) this.placeholders.push('date') - - // Description and reddit-specific placeholders - this.descriptionImages = [] - this.descriptionAnchors = [] - this.fullDescription = this.youtube ? raw['media:group']['media:description']['#'] : cleanup(feed, raw.description, this.descriptionImages, this.descriptionAnchors) // Account for youtube's description - this.description = this.fullDescription - this.description = this.description.length > 800 ? `${this.description.slice(0, 790)}...` : this.description - if (this.description) this.placeholders.push('description') - for (var desImgNum in this.descriptionImages) { - const term = `description:image${parseInt(desImgNum, 10) + 1}` - this.placeholders.push(term) - this[term] = this.descriptionImages[desImgNum] - if (this.enabledRegex) this.placeholdersForRegex.push(term) - } - for (var desAnchorNum in this.descriptionAnchors) { - const term = `description:anchor${parseInt(desAnchorNum, 10) + 1}` - this.placeholders.push(term) - this[term] = this.descriptionAnchors[desAnchorNum] - if (this.enabledRegex) this.placeholdersForRegex.push(term) - } - - if (this.reddit) { - // Truncate the useless end of reddit description after anchors are removed - this.fullDescription = this.fullDescription.replace('\n[link] [comments]', '') - this.description = this.description.replace('\n[link] [comments]', '') - } - - // Summary - this.summaryImages = [] - this.summaryAnchors = [] - this.fullSummary = cleanup(feed, raw.summary, this.summaryImages, this.summaryAnchors) - this.summary = this.fullSummary.length > 800 ? `${this.fullSummary.slice(0, 790)}...` : this.fullSummary - if (this.summary && raw.summary !== raw.description) this.placeholders.push('summary') - for (var sumImgNum in this.summaryImages) { - const term = `summary:image${+sumImgNum + 1}` - if (this.summaryImages[sumImgNum] !== this.descriptionImages[sumImgNum]) { - this.placeholders.push(term) - this[term] = this.summaryImages[sumImgNum] - } - if (this.enabledRegex) this.placeholdersForRegex.push(term) - } - for (var sumAnchorNum in this.summaryAnchors) { - const term = `summary:anchor${+sumAnchorNum + 1}` - if (this.summaryAnchors[sumImgNum] !== this.descriptionAnchors[sumImgNum]) { - this.placeholders.push(term) - this[term] = this.summaryAnchors[sumAnchorNum] - } - if (this.enabledRegex) this.placeholdersForRegex.push(term) - } - - // Image links - const imageLinks = [] - findImages(raw, imageLinks) - this.images = (imageLinks.length === 0) ? undefined : imageLinks - for (var imageNum in imageLinks) { - const term = `image${parseInt(imageNum, 10) + 1}` - this.placeholders.push(term) - this[term] = imageLinks[imageNum] - if (this.enabledRegex) this.placeholdersForRegex.push(term) - } - - // Categories/Tags - if (raw.categories) { - let categoryList = '' - const cats = raw.categories - for (var category in cats) { - if (typeof cats[category] !== 'string') continue - categoryList += cats[category].trim() - if (parseInt(category, 10) !== cats.length - 1) categoryList += '\n' - } - this.tags = cleanup(feed, categoryList) - if (this.tags) this.placeholders.push('tags') - } - - // Regex-defined custom placeholders - if (this.enabledRegex) { - this.regexPlaceholders = {} // Each key is a validRegexPlaceholder, and their values are an object of named placeholders with the modified content - for (var b in this.placeholdersForRegex) { - const placeholderName = this.placeholdersForRegex[b] - const regexResults = evalRegexConfig(feed, this[placeholderName], placeholderName) - this.regexPlaceholders[placeholderName] = regexResults - } - } - - // Finally subscriptions - this MUST be done last after all variables have been defined for filter testing - this.subscribers = '' - - // Get filtered subscriptions - const subscribers = feedData.subscribers - if (subscribers) { - for (const subscriber of subscribers) { - const type = subscriber.type - if (type !== 'role' && type !== 'user') { - continue - } - const mentionText = type === 'role' ? `<@&${subscriber.id}> ` : `<@${subscriber.id}> ` - if (subscriber.filters && this.testFilters(subscriber.filters).passed) { - this.subscribers += mentionText - } else if (!subscriber.filters || Object.keys(subscriber.filters).length === 0) { - this.subscribers += mentionText - } - } - } - - if (this.subscribers) { - this.placeholders.push('subscriptions') - this.placeholders.push('subscribers') - } - } - - // List all {imageX} to string - listImages () { - const images = this.images - let imageList = '' - for (var image in images) { - imageList += `[Image${parseInt(image, 10) + 1} URL]: {image${parseInt(image, 10) + 1}}\n${images[image]}` - if (parseInt(image, 10) !== images.length - 1) imageList += '\n' - } - return imageList - } - - // List all {placeholder:imageX} to string - listPlaceholderImages () { - const listedImages = [] - let list = '' - for (var k in VALID_PH_IMGS) { - const placeholderImgs = this[VALID_PH_IMGS[k] + 'Images'] - for (var l in placeholderImgs) { - if (listedImages.includes(placeholderImgs[l])) continue - listedImages.push(placeholderImgs[l]) - const placeholder = VALID_PH_IMGS[k].slice(0, 1).toUpperCase() + VALID_PH_IMGS[k].substr(1, VALID_PH_IMGS[k].length) - const imgNum = parseInt(l, 10) + 1 - list += `\n[${placeholder} Image${imgNum}]: {${VALID_PH_IMGS[k]}:image${imgNum}}\n${placeholderImgs[l]}` - } - } - - return list.trim() - } - - // List all {placeholder:imageX} to string - listPlaceholderAnchors () { - const listedAnchors = [] - let list = '' - for (var k in VALID_PH_ANCHORS) { - const placeholderAnchors = this[VALID_PH_ANCHORS[k] + 'Anchors'] - for (var l in placeholderAnchors) { - if (listedAnchors.includes(placeholderAnchors[l])) continue - listedAnchors.push(placeholderAnchors[l]) - const placeholder = VALID_PH_ANCHORS[k].slice(0, 1).toUpperCase() + VALID_PH_ANCHORS[k].substr(1, VALID_PH_ANCHORS[k].length) - const anchorNum = parseInt(l, 10) + 1 - list += `\n[${placeholder} Anchor${anchorNum}]: {${VALID_PH_ANCHORS[k]}:anchor${anchorNum}}\n${placeholderAnchors[l]}` - } - } - - return list.trim() - } - - resolvePlaceholderImg (input) { - const inputArr = input.split('||') - let img = '' - for (var x = inputArr.length - 1; x >= 0; x--) { - const term = inputArr[x] - if (term.startsWith('http')) { - img = term.replace(/\{|\}/g, '') - continue - } - const arr = term.split(':') - if (term.startsWith('{image')) { - img = this.convertImgs(term) - continue - } else if (arr.length === 1 || arr[1].search(/image[1-9]/) === -1) continue - const placeholder = arr[0].replace(/{|}/, '') - const placeholderImgs = this[placeholder + 'Images'] - if (!VALID_PH_IMGS.includes(placeholder) || !placeholderImgs || placeholderImgs.length < 1) continue - - const imgNum = parseInt(arr[1].substr(arr[1].search(/[1-9]/), 1), 10) - 1 - if (isNaN(imgNum) || imgNum > 4 || imgNum < 0 || !placeholderImgs[imgNum]) continue - img = placeholderImgs[imgNum] - } - return img - } - - // {imageX} and {placeholder:imageX} - convertImgs (content) { - const imgDictionary = {} - const imgLocs = content.match(/{image[1-9](\|\|(.+))*}/g) - const phImageLocs = content.match(/({(description|title|summary):image[1-9](\|\|(.+))*})/gi) - if (imgLocs) { - for (var loc in imgLocs) { - const term = imgLocs[loc] - const termList = term.split('||') - if (termList.length === 1) { - const imgNum = parseInt(term[term.search(/[1-9]/)], 10) - if (this.images && this.images[imgNum - 1]) imgDictionary[term] = this.images[imgNum - 1] // key is {imageX}, value is article image URL - else imgDictionary[term] = '' - } else { - let decidedImage = '' - for (var p = termList.length - 1; p >= 0; p--) { // Work though fallback images backwards - not very efficient but it works - const subTerm = p === 0 ? `${termList[p]}}` : p === termList.length - 1 ? `{${termList[p]}` : `{${termList[p]}}` // Format for use in convertImgs - const subImg = this.convertImgs(subTerm) - if (subImg) decidedImage = subImg - } - imgDictionary[term] = decidedImage - } - } - for (var imgKeyword in imgDictionary) { - content = content.replace(new RegExp(escapeRegExp(imgKeyword), 'g'), imgDictionary[imgKeyword]) - } - } else if (phImageLocs) { - for (var h in phImageLocs) { - content = this.resolvePlaceholderImg(phImageLocs[h]) ? content.replace(phImageLocs[h], this.resolvePlaceholderImg(phImageLocs[h])) : content.replace(phImageLocs[h], '') - } - } - return content - } - - resolvePlaceholderAnchor (input) { - const arr = input.split(':') - if (arr.length === 1 || arr[1].search(/anchor[1-5]/) === -1) return '' - const placeholder = arr[0].replace(/{|}/, '') - const placeholderAnchors = this[placeholder + 'Anchors'] - if (!VALID_PH_ANCHORS.includes(placeholder) || !placeholderAnchors || placeholderAnchors.length < 1) return '' - const num = parseInt(arr[1].substr(arr[1].search(/[1-5]/), 1), 10) - 1 - if (isNaN(num) || num > 4 || num < 0) return '' - return placeholderAnchors[num] - } - - convertAnchors (content) { - const phAnchorLocs = content.match(/({(description|title|summary):anchor[1-5](\|\|(.+))*})/gi) - if (!phAnchorLocs) return content - for (var h in phAnchorLocs) { - content = this.resolvePlaceholderAnchor(phAnchorLocs[h]) ? content.replace(phAnchorLocs[h], this.resolvePlaceholderAnchor(phAnchorLocs[h])) : content.replace(phAnchorLocs[h], '') - } - return content - } - - convertRawPlaceholders (content) { - let result - const matches = {} - const config = getConfig() - const regex = new RegExp('{raw:([^{}]+)}', 'g') - do { - result = regex.exec(content) - if (!result) continue - if (!this.flattenedJSON) this.flattenedJSON = new FlattenedJSON(this.raw, this.feed) - const fullMatch = result[0] - const matchName = result[1] - matches[fullMatch] = this.flattenedJSON.results[matchName] || '' - - // Format the date if it is one - if (Object.prototype.toString.call(matches[fullMatch]) === '[object Date]') { - const guildTimezone = this.profile.timezone - const timezone = guildTimezone && moment.tz.zone(guildTimezone) ? guildTimezone : config.feeds.timezone - const dateFormat = this.profile.dateFormat ? this.profile.dateFormat : config.feeds.dateFormat - const localMoment = moment(matches[fullMatch]) - if (this.profile.dateLanguage) localMoment.locale(this.profile.dateLanguage) - const useTimeFallback = config.feeds.timeFallback === true && matches[fullMatch].toString() !== 'Invalid Date' && dateHasNoTime(matches[fullMatch]) - matches[fullMatch] = useTimeFallback ? setCurrentTime(localMoment).tz(timezone).format(dateFormat) : localMoment.tz(timezone).format(dateFormat) - } - } while (result !== null) - for (var phName in matches) { - content = content.replace(new RegExp(escapeRegExp(phName), 'g'), matches[phName]) - } - return content - } - - getRawPlaceholders () { - if (!this.flattenedJSON) this.flattenedJSON = new FlattenedJSON(this.raw, this.feed) - return this.flattenedJSON.results - } - - getRawPlaceholderContent (phName) { - if (!phName.startsWith('raw:')) return '' - if (this.flattenedJSON) return this.flattenedJSON.results[phName.replace(/raw:/, '')] || '' - else { - this.flattenedJSON = new FlattenedJSON(this.raw, this.feed) - return this.flattenedJSON.results[phName.replace(/raw:/, '')] || '' - } - } - - formatDate (date, tz) { - const config = getConfig() - if (date && date.toString() !== 'Invalid Date') { - const timezone = tz && moment.tz.zone(tz) ? tz : config.feeds.timezone - const dateFormat = this.profile.dateFormat ? this.profile.dateFormat : config.feeds.dateFormat - - const useDateFallback = config.feeds.dateFallback === true && (!date || date.toString() === 'Invalid Date') - const useTimeFallback = config.feeds.timeFallback === true && date.toString() !== 'Invalid Date' && dateHasNoTime(date) - const useDate = useDateFallback ? new Date() : date - const localMoment = moment(useDate) - if (this.profile.dateLanguage) { - localMoment.locale(this.profile.dateLanguage) - } - const vanityDate = useTimeFallback ? setCurrentTime(localMoment).tz(timezone).format(dateFormat) : localMoment.tz(timezone).format(dateFormat) - return vanityDate === 'Invalid Date' ? '' : vanityDate - } - return '' - } - - createTestText () { - const filterResults = this.testFilters(this.feed.filters) - let testDetails = '' - const footer = `\nBelow is the configured message to be sent for this feed in the channel <#${this.feed.channel}>:\n\n--` - testDetails += '```Markdown\n# BEGIN TEST DETAILS #``````Markdown' - - if (this.title) { - testDetails += `\n\n[Title]: {title}\n${this.title}` - } - - // Do not add summary if summary === description - if (this.summary && this.summary !== this.description) { - let testSummary - if (this.description && this.description.length > 500) { - // If description is long, truncate summary. - testSummary = (this.summary.length > 500) ? `${this.summary.slice(0, 490)} [...]\n\n**(Truncated summary for shorter rsstest)**` : this.summary - } else { - testSummary = this.summary - } - testDetails += `\n\n[Summary]: {summary}\n${testSummary}` - } - - if (this.description) { - let testDescrip - if (this.summary && this.summary.length > 500) { - // If summary is long, truncate description. - testDescrip = (this.description.length > 500) ? `${this.description.slice(0, 490)} [...]\n\n**(Truncated description for shorter rsstest)**` : this.description - } else { - testDescrip = this.description - } - testDetails += `\n\n[Description]: {description}\n${testDescrip}` - } - - if (this.date) testDetails += `\n\n[Published Date]: {date}\n${this.date}` - if (this.author) testDetails += `\n\n[Author]: {author}\n${this.author}` - if (this.link) testDetails += `\n\n[Link]: {link}\n${this.link}` - if (this.subscribers) testDetails += `\n\n[Subscribers]: {subscribers}\n${this.subscribers.split(' ').length - 1} subscriber(s)` - if (this.images) testDetails += `\n\n${this.listImages()}` - const placeholderImgs = this.listPlaceholderImages() - if (placeholderImgs) testDetails += `\n\n${placeholderImgs}` - const placeholderAnchors = this.listPlaceholderAnchors() - if (placeholderAnchors) testDetails += `\n\n${placeholderAnchors}` - if (this.tags) testDetails += `\n\n[Tags]: {tags}\n${this.tags}` - if (this.feed.filters) { - testDetails += `\n\n[Passed Filters?]: ${filterResults.passed ? 'Yes' : 'No'}${filterResults.passed ? filterResults.listMatches(false) + filterResults.listMatches(true) : filterResults.listMatches(true) + filterResults.listMatches(false)}` - } - testDetails += '```' + footer - - return testDetails - } - - /** - * @param {string[]} userFilters - * @param {string} reference - */ - testArrayNegatedFilters (userFilters, reference) { - // Deal with inverted first - const filters = userFilters.map(word => new Filter(word)) - const invertedFilters = filters.filter(filter => filter.inverted) - const regularFilters = filters.filter(filter => !filter.inverted) - const returnData = { - inverted: invertedFilters.map(f => f.content), - regular: regularFilters.map(f => f.content) - } - if (!reference) { - return { - ...returnData, - passed: true - } - } - const blocked = invertedFilters.find(filter => !filter.passes(reference)) - if (blocked) { - return { - ...returnData, - passed: false - } - } - - return { - ...returnData, - passed: true - } - } - - /** - * @param {string[]} userFilters - * @param {string} reference - */ - testArrayRegularFilters (userFilters, reference) { - // Deal with inverted first - const filters = userFilters.map(word => new Filter(word)) - const invertedFilters = filters.filter(filter => filter.inverted) - const regularFilters = filters.filter(filter => !filter.inverted) - const returnData = { - inverted: invertedFilters.map(f => f.content), - regular: regularFilters.map(f => f.content) - } - - if (!reference) { - return { - ...returnData, - passed: false - } - } - - const passed = !!regularFilters.find(filter => filter.passes(reference)) - return { - ...returnData, - passed - } - } - - /** - * @param {string} userFilter - * @param {string} reference - */ - testRegexFilter (userFilter, reference) { - if (!reference) { - return { - inverted: [], - regular: [userFilter], - passed: false - } - } - const filter = new FilterRegex(userFilter) - const filterPassed = filter.passes(reference) - if (filterPassed) { - return { - inverted: [], - regular: [userFilter], - passed: true - } - } else { - return { - inverted: [userFilter], - regular: [], - passed: false - } - } - } - - getFilterReference (type) { - const referenceOverrides = { - description: this.fullDescription, - summary: this.fullSummary, - title: this.fullTitle - } - if (type.startsWith('raw:')) { - return this.getRawPlaceholderContent(type) - } else { - return referenceOverrides[type.replace('other:', '')] || this[type.replace('other:', '')] - } - } - - // Filters are pending for a serious rewrite due to the complexity/debt involved here - testFilters (filters) { - const filterResults = new FilterResults() - if (Object.keys(filters).length === 0) { - filterResults.passed = true - return filterResults - } - - let hasOneBlock = false - // First check if any filters block this article - for (const filterTypeName in filters) { - const userFilters = filters[filterTypeName] - const reference = this.getFilterReference(filterTypeName) - if (!reference) { - continue - } - // Filters can either be an array of words or a string (regex) - let results - if (Array.isArray(userFilters)) { - results = this.testArrayNegatedFilters(userFilters, reference) - } else { - results = this.testRegexFilter(userFilters, reference) - } - const invertedFilters = results.inverted - const regularFilters = results.regular - if (regularFilters.length > 0) { - filterResults.add(filterTypeName, regularFilters, false) - } - if (invertedFilters.length > 0) { - filterResults.add(filterTypeName, invertedFilters, true) - } - if (!results.passed) { - hasOneBlock = true - } - } - if (hasOneBlock) { - filterResults.passed = false - return filterResults - } - // Then do regular filters - let passed = false - let hasRegularFilters = false - for (const filterTypeName in filters) { - const userFilters = filters[filterTypeName] - const reference = this.getFilterReference(filterTypeName) - let results - // Filters can either be an array of words or a string (regex) - if (Array.isArray(userFilters)) { - results = this.testArrayRegularFilters(userFilters, reference) - } else { - results = this.testRegexFilter(userFilters, reference) - } - if (results.regular.length > 0) { - hasRegularFilters = true - } - passed = results.passed || passed - } - // If there are no regular filters, then it should pass - filterResults.passed = hasRegularFilters ? passed : true - return filterResults - } - - // replace simple keywords - convertKeywords (word = '', ignoreCharLimits) { - if (word.length === 0) return word - let content = word.replace(/{title}/g, ignoreCharLimits ? this.fullTitle : this.title) - .replace(/{author}/g, this.author) - .replace(/{summary}/g, ignoreCharLimits ? this.fullSummary : this.summary) - .replace(/({subscriptions})|({subscribers})/g, this.subscribers) - .replace(/{link}/g, this.link) - .replace(/{description}/g, ignoreCharLimits ? this.fullDescription : this.description) - .replace(/{tags}/g, this.tags) - .replace(/{guid}/g, this.guid) - .replace(/\\u200b/g, '\u200b') - - const dateRegex = new RegExp('{date(:[a-zA-Z_/]*)?}') - - let result = dateRegex.exec(content) - while (result !== null) { - const zone = result[1] ? result[1].slice(1, result[1].length) : undefined // timezone within placeholder, e.g. {date:UTC} - const fullLength = result[0].length // full match - let convertedDate = '' - if (zone === undefined) { - // no custom timezone was defined after date within the placeholder - convertedDate = this.date - } else if (moment.tz.zone(zone)) { - convertedDate = this.formatDate(this.fullDate, zone) - } - - content = content.substring(0, result.index) + convertedDate + content.substring(result.index + fullLength, content.length) - result = dateRegex.exec(content) - } - - const regexPlaceholders = this.regexPlaceholders - for (var placeholder in regexPlaceholders) { - for (var customName in regexPlaceholders[placeholder]) { - const replacementQuery = new RegExp(`{${placeholder}:${escapeRegExp(customName)}}`, 'g') - const replacementContent = regexPlaceholders[placeholder][customName] - content = content.replace(replacementQuery, replacementContent) - } - } - - return this.convertRawPlaceholders(this.convertAnchors(this.convertImgs(content))) - } - - toJSON () { - const data = {} - // Regular - for (const placeholder of this.placeholders) { - data[placeholder] = this[placeholder] - } - - // Private - for (const placeholder of this.privatePlaceholders) { - data[`_${placeholder}`] = this[placeholder] - } - - // Regex - for (const placeholder in this.regexPlaceholders) { - const value = this.regexPlaceholders[placeholder] - for (const customName in value) { - data[`regex:${placeholder}:${customName}`] = value[customName] - } - } - - // Raw - const rawPlaceholders = this.getRawPlaceholders() - for (const rawPlaceholder in rawPlaceholders) { - data[`raw:${rawPlaceholder}`] = rawPlaceholders[rawPlaceholder] - } - - return data - } -} diff --git a/services/bot/src/structs/ArticleIDResolver.js b/services/bot/src/structs/ArticleIDResolver.js deleted file mode 100644 index 53564a949..000000000 --- a/services/bot/src/structs/ArticleIDResolver.js +++ /dev/null @@ -1,92 +0,0 @@ -class ArticleIDResolver { - constructor () { - /** - * Object of placeholder types as keys and sets of article values to see if such values were seen before. - * If a article value was seen before in this set, then that placeholder should be made invalid - * @type {Object>} - * */ - this.idsRecorded = {} - - /** - * Initially holds all possible ID types after instantiation. ID types are removed as articles are recorded. - * @type {Set} - * */ - this.useIdTypes = new Set() - - /** - * Holds the merged ID types. - * @type {Array} - * */ - this.mergedTypeNames = [] // An extension of idTypeNames - must be an array to maintain order - - /** - * Placeholders that should not be used. Array is used to maintain the order in which they fail. - * In case all possible placeholders fail, then use the last one that failed. - * @type {Array} - */ - this.failedTypeNames = [] - - const typeNames = ArticleIDResolver.ID_TYPE_NAMES - for (let i = 0; i < typeNames.length; ++i) { - const idType = typeNames[i] - this.idsRecorded[idType] = new Set() - this.useIdTypes.add(idType) - for (let j = i + 1; j < typeNames.length; ++j) { - const nextIdType = typeNames[j] - const mergedName = `${idType},${nextIdType}` - this.idsRecorded[mergedName] = new Set() - this.useIdTypes.add(mergedName) - this.mergedTypeNames.push(mergedName) - } - } - } - - static get ID_TYPE_NAMES () { - return ['guid', 'pubdate', 'title'] - } - - /** - * A function that would be repeatedly called for every article in a feed to determine - * the ID that should be used. ID types that have duplicate values for multiple articles - * are invalidated. - * @param {Object} article - The raw article object - */ - recordArticle (article) { - const { useIdTypes, idsRecorded } = this - useIdTypes.forEach(idType => { - const articleValue = ArticleIDResolver.getIDTypeValue(article, idType) - if (!articleValue || idsRecorded[idType].has(articleValue)) { - useIdTypes.delete(idType) - this.failedTypeNames.push(idType) - } else { - idsRecorded[idType].add(articleValue) - } - }) - } - - /** - * Returns the first valid id type - * @returns {string} - */ - getIDType () { - const idTypes = ArticleIDResolver.ID_TYPE_NAMES.concat(this.mergedTypeNames) - for (const idType of idTypes) { - if (this.useIdTypes.has(idType)) { - return idType - } - } - return this.failedTypeNames[this.failedTypeNames.length - 1] - } - - /** - * Get the article's value of an ID type. Auto-resolves the value for merged id types. - * @param {Object} article - The raw article object - * @param {string} idType - The ID type - */ - static getIDTypeValue (article, idType) { - const properties = idType.split(',') - return properties.map(property => article[property]).join('') - } -} - -module.exports = ArticleIDResolver diff --git a/services/bot/src/structs/ArticleMessage.js b/services/bot/src/structs/ArticleMessage.js deleted file mode 100644 index b7bcef622..000000000 --- a/services/bot/src/structs/ArticleMessage.js +++ /dev/null @@ -1,420 +0,0 @@ -const Discord = require('discord.js') -const Article = require('./Article.js') -const getConfig = require('../config.js').get -const createLogger = require('../util/logger/create.js') -const devLevels = require('../util/devLevels.js') -const Feed = require('./db/Feed.js') -const FeedData = require('./FeedData.js') - -class ArticleMessage { - /** - * @param {Object} article - * @param {import('./FeedData.js')} feedData - * @param {boolean} [debug] - * @param {{ - * forceMultipleEmbeds?: boolean - * }} - */ - constructor (article, feedData, debug = false, options) { - this.config = getConfig() - this.debug = debug - this.article = article - this.feed = feedData.feed - this.filteredFormats = feedData.filteredFormats - this.sendFailed = 0 - this.parsedArticle = new Article(article, feedData) - this.forceMultipleEmbeds = options ? (options.forceMultipleEmbeds || false) : false - } - - /** - * @param {import('./db/Feed.js')|Object} feed - * @param {Object} article - * @param {boolean} [debug] - * @param {{ - * forceMultipleEmbeds?: boolean - * }} - */ - static async create (feed, article, debug, options) { - if (feed instanceof Feed) { - const feedData = await FeedData.ofFeed(feed) - return new ArticleMessage(article, feedData, debug, { - forceMultipleEmbeds: options.forceMultipleEmbeds - }) - } else { - const reconstructedFeed = new Feed(feed) - const feedData = await FeedData.ofFeed(reconstructedFeed) - return new ArticleMessage(article, feedData, debug, { - forceMultipleEmbeds: options.forceMultipleEmbeds - }) - } - } - - passedFilters () { - const { filters, rfilters } = this.feed - if (Object.keys(rfilters).length > 0) { - return this.parsedArticle.testFilters(rfilters).passed - } else { - return this.parsedArticle.testFilters(filters).passed - } - } - - /** - * @param {import('discord.js').Client} bot - */ - getChannel (bot) { - const channel = bot.channels.cache.get(this.feed.channel) - return channel - } - - determineFormat () { - const { feed, parsedArticle, filteredFormats } = this - let text = feed.text || this.config.feeds.defaultText - let embeds = feed.embeds - - // See if there are any filter-specific messages - if (filteredFormats.length > 0) { - const matched = { } - let highestPriority = -1 - let selectedFormat - for (const filteredFormat of filteredFormats) { - let thisPriority = filteredFormat.priority - if (thisPriority === undefined || thisPriority < 0) { - thisPriority = 0 - } - const res = parsedArticle.testFilters(filteredFormat.filters) - if (!res.passed) { - continue - } - if (matched[thisPriority] === undefined) { - matched[thisPriority] = 1 - } else { - ++matched[thisPriority] - } - if (thisPriority >= highestPriority) { - highestPriority = thisPriority - selectedFormat = filteredFormat - } - } - // Only formats with 1 match will get the filtered format - if (highestPriority > -1 && matched[highestPriority] === 1) { - // If it's undefined, then it will use the feed's (or the config default, if applicable) message - if (selectedFormat.text) { - text = selectedFormat.text - } - if (selectedFormat.embeds) { - embeds = selectedFormat.embeds - } - } - } - - return { text, embeds } - } - - convertEmbeds (embeds) { - const { parsedArticle } = this - const richEmbeds = [] - const convert = parsedArticle.convertKeywords.bind(parsedArticle) - for (const objectEmbed of embeds) { - const richEmbed = new Discord.MessageEmbed() - - const title = convert(objectEmbed.title) - if (title) { - richEmbed.setTitle(title.length > 256 ? title.slice(0, 250) + '...' : title) - } - - const description = convert(objectEmbed.description) - if (description) { - richEmbed.setDescription(description) - } - - const url = convert(objectEmbed.url) - if (url) { - richEmbed.setURL(url || null) - } - - const color = objectEmbed.color - if (color !== null && color !== undefined && color <= 16777215 && color >= 0) { - richEmbed.setColor(parseInt(color, 10)) - } - - const footerText = convert(objectEmbed.footerText) - if (footerText) { - const footerIconURL = convert(objectEmbed.footerIconURL) - richEmbed.setFooter(footerText, footerIconURL || null) - } - - const authorName = convert(objectEmbed.authorName) - if (authorName) { - const authorIconURL = convert(objectEmbed.authorIconURL) - const authorURL = convert(objectEmbed.authorURL) - richEmbed.setAuthor(authorName, authorIconURL || null, authorURL || null) - } - - const thumbnailURL = convert(objectEmbed.thumbnailURL) - if (thumbnailURL) { - richEmbed.setThumbnail(thumbnailURL || null) - } - - const imageURL = convert(objectEmbed.imageURL) - if (imageURL) { - richEmbed.setImage(imageURL || null) - } - - const timestamp = objectEmbed.timestamp - if (timestamp === 'article') { - richEmbed.setTimestamp(new Date(parsedArticle.fullDate)) - } else if (timestamp === 'now') { - richEmbed.setTimestamp(new Date()) - } - - const fields = objectEmbed.fields - if (Array.isArray(fields)) { - for (const field of fields) { - const inline = field.inline === true - - let name = convert(field.name) - if (name.length > 256) { - name = name.slice(0, 250) + '...' - } else if (field.name && !name) { - // If a placeholder is empty - name = '\u200b' - } - - let value = convert(field.value) - if (value.length > 1024) { - value = value.slice(0, 1020) + '...' - } else if (field.value && !value) { - // If a placeholder is empty - value = '\u200b' - } - - if (richEmbed.fields.length < 10) { - richEmbed.addField(name, value, inline) - } - } - } - richEmbeds.push(richEmbed) - } - - return richEmbeds - } - - /** - * @param {import('discord.js').Client} bot - */ - async getWebhook (bot) { - const { feed } = this - const channel = this.getChannel(bot) - if (!channel) { - return - } - const permission = Discord.Permissions.FLAGS.MANAGE_WEBHOOKS - if (!feed.webhook || !channel.guild.me.permissionsIn(channel).has(permission)) { - return - } - if (feed.webhook.disabled) { - return - } - try { - const hooks = await channel.fetchWebhooks() - const hook = hooks.get(feed.webhook.id) - if (!hook) { - return - } - return hook - } catch (err) { - const log = createLogger(bot.shard.ids[0]) - log.warn({ - channel, - error: err - }, 'Cannot fetch webhooks for ArticleMessage webhook initialization to send message') - } - } - - /** - * @param {Object} [feedWebhook] A feed's webhook object if it exists - */ - getWebhookNameAvatar (feedWebhook) { - const { feed, parsedArticle } = this - const options = { - username: feedWebhook.name - } - if (feed.webhook.name) { - options.username = parsedArticle.convertKeywords(feed.webhook.name).slice(0, 32) - } - if (feed.webhook.avatar) { - options.avatarURL = parsedArticle.convertImgs(feed.webhook.avatar) - } - return options - } - - generateMessage (ignoreLimits = !!this.feed.split) { - const { parsedArticle } = this - const { text, embeds } = this.determineFormat() - - // Determine what the text/embeds are, based on whether an embed exists - let useEmbeds = embeds - let useText = text - if (embeds.length > 0) { - useEmbeds = this.convertEmbeds(embeds) - let convert = text - if (text === '{empty}') { - convert = '' - } - useText = parsedArticle.convertKeywords(convert, ignoreLimits) - } else { - let convert = text - if (text === '{empty}') { - convert = this.config.feeds.defaultText - } - useText = parsedArticle.convertKeywords(convert, ignoreLimits) - } - if (useText.length > 1950 && !ignoreLimits) { - useText = `Error: Feed Article could not be sent for ${this.article.link} due to a single message's character count >1950.` - } - if (useText.length === 0 && embeds.length === 0) { - useText = `Unable to send empty message for feed article <${this.article.link}> (${this.feed._id}).` - } - return { - embeds: useEmbeds, - text: useText - } - } - - createOptions (embeds, feedWebhook) { - const options = { - allowedMentions: { - parse: ['roles', 'users', 'everyone'] - } - } - if (feedWebhook) { - options.embeds = embeds - const webhookSettings = this.getWebhookNameAvatar(feedWebhook) - options.username = webhookSettings.username - options.avatarURL = webhookSettings.avatarURL - } else if (this.forceMultipleEmbeds) { - options.embeds = embeds - } else { - options.embed = embeds[0] - } - if (this.feed.split && this.feed.split.enabled) { - options.split = this.feed.split - } - return options - } - - /** - * Create an API payload meant for use with the delivery microservice - */ - createAPIPayload (feedWebhook) { - const { text, embeds } = this.generateMessage() - const options = this.createOptions(embeds, feedWebhook) - // First convert camel case properties to snake case - const transformed = { - ...options, - allowed_mentions: options.allowedMentions - } - delete transformed.allowedMentions - if (transformed.avatarURL) { - transformed.avatar_url = options.avatarURL - delete transformed.avatarURL - } - return { - ...transformed, - content: text - } - } - - /** - * Create multiple API payloads from one payload if its text content - * exceeds 2000 characters - */ - createAPIPayloads (feedWebhook) { - const apiPayload = this.createAPIPayload(feedWebhook) - const text = apiPayload.content - if (!apiPayload.split || text.length < 2000) { - return [apiPayload] - } - // Each string in parts should not exceed 2000 characters - let textParts = [text] - if (apiPayload.split) { - textParts = Discord.Util.splitMessage(text) - } - const payloadParts = [] - for (let i = 0; i < textParts.length; ++i) { - const thisApiPayload = { - ...apiPayload, - content: textParts[i] - } - // Delete the embed fields for a clean slate - delete thisApiPayload.embed - delete thisApiPayload.embeds - const isLastElement = i === textParts.length - 1 - // Channel messages use a "embed" object field and cannot have multiple embeds - const channelEmbed = feedWebhook || !isLastElement || !apiPayload.embed ? undefined : apiPayload.embed - // Webhook messages uses a "embeds" array field and can have multiple embeds - const webhookEmbeds = !feedWebhook || !isLastElement ? undefined : apiPayload.embeds - if (channelEmbed) { - thisApiPayload.embed = channelEmbed - } else if (webhookEmbeds) { - thisApiPayload.embeds = webhookEmbeds - } - payloadParts.push(thisApiPayload) - } - return payloadParts - } - - createTextAndOptions (feedWebhook) { - const { text, embeds } = this.generateMessage() - const options = this.createOptions(embeds, feedWebhook) - return { - text, - options - } - } - - /** - * @param {import('discord.js').Client} bot - */ - async getMedium (bot) { - const webhook = await this.getWebhook(bot) - if (webhook) { - return webhook - } - const channel = this.getChannel(bot) - return channel - } - - /** - * @param {import('discord.js').Client} bot - */ - async send (bot) { - if (devLevels.disableOutgoingMessages()) { - return - } - const medium = await this.getMedium(bot) - if (!medium) { - throw new Error('Missing medium to send message to') - } - const feedWebhook = medium instanceof Discord.Webhook ? this.feed.webhook : null - const { text, options } = this.createTextAndOptions(feedWebhook) - // Send the message, and repeat attempt if failed - try { - return await medium.send(text, options) - } catch (err) { - // 50013 = Missing Permissions, 50035 = Invalid form - if (err.code === 50013 || err.code === 50035 || this.sendFailed++ === 3) { - if (this.debug) { - const log = createLogger(bot.shard.ids[0]) - log.info({ - error: err - }, `${this.feed._id}: Message has been translated but could not be sent (TITLE: ${this.article.title})`) - } - throw err - } - return this.send(bot, medium) - } - } -} - -module.exports = ArticleMessage diff --git a/services/bot/src/structs/ArticleMessageRateLimiter.js b/services/bot/src/structs/ArticleMessageRateLimiter.js deleted file mode 100644 index 11087e36a..000000000 --- a/services/bot/src/structs/ArticleMessageRateLimiter.js +++ /dev/null @@ -1,158 +0,0 @@ -const GeneralStats = require('../models/GeneralStats.js') -const DeliveryRecord = require('../models/DeliveryRecord.js') -const Supporter = require('./db/Supporter.js') -const configuration = require('../config.js') -const createLogger = require('../util/logger/create.js') - -class ArticleRateLimiter { - /** - * @param {string} channelID - * @param {boolean} increased - */ - constructor (channelID, increased) { - const config = configuration.get() - const refreshRateMinutes = config.feeds.refreshRateMinutes - const articlesLimit = config.feeds.articleRateLimit - this.channelID = channelID - this.increased = increased - this.articlesLimit = increased ? articlesLimit * 5 : articlesLimit - this.articlesRemaining = this.articlesLimit - if (this.articlesLimit !== 0) { - this.timer = setInterval(() => { - this.articlesRemaining = this.articlesLimit - }, 1000 * 60 * refreshRateMinutes) - } - } - - static get ERRORS () { - return { - RATE_LIMITED_ARTICLE_ERROR: 'Rate limited article', - DAILY_LIMITED_ARTICLE_ERROR: 'Daily limited article' - } - } - - static isRateLimitError (error) { - return Array.from(Object.values(this.ERRORS)).includes(error.message) - } - - static async updateArticlesBlocked () { - if (this.blocked === 0 || !Supporter.isMongoDatabase) { - return - } - /** - * @type {import('mongoose').Document} - */ - const found = await GeneralStats.Model.findById(GeneralStats.TYPES.ARTICLES_BLOCKED) - if (!found) { - const stat = new GeneralStats.Model({ - _id: GeneralStats.TYPES.ARTICLES_BLOCKED, - data: ArticleRateLimiter.blocked - }) - await stat.save() - } else { - await found.updateOne({ - $inc: { - data: ArticleRateLimiter.blocked - } - }) - } - this.blocked = 0 - } - - /** - * @param {string} channelID - * @param {boolean} isSupporterGuild - */ - static create (channelID, isSupporterGuild) { - const highLimit = Supporter.enabled ? isSupporterGuild : false - const limiter = new ArticleRateLimiter(channelID, highLimit) - this.limiters.set(channelID, limiter) - return limiter - } - - static hasLimiter (channelID) { - return this.limiters.has(channelID) - } - - static getLimiter (channelID) { - if (!this.hasLimiter(channelID)) { - return this.create(channelID) - } else { - return this.limiters.get(channelID) - } - } - - /** - * @param {import('../structs/ArticleMessage.js')} articleMessage - */ - static async assertWithinLimits (articleMessage) { - const channelID = articleMessage.feed.channel - const articleLimiter = ArticleRateLimiter.getLimiter(channelID) - if (articleLimiter.isAtLimit()) { - ++ArticleRateLimiter.blocked - throw new Error(this.ERRORS.RATE_LIMITED_ARTICLE_ERROR) - } - if (await articleLimiter.isAtDailyLimit()) { - ++ArticleRateLimiter.blocked - throw new Error(this.ERRORS.DAILY_LIMITED_ARTICLE_ERROR) - } - ++ArticleRateLimiter.sent - --articleLimiter.articlesRemaining - } - - isAtLimit () { - if (this.articlesLimit === 0) { - return false - } else { - return this.articlesRemaining === 0 - } - } - - static getUTCStartOfToday () { - const now = new Date() - const nowUTC = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 0, 0, 0) - return new Date(nowUTC) - } - - async isAtDailyLimit () { - const config = configuration.get() - const dailyLimit = config.feeds.articleDailyChannelLimit - if (this.increased || !dailyLimit) { - return false - } - const count = await DeliveryRecord.Model.where({ - channel: this.channelID, - delivered: true, - addedAt: { - $gte: ArticleRateLimiter.getUTCStartOfToday() - } - }).countDocuments() - return count >= dailyLimit - } - - async send (articleMessage, bot) { - --this.articlesRemaining - const sent = await articleMessage.send(bot) - return sent - } -} - -/** - * @type {Map} - */ -ArticleRateLimiter.limiters = new Map() - -ArticleRateLimiter.blocked = 0 - -if (process.env.NODE_ENV !== 'test') { - ArticleRateLimiter.timer = setInterval(async () => { - try { - await ArticleRateLimiter.updateArticlesBlocked() - } catch (err) { - const log = createLogger() - log.error(err, 'Failed to update article stats') - } - }, 10000) -} - -module.exports = ArticleRateLimiter diff --git a/services/bot/src/structs/ArticleQueue.js b/services/bot/src/structs/ArticleQueue.js deleted file mode 100644 index f1a194a20..000000000 --- a/services/bot/src/structs/ArticleQueue.js +++ /dev/null @@ -1,278 +0,0 @@ -const Feed = require('./db/Feed') -const DeliveryRecord = require('../models/DeliveryRecord.js') -const createLogger = require('../util/logger/create') -const configuration = require('../config.js') -const Supporter = require('./db/Supporter') -const GeneralStats = require('../models/GeneralStats.js') -const fetch = require('node-fetch') -const { Webhook } = require('discord.js') - -/** - * @typedef {Object} ArticleDetails - * @property {Object} newArticle - * @property {import('./ArticleMessage')} articleMessage -*/ - -/** - * Staggers the delivery of new articles - */ -class ArticleQueue { - constructor (client) { - /** - * @type {ArticleDetails[]} - */ - this.serviceBacklogQueue = [] - /** - * @type {ArticleDetails[]} - */ - this.queue = [] - /** - * @type {import('discord.js').Client} - */ - this.client = client - this.log = createLogger(this.client.shard.ids[0]) - const config = configuration.get() - const dequeueRate = config.feeds.articleDequeueRate - const dequeueAmount = dequeueRate <= 1 ? 1 : dequeueRate - const intervalSeconds = dequeueRate <= 1 ? 1 / dequeueRate : 1 - setInterval(() => { - // The service backlog always takes priority since they came first - if (this.serviceBacklogQueue.length > 0) { - this.dequeue(this.serviceBacklogQueue, dequeueAmount) - } else { - this.dequeue(this.queue, dequeueAmount) - } - }, 1000 * intervalSeconds) - } - - /** - * @param {ArticleDetails} articleData - * @param {string} message - */ - _logDebug (articleData, message) { - const { article, feedObject } = articleData.newArticle - this.log.debug(`ArticleQueue ${message} (${article._id}, feed: ${feedObject._id})`) - } - - /** - * Dequeue a certain amount of articles from the queue - * and send them in order - * - * @param {ArticleDetails[]} queue - * @param {number} dequeueAmount - */ - async dequeue (queue, dequeueAmount) { - // async must be used within the loop to main the order - // in which articles are sent - for (let i = 0; i < dequeueAmount; ++i) { - if (queue.length === 0) { - continue - } - const articleData = queue.shift() - this._logDebug(articleData, 'Dequeuing') - await this.send(articleData) - } - } - - /** - * Send an article message - * - * @param {ArticleDetails} articleData - */ - async send (articleData) { - const deliveryServiceURL = configuration.get().deliveryServiceURL - try { - if (deliveryServiceURL) { - this._logDebug(articleData, 'Sending by service') - await this.sendByService(articleData, deliveryServiceURL) - } else { - this._logDebug(articleData, 'Sending by client') - await articleData.articleMessage.send(this.client) - } - // If it's in the backlog, it wasn't a success - if (!this.serviceBacklogQueue.includes(articleData)) { - await this.recordSuccess(articleData.newArticle) - ArticleQueue.sent++ - } - } catch (err) { - await this.recordFailure(articleData.newArticle, err.message) - } - } - - /** - * Send an article via the external service - * - * @param {ArticleDetails} articleData - * @param {string} serviceURL - */ - async sendByService (articleData, serviceURL) { - const articleMessage = articleData.articleMessage - const articleID = articleData.newArticle.article._id - const feedID = articleData.newArticle.feedObject._id - // Assert that the medium (either a channel or webhook) still exists - const medium = await articleMessage.getMedium(this.client) - if (!medium) { - throw new Error('Missing medium to send article via service') - } - // Make the fetch - const apiPayload = articleMessage.createAPIPayload( - medium instanceof Webhook ? articleData.newArticle.feedObject.webhook : null - ) - const apiRoute = medium instanceof Webhook ? `/webhooks/${medium.id}/${medium.token}` : `/channels/${medium.id}/messages` - let res - try { - res = await fetch(`${serviceURL}/api/request`, { - method: 'POST', - body: JSON.stringify({ - method: 'POST', - url: `https://discord.com/api${apiRoute}`, - body: apiPayload - }), - headers: { - Authorization: `Bot ${this.client.token}`, - 'Content-Type': 'application/json', - Accept: 'application/json' - } - }) - } catch (err) { - // Network error, put it in the service backlog - /** - * Deliver the articles at a later time once the service is available - * again. No need to check if it already exists in the array since it's - * shifted out of the array in dequeue(), which calls send(), which - * calls this - */ - this.serviceBacklogQueue.push(articleData) - this._logDebug(articleData, `Failed to send article to service (${err.message}). Backlog length: ${this.serviceBacklogQueue.length}`) - /** - * Don't throw an error, otherwise it'll be marked as a failure. We're - * just delaying the article for later delivery - */ - return - } - if (res.ok) { - // Successfully delivered - const wasBacklogged = this.serviceBacklogQueue.includes(articleData) - this._logDebug(articleData, `Successfully sent via service (was backlogged: ${wasBacklogged})`) - if (this.serviceBacklogQueue.includes(articleData)) { - this.serviceBacklogQueue.splice(this.serviceBacklogQueue.indexOf(articleData), 1) - } - return - } - // Bad status code from service - let json - try { - // The service should always send a message in the response - json = await res.json() - const isDiscordError = json.discord === true - this.log.warn(`Bad status code ${res.status} from service${isDiscordError ? ' (propagated by Discord)' : ''}. (${JSON.stringify(json)}) for article ${articleID}, feed ${feedID}`) - } catch (err) { - this.log.error(err, `Bad status code ${res.status} from service for article ${articleID}, ${feedID}`) - throw new Error(`Bad status code (${res.status}) from service`) - } - // JSON was successfully parsed, use the server response as the error - if (json) { - throw new Error(json.message) - } - } - - /** - * Enqueue an article to be dequeued and sent at - * the rate set in config - * - * @param {Object} newArticle - * @param {import('./ArticleMessage')} articleMessage - */ - enqueue (newArticle, articleMessage) { - this.queue.push({ - newArticle, - articleMessage - }) - } - - async recordFailure (newArticle, errorMessage) { - if (!Feed.isMongoDatabase) { - return - } - const { article, feedObject } = newArticle - const channel = feedObject.channel - const data = { - articleID: article._id, - feedURL: feedObject.url, - channel, - delivered: false, - comment: errorMessage - } - this.log.debug({ - data - }, 'Recording delivery record failure') - try { - const record = new DeliveryRecord.Model(data) - await record.save() - } catch (err) { - this.log.error(err, `Failed to record article ${article._id} delivery failure in channel ${channel} (error: ${errorMessage}) in ArticleQueue`) - } - } - - async recordSuccess (newArticle) { - if (!Feed.isMongoDatabase) { - return - } - const { article, feedObject } = newArticle - const channel = feedObject.channel - const data = { - articleID: article._id, - feedURL: feedObject.url, - delivered: true, - channel - } - this.log.debug({ - data - }, 'Recording delivery record success') - try { - const record = new DeliveryRecord.Model(data) - await record.save() - } catch (err) { - this.log.error(err, `Failed to record article ${article._id} delivery success in channel ${channel} in ArticleQueue`) - } - } - - static async updateArticlesSent () { - if (this.sent === 0 || !Supporter.isMongoDatabase) { - return - } - /** - * @type {import('mongoose').Document} - */ - const found = await GeneralStats.Model.findById(GeneralStats.TYPES.ARTICLES_SENT) - if (!found) { - const stat = new GeneralStats.Model({ - _id: GeneralStats.TYPES.ARTICLES_SENT, - data: ArticleQueue.sent - }) - await stat.save() - } else { - await found.updateOne({ - $inc: { - data: ArticleQueue.sent - } - }) - } - ArticleQueue.sent = 0 - } -} - -ArticleQueue.sent = 0 - -if (process.env.NODE_ENV !== 'test') { - ArticleQueue.timer = setInterval(async () => { - try { - await ArticleQueue.updateArticlesSent() - } catch (err) { - const log = createLogger() - log.error(err, 'Failed to update article stats') - } - }, 10000) -} - -module.exports = ArticleQueue diff --git a/services/bot/src/structs/ArticleTestMessage.js b/services/bot/src/structs/ArticleTestMessage.js deleted file mode 100644 index 9b1779551..000000000 --- a/services/bot/src/structs/ArticleTestMessage.js +++ /dev/null @@ -1,9 +0,0 @@ -const ArticleMessage = require('./ArticleMessage.js') - -class ArticleTestMessage extends ArticleMessage { - passedFilters () { - return true - } -} - -module.exports = ArticleTestMessage diff --git a/services/bot/src/structs/BlacklistCache.js b/services/bot/src/structs/BlacklistCache.js deleted file mode 100644 index be454bfdb..000000000 --- a/services/bot/src/structs/BlacklistCache.js +++ /dev/null @@ -1,35 +0,0 @@ -const Blacklist = require('./db/Blacklist.js') - -class BlacklistCache { - /** - * Directly from the database - * @param {import('./db/Blacklist.js')[]} blacklists - */ - constructor (blacklists) { - /** - * Blacklisted user IDs - * @type {Set} - */ - this.users = new Set() - - /** - * Blacklisted guild IDs - * @type {Set} - */ - this.guilds = new Set() - - for (const blacklist of blacklists) { - switch (blacklist.type) { - case Blacklist.TYPES.USER: - this.users.add(blacklist.id) - break - case Blacklist.TYPES.GUILD: - this.guilds.add(blacklist.id) - break - default: - } - } - } -} - -module.exports = BlacklistCache diff --git a/services/bot/src/structs/Client.js b/services/bot/src/structs/Client.js deleted file mode 100644 index eb07e141b..000000000 --- a/services/bot/src/structs/Client.js +++ /dev/null @@ -1,383 +0,0 @@ -const Discord = require('discord.js') -const EventEmitter = require('events') -const listeners = require('../util/listeners.js') -const maintenance = require('../maintenance/index.js') -const ipc = require('../util/ipc.js') -const Profile = require('./db/Profile.js') -const initialize = require('../initialization/index.js') -const getConfig = require('../config.js').get -const createLogger = require('../util/logger/create.js') -const connectDb = require('../util/connectDatabase.js') -const { once } = require('events') -const devLevels = require('../util/devLevels.js') -const dumpHeap = require('../util/dumpHeap.js') -const DeliveryPipeline = require('./DeliveryPipeline.js') -const RateLimitCounter = require('./RateLimitHitCounter.js') -const { RESTProducer } = require('@synzen/discord-rest') - -const STATES = { - STOPPED: 'STOPPED', - STARTING: 'STARTING', - READY: 'READY', - EXITING: 'EXITING' -} -/** - * @type {import('discord.js').ClientOptions} - */ -const CLIENT_OPTIONS = { - /** - * Allow minimal caching for message reactions/pagination - * handling. After 10 messages after the initial - * paginated embed, the pagination will stop working - */ - messageCacheMaxSize: 10, - ws: { - intents: [ - 'GUILDS', - 'GUILD_MESSAGES', - 'GUILD_MESSAGE_REACTIONS' - ] - } -} - -class Client extends EventEmitter { - constructor () { - super() - this.STATES = STATES - this.state = STATES.STOPPED - /** - * @type {import('./DeliveryPipeline.js')} - */ - this.deliveryPipeline = undefined - /** - * @type {import('pino').Logger} - */ - this.log = createLogger('-') - /** - * @type {RESTProducer|null} - */ - this.restProducer = null - this.rateLimitCounter = new RateLimitCounter() - this.rateLimitCounter.on('limitReached', () => { - const config = getConfig() - if (config.bot.exitOnExcessRateLimits) { - this.log.error('Forcing bot to exit due to excess rate limit hits (config.bot.exitOnExcessRateLimits)') - this.sendKillMessage() - } - }) - } - - async setupRESTProducer () { - const config = getConfig() - const { - apis: { - discordHttpGateway: { - enabled: serviceEnabled, - rabbitmqUri - } - }, - bot: { - clientId - } - } = config - if (serviceEnabled) { - const producer = new RESTProducer(rabbitmqUri, { - clientId - }) - await producer.initialize() - - return producer - } - return null - } - - async login (token) { - if (this.bot) { - return this.log.warn('Cannot login when already logged in') - } - if (typeof token !== 'string') { - throw new TypeError('Argument must a string') - } - const client = new Discord.Client(CLIENT_OPTIONS) - try { - await client.login(token) - this.restProducer = await this.setupRESTProducer() - this.deliveryPipeline = new DeliveryPipeline(client, this.restProducer) - this.log = createLogger(client.shard.ids[0].toString()) - this.bot = client - this.shardID = client.shard.ids[0] - this.listenToShardedEvents(client) - if (!client.readyAt) { - await once(client, 'ready') - this._setup() - } else { - this._setup() - } - } catch (err) { - this.log.error(err, 'MonitoRSS failed to start') - this.sendKillMessage() - } - } - - async connectToDatabase () { - const config = getConfig() - const mongo = await connectDb(config.database.uri, config.database.connection) - mongo.on('error', (error) => { - this.log.fatal(error, 'MongoDB connection error') - this.sendKillMessage() - }) - mongo.on('disconnected', () => { - if (this.state === STATES.EXITING) { - return - } - this.log.error('MongoDB disconnected') - if (config.bot.exitOnDatabaseDisconnect) { - this.log.info('Stopping processes due to exitOnDatabaseDisconnect') - this.sendKillMessage() - } else { - this.stop() - } - }) - mongo.on('reconnected', () => { - this.log.info('MongoDB reconnected') - this.restart() - }) - return mongo - } - - _setup () { - const bot = this.bot - bot.on('error', err => { - this.log.warn('Websocket error', err) - const config = getConfig() - if (config.bot.exitOnSocketIssues === true) { - this.log.info('Stopping all processes due to config.bot.exitOnSocketIssues') - this.sendKillMessage() - } else { - this.stop() - } - }) - bot.on('debug', info => { - const config = getConfig() - if (info.includes('429')) { - this.rateLimitCounter.hit() - if (config.log.rateLimitHits) { - this.log.warn(info) - } - } - }) - bot.on('resume', () => { - this.log.info('Websocket resumed') - this.start() - }) - bot.on('disconnect', () => { - this.log.warn(`SH ${this.shardID} Websocket disconnected`) - const config = getConfig() - if (config.bot.exitOnSocketIssues === true) { - this.log.general.info('Stopping all processes due to config.bot.exitOnSocketIssues') - this.sendKillMessage() - } else { - this.stop() - } - }) - if (devLevels.dumpHeap()) { - this.setupHeapDumps() - } - this.log.info(`MonitoRSS has logged in as "${bot.user.username}" (ID ${bot.user.id})`) - ipc.send(ipc.TYPES.SHARD_READY, { - guildIds: bot.guilds.cache.keyArray(), - channelIds: bot.channels.cache.keyArray() - }) - } - - setupHeapDumps () { - // Every 10 minutes - const prefix = `s${this.bot.shard.ids[0]}` - dumpHeap(prefix) - setInterval(() => { - dumpHeap(prefix) - }, 1000 * 60 * 15) - } - - listenToShardedEvents (bot) { - process.on('message', async message => { - if (!ipc.isValid(message)) { - return - } - try { - switch (message.type) { - case ipc.TYPES.KILL: - this.handleKillMessage() - break - case ipc.TYPES.START_INIT: { - const data = message.data - if (data.setPresence) { - const config = getConfig() - bot.user.setPresence({ - status: config.bot.status, - activity: { - name: config.bot.activityName, - type: config.bot.activityType, - url: config.bot.streamActivityURL || undefined - } - }).catch(err => this.log.warn({ - error: err - }, 'Failed to set presence')) - } - this.start() - break - } - case ipc.TYPES.NEW_ARTICLE: - this.onNewArticle(message.data.newArticle, message.data.debug) - break - case ipc.TYPES.FINISHED_INIT: - break - case ipc.TYPES.SEND_USER_ALERT: - this.sendUserAlert(message.data.channel, message.data.message) - .catch(err => this.log.warn(`Failed to send inter-process alert to channel ${message.data.channel}`, err)) - } - } catch (err) { - this.log.error(err, 'client') - } - }) - } - - async onNewArticle (newArticle, debug) { - try { - await this.deliveryPipeline.deliver(newArticle, debug) - } catch (err) { - this.log.error(err, 'Delivery pipeline') - } - } - - async sendChannelMessage (channelID, message) { - const channel = this.bot.channels.cache.get(channelID) - if (!channel) { - return - } - /** - * @type {import('discord.js').GuildMember} - */ - const guildMeMember = channel.guild.me - if (!guildMeMember.permissionsIn(channel).has(Discord.Permissions.FLAGS.SEND_MESSAGES)) { - this.log.warn({ - channel, - string: message - }, 'Failed to send Client message to channel') - return - } - channel.send(message) - } - - async sendUserAlert (channelID, message) { - const fetchedChannel = this.bot.channels.cache.get(channelID) - if (!fetchedChannel) { - return - } - const alertMessage = `**ALERT**\n\n${message}` - try { - const profile = await Profile.get(fetchedChannel.guild.id) - if (!profile || !profile.alert || !profile.alert.length) { - return this.sendChannelMessage(channelID, alertMessage) - } - const alertTo = profile.alert - for (const id of alertTo) { - const user = await this.bot.users.fetch(id, false) - if (user) { - await user.send(alertMessage) - } - } - } catch (err) { - this.log.warn({ - error: err - }, `Failed to send user alert to channel ${channelID}`) - return this.sendChannelMessage(alertMessage) - } - } - - async start () { - if (this.state === STATES.STARTING || this.state === STATES.READY) { - return this.log.warn(`Ignoring start command because of ${this.state} state`) - } - const config = getConfig() - const disableCommands = devLevels.disableCommands() || !config.bot.enableCommands - this.state = STATES.STARTING - try { - if ((this.mongo && this.mongo.readyState !== 1) || Profile.isMongoDatabase) { - this.mongo = await this.connectToDatabase() - } - await initialize.setupModels(this.mongo) - await initialize.setupCommands(disableCommands) - const uri = config.database.uri - this.log.info(`Database URI detected as a ${uri.startsWith('mongo') ? 'MongoDB URI' : 'folder URI'}`) - await maintenance.pruneWithBot(this.bot, this.restProducer) - this.state = STATES.READY - await initialize.setupRateLimiters(this.bot) - this.log.info(`Commands have been ${config.bot.enableCommands ? 'enabled' : 'disabled'}.`) - ipc.send(ipc.TYPES.INIT_COMPLETE) - listeners.createManagers(this.bot, disableCommands) - this.emit('finishInit') - } catch (err) { - this.log.error(err, 'Client start') - } - } - - handleKillMessage () { - this.state = STATES.EXITING - this.log.info('Received kill signal from sharding manager, closing MongoDB connection') - if (this.bot) { - this.bot.destroy() - } - if (this.restHandler) { - this.restHandler.disconnectRedis() - } - const handleMongoClose = (err) => { - if (err) { - this.log.error(err, 'Failed to close mongo connection on shard kill message') - } - this.log.info('Exiting with status code 1') - process.exit(1) - } - if (this.mongo) { - this.mongo.close(handleMongoClose) - } else { - handleMongoClose() - } - } - - sendKillMessage () { - this.log.info('Sending kill signal to sharding manager') - const handleMongoClose = (err) => { - this.log.error(err, 'Failed to close mongo connection on shard kill message emit') - ipc.send(ipc.TYPES.KILL) - } - if (this.mongo) { - this.mongo.close(handleMongoClose) - } else { - handleMongoClose() - } - } - - stop () { - if (this.state === STATES.STARTING || this.state === STATES.STOPPED || this.state === STATES.EXITING) { - return this.log.warn(`Ignoring stop command because of ${this.state} state`) - } - this.log.info('MonitoRSS has received stop command') - clearInterval(this.maintenance) - listeners.disableAll(this.bot) - this.state = STATES.STOPPED - ipc.send(ipc.TYPES.SHARD_STOPPED) - } - - async restart () { - if (this.state === STATES.STARTING) { - return this.log.warn(`Ignoring restart command because of ${this.state} state`) - } - if (this.state === STATES.READY) { - this.stop() - } - return this.start() - } -} - -module.exports = Client diff --git a/services/bot/src/structs/ClientManager.js b/services/bot/src/structs/ClientManager.js deleted file mode 100644 index e60906eeb..000000000 --- a/services/bot/src/structs/ClientManager.js +++ /dev/null @@ -1,376 +0,0 @@ -const path = require('path') -const Discord = require('discord.js') -const connectDb = require('../util/connectDatabase.js') -const Patron = require('./db/Patron.js') -const Supporter = require('./db/Supporter.js') -const ScheduleManager = require('./ScheduleManager.js') -const EventEmitter = require('events').EventEmitter -const maintenance = require('../maintenance/index.js') -const initialize = require('../initialization/index.js') -const createLogger = require('../util/logger/create.js') -const ipc = require('../util/ipc.js') -const configuration = require('../config.js') -const setConfig = configuration.set -const getConfig = configuration.get -const devLevels = require('../util/devLevels.js') -const dumpHeap = require('../util/dumpHeap.js') - -/** - * @typedef {Object} ClientManagerOptions - * @property {Object>} schedules - * @property {boolean} setPresence - * @property {string} config - */ - -class ClientManager extends EventEmitter { - /** - * @param {ClientManagerOptions} options - */ - constructor (options = {}) { - super() - const nodeMajorVersion = Number(process.version.split('.')[0].replace('v', '')) - if (nodeMajorVersion < 12) { - throw new Error('MonitoRSS requires Node.js v12 or higher') - } - this.config = setConfig(options.config) - process.env.DRSS_CONFIG = JSON.stringify(this.config) - this.log = createLogger('M') - this.finishedSetup = false - this.setPresence = options ? options.setPresence : false - this.customSchedules = options ? options.schedules : {} - ClientManager.validateCustomSchedules(this.customSchedules) - this.maintenance = maintenance.cycle() - this.guildIdsByShard = new Map() - this.channelIdsByShard = new Map() - this.queuedArticles = new Map() // Articles that are not sent due to Clients with stop status - this.shardsReady = 0 // Shards that have reported that they're ready - this.shardsDone = 0 // Shards that have reported that they're done initializing - this.shardsStopped = new Set() - this.scheduleManager = this.createScheduleManager() - this.shardingManager = new Discord.ShardingManager(path.join(__dirname, '..', '..', 'shard.js'), { - respawn: false, - token: this.config.bot.token - }) - this.shardingManager.on('shardCreate', shard => { - shard.on('message', message => this.messageHandler(shard, message)) - shard.on('death', () => { - this.log.info('Detected shard exit, sending kill signal') - this.kill() - }) - }) - const exitSignals = ['exit', 'SIGTERM', 'SIGINT', 'uncaughtException'] - exitSignals.forEach(signal => { - process.once(signal, () => { - this.log.info(`Detected process ${signal}, sending kill signal to shards`) - this.kill() - }) - }) - } - - setupHeapDumps () { - // Every 10 minutes - const prefix = 'sm' - dumpHeap(prefix) - setInterval(() => { - dumpHeap(prefix) - }, 1000 * 60 * 15) - } - - createScheduleManager () { - const scheduleManager = new ScheduleManager() - scheduleManager.on('newArticle', newArticle => { - const { article } = newArticle - this.log.debug({ - newArticle - }, `ScheduleManager emitted new article article id ${article._id}`) - this.handleNewArticle(newArticle) - }) - scheduleManager.on('alert', (channelID, message) => { - this.broadcastUserAlert(channelID, message) - }) - return scheduleManager - } - - broadcast (type, data) { - this.shardingManager.broadcast({ - _drss: true, - type, - data - }) - } - - send (type, data, shard) { - shard.send({ - _drss: true, - type, - data - }) - } - - broadcastNewArticle (newArticle) { - const { feedObject } = newArticle - const debug = this.scheduleManager.isDebugging(feedObject._id) - this.broadcast(ipc.TYPES.NEW_ARTICLE, { - debug, - newArticle - }) - } - - broadcastUserAlert (channelID, message) { - this.broadcast(ipc.TYPES.SEND_USER_ALERT, { - channel: channelID, - message - }) - } - - sendNewArticle (newArticle, shard) { - const { feedObject } = newArticle - const debug = this.scheduleManager.isDebugging(feedObject._id) - this.send(ipc.TYPES.NEW_ARTICLE, { - debug, - newArticle - }, shard) - } - - handleNewArticle (newArticle) { - const { article, feedObject } = newArticle - this.log.debug(`Got new feed $${feedObject._id} article ${article._id}`) - if (this.shardsStopped.size > 0) { - this.shardingManager.shards.forEach(shard => { - const shardID = shard.id - if (this.shardsStopped.has(shardID)) { - this.log.debug(`Queueing feed ${feedObject._id} article ${article._id} for shard ${shardID} with some stopped shards`) - this.queuedArticles.get(shardID).push(article) - } else { - this.log.debug(`Sending feed ${feedObject._id} article ${article._id} for shard ${shardID} with some stopped shards`) - this.sendNewArticle(newArticle, shard) - } - }) - } else { - this.log.debug(`Broadcasting feed ${feedObject._id} article ${article._id}`) - this.broadcastNewArticle(newArticle) - } - } - - sendQueuedArticles (shard) { - this.log.debug(`About to send queued articles for ${shard.id}`) - /** @type {Object[]} */ - const queue = this.queuedArticles.get(shard.id) - const length = queue.length - for (var i = 0; i < length; ++i) { - const article = queue[i] - this.log.debug(`Sending queued article ${article._id} to shard ${shard.id}`) - this.sendNewArticle(article, shard) - } - queue.length = 0 - } - - static validateCustomSchedules (customSchedules) { - const config = getConfig() - const addedRates = new Set() - addedRates.add(config.feeds.refreshRateMinutes) - for (const name in customSchedules) { - const schedule = customSchedules[name] - if (name === 'example') { - continue - } - if (addedRates.has(schedule.refreshRateMinutes)) { - throw new Error('Duplicate schedule refresh rates are not allowed') - } - addedRates.add(schedule.refreshRateMinutes) - } - } - - async start () { - const shardCount = this.config.advanced.shards - try { - if (Supporter.isMongoDatabase) { - this.mongo = await this.connectToDatabase() - } - await initialize.setupModels(this.mongo) - await initialize.populateKeyValues() - const schedules = await initialize.populateSchedules(this.customSchedules) - this.scheduleManager.addSchedules(schedules) - await this.shardingManager.spawn(shardCount || undefined, 5500, 240000) - } catch (err) { - if (err.headers) { - const isJSON = err.headers.get('content-type') === 'application/json' - const promise = isJSON ? err.json() : err.text() - promise.then((response) => { - this.log.error({ response }, 'ClientManager failed to start. Verify token and observe rate limits.') - }).catch((parseErr) => { - this.log.error(err, 'ClientManager failed to start') - this.log.error(parseErr, `Failed to parse response from ClientManager spawn (Status ${err.status})`) - }).finally(() => { - this.kill() - }) - } else { - this.log.error(err, 'ClientManager failed to start') - this.kill() - } - } - } - - async connectToDatabase () { - const mongo = await connectDb(this.config.database.uri, this.config.database.connection) - mongo.on('error', (error) => { - this.log.fatal(error, 'MongoDB connection error') - this.kill() - }) - mongo.on('disconnected', () => { - this.log.error('MongoDB disconnected') - if (this.config.bot.exitOnDatabaseDisconnect) { - this.log.info('Stopping processes due to exitOnDatabaseDisconnect') - this.kill() - } else { - this.stopFetching() - } - }) - mongo.on('reconnected', () => { - this.log.info('MongoDB reconnected') - if (!this.finishedSetup) { - return - } - this.startFetching() - }) - return mongo - } - - messageHandler (shard, message) { - if (!ipc.isValid(message)) { - return - } - if (ipc.isLoopback(message)) { - return this.shardingManager.broadcast(message) - .catch(err => { - this.log.error(err, `Sharding Manager broadcast message handling error for message type ${message.type}`) - this.kill() - }) - } - switch (message.type) { - case ipc.TYPES.KILL: this.kill(); break - case ipc.TYPES.SHARD_READY: this._shardReadyEvent(shard, message); break - case ipc.TYPES.INIT_COMPLETE: this._initCompleteEvent(shard); break - case ipc.TYPES.SHARD_STOPPED: this._shardStoppedEvent(shard); break - case ipc.TYPES.ADD_DEBUG_FEEDID: this._addDebugFeedIDEvent(message.data); break - case ipc.TYPES.REMOVE_DEBUG_FEEDID: this._removeDebugFeedIDEvent(message.data); break - } - } - - startFetching () { - if (this.scheduleManager.timers.length > 0) { - // Aready running - return - } - if (!devLevels.disableCycles() && !this.config.disableFeedCycles) { - this.scheduleManager.beginTimers() - this.log.info('Started fetch intervals') - } else { - this.log.info('Feed cycles disabled, either due to dev levels or config.disableFeedCycles') - } - } - - stopFetching () { - this.scheduleManager.terminateAllRuns() - if (this.scheduleManager.timers.length === 0) { - return - } - this.scheduleManager.terminateAllRuns() - this.scheduleManager.clearTimers() - this.log.info('Stopped fetch intervals') - } - - kill () { - const handleMongoClose = (err) => { - if (err) { - this.log.error(err, 'Failed to close mongo connection on kill signal for sharding manager') - } - this.broadcast(ipc.TYPES.KILL) - process.exit(1) - } - this.scheduleManager.terminateAllRuns() - if (this.mongo) { - this.mongo.close(handleMongoClose) - } else { - handleMongoClose() - } - } - - async _shardReadyEvent (shard, message) { - this.log.debug(`Shard ${shard.id} is ready`) - this.shardsStopped.delete(shard.id) - message.data.guildIds.forEach(id => { - this.guildIdsByShard.set(id, shard.id) - }) - message.data.channelIds.forEach(id => { - this.channelIdsByShard.set(id, shard.id) - }) - if (++this.shardsReady < this.shardingManager.totalShards) { - return - } - // Only after all shards are ready do we broadcast start init - try { - this.log.debug('Running pre-init') - await maintenance.prunePreInit(this.guildIdsByShard, this.channelIdsByShard) - const data = { - setPresence: this.setPresence || false, - customSchedules: this.customSchedules || [] - } - if (this.shardsReady === this.shardingManager.totalShards) { - this.broadcast(ipc.TYPES.START_INIT, data) - } - } catch (err) { - this.log.fatal(err, 'Failed to execute prune pre init in sharding manager') - } - } - - async _initCompleteEvent (shard) { - if (this.finishedSetup) { - return - } - if (!this.queuedArticles.has(shard.id)) { - this.queuedArticles.set(shard.id, []) - } - if (++this.shardsDone < this.shardingManager.totalShards) { - return - } - try { - this.log.info('All shards have initialized by the Sharding Manager.') - this.sendQueuedArticles(shard) - this.log.debug('Running post-init') - await maintenance.prunePostInit(this.guildIdsByShard) - this.log.debug('Post-init finished') - if (Supporter.enabled) { - Patron.refresh().catch(err => { - this.log.error(err, 'Failed to refresh patrons') - }) - } - this.startFetching() - if (devLevels.dumpHeap()) { - this.setupHeapDumps() - } - this.broadcast(ipc.TYPES.FINISHED_INIT) - this.emit('finishInit') - this.finishedSetup = true - } catch (err) { - this.log.fatal(err, 'Post-initialization failed in sharding manager') - } - } - - _shardStoppedEvent (shard) { - this.log.debug(`Added shard ${shard.id} to shards stopped`) - this.shardsStopped.add(shard.id) - } - - _addDebugFeedIDEvent (feedID) { - this.log.info(`Adding ${feedID} to schedule manager debug`) - this.scheduleManager.addDebugFeedID(feedID) - } - - _removeDebugFeedIDEvent (feedID) { - this.log.info(`Removing ${feedID} to schedule manager debug`) - this.scheduleManager.removeDebugFeedID(feedID) - } -} - -module.exports = ClientManager diff --git a/services/bot/src/structs/Command.js b/services/bot/src/structs/Command.js deleted file mode 100644 index 4f2c91f96..000000000 --- a/services/bot/src/structs/Command.js +++ /dev/null @@ -1,392 +0,0 @@ -const path = require('path') -const fsPromises = require('fs').promises -const Discord = require('discord.js') -const { DiscordPromptRunner } = require('discord.js-prompts') -const Profile = require('../structs/db/Profile.js') -const Guild = require('../structs/Guild.js') -const Blacklist = require('../structs/db/Blacklist.js') -const BlacklistCache = require('../structs/BlacklistCache.js') -const Supporter = require('../structs/db/Supporter.js') -const Permissions = Discord.Permissions.FLAGS -const getConfig = require('../config.js').get - -class Command { - /** - * @param {string} name - Command name - * @param {function} func - Command function - * @param {boolean} owner - If this is an owner command - */ - constructor (name, func, owner = false) { - this.owner = owner - this.name = name - this.func = func - } - - static get USER_PERMISSIONS () { - return { - sub: [], - unsub: [], - 'sub.filters': [] - } - } - - static get BOT_PERMISSIONS () { - return { - clone: [Permissions.EMBED_LINKS], - date: [Permissions.EMBED_LINKS], - dump: [Permissions.EMBED_LINKS, Permissions.ATTACH_FILES], - embed: [Permissions.EMBED_LINKS], - filters: [Permissions.EMBED_LINKS], - list: [Permissions.EMBED_LINKS], - mention: [Permissions.EMBED_LINKS], - move: [Permissions.EMBED_LINKS], - options: [Permissions.EMBED_LINKS], - remove: [Permissions.EMBED_LINKS], - refresh: [Permissions.EMBED_LINKS], - split: [Permissions.EMBED_LINKS], - sub: [Permissions.EMBED_LINKS, Permissions.MANAGE_ROLES], - test: [Permissions.EMBED_LINKS], - text: [Permissions.EMBED_LINKS], - unsub: [Permissions.EMBED_LINKS, Permissions.MANAGE_ROLES], - webhook: [Permissions.EMBED_LINKS] - } - } - - /** - * Set the enabled flag to true - */ - static enable () { - this.enabled = true - } - - /** - * Set the enabled flag to false - */ - static disable () { - this.enabled = false - } - - /** - * Check if a message should only be accepted if it's a patron - * @param {import('discord.js').Message} message - * @returns - */ - static async blockIfNotSupporter (message) { - if (!Supporter.restricted) { - return false - } - const guild = new Guild(message.guild.id) - const supporter = await guild.hasSupporterOrSubscriber() - return !supporter - } - - /** - * If an ID is blacklisted from commands - * @param {string} id - */ - static isBlacklistedID (id) { - return this.blacklistCache.users.has(id) || this.blacklistCache.guilds.has(id) - } - - /** - * If a message should skip command parsing - * @param {import('discord.js').Message} message - * @param {import('pino').Logger} log - */ - static shouldIgnore (message, log) { - const { author, client, guild, channel } = message - if (!guild) { - log.trace('Ignored message from non-guild') - return true - } else if (author.id === client.user.id) { - log.trace('Ignored message from self (bot)') - return true - } else if (DiscordPromptRunner.isActiveChannel(channel.id)) { - log.trace('Ignored message because of active menu in channel') - return true - } else if (this.isBlacklistedID(guild.id)) { - log.trace('Ignored message from blacklisted guild') - return true - } else if (this.isBlacklistedID(author.id)) { - log.trace('Ignored message from blacklisted user') - return true - } - return false - } - - /** - * Check if an ID is a owner's - * @param {string} id - */ - static isOwnerID (id) { - const config = getConfig() - /** @type {stirng[]} */ - const ownerIDs = config.bot.ownerIDs - return ownerIDs.includes(id) - } - - /** - * Get the permission names required - * @param {number[]} numbers - Permission values - * @returns {string[]} - */ - static getPermissionNames (numbers) { - const permissions = numbers.reduce((accumulated, cur) => { - return accumulated.add(cur) - }, new Discord.Permissions()) - return permissions.toArray() - } - - /** - * Return list of command names - * @param {boolean} owner - If owner commands - */ - static async readCommands (owner) { - const folderPath = path.join(__dirname, '..', 'commands', owner ? 'owner' : '') - const fileNames = await fsPromises.readdir(folderPath) - return fileNames.filter(name => /\.js$/.test(name)).map(name => name.replace('.js', '')) - } - - /** - * Read and store all commands - */ - static async initialize () { - if (this.initialized) { - return - } - this.blacklistCache = new BlacklistCache(await Blacklist.getAll()) - const commandNames = await this.readCommands() - for (const name of commandNames) { - const func = require(`../commands/${name}.js`) - this.commands.set(name, new Command(name, func)) - } - const ownerCommandNames = await this.readCommands(true) - for (const name of ownerCommandNames) { - const func = require(`../commands/owner/${name}.js`) - this.commands.set(name, new Command(name, func, true)) - } - this.initialized = true - } - - /** - * Get the default prefix - * @returns {string} - */ - static getDefaultPrefix () { - const config = getConfig() - return config.bot.prefix - } - - /** - * Gets a guild's prefix - * @param {string} guildID - * @returns {string} - */ - static getPrefix (guildID) { - const guildPrefix = Profile.getPrefix(guildID) - return guildPrefix || this.getDefaultPrefix() - } - - /** - * Try to get a command name from user input - * @param {string} string - * @param {boolean} withDefault - * @returns {Promise} - The command name - */ - static parseForName (string, prefix) { - if (!string.startsWith(prefix)) { - return '' - } - // This assumes the prefix has no spaces - const target = string.split(' ')[0] - const name = target.slice(prefix.length, target.length) - return name - } - - /** - * Try to get a Command from a message - * @param {import('discord.js').Message} message - * @param {import('pino').Logger} log - */ - static tryGetCommand (message, log) { - const { guild } = message - // With guild prefix - const guildPrefix = this.getPrefix(guild.id) - let name = this.parseForName(message.content, guildPrefix) - let command = this.get(name) - log.trace(`Parsed for command name with guild prefix as ${name}`) - if (command) { - return command - } - // With default prefix - const defaultPrefix = this.getDefaultPrefix() - name = this.parseForName(message.content, defaultPrefix) - log.trace(`Parsed for command name with default prefix as ${name}`) - command = this.get(name) - return command - } - - /** - * If a command exists - * @param {string} name - * @returns {boolean} - */ - static has (name) { - return Command.commands.has(name) - } - - /** - * Get a command - * @param {string} name - * @returns {Command} - */ - static get (name) { - return Command.commands.get(name) - } - - /** - * Get the required user permissions - * @returns {number[]} - */ - getMemberPermission () { - const name = this.name - if (name in Command.USER_PERMISSIONS) { - return Command.USER_PERMISSIONS[name] - } else { - return [Permissions.MANAGE_CHANNELS] - } - } - - /** - * @param {number[]} - * @param {import('discord.js').GuildMember} member - * @param {import('discord.js').TextChannel} channel - */ - getMissingChannelPermissions (perms, member, channel) { - const missing = perms.filter(perm => { - if (perm === Discord.Permissions.FLAGS.MANAGE_ROLES) { - return !member.permissions.has(perm) - } else { - return !member.permissionsIn(channel).has(perm) - } - }) - return missing - } - - /** - * Get the required bot permissions - * @returns {number[]} - */ - getBotPermissions () { - const name = this.name - const base = [Permissions.SEND_MESSAGES] - if (name in Command.BOT_PERMISSIONS) { - return Command.BOT_PERMISSIONS[name].concat(base) - } else { - return base - } - } - - /** - * Check if a user has permission to run this command - * @param {import('discord.js').Message} message - * @returns {boolean} - */ - hasMemberPermission (message) { - const { member, channel } = message - const isOwnerUser = Command.isOwnerID(member.user.id) - if (isOwnerUser || this.owner) { - return isOwnerUser - } - const memberPermissions = this.getMemberPermission() - return member.permissionsIn(channel).has(memberPermissions) - } - - /** - * Check if the bot has permission to run this command - * @param {import('discord.js').Message} message - * @returns {boolean} - */ - hasBotPermission (message) { - const { channel, guild } = message - const botPermissions = this.getBotPermissions() - return botPermissions.every(perm => { - // Some guild-wide permissions will still return false in channel permissions - const channelPermission = guild.me.permissionsIn(channel).has(perm) - const guildPermission = guild.me.hasPermission(perm) - if (perm === Discord.Permissions.FLAGS.MANAGE_ROLES) { - return guildPermission - } else { - return channelPermission - } - }) - } - - /** - * Send a message about sending missing bot perms - * @param {import('discord.js').Message} message - * @param {number[]} perms - * @returns {string[]} - Permissio names - */ - async notifyMissingBotPerms (message, perms) { - const channel = message.channel - const permissionNames = Command.getPermissionNames(perms) - if (!message.guild.me.permissionsIn(message.channel).has(Permissions.SEND_MESSAGES)) { - return permissionNames - } - await channel.send(`I am missing one or more of the following permissions:\n\n${permissionNames.join('\n')}`) - return permissionNames - } - - /** - * Send a message about missing member perms - * @param {import('discord.js').Message} message - * @param {number[]} perms - * @returns {string[]} - Permission names - */ - async notifyMissingMemberPerms (message, perms) { - const channel = message.channel - const permissionNames = Command.getPermissionNames(perms) - if (!message.guild.me.permissionsIn(message.channel).has(Permissions.SEND_MESSAGES)) { - return permissionNames - } - if (this.owner) { - await message.channel.send('You must be an owner to use this command.') - return ['owner'] - } - await channel.send(`You are missing one or more of the following permissions:\n\n${permissionNames.join('\n')}`) - return permissionNames - } - - /** - * Run a command - * @param {import('discord.js').Message} message - */ - async run (message) { - const channelID = message.channel.id - DiscordPromptRunner.addActiveChannel(channelID) - try { - await this.func(message, this.name) - } finally { - DiscordPromptRunner.deleteActiveChannel(channelID) - } - } -} - -/** - * If commands have been read and stored - */ -Command.initialized = false - -/** - * If commands are enabled - */ -Command.enabled = false - -/** - * Initialized non-owner commands - * @type {Map} - */ -Command.commands = new Map() - -module.exports = Command diff --git a/services/bot/src/structs/DecodedFeedParser.js b/services/bot/src/structs/DecodedFeedParser.js deleted file mode 100644 index af9746e57..000000000 --- a/services/bot/src/structs/DecodedFeedParser.js +++ /dev/null @@ -1,25 +0,0 @@ -const FeedParser = require('feedparser') -const iconv = require('iconv-lite') -const getConfig = require('../config.js').get - -class DecodedFeedParser extends FeedParser { - constructor (options, url, charset) { - super(options) - this.url = url - this.charset = charset - } - - _transform (chunk, encoding, done) { - const config = getConfig() - const charset = config.feeds.decode[this.url] || this.charset - if (/utf-*8/i.test(charset) || !charset || !iconv.encodingExists(charset)) { - this.stream.write(chunk) - } else { - // Assumes that the encoding specified is valid, and will not check via iconv.encodingExists() - this.stream.write(iconv.decode(chunk, charset)) - } - done() - } -} - -module.exports = DecodedFeedParser diff --git a/services/bot/src/structs/DeliveryPipeline.js b/services/bot/src/structs/DeliveryPipeline.js deleted file mode 100644 index 7280bfd25..000000000 --- a/services/bot/src/structs/DeliveryPipeline.js +++ /dev/null @@ -1,256 +0,0 @@ -const DeliveryRecord = require('../models/DeliveryRecord.js') -const ArticleMessage = require('./ArticleMessage.js') -const Feed = require('./db/Feed.js') -const ArticleRateLimiter = require('./ArticleMessageRateLimiter.js') -const createLogger = require('../util/logger/create.js') -const configuration = require('../config.js') -const ArticleQueue = require('./ArticleQueue.js') -const { Webhook } = require('discord.js') - -/** - * Core delivery pipeline - */ -class DeliveryPipeline { - constructor (bot, restProducer) { - this.bot = bot - this.log = createLogger(this.bot ? this.bot.shard.ids[0] : '') - const config = configuration.get() - this.logFiltered = config.log.unfiltered === true - /** - * @type {import('@synzen/discord-rest').RESTProducer|null} - */ - this.restProducer = restProducer - /** - * ArticleQueues mapped by channel ID. For delivering - * articles within this client and not an external - * service. - * - * @type {Map} - */ - this.queues = new Map() - } - - /** - * Send an article to the service responsible for sending messages to Discord. - * - * @param {Object} newArticle - * @param {import('./ArticleMessage.js')} articleMessage - */ - async sendToService (newArticle, articleMessage) { - const { article, feedObject } = newArticle - // Assert that the medium (either a channel or webhook) still exists - const medium = await articleMessage.getMedium(this.bot) - if (!medium) { - throw new Error('Missing medium to send article via service') - } - // Make the fetch - const apiPayloads = articleMessage.createAPIPayloads( - medium instanceof Webhook ? feedObject.webhook : null - ) - const apiRoute = medium instanceof Webhook ? `/webhooks/${medium.id}/${medium.token}` : `/channels/${medium.id}/messages` - return Promise.all( - apiPayloads.map(apiPayload => this.restProducer.enqueue(`https://discord.com/api${apiRoute}`, { - method: 'POST', - body: JSON.stringify(apiPayload) - }, { - articleID: article._id, - feedURL: feedObject.url, - channel: feedObject.channel, - feedId: feedObject._id, - guildId: feedObject.guild - })) - ) - } - - /** - * Send an article to the service responsible for sending messages to Discord. - * - * @param {Object} newArticle - * @param {import('./ArticleMessage.js')} articleMessage - */ - async sendToServiceWithoutBot (newArticle, articleMessage) { - const { article, feedObject } = newArticle - const feedWebhook = feedObject.webhook && !feedObject.webhook.disabled ? feedObject.webhook : null - const apiPayloads = articleMessage.createAPIPayloads(feedWebhook) - const apiRoute = feedWebhook ? feedWebhook.url : `https://discord.com/api/v9/channels/${feedObject.channel}/messages` - - await Promise.all( - apiPayloads.map(apiPayload => this.restProducer.enqueue(apiRoute, { - method: 'POST', - body: JSON.stringify(apiPayload) - }, { - articleID: article._id, - feedURL: feedObject.url, - channel: feedObject.channel, - feedId: feedObject._id, - guildId: feedObject.guild - })) - ) - } - - getChannel (newArticle) { - const { feedObject } = newArticle - return this.bot.channels.cache.get(feedObject.channel) - } - - async createArticleMessage (newArticle, debug, withoutBot) { - const { article, feedObject } = newArticle - return ArticleMessage.create(feedObject, article, debug, { - forceMultipleEmbeds: !!withoutBot - }) - } - - async deliver (newArticle, debug, withoutBot) { - try { - const articleMessage = await this.createArticleMessage(newArticle, debug, withoutBot) - if (!articleMessage.passedFilters()) { - return await this.handleArticleBlocked(newArticle) - } - this.log.debug(`Preparing to send new article ${newArticle.article._id} of feed ${newArticle.feedObject._id}`) - await this.sendNewArticle(newArticle, articleMessage, withoutBot) - } catch (err) { - await this.handleArticleFailure(newArticle, err) - if (withoutBot && !ArticleRateLimiter.isRateLimitError(err)) { - throw err - } - } - } - - async handleArticleBlocked (newArticle) { - const { article } = newArticle - if (this.logFiltered) { - this.log.info(`'${article.link || article.title}' did not pass filters and was not sent`) - } - await this.recordFilterBlock(newArticle) - } - - async handleArticleFailure (newArticle, err) { - const { article, feedObject } = newArticle - await this.recordFailure(newArticle, err.message || 'N/A') - if (err.message.includes('limited')) { - this.log.debug({ - error: err - }, 'Ignoring rate-limited article') - return - } - this.log.warn({ - error: err - }, `Failed to deliver article ${article._id} (${article.link}) of feed ${feedObject._id}`) - if (err.code === 50035) { - // Invalid format within an embed for example - const message = `Failed to send article <${article.link}>.\`\`\`${err.message}\`\`\`` - if (this.restProducer) { - await this.restProducer.enqueue(`https://discord.com/api/channels/${feedObject.channel}/messages`, { - method: 'POST', - body: JSON.stringify({ - content: message - }) - }) - this.log.debug(`Sent article ${article._id} of feed ${feedObject._id} to service`) - } else { - const channel = this.getChannel(newArticle) - await channel.send(`Failed to send article <${article.link}>.\`\`\`${err.message}\`\`\``) - } - } - } - - /** - * @param {Object} newArticle - * @param {import('./ArticleMessage.js')} articleMessage - * @param {boolean} withoutBot - */ - async sendNewArticle (newArticle, articleMessage, withoutBot) { - const { article, feedObject } = newArticle - await ArticleRateLimiter.assertWithinLimits(articleMessage) - if (this.restProducer) { - if (withoutBot) { - await this.sendToServiceWithoutBot(newArticle, articleMessage) - } else { - await this.sendToService(newArticle, articleMessage) - } - this.log.debug(`Sent article ${article._id} of feed ${feedObject._id} to service`) - } else { - // The articleMessage is within all limits - const medium = await articleMessage.getMedium(this.bot) - if (!medium) { - /** - * Do not enqueue the article if the medium is not found. This can happen if the medium was deleted, - * or the article does not belong to this shard. - */ - this.log.debug(`No medium found for article ${article._id} of feed ${feedObject._id}. This article may be delegated to another shard.`) - return - } - const channelID = feedObject.channel - const queue = this.getQueueForChannel(channelID) - queue.enqueue(newArticle, articleMessage) - this.log.debug(`Enqueued article ${article._id} of feed ${feedObject._id} within client`) - } - } - - async recordFailure (newArticle, errorMessage) { - if (!Feed.isMongoDatabase) { - return - } - const { article, feedObject } = newArticle - const channel = feedObject.channel - const data = { - articleID: article._id, - feedURL: feedObject.url, - channel, - delivered: false, - comment: errorMessage - } - this.log.debug({ - data - }, 'Recording delivery record failure') - try { - const record = new DeliveryRecord.Model(data) - await record.save() - } catch (err) { - this.log.error(err, `Failed to record article ${article._id} delivery failure in channel ${channel} (error: ${errorMessage})`) - } - } - - async recordFilterBlock (newArticle) { - if (!Feed.isMongoDatabase) { - return - } - const { article, feedObject } = newArticle - const channel = feedObject.channel - const data = { - articleID: article._id, - feedURL: feedObject.url, - channel, - delivered: false, - comment: 'Blocked by filters' - } - this.log.debug({ - data - }, 'Recording delivery record filter block') - try { - const record = new DeliveryRecord.Model(data) - await record.save() - } catch (err) { - this.log.error(err, `Failed to record article ${article._id} delivery blocked by filters in channel ${channel}`) - } - } - - /** - * Returns the new article queue for a channel. - * If none exists, it creates a new one automatically - * - * @param {string} channelID - * @returns {import('./ArticleQueue')} - */ - getQueueForChannel (channelID) { - if (!this.queues.has(channelID)) { - const newQueue = new ArticleQueue(this.bot) - this.queues.set(channelID, newQueue) - return newQueue - } else { - return this.queues.get(channelID) - } - } -} - -module.exports = DeliveryPipeline diff --git a/services/bot/src/structs/FeedData.js b/services/bot/src/structs/FeedData.js deleted file mode 100644 index e44587022..000000000 --- a/services/bot/src/structs/FeedData.js +++ /dev/null @@ -1,138 +0,0 @@ -const Profile = require('./db/Profile.js') -const Feed = require('./db/Feed.js') - -class FeedData { - /** - * @param {Object} data - * @param {Feed} data.feed - * @param {import('./Profile.js')} data.profile - * @param {import('./Subscriber.js')[]} data.subscribers - * @param {import('./FilteredFormat.js')[]} data.filteredFormats - */ - constructor (data) { - this.feed = data.feed - if (!this.feed) { - throw new TypeError('Missing feed for FeedData') - } - this.profile = data.profile - this.subscribers = data.subscribers - this.filteredFormats = data.filteredFormats - } - - toObject () { - return { - ...this.feed.toObject(), - profile: this.profile ? this.profile.toObject() : undefined, - subscribers: this.subscribers.map(s => s.toObject()), - filteredFormats: this.filteredFormats.map(f => f.toObject()) - } - } - - toJSON () { - return { - ...this.feed.toJSON(), - profile: this.profile ? this.profile.toJSON() : undefined, - subscribers: this.subscribers.map(s => s.toJSON()), - filteredFormats: this.filteredFormats.map(f => f.toJSON()) - } - } - - /** - * Get associated info of a feed, including profile, subscribers - * and filtered formats - * @param {Feed} feed - */ - static async getFeedAssociations (feed) { - const [ - profile, - subscribers, - filteredFormats - ] = await Promise.all([ - Profile.get(feed.guild), - feed.getSubscribers(), - feed.getFilteredFormats() - ]) - return { - profile, - subscribers, - filteredFormats - } - } - - /** - * Get a particular feed's data - * @param {string} id - */ - static async get (id) { - /** @type {Feed} */ - const feed = await Feed.get(id) - if (!feed) { - return null - } - const { - profile, - subscribers, - filteredFormats - } = await FeedData.getFeedAssociations(feed) - return { - feed, - profile, - subscribers, - filteredFormats - } - } - - /** - * Get many feed datas - * @param {string} field - * @param {string} value - */ - static async getManyBy (field, value) { - const feeds = await Feed.getManyBy(field, value) - const associations = await Promise.all(feeds.map(this.getFeedAssociations)) - return feeds.map((feed, i) => new FeedData({ - feed, - profile: associations[i].profile, - subscribers: associations[i].subscribers, - filteredFormats: associations[i].filteredFormats - })) - } - - static async getManyByQuery (query) { - const feeds = await Feed.getManyByQuery(query) - const associations = await Promise.all(feeds.map(this.getFeedAssociations)) - return feeds.map((feed, i) => new FeedData({ - feed, - profile: associations[i].profile, - subscribers: associations[i].subscribers, - filteredFormats: associations[i].filteredFormats - })) - } - - /** - * Get all feed datas - */ - static async getAll () { - const feeds = await Feed.getAll() - const associations = await Promise.all(feeds.map(this.getFeedAssociations)) - return feeds.map((feed, i) => new FeedData({ - feed, - profile: associations[i].profile, - subscribers: associations[i].subscribers, - filteredFormats: associations[i].filteredFormats - })) - } - - /** - * @param {import('./db/Feed.js')} feed - */ - static async ofFeed (feed) { - const associations = await this.getFeedAssociations(feed) - return new FeedData({ - feed, - ...associations - }) - } -} - -module.exports = FeedData diff --git a/services/bot/src/structs/Filter.js b/services/bot/src/structs/Filter.js deleted file mode 100644 index 68ed53dda..000000000 --- a/services/bot/src/structs/Filter.js +++ /dev/null @@ -1,82 +0,0 @@ -class Filter { - /** - * @param {string} content - */ - constructor (content) { - /** - * The original filter input - * @type {string} - */ - this.content = content.toLowerCase() - - /** - * Filter content without modifiers - * @type {string} - */ - this.searchTerm = this.parseWord(this.content) - } - - /** - * @param {string} string - */ - static escapeRegex (string) { - return string.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&') - } - - /** - * Remove modifiers from the filter content - * @returns {string} - */ - parseWord () { - const { content } = this - if (content.startsWith('\\~') || content.startsWith('\\!')) { - return content.slice(1, content.length) - } - return content.replace(/^(~!|!~|!|~)/, '') - } - - /** - * Whether the filter is modified to be broad - * @returns {boolean} - */ - get broad () { - const { content } = this - return content.startsWith('~') || content.startsWith('!~') || content.startsWith('~!') - } - - /** - * Whether the filter is modified to be inverted - * @returns {boolean} - */ - get inverted () { - const { content } = this - return content.startsWith('!') || content.startsWith('!~') || content.startsWith('~!') - } - - /** - * Whether the parsed filter content is contained in a string - * @param {string} string - */ - foundIn (string) { - if (this.broad) { - return string.toLowerCase().includes(this.searchTerm) - } else { - const regex = new RegExp(`(\\s|^)${Filter.escapeRegex(this.searchTerm)}(\\s|$)`, 'i') - return string.search(regex) !== -1 - } - } - - /** - * @param {string} string - */ - passes (string) { - const found = this.foundIn(string) - if (this.inverted) { - return !found - } else { - return found - } - } -} - -module.exports = Filter diff --git a/services/bot/src/structs/FilterRegex.js b/services/bot/src/structs/FilterRegex.js deleted file mode 100644 index 4c587e80b..000000000 --- a/services/bot/src/structs/FilterRegex.js +++ /dev/null @@ -1,24 +0,0 @@ -class FilterRegex { - /** - * @param {string} content - * @param {boolean} regex - */ - constructor (content) { - /** - * The original filter input - * @type {string} - */ - this.content = content - } - - /** - * Whether the filter content is contained in a string - * @param {string} string - */ - passes (string) { - const regex = new RegExp(this.content, 'i') - return string.search(regex) !== -1 - } -} - -module.exports = FilterRegex diff --git a/services/bot/src/structs/FilterResults.js b/services/bot/src/structs/FilterResults.js deleted file mode 100644 index 1afd91626..000000000 --- a/services/bot/src/structs/FilterResults.js +++ /dev/null @@ -1,29 +0,0 @@ -class FilterResults { - constructor () { - this.matches = {} - this.invertedMatches = {} - this.passed = true - } - - add (type, matches, inverted) { - if (inverted) this.invertedMatches[type] = matches - else this.matches[type] = matches - } - - listMatches (inverted) { - const matchList = inverted ? this.invertedMatches : this.matches - let str = '' - for (var type in matchList) { - let list = '' - const typeMatches = matchList[type] - for (var x in typeMatches) { - list += ` ${typeMatches[x]}` - if (parseInt(x, 10) !== typeMatches.length - 1) list += ' |' - } - str += `\n${type}:${list}` - } - return str - } -} - -module.exports = FilterResults diff --git a/services/bot/src/structs/FlattenedJSON.js b/services/bot/src/structs/FlattenedJSON.js deleted file mode 100644 index 18dd5f850..000000000 --- a/services/bot/src/structs/FlattenedJSON.js +++ /dev/null @@ -1,155 +0,0 @@ -const htmlConvert = require('html-to-text') -const getConfig = require('../config.js').get -const EXCLUDED_KEYS = ['title', 'description', 'summary', 'author', 'pubDate', 'pubdate', 'date'] - -function cleanup (feed, text) { - if (!text) return '' - - const config = getConfig() - let newText = text - newText = newText.replace(/\*/gi, '') - .replace(/<(strong|b)>(.*?)<\/(strong|b)>/gi, '**$2**') // Bolded markdown - .replace(/<(em|i)>(.*?)<(\/(em|i))>/gi, '*$2*') // Italicized markdown - .replace(/<(u)>(.*?)<(\/(u))>/gi, '__$2__') // Underlined markdown - - newText = htmlConvert.fromString(newText, { - tables: (feed.formatTables !== undefined && typeof feed.formatTables === 'boolean' ? feed.formatTables : config.feeds.formatTables) === true ? true : [], - wordwrap: null, - ignoreHref: true, - noLinkBrackets: true, - format: { - image: node => { - const isStr = typeof node.attribs.src === 'string' - let link = isStr ? node.attribs.src.trim() : node.attribs.src - if (isStr && link.startsWith('//')) link = 'http:' + link - else if (isStr && !link.startsWith('http://') && !link.startsWith('https://')) link = 'http://' + link - - let exist = true - const globalExistOption = config.feeds.imgLinksExistence - exist = globalExistOption - const specificExistOption = feed.imgLinksExistence - exist = typeof specificExistOption !== 'boolean' ? exist : specificExistOption - if (!exist) return '' - - let image = '' - const globalPreviewOption = config.feeds.imgPreviews - image = globalPreviewOption ? link : `<${link}>` - const specificPreviewOption = feed.imgPreviews - image = typeof specificPreviewOption !== 'boolean' ? image : specificPreviewOption === true ? link : `<${link}>` - - return image - }, - blockquote: (node, fn, options) => { - const orig = fn(node.children, options).trim() - return '> ' + orig.replace(/(?:\n)/g, '\n> ') + '\n' - } - } - }) - - newText = newText.replace(/\n\s*\n\s*\n/g, '\n\n') // Replace triple line breaks with double - const arr = newText.split('\n') - for (var q = 0; q < arr.length; ++q) arr[q] = arr[q].replace(/\s+$/, '') // Remove trailing spaces - return arr.join('\n') -} - -class FlattenedJSON { - constructor (data, feed) { - this.feed = feed - this.data = data - this.results = {} - this.text = '' - // Populate this.results with a trampoline to avoid stack call exceeded - this._trampolineIteration(this._iterateOverObject.bind(this), data) - // Generate the text from this.results - this._generateText() - } - - static isObject (value) { - return Object.prototype.toString.call(value) === '[object Object]' - } - - static isDateObject (value) { - return Object.prototype.toString.call(value) === '[object Date]' - } - - _trampolineIteration (fun, obj, previousKeyNames) { - for (var key in obj) { - let val = fun.bind(this)(obj[key], key, previousKeyNames) - while (typeof val === 'function') { - val = val() - } - } - } - - _iterateOverObject (item, keyName, previousKeyNames) { - const keyNameWithPrevious = (previousKeyNames ? `${previousKeyNames}_${keyName}` : keyName).replace(':', '-') // Replace colons to avoid emoji conflicts - if (!item || EXCLUDED_KEYS.includes(keyName) || item === this.results[keyNameWithPrevious.toLowerCase()]) { - return - } - if (Array.isArray(item)) { - for (let i = 0; i < item.length; ++i) { - const entry = item[i] - const thisKeyNameWithPrevious = `${keyNameWithPrevious}[${i}]` - if (FlattenedJSON.isObject(entry)) { - this._trampolineIteration(this._iterateOverObject, entry, thisKeyNameWithPrevious) - } else { - this.results[thisKeyNameWithPrevious] = entry - } - } - } else if (FlattenedJSON.isObject(item)) { - this._trampolineIteration(this._iterateOverObject, item, keyNameWithPrevious) - } else { - this.results[keyNameWithPrevious] = cleanup(this.feed, item) - } - } - - _generateText () { - let nameHeader = 'PROPERTY NAME' - let valueHeader = 'VALUE' - let longestNameLen = 0 - let longestValLen = 0 - for (const key in this.results) { - const val = this.data[key] - if (key.length > longestNameLen) longestNameLen = key.length - if (val && val.length > longestValLen) longestValLen = val.length - } - - if (nameHeader.length > longestNameLen) longestNameLen = nameHeader - if (valueHeader.length > longestValLen) longestValLen = valueHeader - longestNameLen += 10 - - while (nameHeader.length < longestNameLen) nameHeader += ' ' - while (valueHeader.length < longestValLen) valueHeader += ' ' - - nameHeader += '| ' - const header = nameHeader + valueHeader - let bar = '' - while (bar.length < header.length) bar += '-' - this.text = header + '\r\n' + bar + '\r\n' - - // Add in the key/values - for (const key in this.results) { - let curStr = key - while (curStr.length < longestNameLen) curStr += ' ' - const propNameLength = curStr.length - const valueLines = FlattenedJSON.isDateObject(this.results[key]) - ? [this.results[key].toString() + ' [DATE OBJECT]'] - : cleanup(this.feed, this.results[key].toString()).split('\n') - for (let u = 0; u < valueLines.length; ++u) { - curStr += u === 0 ? `| ${valueLines[u]}\r\n` : ` ${valueLines[u]}\r\n` - if (u < valueLines.length - 1) { - let emptyPropName = '' - while (emptyPropName.length < propNameLength) emptyPropName += ' ' - curStr += emptyPropName - } - } - this.text += curStr - } - } - - getValue (str) { - return this.results[str] || '' - } -} - -module.exports = FlattenedJSON diff --git a/services/bot/src/structs/Guild.js b/services/bot/src/structs/Guild.js deleted file mode 100644 index 8df5ba3a9..000000000 --- a/services/bot/src/structs/Guild.js +++ /dev/null @@ -1,92 +0,0 @@ -const configuration = require('../config') -const GuildSubscription = require('./GuildSubscription.js') -const Supporter = require('./db/Supporter.js') - -class Guild { - constructor (guildId) { - this.id = guildId - } - - /** - * @returns {Promise>} - */ - static async getAllUniqueFeedLimits () { - if (!Supporter.enabled) { - return new Map() - } - const supporterLimits = new Map() - const supporters = await Supporter.getValidSupporters() - for (const supporter of supporters) { - const maxFeeds = await supporter.getMaxFeeds() - const guilds = supporter.guilds - for (const guildId of guilds) { - supporterLimits.set(guildId, maxFeeds) - } - } - const allSubs = await GuildSubscription.getAllSubscriptions() - allSubs.forEach(({ guildId, maxFeeds }) => { - supporterLimits.set(guildId, maxFeeds) - }) - return supporterLimits - } - - async getSupporter () { - if (!Supporter.enabled) { - return null - } - return Supporter.getValidSupporterOfGuild(this.id) - } - - async getSubscription () { - if (!Supporter.enabled) { - return null - } - return GuildSubscription.getSubscription(this.id) - } - - async getMaxFeeds () { - const config = configuration.get() - const data = await this.getSubscription(this.id) - let maxFeeds = config.feeds.max - if (!Supporter.enabled) { - return maxFeeds - } - maxFeeds = data ? Math.max(maxFeeds, data.maxFeeds) : maxFeeds - // Check the supporter for backwards compatibility - const supporter = await this.getSupporter(this.id) - if (!supporter) { - return maxFeeds - } - const supporterMaxFeeds = await supporter.getMaxFeeds() - return Math.max(maxFeeds, supporterMaxFeeds) - } - - async hasSupporter () { - if (!Supporter.enabled) { - return false - } - return Supporter.hasValidGuild(this.id) - } - - async isSubscriber () { - if (!Supporter.enabled) { - return false - } - return !!(await this.getSubscription()) - } - - async hasSupporterOrSubscriber () { - return (await this.hasSupporter()) || (await this.isSubscriber()) - } - - static async getFastSupporterAndSubscriberGuildIds () { - const supporterGuildIds = await Supporter.getValidFastGuilds() - const subscriptionGuilds = await GuildSubscription.getAllSubscriptions() - const subscriptionGuildIds = subscriptionGuilds - .filter((guild) => !guild.slowRate) - .map((s) => s.guildId) - return new Set([...supporterGuildIds, ...subscriptionGuildIds]) - } -} - -module.exports = Guild diff --git a/services/bot/src/structs/GuildData.js b/services/bot/src/structs/GuildData.js deleted file mode 100644 index 50bdec22d..000000000 --- a/services/bot/src/structs/GuildData.js +++ /dev/null @@ -1,175 +0,0 @@ -const Profile = require('./db/Profile.js') -const Feed = require('./db/Feed.js') -const FilteredFormat = require('./db/FilteredFormat.js') -const Subscriber = require('./db/Subscriber.js') -const createLogger = require('../util/logger/create.js') - -class GuildData { - /** - * @param {Object} data - * @param {Object} data.profile - * @param {Object[]} data.feeds - * @param {Object[]} data.filteredFormats - * @param {Object[]} data.subscribers - */ - constructor (data) { - this.data = data - const { profile, feeds, filteredFormats, subscribers } = data - if (profile && !profile._id) { - throw new Error('Profile missing _id') - } - const feedIDs = new Set() - const guildIDs = new Set() - for (const feed of feeds) { - guildIDs.add(feed.guild) - if (guildIDs.size > 1) { - throw new Error('Mismatched guild IDs found for feeds') - } - feedIDs.add(feed._id) - if (profile && feed.guild !== profile._id) { - throw new Error(`Feed ${feed._id} does not match profile`) - } - } - for (const format of filteredFormats) { - if (!feedIDs.has(format.feed)) { - throw new Error(`FilteredFormat ${format._id} does not match any given feeds`) - } - } - for (const subscriber of subscribers) { - if (!feedIDs.has(subscriber.feed)) { - throw new Error(`Subscriber ${subscriber._id} does not match any given feeds`) - } - } - /** - * The guild's ID. - * @type {string} - */ - this.id = Array.from(guildIDs.keys())[0] - - this.profile = profile - this.feeds = feeds - this.filteredFormats = filteredFormats - this.subscribers = subscribers - } - - /** - * Gets all of a guild's data and returns a GuildData - * @param {guildId} guildId - * @returns {GuildData} - */ - static async get (guildId) { - const [profile, feeds] = await Promise.all([ - Profile.get(guildId), - Feed.getManyBy('guild', guildId) - ]) - const filteredFormats = await Promise.all(feeds.map(feed => feed.getFilteredFormats())) - const feedSubscribers = await Promise.all(feeds.map(feed => feed.getSubscribers())) - // const [ filteredFormats, feedSubscribers ] = await Promise.all([ - // Promise.all(feeds.map(feed => feed.getFilteredFormats())), - // Promise.all(feeds.map(feed => feed.getSubscribers())) - // ]) - const allSubscribers = [] - feedSubscribers.forEach(subscribers => { - subscribers.forEach(s => allSubscribers.push(s)) - }) - const allFilteredFormats = [] - filteredFormats.forEach(formats => { - formats.forEach(f => allFilteredFormats.push(f)) - }) - - const data = { - profile: profile ? profile.toJSON() : null, - feeds: feeds.map(feed => feed.toJSON()), - filteredFormats: allFilteredFormats.map(f => f.toJSON()), - subscribers: allSubscribers.map(s => s.toJSON()) - } - return new GuildData(data) - } - - toJSON () { - return this.data - } - - /** - * If the GuildData has no profile and no feeds. If there are - * no feeds, then there are also no formats or subscribers. - * @returns {boolean} - */ - isEmpty () { - const { profile, feeds } = this - const noProfile = !profile - const noFeeds = feeds.length === 0 - return noProfile && noFeeds - } - - /** - * Deletes all associated data of this guild. Used to - * delete any conflicting data before restoring - */ - async delete () { - const deletions = [] - if (this.profile) { - const profile = await Profile.get(this.profile._id) - if (profile) { - deletions.push(profile.delete()) - } - } - const models = [Feed, Subscriber, FilteredFormat] - const toLoopOver = [this.feeds, this.subscribers, this.filteredFormats] - for (let i = 0; i < toLoopOver.length; ++i) { - const Model = models[i] - const list = toLoopOver[i] - for (const item of list) { - const _id = item._id - if (!_id) { - continue - } - const found = await Model.get(_id) - if (found) { - deletions.push(found.delete()) - } - } - } - await Promise.all(deletions) - } - - /** - * Restore this back to the database - */ - async restore () { - await this.delete() - const feeds = [] - const filteredFormats = [] - const subscribers = [] - this.feeds.forEach(feed => { - feeds.push(new Feed(feed)) - }) - this.filteredFormats.forEach(format => { - filteredFormats.push(new FilteredFormat(format)) - }) - this.subscribers.forEach(subscriber => { - subscribers.push(new Subscriber(subscriber)) - }) - let data = this.profile ? [new Profile(this.profile)] : [] - data = data.concat(feeds) - .concat(filteredFormats) - .concat(subscribers) - try { - if (this.profile) { - await data[0].save() - } - await Promise.all(feeds.map(f => f.save())) - await Promise.all(filteredFormats.map(f => f.save())) - await Promise.all(subscribers.map(s => s.save())) - } catch (err) { - Promise.all(data.map(d => d.delete())) - .catch(err => { - const log = createLogger() - log.error(err, 'Failed to rollback saves after GuildData restore') - }) - throw err - } - } -} - -module.exports = GuildData diff --git a/services/bot/src/structs/GuildSubscription.js b/services/bot/src/structs/GuildSubscription.js deleted file mode 100644 index a6dce2aff..000000000 --- a/services/bot/src/structs/GuildSubscription.js +++ /dev/null @@ -1,100 +0,0 @@ -const configuration = require('../config') -const fetch = require('node-fetch') - -class GuildSubscription { - constructor ({ - guildId, - maxFeeds, - refreshRate, - expireAt, - slowRate - }) { - this.guildId = guildId - this.maxFeeds = maxFeeds - this.refreshRate = refreshRate - this.expireAt = expireAt - this.slowRate = slowRate - } - - static getApiConfig () { - return configuration.get().apis.pledge - } - - static mapApiResponse (response) { - const config = configuration.get() - const refreshRateMinutes = response.refresh_rate / 60 - const ignoreFasterRefreshRate = response.ignore_refresh_rate_benefit - const slowRate = ignoreFasterRefreshRate || refreshRateMinutes >= config.feeds.refreshRateMinutes - return { - guildId: response.guild_id, - maxFeeds: configuration.get().feeds.max + response.extra_feeds, - refreshRate: refreshRateMinutes, - expireAt: response.expire_at, - slowRate - } - } - - static async getSubscription (guildId) { - const { url, accessToken, enabled } = this.getApiConfig() - if (!enabled) { - // The service is disabled/not configured - return null - } - try { - const res = await fetch(`${url}/guilds/${guildId}`, { - headers: { - Authorization: accessToken - } - }) - if (res.status === 200) { - const json = await res.json() - return new GuildSubscription(this.mapApiResponse(json)) - } - if (res.status === 404) { - return null - } - throw new Error(`Bad status code ${res.status}`) - } catch (err) { - console.error(err) - /** - * Errors should not be propagated to maintain normal functions. - */ - return null - } - } - - /** - * @returns {Promise} - */ - static async getAllSubscriptions () { - const { url, accessToken, enabled } = this.getApiConfig() - if (!enabled) { - // The service is disabled/not configured - return [] - } - try { - const res = await fetch(`${url}/guilds`, { - headers: { - Authorization: accessToken - } - }) - if (res.status === 200) { - const data = await res.json() - return data.map((sub) => new GuildSubscription(this.mapApiResponse(sub))) - } - throw new Error(`Bad status code ${res.status}`) - } catch (err) { - console.error(err) - /** - * Errors should not be propagated to maintain normal functions. - */ - return [] - } - } - - hasSlowRate () { - return this.slowRate - } -} - -module.exports = GuildSubscription diff --git a/services/bot/src/structs/LinkLogic.js b/services/bot/src/structs/LinkLogic.js deleted file mode 100644 index e190a4602..000000000 --- a/services/bot/src/structs/LinkLogic.js +++ /dev/null @@ -1,323 +0,0 @@ -const { EventEmitter } = require('events') -const NewArticle = require('./NewArticle.js') -const createLogger = require('../util/logger/create.js') - -/** - * @typedef {Object} FeedArticle - */ - -/** - * @typedef {Object} SourceSettings - * @property {boolean} checkTitles - * @property {boolean} checkDates - */ - -/** - * @typedef {Object} FormattedArticle - * @property {Object} _feed - */ - -/** - * @typedef {Object} LinkData - * @property {Object} rssList - Aggregated feed objects by feed ID from all guilds who use the same feed URL - * @property {FeedArticle[]} articleList - Feed articles - * @property {string[]} debugFeeds - Array of feed IDs to show debug info for - * @property {string} link - The feed URL - * @property {Object} config - config.js values - */ - -class LinkLogic extends EventEmitter { - /** - * @param {LinkData} data - */ - constructor (data) { - super() - const { rssList, articleList, debugFeeds, link, config } = data - this.rssList = rssList - this.articleList = articleList - this.link = link - this.config = config - this.log = createLogger('LL') - - /** - * @type {Set} - */ - this.debug = new Set(debugFeeds || []) - - /** - * dbReferences structure for each feed ID - * @type {Map>>} - */ - this.sentReferences = new Map() - } - - _logDebug (feedID, message, object) { - let logObject = {} - if (object) { - logObject = { - ...logObject, - ...object - } - } else { - logObject.feedID = feedID - } - if (this.debug.has(feedID)) { - this.log.info(logObject, message) - } - } - - /** - * @param {Object} article - * @param {string} accessor - */ - static getArticleProperty (article, accessor) { - const layers = accessor.split('_') - let valueSoFar = article[layers[0]] - for (let i = 1; i < layers.length; ++i) { - const property = layers[i] - const newValueSoFar = valueSoFar[property] - if (!newValueSoFar) { - return - } - valueSoFar = newValueSoFar - } - return valueSoFar - } - - /** - * @param {Object[]} docs - */ - static getComparisonReferences (docs) { - /** @type {Map>} */ - const dbReferences = new Map() - for (const doc of docs) { - // Deal with article-specific properties - const properties = doc.properties - for (const property in properties) { - /** @type {string[]} */ - const propertyValue = properties[property] - - // Create or add to the property sets - if (!dbReferences.has(property)) { - dbReferences.set(property, new Set([propertyValue])) - } else { - dbReferences.get(property).add(propertyValue) - } - } - } - return dbReferences - } - - /** - * Negative comparisons block articles from passing if ID was not seen - * @param {Object[]} articleList - * @param {string[]} comparisons - * @param {Map>} dbReferences - * @param {Map>} sentReferencesOfFeed - Specifically for a particular feed ID - */ - static negativeComparisonBlocks (article, comparisons, dbReferences, sentReferencesOfFeed) { - if (comparisons.length === 0) { - return false - } - for (const property of comparisons) { - const value = this.getArticleProperty(article, property) - if (!value || typeof value !== 'string') { - continue - } - const propertyValues = dbReferences.get(property) - if (propertyValues && propertyValues.has(value)) { - return true - } - const tempPropertyValues = sentReferencesOfFeed ? sentReferencesOfFeed.get(property) : null - if (tempPropertyValues && tempPropertyValues.has(value)) { - return true - } - } - // At this point, the property is not stored - return false - } - - /** - * Positive comparisons sends articles through if ID was seen - * @param {Object[]} articleList - * @param {string[]} comparisons - * @param {Map>} dbReferences - * @param {Map>} sentReferencesOfFeed - */ - static positiveComparisonPasses (article, comparisons, dbReferences, sentReferencesOfFeed) { - if (comparisons.length === 0) { - return false - } - for (const property of comparisons) { - const value = this.getArticleProperty(article, property) - if (!value || typeof value !== 'string') { - continue - } - const propertyValues = dbReferences.get(property) - if (!propertyValues) { - /** - * Property is uninitialized in database. Without - * this check, all articles would send when a - * pcomparison is added. - */ - continue - } - if (propertyValues && propertyValues.has(value)) { - continue - } - const tempPropertyValues = sentReferencesOfFeed ? sentReferencesOfFeed.get(property) : null - if (tempPropertyValues && tempPropertyValues.has(value)) { - continue - } - // At this point, the property is not stored - return true - } - return false - } - - /** - * @param {Set} dbIDs - * @param {Object} article - * @param {boolean} checkDates - * @param {Map>} comparisonReferences - * @param {Object} feed - */ - isNewArticle (dbIDs, article, feed, checkDates, comparisonReferences) { - const { sentReferences, config } = this - const cutoffDay = new Date() - const useMaxAge = feed.articleMaxAge != null ? feed.articleMaxAge : config.feeds.cycleMaxAge - cutoffDay.setDate(cutoffDay.getDate() - useMaxAge) - - const feedID = feed._id - const sentReferencesOfFeed = sentReferences.get(feed._id) - const articleID = article._id - const { ncomparisons, pcomparisons } = feed - if (!articleID) { - this._logDebug(feedID, 'No article ID found for article. Blocked.', { - article - }) - return false - } - if (!dbIDs.has(articleID)) { - // Normally passes since ID is unseen, unless negative comparisons blocks - const blocked = LinkLogic.negativeComparisonBlocks(article, ncomparisons, comparisonReferences, sentReferencesOfFeed) - if (blocked) { - this._logDebug(feedID, `Article ID ${articleID} not found in DB, but blocked by N-Comparisons. Blocked.`, { - ncomparisons, - sentReferencesOfFeed - }) - return false - } - } else { - // Normally blocked since the ID is seen, unless positive comparisons passes - const passed = LinkLogic.positiveComparisonPasses(article, pcomparisons, comparisonReferences, sentReferencesOfFeed) - if (!passed) { - this._logDebug(feedID, `Article ID ${articleID} found in DB, but P-Comparisons made no difference. Blocked.`, { - pcomparisons, - sentReferencesOfFeed - }) - return false - } - } - // At this point, the article should send. - if (checkDates) { - const block = !article.pubdate || article.pubdate.toString() === 'Invalid Date' || article.pubdate < cutoffDay - if (block) { - this._logDebug(feedID, `Article ID ${articleID} not found in DB, but check comparisons blocked article. Blocked.`, { - noPubdate: !article.pubdate, - invalidDateString: !article.pubdate ? false : article.pubdate.toString() === 'Invalid Date', - beforeCutoffDate: !article.pubdate ? false : article.pubdate < cutoffDay - }) - return false - } - } - - this._logDebug(feedID, `Article ID ${articleID} not found in DB, marking new. Pass.`) - // Store the property value into buffers - this.storePropertiesToBuffer(feed, article) - return true - } - - /** - * @param {Object} feed - * @param {Object} article - */ - storePropertiesToBuffer (feed, article) { - const { sentReferences } = this - const feedID = feed._id - const properties = [...feed.ncomparisons, ...feed.pcomparisons] - for (const property of properties) { - const value = LinkLogic.getArticleProperty(article, property) - if (!value || typeof value !== 'string') { - continue - } - if (!sentReferences.has(feedID)) { - sentReferences.set(feedID, new Map([[property, new Set([value])]])) - } else if (!sentReferences.get(feedID).has(property)) { - sentReferences.get(feedID).set(property, new Set([value])) - } else { - sentReferences.get(feedID).get(property).add(value) - } - } - } - - static shouldCheckDates (config, feed) { - const globalDateCheck = config.feeds.checkDates - const localDateCheck = feed.checkDates - const checkDates = typeof localDateCheck !== 'boolean' ? globalDateCheck : localDateCheck - return checkDates - } - - /** - * @param {Set} dbIDs - * @param {Object} feed - * @param {Object[]} articleList - * @param {Map>} comparisonReferences - */ - getNewArticlesOfFeed (dbIDs, feed, articleList, comparisonReferences) { - const { config } = this - const feedID = feed._id - const totalArticles = articleList.length - const checkDates = LinkLogic.shouldCheckDates(config, feed) - - const mapObject = {} - comparisonReferences.forEach((val, key) => { mapObject[key] = val }) - this._logDebug(feedID, `Processing collection. Total article list length: ${totalArticles}`, { - comparisonReferences: mapObject, - dbIDs: Array.from(dbIDs) - }) - - const newArticles = [] - // Loop from oldest to newest so the queue that sends articleMessages work properly, sending the older ones first - for (let a = totalArticles - 1; a >= 0; --a) { - const article = articleList[a] - const isNew = this.isNewArticle(dbIDs, article, feed, checkDates, comparisonReferences) - if (isNew) { - newArticles.push(new NewArticle(article, feed)) - } - } - return newArticles - } - - async run (docs) { - const { link, rssList, articleList } = this - const newArticles = [] - const dbIDs = new Set(docs.map(doc => doc.id)) - const comparisonReferences = await LinkLogic.getComparisonReferences(docs) - for (const feedID in rssList) { - const feed = rssList[feedID] - // Database collection is uninitialized if no db IDs. Don't send any articles, just store. - if (dbIDs.size === 0) { - continue - } - const articlesToSend = this.getNewArticlesOfFeed(dbIDs, feed, articleList, comparisonReferences) - articlesToSend.forEach(a => newArticles.push(a)) - } - return { - link, - newArticles - } - } -} - -module.exports = LinkLogic diff --git a/services/bot/src/structs/NewArticle.js b/services/bot/src/structs/NewArticle.js deleted file mode 100644 index 25de52938..000000000 --- a/services/bot/src/structs/NewArticle.js +++ /dev/null @@ -1,19 +0,0 @@ -class NewArticle { - /** - * @param {Object} article - * @param {Object|import('./db/Feed.js')} feedObject - */ - constructor (article, feedObject) { - this.article = article - this.feedObject = feedObject - } - - toJSON () { - return { - article: this.article, - feedObject: this.feedObject - } - } -} - -module.exports = NewArticle diff --git a/services/bot/src/structs/Processor.js b/services/bot/src/structs/Processor.js deleted file mode 100644 index 3dabad430..000000000 --- a/services/bot/src/structs/Processor.js +++ /dev/null @@ -1,22 +0,0 @@ -const path = require('path') -const fork = require('child_process').fork - -class Processor { - constructor () { - this.process = fork(path.join(__dirname, '..', 'util', 'processor.js')) - } - - on (...args) { - return this.process.on(...args) - } - - send (...args) { - return this.process.send(...args) - } - - kill () { - this.process.kill() - } -} - -module.exports = Processor diff --git a/services/bot/src/structs/ProcessorPool.js b/services/bot/src/structs/ProcessorPool.js deleted file mode 100644 index 1484456d0..000000000 --- a/services/bot/src/structs/ProcessorPool.js +++ /dev/null @@ -1,44 +0,0 @@ -const Processor = require('./Processor.js') -const createLogger = require('../util/logger/create.js') - -class ProcessorPool { - constructor (logMarker) { - /** - * @type {import('./Processor.js')[]} - */ - this.pool = [] - this.log = createLogger(logMarker) - } - - create () { - const processor = new Processor() - this.log.debug(`Created new processor, pid ${processor.process.pid}`) - this.pool.push(processor) - return processor - } - - get () { - const created = this.create() - return created - } - - /** - * @param {import('./Processor.js')} processor - */ - kill (processor) { - processor.kill() - const index = this.pool.indexOf(processor) - this.pool.splice(index, 1) - this.log.debug(`Killed processor at index ${index}, pid ${processor.process.pid}`) - } - - killAll () { - for (let i = this.pool.length - 1; i >= 0; --i) { - const processor = this.pool[i] - this.kill(processor) - } - this.log.debug('Killed all processors') - } -} - -module.exports = ProcessorPool diff --git a/services/bot/src/structs/RateLimitHitCounter.js b/services/bot/src/structs/RateLimitHitCounter.js deleted file mode 100644 index 1ba03795e..000000000 --- a/services/bot/src/structs/RateLimitHitCounter.js +++ /dev/null @@ -1,28 +0,0 @@ -const EventEmitter = require('events').EventEmitter - -/** - * Emits a limitReached event when more than 100 hit - * calls are called within a minute - */ -class RateLimitCounter extends EventEmitter { - constructor () { - super() - this.hits = 0 - setInterval(() => { - this.clear() - }, 1000 * 60) // Reset every minute - } - - hit () { - this.hits++ - if (this.hits > 100) { - this.emit('limitReached') - } - } - - clear () { - this.hits = 0 - } -} - -module.exports = RateLimitCounter diff --git a/services/bot/src/structs/ScheduleManager.js b/services/bot/src/structs/ScheduleManager.js deleted file mode 100644 index 428fba703..000000000 --- a/services/bot/src/structs/ScheduleManager.js +++ /dev/null @@ -1,332 +0,0 @@ -const FailRecord = require('../structs/db/FailRecord.js') -const ScheduleRun = require('./ScheduleRun.js') -const createLogger = require('../util/logger/create.js') -const EventEmitter = require('events').EventEmitter -const getConfig = require('../config.js').get -const devLevels = require('../util/devLevels.js') -const dumpHeap = require('../util/dumpHeap.js') -const DebugFeed = require('../structs/db/DebugFeed.js') - -/** - * @typedef {string} FeedURL - */ - -/** - * @typedef {Object[]>} MemoryCollection - */ - -class ScheduleManager extends EventEmitter { - constructor () { - super() - this.log = createLogger('M') - this.timers = [] - /** - * @type {Set} - */ - this.debugFeedIDs = new Set() - /** - * @type {import('./db/Schedule.js')[]} - * */ - this.schedules = [] - /** - * @type {import('./ScheduleRun.js')[]} - * */ - this.scheduleRuns = [] - /** - * @type {Map} - */ - this.scheduleRunCounts = new Map() - this.urlFailuresRecording = new Set() - this.urlSuccessesRecording = new Set() - this.sendingEnabledNotifications = new Set() - this.sendingDisabledNotifications = new Set() - } - - async _onNewArticle (newArticle) { - const { article, feedObject } = newArticle - if (devLevels.disableOutgoingMessages()) { - return - } - if (this.debugFeedIDs.has(feedObject._id)) { - this.log.info(`${feedObject._id} ScheduleManager queueing article ${article.link} to send`) - } - this.emit('newArticle', newArticle) - } - - /** - * Handle fail records in ScheduleManager since multiple - * runs could be trying to record the same failure at the - * same time, causing race conditions - * - * @param {string} url - * @param {string} [reason] - */ - async _onConnectionFailure (url, reason) { - if (this.urlFailuresRecording.has(url) || this.testRuns || devLevels.disableCycleDatabase()) { - return - } - this.urlFailuresRecording.add(url) - try { - await FailRecord.record(url, reason) - } catch (err) { - this.log.error(err, `Failed to record url fail record ${url} with reason ${reason}`) - } - this.urlFailuresRecording.delete(url) - this.emit('connectionFailure', url, reason) - } - - /** - * @param {string} url - */ - async _onConnectionSuccess (url) { - if (this.urlSuccessesRecording.has(url) || this.testRuns) { - return - } - this.urlSuccessesRecording.add(url) - try { - await FailRecord.reset(url) - } catch (err) { - this.log.error(err, `Failed to reset url fail record ${url}`) - } - this.urlSuccessesRecording.delete(url) - } - - /** - * @param {import('./db/Feed.js')} feed - */ - async _onFeedDisabled (feed) { - if (this.sendingDisabledNotifications.has(feed._id) || devLevels.disableOutgoingMessages()) { - return - } - this.sendingDisabledNotifications.add(feed._id) - const message = `Feed <${feed.url}> has been disabled in <#${feed.channel}> to due limit changes.` - this.log.info(`Sending disabled notification for feed ${feed._id} in channel ${feed.channel}`) - this.emitAlert(feed.channel, message) - this.sendingDisabledNotifications.delete(feed._id) - } - - /** - * @param {import('./db/Feed.js')} feed - */ - async _onFeedEnabled (feed) { - if (this.sendingEnabledNotifications.has(feed._id) || devLevels.disableOutgoingMessages()) { - return - } - this.sendingEnabledNotifications.add(feed._id) - const message = `Feed <${feed.url}> has been enabled in <#${feed.channel}> to due limit changes.` - this.log.info(`Sending enabled notification for feed ${feed._id} in channel ${feed.channel}`) - this.emitAlert(feed.channel, message) - this.sendingEnabledNotifications.delete(feed._id) - } - - /** - * @param {import('./db/FailRecord.js')} record - * @param {import('./db/Feed.js')[]} associatedFeeds - */ - async alertFailRecord (record, associatedFeeds) { - if (devLevels.disableOutgoingMessages()) { - return - } - const config = getConfig() - const url = record._id - record.alerted = true - await record.save() - const numberOfChannels = new Set(associatedFeeds.map((feed) => feed.channel)) - this.log.info(`Sending fail notification for ${url} to ${numberOfChannels.size} channels`) - associatedFeeds.forEach(({ channel }) => { - const message = `Feed <${url}> in channel <#${channel}> has reached the connection failure limit after continuous (${config.feeds.hoursUntilFail} hours) connection failures (recorded reason: ${record.reason}). The feed will not be retried until it is manually refreshed by any server using this feed. Use the \`list\` command in your server for more information.` - this.emitAlert(channel, message) - }) - } - - /** - * @param {string} channelID - * @param {string} message - */ - emitAlert (channelID, message) { - this.emit('alert', channelID, message) - } - - /** - * Add a schedule and initialize relevant data for it - * - * @param {import('./db/Schedule.js')} schedule - */ - addSchedule (schedule) { - this.schedules.push(schedule) - this.scheduleRunCounts.set(schedule, 0) - } - - /** - * Add multiple schedules - * - * @param {import('./db/Schedule.js')[]} schedules - */ - addSchedules (schedules) { - for (const schedule of schedules) { - this.addSchedule(schedule) - } - } - - /** - * Get current schedule runs of a schedule - * - * @param {import('./db/Schedule.js')} schedule - */ - getRuns (schedule) { - return this.scheduleRuns.filter(r => r.schedule === schedule) - } - - /** - * @param {import('./ScheduleRun.js')} run - * @param {import('./db/Schedule.js')} schedule - */ - endRun (run, schedule) { - run.removeAllListeners() - this.scheduleRuns.splice(this.scheduleRuns.indexOf(run), 1) - this.incrementRunCount(schedule) - if (schedule.name === 'default' && devLevels.dumpHeap()) { - dumpHeap('schedulerun') - } - } - - /** - * Terminate a run by killing its children, removing - * listeners and deleting it from storage - * - * @param {import('./ScheduleRun.js')} run - */ - terminateRun (run) { - run.terminate() - this.scheduleRuns.splice(this.scheduleRuns.indexOf(run), 1) - } - - /** - * Terminate all the runs of every schedule - */ - terminateAllRuns () { - this.scheduleRuns.forEach((run) => { - this.terminateRun(run) - }) - } - - /** - * Terminate multiple runs of a schedule - * - * @param {import('./db/Schedule.js')} schedule - */ - terminateScheduleRuns (schedule) { - const runs = this.getRuns(schedule) - runs.forEach(r => this.terminateRun(r)) - } - - /** - * Check if the number of current runs of a schedule - * exceeds the max allowed - * - * @param {import('./db/Schedule')} schedule - */ - atMaxRuns (schedule) { - const maxRuns = getConfig().advanced.parallelRuns - const runs = this.getRuns(schedule) - return runs.length === maxRuns - } - - /** - * Increment run count of a schedule - * - * @param {import('./db/Schedule.js')} schedule - */ - incrementRunCount (schedule) { - const counts = this.scheduleRunCounts - counts.set(schedule, counts.get(schedule) + 1) - } - - /** - * Record the failure of hung up URLs - * - * @param {string[]} urls - */ - failURLs (urls) { - for (const url of urls) { - FailRecord.record(url) - .catch(err => this.log.error(err, `Unable to record url failure ${url}`)) - } - } - - /** - * Run a schedule - * - * @param {import('./db/Schedule.js')} schedule - */ - async run (schedule) { - if (this.atMaxRuns(schedule)) { - const runs = this.getRuns(schedule) - const hungupURLs = runs.map(run => run.hasHungUpURLRecords() ? run.getHungUpURLs() : null) - this.log.warn({ - urls: hungupURLs - }, `Previous schedule runs were not finished (${runs.length} run(s)). Terminating all runs. If repeatedly seeing this message, consider increasing your refresh rate.`) - hungupURLs.forEach((hangups) => { - if (hangups) { - this.failURLs(hangups.summary.flat(3)) - } - }) - this.terminateScheduleRuns(schedule) - } - const runCount = this.scheduleRunCounts.get(schedule) - const run = new ScheduleRun(schedule, runCount, this.testRuns) - run.on('newArticle', this._onNewArticle.bind(this)) - run.on('conFailure', this._onConnectionFailure.bind(this)) - run.on('conSuccess', this._onConnectionSuccess.bind(this)) - run.on('alertFail', this.alertFailRecord.bind(this)) - run.on('feedEnabled', this._onFeedEnabled.bind(this)) - run.on('feedDisabled', this._onFeedDisabled.bind(this)) - this.scheduleRuns.push(run) - try { - const feedIds = await DebugFeed.getAllFeedIds() - if (feedIds.size > 0) { - // New way to track feeds to debug - existing behavior is left in for backwards compat - this.debugFeedIDs = feedIds - } - await run.run(new Set([...this.debugFeedIDs, ...feedIds])) - this.endRun(run, schedule) - } catch (err) { - this.log.error(err, 'Error during schedule run') - this.endRun(run, schedule) - } - } - - /** - * Disable all schedule timers - */ - clearTimers () { - if (this.timers.length === 0) { - return - } - this.timers.forEach(timer => clearInterval(timer)) - this.timers.length = 0 - } - - /** - * Create auto-running schedule timers - */ - beginTimers () { - const config = getConfig() - const immediatelyRun = config.bot.runSchedulesOnStart - this.clearTimers() - this.schedules.forEach(schedule => { - if (immediatelyRun) { - this.run(schedule) - } - this.timers.push(setInterval(() => { - this.run(schedule) - }, schedule.refreshRateMinutes * 60000)) - }) - } - - isDebugging (feedID) { - return this.debugFeedIDs.has(feedID) - } -} - -module.exports = ScheduleManager diff --git a/services/bot/src/structs/ScheduleRun.js b/services/bot/src/structs/ScheduleRun.js deleted file mode 100644 index 5d74c0ebd..000000000 --- a/services/bot/src/structs/ScheduleRun.js +++ /dev/null @@ -1,601 +0,0 @@ -const EventEmitter = require('events').EventEmitter -const Schedule = require('./db/Schedule.js') -const FailRecord = require('./db/FailRecord.js') -const Feed = require('./db/Feed.js') -const Guild = require('./Guild.js') -const Supporter = require('./db/Supporter.js') -const ScheduleStats = require('./db/ScheduleStats.js') -const ProcessorPool = require('./ProcessorPool.js') -const promisify = require('util').promisify -const maintenance = require('../maintenance/index.js') -const getConfig = require('../config.js').get -const createLogger = require('../util/logger/create.js') -const { randomUUID } = require('crypto') - -/** - * @typedef {string} FeedID - */ - -/** - * @typedef {string} FeedURL - */ - -/** - * @typedef {Object} FeedObject - */ - -/** - * With a large number of feeds, Promise.all will cause hangups - * for some inexplicable reason. This implementation fixes that. - * - * @param {Promise[]} promises - */ -async function promiseAll (promises) { - return new Promise((resolve, reject) => { - const completed = promises.map((p) => false) - for (let i = 0; i < promises.length; ++i) { - const promise = promises[i] - promise.then(() => { - completed[i] = true - if (!completed.find(complete => !complete)) { - resolve() - } - }).catch(reject) - } - }) -} - -class ScheduleRun extends EventEmitter { - /** - * @param {import('./db/Schedule.js')} schedule - * @param {number} runCount - * @param {Object} memoryCollections - */ - constructor (schedule, runCount, testRun = false) { - if (!schedule.refreshRateMinutes) { - throw new Error('No refreshRateMinutes has been declared for a schedule') - } - if (schedule.name !== 'default' && schedule.name !== Supporter.schedule.name && schedule.keywords.length === 0 && schedule.feeds.length === 0) { - throw new Error(`Cannot create a ScheduleRun with invalid/empty keywords array for nondefault schedule (name: ${schedule.name})`) - } - super() - this.name = schedule.name - this.schedule = schedule - this.log = createLogger(this.name) - this.processorPool = new ProcessorPool(this.name) - /** - * @type {Set[][]} - */ - this.urlBatchGroups = [] - /** - * @type {number[][]} - */ - this.urlSizeGroups = [] - /** - * @type {Set} - */ - this.failedURLs = new Set() - this.succeededURLs = new Set() - this._cycleTotalCount = 0 - this.feedCount = 0 // For statistics - this.ran = runCount // # of times this schedule has ran - if (!ScheduleRun.headers.has(schedule)) { - ScheduleRun.headers.set(schedule, {}) - } - this.headers = ScheduleRun.headers.get(schedule) - if (!Schedule.isMongoDatabase && !ScheduleRun.memoryCollections.has(schedule)) { - ScheduleRun.memoryCollections.set(schedule, {}) - } - // ONLY FOR DATABASELESS USE. Object of collection ids as keys, and arrays of objects (AKA articles) as values - this.memoryCollections = ScheduleRun.memoryCollections.get(schedule) - this.testRun = testRun - } - - /** - * @param {import('./db/Feed.js')[]} feeds - * @param {Set} debugFeedIDs - */ - getDebugURLs (feeds, debugFeedIDs) { - const debugFeedURLs = new Set() - if (debugFeedIDs.size === 0) { - return debugFeedURLs - } - const mappedURLs = new Map() - for (var i = feeds.length - 1; i >= 0; --i) { - const feed = feeds[i] - mappedURLs.set(feed._id, feed.url) - } - debugFeedIDs.forEach((feedID) => { - if (mappedURLs.has(feedID)) { - debugFeedURLs.add(mappedURLs.get(feedID)) - } - }) - return debugFeedURLs - } - - getProcessor () { - const processor = this.processorPool.get() - return processor - } - - /** - * @param {import('./Processor.js')} processor - */ - killProcessor (processor) { - this.processorPool.kill(processor) - } - - killAllProcessors () { - this.processorPool.killAll() - } - - /** - * @param {import('./db/Feed.js')[]} feeds - */ - async updateFeedsStatus (feeds) { - if (this.testRun) { - return - } - const { enabled, disabled } = await maintenance.checkLimits.limits(feeds) - enabled.forEach(feed => this.emit('feedEnabled', feed)) - disabled.forEach(feed => this.emit('feedDisabled', feed)) - } - - /** - * @param {import('./db/Feed.js')[]} feeds - */ - async getFailRecordsMap (feeds) { - const urls = new Set() - for (var i = feeds.length - 1; i >= 0; --i) { - urls.add(feeds[i].url) - } - const failRecords = await FailRecord.getManyByQuery({ - _id: { - $in: Array.from(urls) - } - }) - const failRecordsMap = new Map() - for (const record of failRecords) { - failRecordsMap.set(record._id, record) - } - return failRecordsMap - } - - /** - * @param {import('./db/Feed.js')[]} scheduleFeeds - * @param {Map { - if (record.hasFailed() && !record.alerted) { - const associatedFeeds = scheduleFeeds.filter(({ url }) => url === record._id) - this.emit('alertFail', record, associatedFeeds) - } - }) - } - - /** - * Get the feeds that belong to this schedule - * @param {import('./db/Feed.js')[]} feeds - */ - async getScheduleFeeds (feeds) { - const [schedules, supporterGuilds] = await Promise.all([ - Schedule.getAll(), - Guild.getFastSupporterAndSubscriberGuildIds() - ]) - const feedsLength = feeds.length - const schedulesToFetch = [] - for (var h = 0; h < feedsLength; ++h) { - const feed = feeds[h] - schedulesToFetch.push(feed.determineSchedule(schedules, supporterGuilds)) - } - this.log.debug(`Determing schedules of ${schedulesToFetch.length} feeds`) - const determinedSchedules = await Promise.all(schedulesToFetch) - /** - * @type {import('./db/Feed.js')[]} - */ - const filtered = [] - for (var i = 0; i < feedsLength; ++i) { - const feed = feeds[i] - const name = determinedSchedules[i].name - // Match schedule - if (this.name !== name) { - continue - } - filtered.push(feed) - } - return filtered - } - - /** - * Get the feeds that belong to this schedule - * @param {import('./db/Feed.js')[]} feeds - * @param {Map} failRecordsMap - * @param {Set} debugFeedIDs - */ - async getEligibleFeeds (feeds, failRecordsMap, debugFeedIDs) { - // Modifies the feeds in-place - await this.updateFeedsStatus(feeds) - const feedsLength = feeds.length - /** - * @type {import('./db/Feed.js')[]} - */ - const filtered = [] - for (var i = 0; i < feedsLength; ++i) { - const feed = feeds[i] - if (!this.isEligibleFeed(feed, failRecordsMap, debugFeedIDs)) { - continue - } - filtered.push(feed) - } - return filtered - } - - /** - * Feeds must be be JSON for IPC - * @param {import('./db/Feed.js')[]} feeds - */ - convertFeedsToJSON (feeds) { - const converted = [] - const feedsLength = feeds.length - for (var i = 0; i < feedsLength; ++i) { - converted.push(feeds[i].toJSON()) - } - return converted - } - - /** - * @param {import('./db/Feed.js')} feed - * @param {Map} failRecordsMap - * @param {Set} debugFeedIDs - */ - isEligibleFeed (feed, failRecordsMap, debugFeedIDs) { - const toDebug = debugFeedIDs.has(feed._id) - const debugLog = toDebug ? m => this.log.info(`${feed._id} ${m}`) : () => {} - /** @type {FailRecord} */ - if (feed.disabled) { - debugLog('Skipping feed delegation due to disabled status') - return false - } - const failRecord = failRecordsMap.get(feed.url) - if (failRecord && failRecord.hasFailed()) { - debugLog(`Skipping feed delegation, failed status: ${failRecord.hasFailed()}, alerted: ${failRecord.alerted}`) - return false - } - debugLog('Preparing for feed delegation') - return true - } - - /** - * @typedef {Object} FeedByIDs - */ - - /** - * @typedef {Map} URLMap - */ - - /** - * @param {Object[]} feedObjects - * @param {Set} debugFeedIDs - * @returns {URLMap} - */ - mapFeedsByURL (feedObjects, debugFeedIDs) { - const map = new Map() - for (var i = feedObjects.length - 1; i >= 0; --i) { - const feedObject = feedObjects[i] - - if (this.memoryCollections && !this.memoryCollections[feedObject.url]) { - this.memoryCollections[feedObject.url] = [] - } - - const debug = debugFeedIDs.has(feedObject._id) - const debugLog = debug ? m => this.log.info(`${feedObject._id} ${m}`) : () => {} - - // Each item in the map has a unique URL, with every source with this the same link aggregated below it - if (map.has(feedObject.url)) { - const urlMap = map.get(feedObject.url) - urlMap[feedObject._id] = feedObject - debugLog('Adding to pre-existing source list') - } else { - const urlMap = {} - urlMap[feedObject._id] = feedObject - map.set(feedObject.url, urlMap) - debugLog('Creating new source list') - } - } - return map - } - - /** - * @typedef {Object} URLBatch - */ - - /** - * @param {URLMap} urlMap - * @param {number} batchSize - * @param {Set} debugFeedURLs - * @returns {URLBatch[]} - */ - createBatches (urlMap, batchSize, debugFeedURLs) { - const batches = [] - let batch = {} - urlMap.forEach((feedByIDs, url) => { - if (Object.keys(batch).length >= batchSize) { - batches.push(batch) - batch = {} - } - batch[url] = feedByIDs - if (debugFeedURLs.has(url)) { - this.log.info(`${url}: Attached URL to regular batch list for ${this.name}`) - } - }) - - if (Object.keys(batch).length > 0) { - batches.push(batch) - } - this.batches = batches - this.createURLRecords(this.batches) - return batches - } - - /** - * Create records to track what URLs responded within - * this run, and which hung up. Initially store them, - * and wait for them to be removed - * - * @param {URLBatch[]} batches - */ - createURLRecords (batches) { - const urlBatchRecords = batches.map(batch => new Set(Object.keys(batch))) - const urlSizeRecords = urlBatchRecords.map(batch => batch.size) - this.urlBatchRecords = urlBatchRecords - this.urlSizeRecords = urlSizeRecords - } - - /** - * Mark a URL as "responded" by removing it from records - * - * @param {number} groupIndex - * @param {number} batchIndex - * @param {string} url - */ - removeFromBatchRecords (batchIndex, url) { - const urlBatch = this.urlBatchRecords[batchIndex] - urlBatch.delete(url) - } - - /** - * This check is necessary since urlBatchRecords could be undefined if the DB was - * disconnected before these records could be created for this run - */ - hasHungUpURLRecords () { - return !!this.urlBatchRecords - } - - getHungUpURLs () { - const summary = this.urlBatchRecords.filter((urlBatch, batchIndex) => { - const origBatchSize = this.urlSizeRecords[batchIndex] - /** - * If equal to original batch size, none of the URLs were completed - * If equal to 0, all of them were completed - * - * This summary does not show a batch if it was hung up on all URLs - */ - const someCompleted = urlBatch.size < origBatchSize && urlBatch.size > 0 - return someCompleted - }).map((urlBatch) => Array.from(urlBatch)) - const remaining = this.urlBatchRecords.map(b => b.size) - const total = remaining.reduce((total, cur) => total + cur, 0) - return { - summary, - remaining, - total - } - } - - /** - * @param {Set} debugFeedIDs - */ - async run (debugFeedIDs) { - this._startTime = new Date() - const config = getConfig() - this.log.debug({ - schedule: this.schedule - }, '1 Running schedule, getting all feeds') - const feeds = await Feed.getAllByPagination() - // Check the limits - this.log.debug(`2 Fetched all feeds (${feeds.length}), getting feeds of this schedule`) - // Get eligible feeds of this schedule - const scheduleFeeds = await this.getScheduleFeeds(feeds) - this.log.debug('3 Got feeds of this schedule, getting fail record map') - const failRecordsMap = await this.getFailRecordsMap(scheduleFeeds) - this.alertFailRecords(scheduleFeeds, failRecordsMap) - this.log.debug('4 Got fail record map, getting elgibile feeds') - const eligibleFeeds = await this.getEligibleFeeds(scheduleFeeds, failRecordsMap, debugFeedIDs) - this.log.debug(`5 Got eligibile feeds (${eligibleFeeds.length}/${scheduleFeeds.length}/${feeds.length}), converting all to JSON`) - const feedObjects = this.convertFeedsToJSON(eligibleFeeds) - this.log.debug(`6 Fetched applicable feeds (${feedObjects.length}), mapping feeds by URL`) - this.feedCount = feedObjects.length - // Put all feeds with the same URLs together - const urlMap = this.mapFeedsByURL(feedObjects, debugFeedIDs) - this.log.debug(`7 Mapped feeds by URL (${urlMap.size} URLs), creating batches`) - if (urlMap.size === 0) { - return this.finishNoFeedsCycle() - } - // Batch them up - const debugFeedURLs = this.getDebugURLs(feeds, debugFeedIDs) - const batches = this.createBatches(urlMap, config.advanced.batchSize, debugFeedURLs) - this.batches = batches - this.log.debug(`8 Created ${batches.length} batches`) - const processBatch = promisify(this.processBatch).bind(this) - const processing = [] - const parallelBatches = config.advanced.parallelBatches - const spawn = batches.length <= parallelBatches ? batches.length : parallelBatches - const batchStatuses = this.batches.map((b, index) => 0) - for (let i = 0; i < spawn; ++i) { - this.log.debug(`[START] Starting processor ${i + 1}/${spawn}`) - processing.push(processBatch(batches, i, batchStatuses, debugFeedIDs, debugFeedURLs)) - } - await promiseAll(processing) - await this.finishFeedsCycle() - } - - createMessageHandler (batches, batchIndex, debugFeedURLs, runId, onAllConnected, onComplete) { - const thisBatch = batches[batchIndex] - const batchLength = Object.keys(thisBatch).length - let connectedLinks = 0 - let completedLinks = 0 - let thisFailures = 0 - - return linkCompletion => { - const { link, status, lastModified, etag, memoryCollection, newArticle, runId: receivedRunId } = linkCompletion - - if (runId !== receivedRunId) { - return - } - - const debugLog = debugFeedURLs.has(link) ? m => this.log.info({ url: link, status }, m) : () => {} - if (status === 'headers') { - this.headers[link] = { - lastModified, - etag - } - return - } - if (status === 'connected') { - ++connectedLinks - debugLog('Link connected from processor') - if (connectedLinks === batchLength) { - onAllConnected() - } - return - } - if (status === 'newArticle') { - return this.emit('newArticle', newArticle) - } - if (status === 'failed') { - ++thisFailures - this.failedURLs.add(link) - this.emit('conFailure', link, linkCompletion.reason) - } else if (status === 'success') { - this.emit('conSuccess', link) - if (memoryCollection) { - this.memoryCollections[link] = memoryCollection - } - } - - ++this._cycleTotalCount - ++completedLinks - this.removeFromBatchRecords(batchIndex, link) - this.log.trace(`[BATCH ${batchIndex + 1}/${batches.length}] URLs Completed: ${completedLinks}/${batchLength}`) - debugLog('Link completed from processor') - if (completedLinks === batchLength) { - onComplete(thisFailures) - } - } - } - - processBatch (batches, batchIndex, batchStatuses, debugFeedIDs, debugFeedURLs, onBatchesComplete) { - batchStatuses[batchIndex] = 1 - const thisBatch = batches[batchIndex] - const thisBatchLength = Object.keys(thisBatch).length - const processor = this.getProcessor() - this.log.debug(`Batch ${batchIndex + 1}/${this.batches.length} starting. Processors in use: ${this.processorPool.pool.length}`) - const onAllConnected = () => { - this.log.debug(`Batch ${batchIndex + 1}/${this.batches.length} connected`) - for (let i = 0; i < batchStatuses.length; ++i) { - const status = batchStatuses[i] - if (status === 0) { - return this.processBatch(batches, i, batchStatuses, debugFeedIDs, debugFeedURLs, onBatchesComplete) - } - } - } - const onComplete = (failures) => { - batchStatuses[batchIndex] = 2 - this.killProcessor(processor) - this.log.debug(`Batch ${batchIndex + 1}/${this.batches.length} completed (${failures} failed/${thisBatchLength})`) - if (!batchStatuses.find(status => status !== 2)) { - onBatchesComplete() - } - } - const runId = randomUUID() - - const handler = this.createMessageHandler(batches, batchIndex, debugFeedURLs, runId, onAllConnected, onComplete) - processor.on('message', handler.bind(this)) - processor.send({ - config: getConfig(), - currentBatch: thisBatch, - debugFeeds: Array.from(debugFeedIDs), - debugURLs: Array.from(debugFeedURLs), - headers: this.headers, - memoryCollections: this.memoryCollections, - runNum: this.ran, - scheduleName: this.name, - testRun: this.testRun, - runId - }) - } - - terminate () { - this.removeAllListeners() - this.killAllProcessors() - } - - finishNoFeedsCycle () { - const nameParen = this.name !== 'default' ? ` (${this.name})` : '' - this.killAllProcessors() - this.log.info(`Finished feed retrieval cycle${nameParen}. No feeds to retrieve`) - } - - async finishFeedsCycle () { - const cycleTime = (new Date() - this._startTime) / 1000 - await this.updateStats(cycleTime) - const timeTaken = cycleTime.toFixed(2) - const nameParen = this.name !== 'default' ? ` (${this.name})` : '' - const count = this.failedURLs.size > 0 ? ` (${this.failedURLs.size}/${this._cycleTotalCount} failed)` : ` (${this._cycleTotalCount})` - this.killAllProcessors() - this.log.info(`Finished feed retrieval cycle${nameParen}${count}. Cycle Time: ${timeTaken}s`) - } - - async updateStats (cycleTime) { - if (this.testRun) { - return - } - try { - const stats = await ScheduleStats.get(this.name) - const data = { - _id: this.name, - feeds: this.feedCount, - cycleTime, - cycleFails: this.failedURLs.size, - cycleURLs: this._cycleTotalCount, - lastUpdated: new Date().toISOString() - } - if (!stats) { - const newStats = new ScheduleStats(data) - return newStats.save() - } else { - stats.feeds = data.feeds - stats.cycleTime = Math.round((data.cycleTime + stats.cycleTime) / 2) - stats.cycleFails = Math.round((data.cycleFails + stats.cycleFails) / 2) - stats.cycleURLs = data.cycleURLs - stats.lastUpdated = data.lastUpdated - return stats.save() - } - } catch (err) { - this.log.error(err, 'Unable to update statistics after cycle') - } - } -} - -/** - * @type {Map>} - */ -ScheduleRun.headers = new Map() - -/** - * @type {Map>} - */ -ScheduleRun.memoryCollections = new Map() - -module.exports = ScheduleRun diff --git a/services/bot/src/structs/Translator.js b/services/bot/src/structs/Translator.js deleted file mode 100644 index 85ae8fb57..000000000 --- a/services/bot/src/structs/Translator.js +++ /dev/null @@ -1,143 +0,0 @@ -const DEFAULT_LOCALE = 'en-US' -const fs = require('fs') -const path = require('path') -const defaultLocale = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'locales', `${DEFAULT_LOCALE}.json`))) -const fileList = fs.readdirSync(path.join(__dirname, '..', 'locales')) - -const localesData = new Map() -for (const file of fileList) { - const read = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'locales', file))) - localesData.set(file.replace('.json', ''), read) -} - -function escapeRegExp (str) { - return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&') -} - -class Translator { - constructor (locale = DEFAULT_LOCALE) { - /** - * Locale string - * @type {string} - */ - this.locale = locale - } - - /** - * Default locale parsed json - * @constant {object} - */ - static get DEFAULT_LOCALE () { - return defaultLocale - } - - /** - * Map of locales parsed jsons - * @constant {Map} - */ - static get LOCALES_DATA () { - return localesData - } - - /** - * Convert a string according to the translator's locale - * @param {string} string - Accessor - * @param {Object.} params - Keys to replace in string - * @returns {string} - */ - translate (string, params) { - return Translator.translate(string, this.locale, params) - } - - /** - * Returns a translator function for a locale - * @param {string} locale - * @returns {function} - */ - static createLocaleTranslator (locale) { - return (string, params) => this.translate(string, locale, params) - } - - /** - * Gets the locale from a profile and creates a translate func - * @param {import('./db/Profile.js')} profile - */ - static createProfileTranslator (profile) { - const locale = profile ? profile.locale : undefined - return this.createLocaleTranslator(locale) - } - - /** - * Check if a locale exists - * @param {string} locale - * @returns {boolean} - */ - static hasLocale (locale) { - return this.LOCALES_DATA.has(locale) - } - - /** - * Get list of defined locales - * @returns {string[]} - */ - static getLocales () { - return Array.from(this.LOCALES_DATA.keys()).sort() - } - - /** - * Get command descriptions used for rsshelp - * @param {string} locale - * @returns {Object.} - */ - static getCommandDescriptions (locale = DEFAULT_LOCALE) { - return this.LOCALES_DATA.get(locale).commandDescriptions - } - - /** - * Convert a string according to the given locale - * @param {string} string - Accessor - * @param {string} locale - Locale - * @param {Object.} [params] - Keys to replace in the string - * @returns {string} - */ - static translate (string, locale = DEFAULT_LOCALE, params) { - if (typeof string !== 'string') { - throw new TypeError('string is not a string') - } - if (typeof locale !== 'string') { - throw new TypeError('locale is not a string') - } - if (!this.hasLocale(locale)) { - throw new Error('Unknown locale: ' + locale) - } - const properties = string.split('.') - let accessedSoFar = this.LOCALES_DATA.get(locale) - let reference = this.DEFAULT_LOCALE - for (const property of properties) { - accessedSoFar = accessedSoFar[property] - reference = reference[property] - if (accessedSoFar === undefined) { - throw new Error(`Invalid locale accessor (stopped at "${property}") for locale ${locale}`) - } - if (!reference) { - throw new Error(`Invalid locale accessor (no en-US locale reference at "${property}") for locale ${locale}`) - } - } - if (typeof accessedSoFar !== 'string') { - throw new Error(`Invalid locale accessor that stopped with a non-string value for locale ${locale}`) - } - if (accessedSoFar.length === 0) { - accessedSoFar = reference // Use the reference if the original locale is an empty string - } - if (params) { - for (const param in params) { - const term = escapeRegExp(`{{${param}}}`) - const regex = new RegExp(term, 'g') - accessedSoFar = accessedSoFar.replace(regex, params[param]) - } - } - return accessedSoFar - } -} - -module.exports = Translator diff --git a/services/bot/src/structs/db/BannedFeed.js b/services/bot/src/structs/db/BannedFeed.js deleted file mode 100644 index d55efc623..000000000 --- a/services/bot/src/structs/db/BannedFeed.js +++ /dev/null @@ -1,63 +0,0 @@ -const BannedFeedModel = require('../../models/BannedFeed.js') -const Base = require('./Base.js') - -class BannedFeed extends Base { - constructor (data, _saved) { - super(data, _saved) - - /** - * The URL of the feed - * @type {string} - */ - this.url = this.getField('url') - if (this.url === undefined) { - throw new TypeError('url is undefined') - } - - /** - * Optional name of the target - * @type {string|undefined} - */ - this.reason = this.getField('reason') - - /** - * The guild IDs that this feed is banned in. If an empty array, it is banned in all guilds. - * @type {string[]} - */ - this.guildIds = this.getField('guildIds', []) - } - - /** - * Find if a url is banned in a guild. - * - * @param {string} url - * @param {string} guildId - * @returns {Promise} - */ - static async findForUrl (url, guildId) { - return BannedFeed.getByQuery({ - url, - $or: [{ - guildIds: guildId - }, { - guildIds: { - $size: 0 - } - }] - }) - } - - toObject () { - return { - url: this.url, - reason: this.reason, - guildIds: this.guildIds - } - } - - static get Model () { - return BannedFeedModel.Model - } -} - -module.exports = BannedFeed diff --git a/services/bot/src/structs/db/Base.js b/services/bot/src/structs/db/Base.js deleted file mode 100644 index be75e97b1..000000000 --- a/services/bot/src/structs/db/Base.js +++ /dev/null @@ -1,537 +0,0 @@ -const mongoose = require('mongoose') -const fs = require('fs') -const fsPromises = fs.promises -const path = require('path') -const createLogger = require('../../util/logger/create.js') -const getConfig = require('../../config.js').get -const log = createLogger('-') - -/** - * @typedef {import('mongoose').Model} MongooseModel - */ - -class Base { - /** - * Object data - * @param {MongooseModel|Object} data - */ - constructor (data = {}, _saved = false) { - /** - * this.data must be serialized to maintain - * equal function between database and databaseless - * @type {Object} - */ - this.data = data instanceof mongoose.Model ? JSON.parse(JSON.stringify(data.toJSON())) : data - - /** - * Internal ID, usually MongoDB's ObjectId purely used - * to distinguish between documents and to grab from - * database during testing. - * @type {string} - */ - this._id = this.data._id - - /** - * Whether this has been saved to the database already - * @type {boolean} - */ - this._saved = _saved - - /** - * Only used for database methods - * @type {MongooseModel} - */ - this.document = data instanceof mongoose.Model ? data : null - - /** - * The bot version this data model was created on - * @type {string} - */ - this.version = this.getField('version') - - /** - * Time of entry - * @type {string} - */ - this.addedAt = this.getField('addedAt') - - // Run the get method and throw its associated error if unimplemented - // eslint-disable-next-line no-void - void this.constructor.Model - } - - /** - * Remove the internal version key when finding from database - */ - static FIND_PROJECTION () { - return { __v: 0 } - } - - /** - * Returns the Mongoose model specific to the class - * @returns {MongooseModel} - */ - static get Model () { - throw new Error('Model static get method must be implemented by subclasses') - } - - /** - * Checks whether the app is databaseless - * @returns {boolean} - */ - static get isMongoDatabase () { - const config = getConfig() - return config.database.uri.startsWith('mongo') - } - - /** - * Get the folder paths of the intended fs location the data will - * be written to. - * @returns {string[]} - */ - static getFolderPaths () { - const config = getConfig() - const folderPath = config.database.uri - const subfolderPath = path.join(folderPath, this.Model.collection.collectionName) - return [folderPath, subfolderPath] - } - - /** - * Helper function to return undefined for empty objects - * @param {any} field - The field name - * @private - */ - static resolveObject (value) { - if (!value || Object.keys(value).length === 0) { - return undefined - } else { - return value - } - } - - /** - * A function that validates data before saving it. - * Used by extended classes. - */ - async validate () {} - - /** - * Resolves data acquisition based on whether the app is databaseless - * @param {string} field - The field to get - * @param {*} def - The default value if there is no value - * @returns {*} - The field value - */ - getField (field, def) { - const value = this.data[field] - return value === undefined || value === null ? def : value - } - - /** - * Convert class data into an object, but still maintains some data - * structures such as Maps for compatibility with mongoose models. - * @returns {Object} - The object - */ - toObject () { - throw new Error('Method must be implemented by subclasses') - } - - /** - * Convert class data into a plain object with only primitive - * values. - * @returns {Object} - The plain object - */ - toJSON () { - return this.toObject() - } - - /** - * Get a document - * @param {string} id - Guild ID - * @returns {Promise} - */ - static async get (id) { - if (!id) { - throw new Error('Undefined id') - } - if (typeof id !== 'string') { - throw new Error('id must be a string') - } - - /** - * @type {MongooseModel} - */ - const DatabaseModel = this.Model - - // Mongo - if (this.isMongoDatabase) { - const doc = await DatabaseModel.findById(id).exec() - return doc ? new this(doc, true) : null - } - - // Databaseless - const folderPaths = this.getFolderPaths() - const folderPath = folderPaths[folderPaths.length - 1] - const filePath = path.join(folderPath, `${id}.json`) - if (!fs.existsSync(filePath)) { - return null - } - - try { - const readContent = fs.readFileSync(filePath) - return new this(JSON.parse(readContent), true) - } catch (err) { - log.warn(err, `Could not parse ${DatabaseModel.collection.collectionName} JSON from file ${id}`) - return null - } - } - - /** - * Get by a field's value - * @param {string} field - Field name - * @param {any} value - Field value - * @returns {Promise} - */ - static async getBy (field, value) { - return this.getByQuery({ [field]: value }) - } - - /** - * Get one with a custom query - * @param {Object} query - MongoDB-format query - * @returns {Promise} - */ - static async getByQuery (query) { - /** - * @type {MongooseModel} - */ - const DatabaseModel = this.Model - - // Database - if (this.isMongoDatabase) { - const doc = await DatabaseModel.findOne(query, this.FIND_PROJECTION).exec() - return doc ? new this(doc, true) : null - } - - const results = await this.getManyByQuery(query) - return results.length > 0 ? new this(results[0], true) : null - } - - /** - * Get many by a field's value - * @param {string} field - Field name - * @param {any} value - Field value - * @returns {Promise} - */ - static async getManyBy (field, value) { - return this.getManyByQuery({ [field]: value }) - } - - /** - * Get many with a custom query - * @param {Object} query - MongoDB-format query - * @returns {Promise} - */ - static async getManyByQuery (query) { - /** - * @type {MongooseModel} - */ - const DatabaseModel = this.Model - - // Database - if (this.isMongoDatabase) { - const docs = await DatabaseModel.find(query, this.FIND_PROJECTION).exec() - return docs.map(doc => new this(doc, true)) - } - - // Databaseless - very slow - const folderPaths = this.getFolderPaths() - const folderPath = folderPaths[folderPaths.length - 1] - if (!fs.existsSync(folderPath)) { - return [] - } - - const fileNames = await fsPromises.readdir(folderPath) - const resolved = fileNames.map(name => fs.readFileSync(path.join(folderPath, name))) - const jsons = resolved.map((contents, index) => { - try { - return JSON.parse(contents) - } catch (err) { - log.warn(err, `Failed to parse json at ${folderPath} ${fileNames[index]}`) - return null - } - }) - return jsons - .filter(item => { - if (!item) { - return false - } - let allTrue = true - for (const key in query) { - allTrue = allTrue && item[key] === query[key] - } - return allTrue - }) - .map(item => new this(item, true)) - } - - /** - * Get multiple documents - * @param {string[]} ids - Array of guild IDs - * @returns {Promise} - */ - static async getMany (ids) { - return Promise.all(ids.map(id => this.get(id))) - } - - /** - * Prevent the find method from getting too many results in bulk. - * Only supported for MongoDB - * - * @param {number} npp Number (of docs) per page to fetch per query/page - */ - static async getAllByPagination (npp = 7500) { - if (!this.isMongoDatabase) { - // Pagination is not supported for databaseless - return this.getAll() - } - // https://docs.mongodb.com/manual/reference/method/cursor.skip/#using-range-queries - /** - * @type {MongooseModel} - */ - const DatabaseModel = this.Model - - const largestIdDoc = (await DatabaseModel.find().sort({ - _id: -1 - }).limit(1).exec())[0] - - let largestId = largestIdDoc ? largestIdDoc._id : null - - if (!largestId) { - return [] - } - - const allResults = [] - let latestResults = [] - - do { - const results = await DatabaseModel.find({ - _id: { - $lt: largestId - } - }).sort({ - _id: -1 - }).limit(npp).exec() - - if (results.length > 0) { - largestId = results[results.length - 1]._id - } else { - largestId = null - } - - latestResults = results - allResults.push(...results) - } while (latestResults.length === npp && largestId) - - const allResultsLength = allResults.length - /** - * Add doc with the largest ID since getPage does not - * include the doc with the largest id ($lt is less than) - * - * Also use a for loop with var instead of let since this - * function is optimized for performance - */ - const converted = [new this(largestIdDoc, true)] - for (var i = 0; i < allResultsLength; ++i) { - converted.push(new this(allResults[i], true)) - } - return converted - } - - /** - * Get all documents - * @returns {Promise} - */ - static async getAll () { - /** - * @type {MongooseModel} - */ - const DatabaseModel = this.Model - - // Mongo - if (this.isMongoDatabase) { - const documents = await DatabaseModel.find({}, this.FIND_PROJECTION).exec() - return documents.map(doc => new this(doc, true)) - } - - // Databaseless - const folderPaths = this.getFolderPaths() - const fullPath = folderPaths[folderPaths.length - 1] - if (!fs.existsSync(fullPath)) { - return [] - } - const fileReads = fs.readdirSync(fullPath) - .filter(id => /\.json$/.test(id)) - .map(fileName => this.get(fileName.replace(/\.json/i, ''))) - - return Promise.all(fileReads) - } - - /** - * Deletes all from the database - */ - static async deleteAll () { - // Mongo - if (this.isMongoDatabase) { - await this.Model.deleteMany({}).exec() - return - } - - // Databaseless - const folderPaths = this.getFolderPaths() - if (fs.existsSync(folderPaths[1])) { - const files = await fsPromises.readdir(folderPaths[1]) - await Promise.all(files.map(name => fsPromises.unlink(path.join(folderPaths[1], name)))) - await fsPromises.rmdir(folderPaths[1]) - } - } - - /** - * Deletes this from either the database from the file system - * depending on whether the app is databaseless. - */ - async delete () { - if (!this._saved) { - throw new Error('Data has not been saved') - } - - // Mongo - if (this.constructor.isMongoDatabase) { - await this.document.remove() - return - } - - const Model = this.constructor.Model - - // Databaseless - const paths = this.constructor.getFolderPaths() - const folderPath = paths[paths.length - 1] - const filePath = path.join(folderPath, `${this._id}.json`) - if (!fs.existsSync(filePath)) { - log.warn(`Unable to delete ${Model.collection.collectionName} ${this._id} at ${filePath} since its nonexistent`) - } else { - fs.unlinkSync(filePath) - } - } - - /** - * Save the data to either the database or a file depending on whether the - * app is databaseless. - * @returns {Promise} - This instance - */ - async save () { - await this.validate() - if (this.constructor.isMongoDatabase) { - return this.saveToDatabase() - } else { - return this.saveToFile() - } - } - - /** - * Save the data to the database - * @returns {Promise} - */ - async saveToDatabase () { - const toSave = this.toObject() - - /** - * @type {MongooseModel} - */ - const DatabaseModel = this.constructor.Model - - if (!this._saved) { - // Delete all undefined keys - for (const key in toSave) { - if (toSave[key] === undefined) { - delete toSave[key] - } - } - const model = new DatabaseModel(toSave) - const document = await model.save() - - this._saved = true - this._id = document.id - this.document = document - this.data = JSON.parse(JSON.stringify(document.toJSON())) - } else { - for (const key in toSave) { - const value = toSave[key] - // Map values must be individually set and deleted - if (value instanceof Map) { - if (!this.document[key]) { - this.document.set(key, new Map()) - } - const docMap = this.document[key] - // First remove all unknown keys - docMap.forEach((v, key) => { - if (!value.has(key)) { - docMap.delete(key) - } - }) - // Then set the new values - value.forEach((value, mapKey) => docMap.set(mapKey, value)) - } else { - this.document.set(key, toSave[key]) - } - } - const saved = await this.document.save() - this.data = JSON.parse(JSON.stringify(saved.toJSON())) - - // Update class data - for (const key in toSave) { - this[key] = this.data[key] - } - } - - return this - } - - /** - * Saves the data to a file - * @returns {Promise} - */ - async saveToFile () { - const toSave = this.toJSON() - - for (const key in toSave) { - if (toSave[key] === undefined) { - delete toSave[key] - } - } - - const folderPaths = this.constructor.getFolderPaths() - for (const p of folderPaths) { - if (!fs.existsSync(p)) { - fs.mkdirSync(p) - } - } - const folderPath = folderPaths[folderPaths.length - 1] - if (!this._saved) { - let useId = toSave._id - if (!useId) { - useId = new mongoose.Types.ObjectId().toHexString() - toSave._id = useId - } - const serialized = JSON.stringify(toSave, null, 2) - await fs.writeFileSync(path.join(folderPath, `${useId}.json`), serialized) - this._id = useId - this._saved = true - } else { - const serialized = JSON.stringify(toSave, null, 2) - await fs.writeFileSync(path.join(folderPath, `${this._id}.json`), serialized) - } - return this - } -} - -module.exports = Base diff --git a/services/bot/src/structs/db/Blacklist.js b/services/bot/src/structs/db/Blacklist.js deleted file mode 100644 index f24e4638b..000000000 --- a/services/bot/src/structs/db/Blacklist.js +++ /dev/null @@ -1,59 +0,0 @@ -const BlacklistModel = require('../../models/Blacklist.js') -const Base = require('./Base.js') - -class Blacklist extends Base { - constructor (data, _saved) { - super(data, _saved) - - if (!this._id) { - throw new TypeError('_id is undefined') - } - - /** - * Type of blacklist. 0 for user, 1 for guild - * @type {number} - */ - this.type = this.getField('type') - if (this.type === undefined) { - throw new TypeError('type is undefined') - } - if (isNaN(this.type)) { - throw new TypeError('type is not a number') - } - - /** - * Optional name of the target - * @type {string} - */ - this.name = this.getField('name') - } - - /** - * Getter for _id - * @returns {string} - */ - get id () { - return this.getField('_id') - } - - static get TYPES () { - return { - USER: 0, - GUILD: 1 - } - } - - toObject () { - return { - _id: this._id, - type: this.type, - name: this.name - } - } - - static get Model () { - return BlacklistModel.Model - } -} - -module.exports = Blacklist diff --git a/services/bot/src/structs/db/DebugFeed.js b/services/bot/src/structs/db/DebugFeed.js deleted file mode 100644 index 7782bb83c..000000000 --- a/services/bot/src/structs/db/DebugFeed.js +++ /dev/null @@ -1,32 +0,0 @@ -const Base = require('./Base.js') -const DebugFeedModel = require('../../models/DebugFeed.js') - -class DebugFeed extends Base { - constructor (data, _saved) { - super(data, _saved) - - /** - * The ID of the feed to debug - * @type {string} - */ - this.feedId = this.getField('feedId') - } - - toObject () { - return { - _id: this._id, - feedId: this.feedId - } - } - - static async getAllFeedIds () { - const allDebugs = await this.getAll() - return new Set(allDebugs.map((debug) => debug.feedId)) - } - - static get Model () { - return DebugFeedModel.Model - } -} - -module.exports = DebugFeed diff --git a/services/bot/src/structs/db/FailRecord.js b/services/bot/src/structs/db/FailRecord.js deleted file mode 100644 index f7e5daa8f..000000000 --- a/services/bot/src/structs/db/FailRecord.js +++ /dev/null @@ -1,132 +0,0 @@ -const Base = require('./Base.js') -const Feed = require('./Feed.js') -const FailRecordModel = require('../../models/FailRecord.js') -const getConfig = require('../../config.js').get - -class FailRecord extends Base { - constructor (data, _saved) { - super(data, _saved) - - if (!this._id) { - throw new Error('_id is undefined (must be URL)') - } - - /** - * Last recorded reason of failure - * @type {string} - */ - this.reason = this.getField('reason') - - /** - * The date the first failure occurred - * @type {string} - */ - this.failedAt = this.getField('failedAt', new Date().toISOString()) - - /** - * Whether an alert has been sent out for this. Only a - * meta property. If true, this feed will be skipped - * during cycles. - * @type {boolean} - */ - this.alerted = this.getField('alerted', false) - } - - static get cutoff () { - const config = getConfig() - return config.feeds.hoursUntilFail - } - - /** - * Record the failure - * @param {string} url - Feed URL - * @param {string} reason - Reason to provide if failed - * @returns {FailRecord} - */ - static async record (url, reason) { - const record = await FailRecord.get(url) - if (!record) { - const data = { - _id: url, - reason - } - const newRecord = new this(data) - await newRecord.save() - return newRecord - } - if (record.reason !== reason) { - record.reason = reason - await record.save() - } - return record - } - - /** - * Reset the record by deleting it - * @param {string} url - Feed URL - */ - static async reset (url) { - const found = await FailRecord.get(url) - if (found) { - return found.delete() - } - } - - /** - * If a URL has failed - * @param {string} url - */ - static async hasFailed (url) { - const found = await FailRecord.get(url) - if (!found) { - return false - } else { - return found.hasFailed() - } - } - - toObject () { - return { - _id: this._id, - reason: this.reason, - failedAt: this.failedAt, - alerted: this.alerted - } - } - - /** - * Get all feeds with this URL - */ - async getAssociatedFeeds () { - return Feed.getManyBy('url', this._id) - } - - /** - * If past the cutoff date, then this URL should not be fetched - * @returns {boolean} - */ - pastCutoff () { - if (FailRecord.cutoff === 0) { - return false - } - const now = new Date() - const failedAt = new Date(this.failedAt) - const hoursDiff = (now.getTime() - failedAt.getTime()) / 36e5 - return hoursDiff >= FailRecord.cutoff - } - - /** - * If this URL should be considered failed. Determines whether - * a feed will be fetched in FeedSchedule - * @returns {boolean} - */ - hasFailed () { - return this.pastCutoff() - } - - static get Model () { - return FailRecordModel.Model - } -} - -module.exports = FailRecord diff --git a/services/bot/src/structs/db/Feed.js b/services/bot/src/structs/db/Feed.js deleted file mode 100644 index 1b47bde9f..000000000 --- a/services/bot/src/structs/db/Feed.js +++ /dev/null @@ -1,431 +0,0 @@ -const Base = require('./Base.js') -const FilterBase = require('./FilterBase.js') -const FeedModel = require('../../models/Feed.js') -const FilteredFormat = require('./FilteredFormat.js') -const Guild = require('../Guild.js') -const Subscriber = require('./Subscriber.js') -const Schedule = require('./Schedule.js') -const Supporter = require('./Supporter.js') -const FeedFetcher = require('../../util/FeedFetcher.js') -const databaseFuncs = require('../../util/database.js') -const createLogger = require('../../util/logger/create.js') - -class Feed extends FilterBase { - /** - * @param {import('mongoose').Model|Object} data - Data - * @param {string} data.title - Feed meta title - * @param {string} data.url - Feed URL - * @param {string} data.guild - Guild ID - * @param {string} data.channel - Channel ID - * @param {Date} data.addedAt - Date the feed was added - * @param {boolean} data.imgPreviews - Have Discord automatically show embeds for image links - * @param {boolean} data.imgLinksExistence - Show image links in messages - * @param {boolean} data.checkDates - Check dates for determining article newness - * @param {number} data.articleMaxAge - Number of days an article of this feed should be considered if date checks on - * @param {boolean} data.formatTables - Format messages as if they're tables - * @param {string[]} data.checkProperties - Properties to check to decide if articles are new - */ - constructor (data, _saved) { - super(data, _saved) - - /** - * Optinal override for _id. Use for restoring from JSON - * @type {string} - */ - this._id = this.getField('_id') - - /** - * Feed name - * @type {string} - */ - this.title = this.getField('title', 'Unknown') - - /** - * Feed URL - * @type {string} - */ - this.url = this.getField('url') - if (!this.url) { - throw new Error('Undefined url') - } - - /** - * Guild ID - * @type {string} - */ - this.guild = this.getField('guild') - if (!this.guild) { - throw new Error('Undefined guild') - } - - /** - * Feed channel ID - * @type {string} - */ - this.channel = this.getField('channel') - if (!this.channel) { - throw new Error('Undefined channel') - } - - /** - * Feed text message - * @type {string} - */ - this.text = this.getField('text') - - /** - * Feed embeds - * @type {Object} - */ - this.embeds = this.getField('embeds', []) - - /** - * Date the feed was added. No need to initialize since - * mongoose will add a default when it saves. - * @type {Date} - */ - this.addedAt = this.getField('addedAt') - - /** - * Have Discord automatically show embeds for image links - * @type {boolean} - */ - this.imgPreviews = this.getField('imgPreviews') - - /** - * Show image links in messages - * @type {boolean} - */ - this.imgLinksExistence = this.getField('imgLinksExistence') - - /** - * Check dates for determining article newness - * @type {boolean} - */ - this.checkDates = this.getField('checkDates') - - /** - * Format messages as if they're tables - * @type {boolean} - */ - this.formatTables = this.getField('formatTables') - - /** - * Allow direct subscribers through sub command - * @type {boolean} - */ - this.directSubscribers = this.getField('directSubscribers') - - /** - * Disabled status. Either undefined if enabled, or - * a string stating the reason why. - * @type {string} - */ - this.disabled = this.getField('disabled') - - /** - * This feed's webhook. Default value is an empty object, - * as enforced by mongoose. Cannot be empty/null. - * @type {Object} - */ - this._webhook = this.getField('webhook') - - /** - * Split messages that are >2000 chars into multiple - * messages - * @type {Object} - */ - this._split = this.getField('split') - - /** - * RegexOps for custom placeholders - * @type {Object[]>} - */ - this.regexOps = this.getField('regexOps', {}) - - /** - * Additional negative-comparisons if default checks mark - * an article as new. They'll mark an article ineligible - * to be sent. - * @type {string[]} - */ - this.ncomparisons = this.getField('ncomparisons', []) - - /** - * Additional positive-comparisons if default checks mark - * an article as old. They'll mark an article eligible to - * be sent. - * @type {string[]} - */ - this.pcomparisons = this.getField('pcomparisons', []) - - /** - * If date checks are enabled, this is the max number of days - * from now where an article may be considered new. If it's older, - * it will be blocked. - * - * @type {number|undefined} - */ - this.articleMaxAge = this.getField('articleMaxAge') - } - - static get DISABLE_REASONS () { - return { - BAD_FORMAT: 'There was an issue sending an article due to an incorrectly-formatted text or embed. Update the feed and ensure it works to re-enable' - } - } - - static get SPLIT_KEYS () { - return ['char', 'prepend', 'append', 'maxLength'] - } - - static get WEBHOOK_KEYS () { - return ['id', 'name', 'avatar', 'disabled'] - } - - toObject () { - /** - * Use this.webhook instead of this._webhook since mongoose - * will return an empty object when we don't want it to - */ - const regexOpsMap = new Map() - for (const key in this.regexOps) { - regexOpsMap.set(key, this.regexOps[key]) - } - const data = { - ...super.toObject(), - title: this.title, - url: this.url, - guild: this.guild, - channel: this.channel, - text: this.text, - embeds: this.embeds, - addedAt: this.addedAt, - checkDates: this.checkDates, - imgPreviews: this.imgPreviews, - imgLinksExistence: this.imgLinksExistence, - formatTables: this.formatTables, - directSubscribers: this.directSubscribers, - disabled: this.disabled, - webhook: this.webhook, - split: this.split, - ncomparisons: this.ncomparisons, - pcomparisons: this.pcomparisons, - regexOps: regexOpsMap, - articleMaxAge: this.articleMaxAge - } - if (this._id) { - data._id = this._id - } - return data - } - - toJSON () { - return { - ...super.toJSON(), - regexOps: this.regexOps - } - } - - /** - * Webhook getter - * @returns {Object|undefined} - */ - get webhook () { - return Base.resolveObject(this._webhook) - } - - /** - * Split options getter - * @returns {Object|undefined} - */ - get split () { - return Base.resolveObject(this._split) - } - - set webhook (value) { - this._webhook = value - } - - set split (value) { - this._split = value - } - - /** - * Returns both role and user subscribers of this feed. - * @returns {Subscriber[]} - */ - async getSubscribers () { - return Subscriber.getManyBy('feed', this._id) - } - - /** - * Returns all the filtered formats of this feed - * @returns {FilteredFormat[]} - */ - async getFilteredFormats () { - return FilteredFormat.getManyBy('feed', this._id) - } - - /** - * Disable this feed - * @param {string} reason - */ - async disable (reason = 'No reason specified') { - this.disabled = reason - return this.save() - } - - /** - * Enable this feed - */ - async enable () { - this.disabled = undefined - return this.save() - } - - async delete () { - const subscribers = await this.getSubscribers() - const filteredFormats = await this.getFilteredFormats() - const toDelete = subscribers.map(sub => sub.delete()) - .concat(filteredFormats.map(f => f.delete())) - await Promise.all(toDelete) - return super.delete() - } - - /** - * @param {Set} [supporterGuilds] - * @returns {boolean} - */ - async hasFastSupporterSchedule (supporterGuilds) { - if (supporterGuilds) { - return supporterGuilds.has(this.guild) - } - // Check new subscriptions and prioritize it first over legacy Supporters - const guild = new Guild(this.guild) - const subscriber = await guild.getSubscription() - if (subscriber) { - return !subscriber.slowRate - } - const supporter = await guild.getSupporter() - if (supporter) { - return !(await supporter.hasSlowRate()) - } - } - - /** - * @param {Schedule[]} [schedules] - All stored schedules - * @param {Set} [fastSupporterGuilds] - Array of supporter guild IDs - * @returns {Schedule} - */ - async determineSchedule (schedules, fastSupporterGuilds) { - if (!schedules) { - schedules = await Schedule.getAll() - } - - // Take care of our supporters first - if (Supporter.enabled && !this.url.includes('feed43')) { - const isSupporter = await this.hasFastSupporterSchedule(fastSupporterGuilds) - if (isSupporter) { - return Supporter.schedule - } - } - - for (const schedule of schedules) { - if (schedule.name === 'default') { - continue - } - // Check if non-default schedules first - // Feed IDs - const feedIDs = schedule.feeds // Potential array - if (feedIDs && feedIDs.includes(this._id)) { - return schedule - } - // Keywords - const sKeywords = schedule.keywords - if (!sKeywords) { - continue - } - const someMatch = sKeywords.some(word => this.url.includes(word)) - if (someMatch) { - return schedule - } - } - - return schedules.find(s => s.name === 'default') - } - - /** - * @param {string} scheduleName - * @param {Object[]} articleList - */ - async initializeArticles (scheduleName, articleList) { - if (!Base.isMongoDatabase) { - return - } - try { - const docsByURL = await databaseFuncs.getAllDocuments(scheduleName, undefined, [this.url]) - const docs = docsByURL[this.url] || [] - if (docs.length > 0) { - // The collection already exists from a previous addition, no need to initialize - return - } - const comparisons = [...this.ncomparisons, ...this.pcomparisons] - const insert = [] - const meta = { - scheduleName, - feedURL: this.url - } - for (const article of articleList) { - const formatted = databaseFuncs.formatArticleForDatabase(article, comparisons, meta) - insert.push(formatted) - } - await databaseFuncs.insertDocuments(insert) - } catch (err) { - const log = createLogger() - log.error(err, `Unable to initialize ${this.url}`) - } - } - - /** - * Fetch the feed and see if it connects before actually saving - */ - async testAndSave () { - const { articleList } = await FeedFetcher.fetchFeed(this.url) - const feeds = await Feed.getManyBy('guild', this.guild) - for (const feed of feeds) { - if (feed.url === this.url && feed.channel === this.channel) { - const err = new Error('Already exists for this channel.') - err.code = 40003 - err.type = 'resolved' - throw err - } - } - if (this.title === 'Unknown' && articleList.length > 0 && articleList[0].meta.title) { - this.title = articleList[0].meta.title - } - if (this.title.length > 200) { - this.title = this.title.slice(0, 200) + '...' - } - const allArticlesHaveDates = articleList.every(article => !!article.pubdate) - if (!allArticlesHaveDates) { - this.checkDates = false - } - await this.save() - if (articleList.length > 0) { - const schedule = await this.determineSchedule() - await this.initializeArticles(schedule.name, articleList) - } - } - - validate () { - FilteredFormat.pruneEmbeds(this.embeds) - super.validate() - } - - static get Model () { - return FeedModel.Model - } -} - -module.exports = Feed diff --git a/services/bot/src/structs/db/FilterBase.js b/services/bot/src/structs/db/FilterBase.js deleted file mode 100644 index c5f6b1921..000000000 --- a/services/bot/src/structs/db/FilterBase.js +++ /dev/null @@ -1,174 +0,0 @@ -const Base = require('./Base.js') - -class FilterBase extends Base { - constructor (data, _saved) { - super(data, _saved) - - /** - * Regular filters - * @type {Object} - */ - this.filters = this.getField('filters', {}) - - /** - * Regex filters - * @type {Object} - */ - this.rfilters = this.getField('rfilters', {}) - } - - pruneFilters () { - const filters = this.filters - for (const key in filters) { - const value = filters[key] - if (!Array.isArray(value) || filters[key].length === 0) { - delete filters[key] - } - } - } - - validate () { - this.pruneFilters() - } - - toObject () { - const filtersMap = new Map() - const rFiltersMap = new Map() - const filters = this.filters - const rfilters = this.rfilters - for (const key in filters) { - filtersMap.set(key, filters[key]) - } - for (const key in rfilters) { - rFiltersMap.set(key, rfilters[key]) - } - return { - filters: filtersMap, - rfilters: rFiltersMap - } - } - - toJSON () { - /** - * Overwrite the filters map from toObject with the - * regular object - */ - return { - ...this.toObject(), - filters: this.filters, - rfilters: this.rfilters - } - } - - /** - * Get the array index of the filter in a category - * @param {string} category - * @param {string} value - * @returns {number} - */ - getFilterIndex (category, value) { - value = value.toLowerCase().trim() - const filters = this.filters[category] - const index = !filters ? -1 : filters.indexOf(value) - return index - } - - /** - * Remove a filter from a category - * @param {string} category - * @param {string} value - */ - async removeFilter (category, value) { - value = value.toLowerCase().trim() - const index = this.getFilterIndex(category, value) - if (index === -1) { - throw new Error(`"${value}" does not exist`) - } - this.filters[category].splice(index, 1) - return this.save() - } - - /** - * Add a filter to a category - * @param {string} category - * @param {string} value - */ - async addFilter (category, value) { - value = value.toLowerCase().trim() - const index = this.getFilterIndex(category, value) - if (index !== -1) { - throw new Error(`"${value}" already exists`) - } - const filters = this.filters - if (!filters[category]) { - filters[category] = [] - } - filters[category].push(value.toLowerCase()) - return this.save() - } - - /** - * Add multiple filters to a category - * @param {string} category - * @param {string[]} values - */ - async addFilters (category, values) { - values = values.map(value => value.toLowerCase().trim()) - const filters = this.filters - for (const value of values) { - const index = this.getFilterIndex(category, value) - if (index !== -1) { - throw new Error(`"${value}" already exists`) - } - } - if (!filters[category]) { - filters[category] = [] - } - filters[category] = filters[category].concat(values) - return this.save() - } - - /** - * Remove multiple filters from a category - * @param {string} category - * @param {string[]} values - */ - async removeFilters (category, values) { - const indexes = values.map(value => this.getFilterIndex(category, value)) - const filtered = indexes.filter(index => index !== -1) - // Sort from highest to lowest - const sorted = filtered.sort((a, b) => b - a) - if (sorted.length > 0) { - for (const index of sorted) { - this.filters[category].splice(index, 1) - } - await this.save() - } - } - - /** - * Remove all filters - */ - async removeAllFilters () { - this.filters = {} - return this.save() - } - - /** - * If any filters exist - * @returns {boolean} - */ - hasFilters () { - return Object.keys(this.filters).length > 0 - } - - /** - * If any regex filters exist - * @returns {boolean} - */ - hasRFilters () { - return Object.keys(this.rfilters).length > 0 - } -} - -module.exports = FilterBase diff --git a/services/bot/src/structs/db/FilteredFormat.js b/services/bot/src/structs/db/FilteredFormat.js deleted file mode 100644 index 426eebf02..000000000 --- a/services/bot/src/structs/db/FilteredFormat.js +++ /dev/null @@ -1,129 +0,0 @@ -const FilterBase = require('./FilterBase.js') -const FilteredFormatModel = require('../../models/FilteredFormat.js') -const embedSchema = require('../../models/common/Embed.js') - -class FilteredFormat extends FilterBase { - /** - * @param {import('mongoose').Model|Object} data - Data - * @param {string} data.text - Text message - * @param {Object[]} data.embeds - Embeds - */ - constructor (data, _saved) { - super(data, _saved) - - /** - * Feed this format belongs to - * @type {string} - */ - this.feed = this.getField('feed') - if (!this.feed) { - throw new TypeError('feed is undefined') - } - - /** - * Text message - * @type {string|undefined} - */ - this.text = this.getField('text') - - /** - * Embed message - * @type {Object[]|undefined} - */ - this.embeds = this.getField('embeds') - - if (!this.text && (!this.embeds || this.embeds.length === 0)) { - throw new Error('text or embeds must be populated') - } - - /** - * Filters for formats - * @type {Object} - */ - this.filters = this.getField('filters') - - /** - * Optional priority to decide which filtered format to use if - * multiple are matched - */ - this.priority = this.getField('priority') - } - - static isPopulatedEmbedField (field) { - if (!field) { - return false - } - const { name, value } = field - if (!name || !value) { - return false - } - return true - } - - static isPopulatedEmbed (embed, pruneFields) { - if (!embed) { - return false - } - const keys = Object.keys(embedSchema) - keys.splice(keys.indexOf('_id'), 1) - keys.splice(keys.indexOf('fields'), 1) - - let filled = false - for (const key of keys) { - filled = filled || !!embed[key] - } - const fields = embed.fields - if (!fields || fields.length === 0) { - return filled - } - for (let i = fields.length - 1; i >= 0; --i) { - const field = fields[i] - const populatedField = this.isPopulatedEmbedField(field) - filled = filled || populatedField - if (!populatedField && pruneFields === true) { - fields.splice(i, 1) - } - } - - return filled - } - - static pruneEmbeds (embeds) { - for (let i = embeds.length - 1; i >= 0; --i) { - const populated = this.isPopulatedEmbed(embeds[i], true) - if (!populated) { - embeds.splice(i, 1) - } - } - } - - toObject () { - return { - ...super.toObject(), - feed: this.feed, - text: this.text, - embeds: this.embeds, - priority: this.priority - } - } - - async validate () { - if (!this.embeds) { - return - } - this.constructor.pruneEmbeds(this.embeds) - const embeds = this.embeds - for (const embed of embeds) { - const timestamp = embed.timestamp - if (timestamp && timestamp !== 'article' && timestamp !== 'now') { - throw new Error('Timestamp can only be article or now') - } - } - } - - static get Model () { - return FilteredFormatModel.Model - } -} - -module.exports = FilteredFormat diff --git a/services/bot/src/structs/db/KeyValue.js b/services/bot/src/structs/db/KeyValue.js deleted file mode 100644 index 1e192f6fc..000000000 --- a/services/bot/src/structs/db/KeyValue.js +++ /dev/null @@ -1,41 +0,0 @@ -const KeyValueModel = require('../../models/KeyValue.js') -const Base = require('./Base.js') - -class KeyValue extends Base { - constructor (data, _saved) { - super(data, _saved) - - if (!this._id) { - throw new TypeError('_id is undefined') - } - - /** - * Value - * @type {any} - */ - this.value = this.getField('value') - if (this.value === undefined) { - throw new TypeError('value is undefined') - } - } - - static get keys () { - return { - FEED_CONFIG: 'feedConfig', - SUPPORTER_CONFIG: 'supporterConfig' - } - } - - toObject () { - return { - _id: this._id, - value: this.value - } - } - - static get Model () { - return KeyValueModel.Model - } -} - -module.exports = KeyValue diff --git a/services/bot/src/structs/db/Patron.js b/services/bot/src/structs/db/Patron.js deleted file mode 100644 index b6c7c3c53..000000000 --- a/services/bot/src/structs/db/Patron.js +++ /dev/null @@ -1,199 +0,0 @@ -const path = require('path') -const Base = require('./Base.js') -const PatronModel = require('../../models/Patron.js') -const getConfig = require('../../config.js').get - -class Patron extends Base { - constructor (data, _saved) { - super(data, _saved) - - // _id is patron member ID - if (!this._id) { - throw new TypeError('_id is undefined') - } - - /** - * Due to inconsistencies in Patreon's API, the pledge status doesn't seem to be up to date. - * - * This is a temporary measure until payments are moved off of Patreon. - * - * @type {string|undefined} - */ - this.statusOverride = this.getField('statusOverride') - - /** - * @type {string} - */ - this.status = this.statusOverride || this.getField('status') - - /** - * @type {string} - */ - this.lastCharge = this.getField('lastCharge') - - /** - * Due to inconsistencies in Patreon's API, the pledge lifetime doesn't seem to be up to date. - * - * This is a temporary measure until payments are moved off of Patreon. - * - * @type {number|undefined} - */ - this.pledgeLifetimeOverride = this.getField('pledgeLifetimeOverride') - - /** - * @type {number} - */ - this.pledgeLifetime = this.pledgeLifetimeOverride || this.getField('pledgeLifetime') - if (this.pledgeLifetime === undefined) { - throw new TypeError('pledgeLifetime is undefined') - } - - /** - * Due to Patreon's unmaintained API, some people who paid 5 USD in a different currency such - * as 4.5 euros will not receive the 5 USD benefits since Patreon reports the pldge as 4.5. - * - * This is a temporary measure until payments are moved off of Patreon. - * - * @type {number|undefined} - */ - this.pledgeOverride = this.getField('pledgeOverride') - - /** - * @type {number} - */ - this.pledge = this.pledgeOverride || this.getField('pledge') - if (this.pledge === undefined) { - throw new TypeError('pledge is undefined') - } - - /** - * @type {string} - */ - this.discord = this.getField('discord') - - /** - * @type {string} - */ - this.name = this.getField('name') - - /** - * @type {string} - */ - this.email = this.getField('email') - } - - static get SLOW_THRESHOLD () { - return 500 - } - - static async refresh () { - const filePath = path.join(path.resolve(), 'settings', 'api.js') - return require(filePath)() - } - - toObject () { - return { - _id: this._id, - status: this.status, - lastCharge: this.lastCharge, - pledgeLifetimeOverride: this.pledgeLifetimeOverride, - pledgeLifetime: this.pledgeLifetime, - pledgeOverride: this.pledgeOverride, - pledge: this.pledge, - discord: this.discord, - name: this.name, - email: this.email - } - } - - /** - * @returns {boolean} - */ - isActive () { - // As Patreon's API degrades, their status can be active even though it's not - in that case, the pledge may be 0 - const active = this.status === Patron.STATUS.ACTIVE && this.pledge > 0 - if (active) { - return true - } - const declined = this.status === Patron.STATUS.DECLINED - if (!declined || !this.lastCharge) { - return false - } - const now = new Date(new Date().toUTCString()) - const last = new Date(this.lastCharge) - const diffTime = Math.abs(last - now) - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) - return diffDays < 4 - } - - /** - * @returns {number} - */ - determineMaxFeeds () { - const config = getConfig() - if (!this.isActive()) { - return config.feeds.max - } - const pledge = this.pledge - if (pledge >= 2000) { - return 140 - } - if (pledge >= 1500) { - return 105 - } - if (pledge >= 1000) { - return 70 - } - if (pledge >= 500) { - return 35 - } - if (pledge >= 250) { - return 15 - } - return config.feeds.max - } - - /** - * @returns {number} - */ - determineMaxGuilds () { - if (!this.isActive()) { - return 1 - } - if (this.pledgeLifetime >= 2500) { - return 4 - } - if (this.pledgeLifetime >= 1500) { - return 3 - } - if (this.pledgeLifetime >= 500) { - return 2 - } - return 1 - } - - /** - * @returns {number} - */ - determineWebhook () { - if (!this.isActive()) { - return false - } else { - return this.pledge >= 100 - } - } - - static get STATUS () { - return { - ACTIVE: 'active_patron', - FORMER: 'former_patron', - DECLINED: 'declined_patron' - } - } - - static get Model () { - return PatronModel.Model - } -} - -module.exports = Patron diff --git a/services/bot/src/structs/db/Profile.js b/services/bot/src/structs/db/Profile.js deleted file mode 100644 index 027756c72..000000000 --- a/services/bot/src/structs/db/Profile.js +++ /dev/null @@ -1,153 +0,0 @@ -const Base = require('./Base.js') -const ProfileModel = require('../../models/Profile.js') - -class Profile extends Base { - /** - * @param {import('mongoose').Model|Object} data - Data - * @param {string} data._id - Guild ID - * @param {string} data.name - Guild name - * @param {string} data.dateFormat - Date format for date placeholders - * @param {string} data.dateLanguage - Date language for date placeholders - * @param {string} data.timezone - Date timezone for date placeholders - * @param {string} data.prefix - Prefix for commands - * @param {string} data.locale - Locale for commands - */ - constructor (data, _saved) { - super(data, _saved) - - if (!this._id) { - throw new Error('Undefined _id') - } - - /** - * The guild's name - * @type {string} - */ - this.name = this.getField('name') - if (!this.name) { - throw new Error('Undefined name') - } - - /** - * Date format for date placeholders - * @type {string} - */ - this.dateFormat = this.getField('dateFormat') - - /** - * Date language for date placeholders - * @type {string} - */ - this.dateLanguage = this.getField('dateLanguage') - - /** - * Date timezone for date placeholders - * @type {string} - */ - this.timezone = this.getField('timezone') - - /** - * Prefix for commands - * @type {string} - */ - this.prefix = this.getField('prefix') - - /** - * Locale for commands - * @type {string} - */ - this.locale = this.getField('locale') - - /** - * User IDs to send alerts to - * @type {string[]} - */ - this.alert = this.getField('alert', []) - } - - /** - * Store all guild prefixes for reference by commands - */ - static async populatePrefixes () { - const profiles = await this.getAll() - this.prefixes.clear() - for (const profile of profiles) { - if (profile.prefix) { - this.setPrefix(profile._id, profile.prefix) - } - } - } - - /** - * Cache a guild's prefix - * @param {string} guildID - * @param {string} prefix - */ - static setPrefix (guildID, prefix) { - this.prefixes.set(guildID, prefix) - } - - /** - * Delete a guild's prefix from cache - * @param {string} guildID - */ - static deletePrefix (guildID) { - this.prefixes.delete(guildID) - } - - /** - * Get a guild's cached prefix - * @param {string} guildID - */ - static getPrefix (guildID) { - return this.prefixes.get(guildID) - } - - /** - * Getter for this._id since _id and id should be - * the same. - * @returns {string} - */ - get id () { - return this.getField('_id') - } - - /** - * Save and cache a new prefix - * @param {string} prefix - */ - async setPrefixAndSave (prefix) { - this.prefix = prefix - await this.save() - if (prefix) { - Profile.setPrefix(this._id, prefix) - } else { - Profile.deletePrefix(this._id) - } - } - - toObject () { - return { - _id: this._id, - name: this.name, - dateFormat: this.dateFormat, - dateLanguage: this.dateLanguage, - timezone: this.timezone, - prefix: this.prefix, - locale: this.locale, - alert: this.alert - } - } - - static get Model () { - return ProfileModel.Model - } -} - -/** - * Cached prefixes of all guilds, used for commands - * @type {Map} - */ -Profile.prefixes = new Map() - -module.exports = Profile diff --git a/services/bot/src/structs/db/Schedule.js b/services/bot/src/structs/db/Schedule.js deleted file mode 100644 index 9c6a1f617..000000000 --- a/services/bot/src/structs/db/Schedule.js +++ /dev/null @@ -1,55 +0,0 @@ -const Base = require('./Base.js') -const ScheduleModel = require('../../models/Schedule.js') - -class Schedule extends Base { - constructor (data, _saved) { - super(data, _saved) - - /** - * Schedule name - * @type {number} - */ - this.name = this.getField('name') - if (!this.name) { - throw new Error('name is undefined') - } - - /** - * Refresh rate - * @type {number} - */ - this.refreshRateMinutes = this.getField('refreshRateMinutes') - if (!this.refreshRateMinutes) { - throw new Error('refreshRateMinutes is undefined') - } else if (isNaN(this.refreshRateMinutes)) { - throw new Error('refreshRateMinutes must be a number') - } - - /** - * Keywords to match URLs by - * @type {string[]} - */ - this.keywords = this.getField('keywords', []) - - /** - * Feed IDs to explicitly match to this schedule - * @type {string[]} - */ - this.feeds = this.getField('feeds', []) - } - - toObject () { - return { - name: this.name, - refreshRateMinutes: this.refreshRateMinutes, - keywords: this.keywords, - feeds: this.feeds - } - } - - static get Model () { - return ScheduleModel.Model - } -} - -module.exports = Schedule diff --git a/services/bot/src/structs/db/ScheduleStats.js b/services/bot/src/structs/db/ScheduleStats.js deleted file mode 100644 index f42e0eabf..000000000 --- a/services/bot/src/structs/db/ScheduleStats.js +++ /dev/null @@ -1,59 +0,0 @@ -const Base = require('./Base.js') -const ScheduleStatsModel = require('../../models/ScheduleStats') - -class ScheduleStats extends Base { - constructor (data, _saved) { - super(data, _saved) - - if (!this._id) { - throw new Error('Undefined _id') - } - - /** - * Number of feeds on this shard - * @type {number} - */ - this.feeds = this.getField('feeds', 0) - - /** - * Average cycle time of default schedule - * @type {number} - */ - this.cycleTime = this.getField('cycleTime', 0) - - /** - * Average number of failures of default schedule - * @type {number} - */ - this.cycleFails = this.getField('cycleFails', 0) - - /** - * Number of unique feed URLs in default schedule - * @type {number} - */ - this.cycleURLs = this.getField('cycleURLs', 0) - - /** - * ISO Date string - * @type {string} - */ - this.lastUpdated = this.getField('lastUpdated', 'N/A') - } - - toObject () { - return { - _id: this._id, - feeds: this.feeds, - cycleTime: this.cycleTime, - cycleFails: this.cycleFails, - cycleURLs: this.cycleURLs, - lastUpdated: this.lastUpdated - } - } - - static get Model () { - return ScheduleStatsModel.Model - } -} - -module.exports = ScheduleStats diff --git a/services/bot/src/structs/db/Subscriber.js b/services/bot/src/structs/db/Subscriber.js deleted file mode 100644 index c9c4ad110..000000000 --- a/services/bot/src/structs/db/Subscriber.js +++ /dev/null @@ -1,72 +0,0 @@ -const FilterBase = require('./FilterBase.js') -const SubscriberModel = require('../../models/Subscriber.js') - -class Subscriber extends FilterBase { - constructor (data, _saved) { - super(data, _saved) - - /** - * ID of the feed this subscriber belongs to - * @type {string} - */ - this.feed = this.getField('feed') - if (!this.feed) { - throw new Error('feed is undefined') - } - - /** - * Either a role or user ID - * @type {string} - */ - this.id = this.getField('id') - if (!this.id) { - throw new Error('id is undefined') - } - - /** - * Either user or role - * @type {'role'|'user'} - */ - this.type = this.getField('type') - if (this.type !== Subscriber.TYPES.USER && this.type !== Subscriber.TYPES.ROLE) { - throw new Error('type must be "user" or "role"') - } - } - - static get TYPES () { - return { - USER: 'user', - ROLE: 'role' - } - } - - toObject () { - return { - ...super.toObject(), - feed: this.feed, - id: this.id, - type: this.type - } - } - - async validate () { - await super.validate() - if (this.type !== 'role' && this.type !== 'user') { - throw new Error('type must be "user" or "role"') - } - } - - getMentionText () { - if (this.type === Subscriber.TYPES.USER) { - return `<@${this.id}>` - } else if (this.type === Subscriber.TYPES.ROLE) { - return `<@&${this.id}>` - } - } - - static get Model () { - return SubscriberModel.Model - } -} - -module.exports = Subscriber diff --git a/services/bot/src/structs/db/Supporter.js b/services/bot/src/structs/db/Supporter.js deleted file mode 100644 index 910d67d1d..000000000 --- a/services/bot/src/structs/db/Supporter.js +++ /dev/null @@ -1,272 +0,0 @@ -const Base = require('./Base') -const Patron = require('./Patron.js') -const SupporterModel = require('../../models/Supporter.js') -const getConfig = require('../../config.js').get - -class Supporter extends Base { - constructor (data, _saved) { - super(data, _saved) - - // _id is the discord id - if (!this._id) { - throw new TypeError('_id is undefined') - } - - /** - * @type {boolean} - */ - this.patron = this.getField('patron') - - /** - * Only referenced for non-patrons - * @type {boolean} - */ - this.webhook = this.getField('webhook') - - /** - * Only referenced for non-patrons - * @type {number} - */ - this.maxGuilds = this.getField('maxGuilds') - - /** - * Only referenced for non-patrons - * @type {number} - */ - this.maxFeeds = this.getField('maxFeeds') - - /** - * @type {string[]} - */ - this.guilds = this.getField('guilds', []) - - /** - * @type {string} - */ - this.expireAt = this.getField('expireAt') - - /** - * @type {string} - */ - this.comment = this.getField('comment') - - /** - * @type {boolean} - */ - this.slowRate = this.getField('slowRate') - } - - static get keys () { - return { - ENABLED: '_vip', - RESTRICTED: '_vipRestricted', - REFRESH_RATE: '_vipRefreshRateMinutes' - } - } - - static get schedule () { - const config = getConfig() - return { - name: 'supporter', - refreshRateMinutes: config[this.keys.REFRESH_RATE] - } - } - - /** - * @returns {boolean} - */ - static get enabled () { - const config = getConfig() - return config[this.keys.ENABLED] === true - } - - /** - * If only supporters may have access to this bot. - * @returns {true} - */ - static get restricted () { - const config = getConfig() - return config[this.keys.RESTRICTED] === true - } - - /** - * @returns {Supporter[]} - */ - static async getValidSupporters () { - if (!Supporter.enabled) { - return [] - } - const supporters = await this.getAll() - const promises = [] - for (const supporter of supporters) { - promises.push(supporter.isValid()) - } - const statuses = await Promise.all(promises) - return supporters.filter((supporter, index) => statuses[index]) - } - - /** - * @returns {string[]} - */ - static async getValidGuilds () { - const guilds = [] - const validSupporters = await this.getValidSupporters() - validSupporters.forEach(supporter => { - supporter.guilds.forEach(id => guilds.push(id)) - }) - return guilds - } - - static async getValidFastGuilds () { - const guilds = [] - const validSupporters = await this.getValidSupporters() - const hasSlowRates = await Promise.all(validSupporters.map(supporter => supporter.hasSlowRate())) - validSupporters - .filter((supporter, index) => !hasSlowRates[index]) - .forEach((supporter, index) => { - supporter.guilds.forEach(id => guilds.push(id)) - }) - return guilds - } - - /** - * @param {string} guildId - * @returns {Supporter|null} - */ - static async getValidSupporterOfGuild (guildId) { - if (!Supporter.enabled) { - return null - } - /** - * @type {Supporter[]} - */ - const supporters = await this.getManyByQuery({ - guilds: { - $in: [guildId] - } - }) - const validStatuses = await Promise.all(supporters.map(s => s.isValid())) - const validSupporter = supporters.find((supporter, index) => validStatuses[index]) - return validSupporter || null - } - - /** - * @param {string} guildId - * @returns {boolean} - */ - static async hasValidGuild (guildId) { - const guilds = await this.getValidGuilds() - return guilds.includes(guildId) - } - - async findActivePatron () { - const patrons = await Patron.getManyBy('discord', this._id) - return patrons.find(patron => patron.isActive()) - } - - /** - * @returns {number} - */ - async getMaxGuilds () { - let patron - if (this.patron) { - patron = await this.findActivePatron() - } - if (patron) { - return patron.determineMaxGuilds() - } else { - return this.maxGuilds || 1 - } - } - - /** - * @returns {number} - */ - async getMaxFeeds () { - const config = getConfig() - let patron - if (this.patron) { - patron = await this.findActivePatron() - } - if (patron) { - return patron.determineMaxFeeds() - } else { - if (this.maxFeeds) { - if (config.feeds.max > this.maxFeeds) { - return config.feeds.max - } else { - return this.maxFeeds - } - } else { - return config.feeds.max - } - } - } - - /** - * @returns {boolean} - */ - async getWebhookAccess () { - let patron - if (this.patron) { - patron = await this.findActivePatron() - } - if (patron) { - return patron.determineWebhook() - } else { - return this.webhook - } - } - - /** - * @returns {boolean} - */ - async isValid () { - if (!this.patron) { - if (!this.expireAt) { - return true - } else { - const now = new Date() - const expire = new Date(this.expireAt) - return now.getTime() < expire.getTime() - } - } else { - return !!(await this.findActivePatron()) - } - } - - async hasSlowRate () { - // Slow rates may override patron settings - if (this.patron && this.slowRate !== true) { - /** @type {import('./Patron')|undefined} */ - const patron = await this.findActivePatron() - if (patron) { - return patron.pledge < Patron.SLOW_THRESHOLD - } else { - return this.slowRate - } - } else { - return this.slowRate - } - } - - toObject () { - return { - _id: this._id, - patron: this.patron, - webhook: this.webhook, - maxGuilds: this.maxGuilds, - maxFeeds: this.maxFeeds, - guilds: this.guilds, - expireAt: this.expireAt, - comment: this.comment, - slowRate: this.slowRate - } - } - - static get Model () { - return SupporterModel.Model - } -} - -module.exports = Supporter diff --git a/services/bot/src/structs/errors/ArticleMessageError.js b/services/bot/src/structs/errors/ArticleMessageError.js deleted file mode 100644 index ed4701f62..000000000 --- a/services/bot/src/structs/errors/ArticleMessageError.js +++ /dev/null @@ -1,12 +0,0 @@ -class ArticleMessageError extends Error { - constructor (guild, ...params) { - super(...params) - this.guild = guild - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, ArticleMessageError) - } - } -} - -module.exports = ArticleMessageError diff --git a/services/bot/src/structs/errors/FeedParserError.js b/services/bot/src/structs/errors/FeedParserError.js deleted file mode 100644 index 84efe2853..000000000 --- a/services/bot/src/structs/errors/FeedParserError.js +++ /dev/null @@ -1,13 +0,0 @@ -class FeedParserError extends Error { - constructor (code, ...params) { - super(...params) - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, FeedParserError) - } - - if (code) this.code = code - } -} - -module.exports = FeedParserError diff --git a/services/bot/src/structs/errors/MenuOptionError.js b/services/bot/src/structs/errors/MenuOptionError.js deleted file mode 100644 index 714170646..000000000 --- a/services/bot/src/structs/errors/MenuOptionError.js +++ /dev/null @@ -1,11 +0,0 @@ -class MenuOptionError extends Error { - constructor (...params) { - super(...params) - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, MenuOptionError) - } - } -} - -module.exports = MenuOptionError diff --git a/services/bot/src/structs/errors/RequestError.js b/services/bot/src/structs/errors/RequestError.js deleted file mode 100644 index 5a364fdf3..000000000 --- a/services/bot/src/structs/errors/RequestError.js +++ /dev/null @@ -1,13 +0,0 @@ -class RequestError extends Error { - constructor (code, message, cloudflare = false) { - super(message) - - this.cloudflare = cloudflare - if (Error.captureStackTrace) { - Error.captureStackTrace(this, RequestError) - } - this.code = code - } -} - -module.exports = RequestError diff --git a/services/bot/src/structs/errors/http/BadRequestError.js b/services/bot/src/structs/errors/http/BadRequestError.js deleted file mode 100644 index b936ac6fd..000000000 --- a/services/bot/src/structs/errors/http/BadRequestError.js +++ /dev/null @@ -1,12 +0,0 @@ -class BadRequestError extends Error { - constructor (feedId, ...params) { - super(...params) - this.feedId = feedId - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, BadRequestError) - } - } -} - -module.exports = BadRequestError diff --git a/services/bot/src/tests/commands/unit_compare.test.js b/services/bot/src/tests/commands/unit_compare.test.js deleted file mode 100644 index bc7c91822..000000000 --- a/services/bot/src/tests/commands/unit_compare.test.js +++ /dev/null @@ -1,32 +0,0 @@ -const compare = require('../../commands/compare') - -describe('commands/compare', () => { - describe('getValidInputs', () => { - it('returns the the properties correctly', () => { - const input = 'rss.compare +title -description +author +date' - const parts = compare.getValidInputs(input) - expect(parts).toEqual([ - '+title', - '-description', - '+author', - '+date' - ]) - }) - it('ignores invalid input', () => { - const input = 'rss.compare +title description author +date' - const parts = compare.getValidInputs(input) - expect(parts).toEqual([ - '+title', - '+date' - ]) - }) - it('ignores duplicates', () => { - const input = 'rss.compare +title +date +title' - const parts = compare.getValidInputs(input) - expect(parts).toEqual([ - '+title', - '+date' - ]) - }) - }) -}) diff --git a/services/bot/src/tests/maintenance/int_checkArticleIndexes.test.js b/services/bot/src/tests/maintenance/int_checkArticleIndexes.test.js deleted file mode 100644 index 537fe8fff..000000000 --- a/services/bot/src/tests/maintenance/int_checkArticleIndexes.test.js +++ /dev/null @@ -1,112 +0,0 @@ -const mongoose = require('mongoose') -const initialize = require('../../initialization/index.js') -const checkIndexes = require('../../maintenance/checkIndexes.js') -const Article = require('../../models/Article.js') -const DeliveryRecord = require('../../models/DeliveryRecord.js') -const config = require('../../config.js') -const dbName = 'test_int_checkIndexes' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true, - autoIndex: false -} - -jest.mock('../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - } - }) -})) - -describe('Int::maintenance/checkIndexes', function () { - /** @type {import('mongoose').Connection} */ - let con - let articleCollectionName - let deliveryRecordCollectionName - const indexName = 'addedAt_1' - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await initialize.setupModels(con) - articleCollectionName = Article.Model.collection.name - deliveryRecordCollectionName = DeliveryRecord.Model.collection.name - }) - beforeEach(async function () { - await con.db.dropDatabase() - await con.collection(articleCollectionName).insertOne({ - addedAt: new Date() - }) - await con.collection(deliveryRecordCollectionName).insertOne({ - addedAt: new Date() - }) - await con.collection(articleCollectionName).dropIndexes() - await con.collection(deliveryRecordCollectionName).dropIndexes() - }) - it('drops the the index if articles expire is 0', async function () { - await con.collection(articleCollectionName).createIndex({ - addedAt: 1 - }, { - expireAfterSeconds: 86400 * 1 // 1 day - }) - await expect(con.collection(articleCollectionName) - .indexExists(indexName)).resolves.toEqual(true) - jest.spyOn(config, 'get').mockReturnValue({ - database: { - uri: 'mongodb://', - articlesExpire: 0 - } - }) - await checkIndexes.checkIndexes() - await expect(con.collection(articleCollectionName) - .indexExists(indexName)).resolves.toEqual(false) - }) - it('changes the index if config changed', async function () { - const index = { - addedAt: 1 - } - const indexOptions = { - expireAfterSeconds: 86400 * 3 // 3 days - } - await con.collection(articleCollectionName) - .createIndex(index, indexOptions) - jest.spyOn(config, 'get').mockReturnValue({ - database: { - uri: 'mongodb://', - articlesExpire: 10 - } - }) - await checkIndexes.checkIndexes() - const newIndexes = await con.collection(articleCollectionName).indexes() - expect(newIndexes.find(idx => idx.name === indexName).expireAfterSeconds) - .toEqual(86400 * 10) - }) - it.skip('creates the index if articles expire is greater than 0', async function () { - jest.spyOn(config, 'get').mockReturnValue({ - database: { - uri: 'mongodb://', - articlesExpire: 9 - } - }) - await con.collection(articleCollectionName).dropIndexes('addedAt_1') - await con.collection(deliveryRecordCollectionName).dropIndexes('addedAt_1') - await checkIndexes.checkArticleIndexes() - await expect(con.collection(articleCollectionName) - .indexExists(indexName)).resolves.toEqual(true) - }) - it.skip('creates the index if delivery records expire is greater than 0', async function () { - jest.spyOn(config, 'get').mockReturnValue({ - database: { - uri: 'mongodb://', - deliveryRecordsExpire: 9 - } - }) - await checkIndexes.checkDeliveryRecordsIndexes() - await expect(con.collection(deliveryRecordCollectionName) - .indexExists(indexName)).resolves.toEqual(true) - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/maintenance/unit_checkLimit.test.js b/services/bot/src/tests/maintenance/unit_checkLimit.test.js deleted file mode 100644 index 17919150b..000000000 --- a/services/bot/src/tests/maintenance/unit_checkLimit.test.js +++ /dev/null @@ -1,282 +0,0 @@ -process.env.TEST_ENV = true -const Guild = require('../../structs/Guild.js') -const Supporter = require('../../structs/db/Supporter.js') -const checkLimits = require('../../maintenance/checkLimits.js') -const config = require('../../config.js') - -jest.mock('../../config.js', () => ({ - get: jest.fn(() => ({ - feeds: { - max: 2 - } - })) -})) -jest.mock('../../structs/db/Supporter.js') -jest.mock('../../structs/Guild.js') -jest.mock('../../util/ipc.js') - -describe('Unit::maintenance/checkLimits', function () { - beforeEach(function () { - jest.restoreAllMocks() - Supporter.enabled = false - Supporter.getValidSupporters - .mockResolvedValue([]) - Guild.getAllUniqueFeedLimits - .mockResolvedValue(new Map()) - }) - afterEach(function () { - config.get.mockReturnValue({ - feeds: { - max: 2 - } - }) - }) - describe('limits', function () { - it('calls enable on disabled feeds for feeds under limit', async function () { - const feeds = [{ - guild: 'a' - }, { - guild: 'a', - disabled: 'Exceeded feed limit', - enable: jest.fn(), - disable: jest.fn() - }, { - guild: 'a', - disabled: 'Exceeded feed limit', - enable: jest.fn(), - disable: jest.fn() - }] - jest.spyOn(Guild, 'getAllUniqueFeedLimits') - .mockResolvedValue(new Map()) - await checkLimits.limits(feeds) - expect(feeds[1].enable).toHaveBeenCalledTimes(1) - expect(feeds[1].disable).not.toHaveBeenCalled() - expect(feeds[2].enable).not.toHaveBeenCalled() - expect(feeds[2].disable).not.toHaveBeenCalled() - }) - it('calls disable on enabled feeds for feeds over limit', async function () { - const feeds = [{ - // Enabled - guild: 'a', - disabled: undefined, - disable: jest.fn(), - enable: jest.fn() - }, { - // Enabled - guild: 'a', - disabled: undefined, - disable: jest.fn(), - enable: jest.fn() - }, { - // Enabled, over limit - guild: 'a', - disabled: undefined, - disable: jest.fn(), - enable: jest.fn() - }, { - // Disabled, nothing should be called - guild: 'a', - disabled: 'Exceeded feed limit', - disable: jest.fn(), - enable: jest.fn() - }] - jest.spyOn(Guild, 'getAllUniqueFeedLimits') - .mockResolvedValue(new Map()) - await checkLimits.limits(feeds) - expect(feeds[0].disable).not.toHaveBeenCalled() - expect(feeds[0].enable).not.toHaveBeenCalled() - expect(feeds[1].disable).not.toHaveBeenCalled() - expect(feeds[1].enable).not.toHaveBeenCalled() - expect(feeds[2].enable).not.toHaveBeenCalled() - expect(feeds[2].disable).toHaveBeenCalledTimes(1) - expect(feeds[3].enable).not.toHaveBeenCalled() - expect(feeds[3].disable).not.toHaveBeenCalled() - }) - it('calls enable and disable on feeds under and over limits', async function () { - const feeds = [{ - // Enabled - guild: 'a', - disabled: undefined - }, { - // Disabled, should be enabled since it's the 2nd feed - guild: 'a', - disabled: 'Exceeded feed limit', - enable: jest.fn(), - disable: jest.fn() - }, { - // Enabled, should be disabled since the limit is 2 - guild: 'a', - disabled: undefined, - enable: jest.fn(), - disable: jest.fn() - }] - jest.spyOn(Guild, 'getAllUniqueFeedLimits') - .mockResolvedValue(new Map()) - await checkLimits.limits(feeds) - expect(feeds[1].disable).not.toHaveBeenCalled() - expect(feeds[1].enable).toHaveBeenCalledTimes(1) - expect(feeds[2].enable).not.toHaveBeenCalled() - expect(feeds[2].disable).toHaveBeenCalledTimes(1) - }) - it('calls returns the number of enabled and disabled', async function () { - const feeds = [{ - // Enabled - guild: 'a', - disabled: undefined - }, { - // Disabled, should be enabled since it's the 2nd feed - guild: 'a', - disabled: 'Exceeded feed limit', - enable: jest.fn().mockResolvedValue('feed1'), - disable: jest.fn() - }, { - // Enabled, should be disabled since the limit is 2 - guild: 'a', - disabled: undefined, - enable: jest.fn(), - disable: jest.fn().mockResolvedValue('feed2') - }] - jest.spyOn(Guild, 'getAllUniqueFeedLimits') - - .mockResolvedValue(new Map()) - const result = await checkLimits.limits(feeds) - expect(result.enabled).toEqual(['feed1']) - expect(result.disabled).toEqual(['feed2']) - }) - it('uses the supporter limit if available for guild', async function () { - const feeds = [{ - // Enabled - guild: 'a', - disabled: undefined - }, { - // Enabled - guild: 'a', - disabled: undefined - }, { - // Enabled, over limit - guild: 'a', - disabled: undefined, - disable: jest.fn() - }, { - // Disabled, nothing should be called - guild: 'a', - disabled: 'Exceeded feed limit', - disable: jest.fn() - }] - jest.spyOn(Guild, 'getAllUniqueFeedLimits') - - .mockResolvedValue(new Map([['a', 3]])) - await checkLimits.limits(feeds) - expect(feeds[2].disable).not.toHaveBeenCalledTimes(1) - expect(feeds[3].disable).not.toHaveBeenCalled() - }) - it('enables all and disables none if limit is 0', async function () { - config.get.mockReturnValue({ - feeds: { - max: 0 - } - }) - const feeds = [{ - // Enabled - guild: 'a', - disabled: undefined, - disable: jest.fn() - }, { - // Enabled - guild: 'a', - disabled: undefined, - disable: jest.fn() - }, { - // Enabled, over limi - guild: 'a', - disabled: 'Exceeded feed limit', - enable: jest.fn(), - disable: jest.fn() - }, { - // Disabled, enable should be called - guild: 'a', - disabled: 'Exceeded feed limit', - enable: jest.fn(), - disable: jest.fn() - }, { - // Disabled, enable should not be called for unrelated reason - guild: 'a', - disabled: 'ttt', - enable: jest.fn(), - disable: jest.fn() - }] - jest.spyOn(Guild, 'getAllUniqueFeedLimits') - - .mockResolvedValue(new Map()) - await checkLimits.limits(feeds) - expect(feeds[0].disable).not.toHaveBeenCalled() - expect(feeds[1].disable).not.toHaveBeenCalled() - expect(feeds[2].enable).toHaveBeenCalledTimes(1) - expect(feeds[2].disable).not.toHaveBeenCalled() - expect(feeds[3].enable).toHaveBeenCalledTimes(1) - expect(feeds[3].disable).not.toHaveBeenCalled() - expect(feeds[4].enable).not.toHaveBeenCalled() - expect(feeds[4].disable).not.toHaveBeenCalled() - }) - it('only calls enable for feeds with \'Exceeded feed limit\' reason', async function () { - const feeds = [{ - // Disabled, under limit but should not be enabled be limit checks - guild: 'a', - disabled: 'Random reason', - enable: jest.fn() - }, { - // Enabled - guild: 'a', - disabled: undefined - }, { - // Disabled, should be enabled by limit checks - guild: 'a', - disabled: 'Exceeded feed limit', - enable: jest.fn() - }, { - // Disabled, should not be enabled by limit checks - guild: 'a', - disabled: 'Exceeded feed limit', - enable: jest.fn() - }] - jest.spyOn(Guild, 'getAllUniqueFeedLimits') - - .mockResolvedValue(new Map([['a', 3]])) - await checkLimits.limits(feeds) - expect(feeds[0].enable).not.toHaveBeenCalled() - expect(feeds[2].enable).toHaveBeenCalled() - expect(feeds[3].enable).toHaveBeenCalled() - }) - it('enables the correct ones when intertwined with unrelated disables', async function () { - const feeds = [{ - // Disabled, unrelated reason - do not enable - guild: 'a', - disabled: 'Random reason', - enable: jest.fn() - }, { - // Disabled, unrelated reason - do not enable - guild: 'a', - disabled: 'random reasonf', - enable: jest.fn() - }, { - // Disabled, should be enabled by limit checks - guild: 'a', - disabled: 'Exceeded feed limit', - enable: jest.fn() - }, { - // Disabled, should not be enabled by limit checks - guild: 'a', - disabled: 'Exceeded feed limit', - enable: jest.fn() - }] - jest.spyOn(Guild, 'getAllUniqueFeedLimits') - - .mockResolvedValue(new Map()) - await checkLimits.limits(feeds) - expect(feeds[0].enable).not.toHaveBeenCalled() - expect(feeds[1].enable).not.toHaveBeenCalled() - expect(feeds[2].enable).toHaveBeenCalled() - expect(feeds[3].enable).toHaveBeenCalled() - }) - }) -}) diff --git a/services/bot/src/tests/maintenance/unit_checkPermissions.test.js b/services/bot/src/tests/maintenance/unit_checkPermissions.test.js deleted file mode 100644 index 03c284595..000000000 --- a/services/bot/src/tests/maintenance/unit_checkPermissions.test.js +++ /dev/null @@ -1,200 +0,0 @@ -process.env.TEST_ENV = true -const FLAGS = require('discord.js').Permissions.FLAGS -const Feed = require('../../structs/db/Feed.js') -const checkPermissions = require('../../maintenance/checkPermissions.js') - -jest.mock('../../structs/db/Feed.js') -jest.mock('../../util/ipc.js') - -describe('Unit::maintenance/checkPermission', function () { - const permissionsIn = jest.fn() - const bot = { - shard: { - ids: [] - }, - channels: { - cache: { - has: jest.fn(), - get: () => ({ - guild: { - me: { - permissionsIn - } - } - }) - } - } - } - beforeEach(function () { - Feed.mockReset() - bot.channels.cache.has.mockReset() - permissionsIn.mockReset() - jest.restoreAllMocks() - }) - describe('feeds', function () { - it('only calls check function on feeds with a channel the bot has', async function () { - const spy = jest.spyOn(checkPermissions, 'feed') - .mockResolvedValue() - const feeds = [{ - channel: '1' - }, { - channel: '2' - }, { - channel: '3' - }] - bot.channels.cache.has - .mockReturnValueOnce(true) - .mockReturnValueOnce(false) - .mockReturnValueOnce(true) - await checkPermissions.feeds(bot, feeds) - expect(spy).toHaveBeenCalledWith(feeds[0], bot) - expect(spy).not.toHaveBeenCalledWith(feeds[1], bot) - expect(spy).toHaveBeenCalledWith(feeds[2], bot) - }) - }) - describe('feed', function () { - it('calls disable for undisabled feed for missing view channel message', async function () { - const feed = { - disabled: undefined, - disable: jest.fn(() => Promise.resolve()), - embeds: [] - } - permissionsIn.mockReturnValue(new Set([ - FLAGS.SEND_MESSAGES, - FLAGS.EMBED_LINKS - ])) - const res = await checkPermissions.feed(feed, bot) - expect(feed.disable).toHaveBeenCalledTimes(1) - expect(feed.disable).toHaveBeenCalledWith('Missing permissions VIEW_CHANNEL') - expect(res).toEqual(true) - }) - it('calls disable for undisabled feed for missing send messages', async function () { - const feed = { - disabled: undefined, - disable: jest.fn(() => Promise.resolve()), - embeds: [] - } - permissionsIn.mockReturnValue(new Set([ - FLAGS.VIEW_CHANNEL, - FLAGS.EMBED_LINKS - ])) - const res = await checkPermissions.feed(feed, bot) - expect(feed.disable).toHaveBeenCalledTimes(1) - expect(feed.disable).toHaveBeenCalledWith('Missing permissions SEND_MESSAGES') - expect(res).toEqual(true) - }) - it('calls disable for undisabled feed for missing embed links with embed', async function () { - const feed = { - disabled: undefined, - disable: jest.fn(() => Promise.resolve()), - embeds: [{}] - } - permissionsIn.mockReturnValue(new Set([ - FLAGS.VIEW_CHANNEL, - FLAGS.SEND_MESSAGES - ])) - const res = await checkPermissions.feed(feed, bot) - expect(feed.disable).toHaveBeenCalledTimes(1) - expect(feed.disable).toHaveBeenCalledWith('Missing permissions EMBED_LINKS') - expect(res).toEqual(true) - }) - it('does not call disable for undisabled feed for missing embed links with no embed', async function () { - const feed = { - disabled: undefined, - disable: jest.fn(() => Promise.resolve()), - embeds: [] - } - permissionsIn.mockReturnValue(new Set([ - FLAGS.VIEW_CHANNEL, - FLAGS.SEND_MESSAGES - ])) - const res = await checkPermissions.feed(feed, bot) - expect(feed.disable).not.toHaveBeenCalled() - expect(res).toEqual(false) - }) - it('calls disable for undisabled feed for multiple permissions missing', async function () { - const feed = { - disabled: undefined, - disable: jest.fn(() => Promise.resolve()), - embeds: [] - } - permissionsIn.mockReturnValue(new Set([ - FLAGS.EMBED_LINKS - ])) - const res = await checkPermissions.feed(feed, bot) - expect(feed.disable).toHaveBeenCalledTimes(1) - expect(feed.disable).toHaveBeenCalledWith('Missing permissions SEND_MESSAGES, VIEW_CHANNEL') - expect(res).toEqual(true) - }) - it('changes the disable reason if one of the permission status changes', async function () { - const feed = { - disabled: 'Missing permissions VIEW_CHANNEL', - disable: jest.fn(() => Promise.resolve()), - embeds: [] - } - permissionsIn.mockReturnValue(new Set([ - FLAGS.EMBED_LINKS - ])) - const res = await checkPermissions.feed(feed, bot) - expect(feed.disable).toHaveBeenCalledTimes(1) - expect(feed.disable).toHaveBeenCalledWith('Missing permissions SEND_MESSAGES, VIEW_CHANNEL') - expect(res).toEqual(true) - }) - it('does not call disable if feed is already disabled unrelated to permissions', async function () { - const feed = { - disabled: 'hook hok', - disable: jest.fn(() => Promise.resolve()), - embeds: [] - } - permissionsIn.mockReturnValue(new Set()) - const res = await checkPermissions.feed(feed, bot) - expect(feed.disable).not.toHaveBeenCalled() - expect(res).toEqual(true) - }) - it('does not call disable if feed has webhook', async function () { - const feed = { - disable: jest.fn(() => Promise.resolve()), - embeds: [], - webhook: {} - } - permissionsIn.mockReturnValue(new Set()) - const res = await checkPermissions.feed(feed, bot) - expect(feed.disable).not.toHaveBeenCalled() - expect(res).toEqual(false) - }) - it('enables the feed if all permissions found', async function () { - const feed = { - disabled: 'Missing permissions VIEW_CHANNEL', - disable: jest.fn(() => Promise.resolve()), - enable: jest.fn(() => Promise.resolve()), - embeds: [] - } - permissionsIn.mockReturnValue(new Set([ - FLAGS.EMBED_LINKS, - FLAGS.VIEW_CHANNEL, - FLAGS.SEND_MESSAGES - ])) - const res = await checkPermissions.feed(feed, bot) - expect(feed.disable).not.toHaveBeenCalled() - expect(feed.enable).toHaveBeenCalledTimes(1) - expect(res).toEqual(false) - }) - it('does not enable the feed if the disable reason is unrelated', async function () { - const feed = { - disabled: 'whodat', - disable: jest.fn(() => Promise.resolve()), - enable: jest.fn(() => Promise.resolve()), - embeds: [] - } - permissionsIn.mockReturnValue(new Set([ - FLAGS.EMBED_LINKS, - FLAGS.VIEW_CHANNEL, - FLAGS.SEND_MESSAGES - ])) - const res = await checkPermissions.feed(feed, bot) - expect(feed.disable).not.toHaveBeenCalled() - expect(feed.enable).not.toHaveBeenCalled() - expect(res).toEqual(true) - }) - }) -}) diff --git a/services/bot/src/tests/maintenance/unit_pruneFailRecords.test.js b/services/bot/src/tests/maintenance/unit_pruneFailRecords.test.js deleted file mode 100644 index e5c8b26f7..000000000 --- a/services/bot/src/tests/maintenance/unit_pruneFailRecords.test.js +++ /dev/null @@ -1,67 +0,0 @@ -process.env.TEST_ENV = true -const FailRecord = require('../../structs/db/FailRecord.js') -const pruneFailRecords = require('../../maintenance/pruneFailRecords.js') - -jest.mock('../../structs/db/FailRecord.js') - -describe('Unit::maintenance/pruneFailRecords', function () { - beforeEach(function () { - jest.restoreAllMocks() - }) - afterEach(function () { - FailRecord.getAll.mockReset() - }) - it('deletes the records whose url does not exist', async function () { - const failRecords = [{ - _id: 'a', - delete: jest.fn() - }, { - _id: 'b', - delete: jest.fn() - }, { - _id: 'foo', - delete: jest.fn() - }, { - _id: 'c', - delete: jest.fn() - }] - const feeds = [{ - url: 'a' - }, { - url: 'c' - }, { - url: 'z' - }] - FailRecord.getAll.mockResolvedValue(failRecords) - await pruneFailRecords(feeds) - expect(failRecords[0].delete).not.toHaveBeenCalled() - expect(failRecords[1].delete).toHaveBeenCalledTimes(1) - expect(failRecords[2].delete).toHaveBeenCalledTimes(1) - expect(failRecords[3].delete).not.toHaveBeenCalled() - }) - it('returns the number of deleted failRecords', async function () { - const failRecords = [{ - _id: 'a', - delete: jest.fn() - }, { - _id: 'b', - delete: jest.fn() - }, { - _id: 'foo', - delete: jest.fn() - }, { - _id: 'c', - delete: jest.fn() - }] - const feeds = [{ - url: 'a' - }, { - url: 'c' - }, { - url: 'z' - }] - FailRecord.getAll.mockResolvedValue(failRecords) - const result = await pruneFailRecords(feeds) - expect(result).toEqual(2) - }) -}) diff --git a/services/bot/src/tests/maintenance/unit_pruneFeeds.test.js b/services/bot/src/tests/maintenance/unit_pruneFeeds.test.js deleted file mode 100644 index 845bbd782..000000000 --- a/services/bot/src/tests/maintenance/unit_pruneFeeds.test.js +++ /dev/null @@ -1,105 +0,0 @@ -process.env.TEST_ENV = true -const pruneFeeds = require('../../maintenance/pruneFeeds.js') - -describe('Unit::maintenance/pruneFeeds', function () { - beforeEach(function () { - jest.restoreAllMocks() - }) - it('deletes the feeds whose channel is not in channelIds', async function () { - const feed1 = { - guild: 'a', - channel: 'c1', - delete: jest.fn() - } - const feed2 = { - guild: 'b', - channel: 'c2', - delete: jest.fn() - } - const feed3 = { - guild: 'foo', - channel: 'c3', - delete: jest.fn() - } - const feed4 = { - guild: 'c', - channel: 'c4', - delete: jest.fn() - } - const feeds = [feed1, feed2, feed3, feed4] - const guildIds = new Map([['a', 0], ['b', 0]]) - const channelIds = new Map([['c1', 0], ['c2', 0], ['c3', 0], ['c4', 0]]) - await pruneFeeds(feeds, guildIds, channelIds) - expect(feed1.delete).not.toHaveBeenCalled() - expect(feed2.delete).not.toHaveBeenCalled() - expect(feed3.delete).toHaveBeenCalledTimes(1) - expect(feed4.delete).toHaveBeenCalledTimes(1) - }) - it('slices the deleted feeds from the array', async function () { - const feed1 = { - guild: 'a', - channel: 'c1', - delete: jest.fn() - } - const feed2 = { - guild: 'b', - channel: 'c2', - delete: jest.fn() - } - const feed3 = { - guild: 'c', - channel: 'c3', - delete: jest.fn() - } - const feeds = [feed1, feed2, feed3] - const guildIds = new Map([['a', 0], ['d', 0]]) - const channelIds = new Map([['c1', 0], ['c2', 0], ['c4', 0]]) - await pruneFeeds(feeds, guildIds, channelIds) - expect(feeds).not.toContain(feed2) - }) - it('deletes the feeds whose guild is not in guildIds', async function () { - const feed1 = { - guild: 'a', - channel: 'c1', - delete: jest.fn() - } - const feed2 = { - guild: 'b', - channel: 'c2', - delete: jest.fn() - } - const feed3 = { - guild: 'foo', - channel: 'c3', - delete: jest.fn() - } - const feed4 = { - guild: 'c', - channel: 'c4', - delete: jest.fn() - } - const feeds = [feed1, feed2, feed3, feed4] - const guildIds = new Map([['a', 0], ['c', 0], ['z', 0]]) - const channelIds = new Map([['c1', 0], ['c2', 0], ['c3', 0], ['c4', 0]]) - await pruneFeeds(feeds, guildIds, channelIds) - expect(feed1.delete).not.toHaveBeenCalled() - expect(feed2.delete).toHaveBeenCalledTimes(1) - expect(feed3.delete).toHaveBeenCalledTimes(1) - expect(feed4.delete).not.toHaveBeenCalled() - }) - it('returns the number of deleted feeds', async function () { - const feeds = [{ - guild: 'a', - channel: 'c1', - delete: jest.fn() - }, { - guild: 'b', - channel: 'c2', - delete: jest.fn() - }] - const guildIds = new Map([['b', 0], ['c', 0], ['f', 0]]) - const channelIds = new Map([['c1', 0]]) - const result = await pruneFeeds(feeds, guildIds, channelIds) - expect(result).toEqual(2) - }) -}) diff --git a/services/bot/src/tests/maintenance/unit_pruneFilteredFormats.test.js b/services/bot/src/tests/maintenance/unit_pruneFilteredFormats.test.js deleted file mode 100644 index 486219377..000000000 --- a/services/bot/src/tests/maintenance/unit_pruneFilteredFormats.test.js +++ /dev/null @@ -1,68 +0,0 @@ -process.env.TEST_ENV = true -const FilteredFormat = require('../../structs/db/FilteredFormat.js') -const pruneFilteredFormats = require('../../maintenance/pruneFilteredFormats.js') - -jest.mock('../../structs/db/Feed.js') -jest.mock('../../structs/db/FilteredFormat.js') - -describe('Unit::maintenance/pruneFilteredFormats', function () { - beforeEach(function () { - jest.restoreAllMocks() - }) - afterEach(function () { - FilteredFormat.getAll.mockReset() - }) - it('deletes the formats whose feed does not exist', async function () { - const FilteredFormats = [{ - feed: 'a', - delete: jest.fn() - }, { - feed: 'b', - delete: jest.fn() - }, { - feed: 'foo', - delete: jest.fn() - }, { - feed: 'c', - delete: jest.fn() - }] - const feeds = [{ - _id: 'a' - }, { - _id: 'c' - }, { - _id: 'z' - }] - FilteredFormat.getAll.mockResolvedValue(FilteredFormats) - await pruneFilteredFormats(feeds) - expect(FilteredFormats[0].delete).not.toHaveBeenCalled() - expect(FilteredFormats[1].delete).toHaveBeenCalledTimes(1) - expect(FilteredFormats[2].delete).toHaveBeenCalledTimes(1) - expect(FilteredFormats[3].delete).not.toHaveBeenCalled() - }) - it('returns the number of deleted formats', async function () { - const filteredFormats = [{ - feed: 'a', - delete: jest.fn() - }, { - feed: 'b', - delete: jest.fn() - }, { - feed: 'foo', - delete: jest.fn() - }, { - feed: 'c', - delete: jest.fn() - }] - const feeds = [{ - _id: 'a' - }, { - _id: 'c' - }, { - _id: 'z' - }] - FilteredFormat.getAll.mockResolvedValue(filteredFormats) - const result = await pruneFilteredFormats(feeds) - expect(result).toEqual(2) - }) -}) diff --git a/services/bot/src/tests/maintenance/unit_pruneProfileAlerts.test.js b/services/bot/src/tests/maintenance/unit_pruneProfileAlerts.test.js deleted file mode 100644 index 38a16a879..000000000 --- a/services/bot/src/tests/maintenance/unit_pruneProfileAlerts.test.js +++ /dev/null @@ -1,173 +0,0 @@ -process.env.TEST_ENV = true -const Profile = require('../../structs/db/Profile.js') -const pruneProfileAlerts = require('../../maintenance/pruneProfileAlerts.js') - -jest.mock('../../structs/db/Profile.js') - -describe('Unit::maintenance/pruneProfileAlerts', function () { - beforeEach(function () { - jest.restoreAllMocks() - Profile.mockReset() - }) - it('deletes non-number IDs', async function () { - const profiles = [{ - alert: ['a', '123'], - save: jest.fn() - }, { - alert: ['b'], - save: jest.fn() - }] - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: { - get: jest.fn(() => ({ - members: { - fetch: jest.fn(() => ({})) - } - })) - } - } - } - Profile.getAll.mockResolvedValue(profiles) - await pruneProfileAlerts(bot) - expect(profiles[0].alert).toEqual(['123']) - expect(profiles[1].alert).toEqual([]) - expect(profiles[0].save).toHaveBeenCalled() - expect(profiles[1].save).toHaveBeenCalled() - }) - it('does not call save on unchanged profiles', async function () { - const profiles = [{ - alert: [], - save: jest.fn() - }, { - alert: [], - save: jest.fn() - }] - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: { - get: jest.fn(() => ({ - members: { - fetch: jest.fn(() => ({})) - } - })) - } - } - } - Profile.getAll.mockResolvedValue(profiles) - await pruneProfileAlerts(bot) - expect(profiles[0].save).not.toHaveBeenCalled() - expect(profiles[1].save).not.toHaveBeenCalled() - }) - it('deletes unknown members', async function () { - const profiles = [{ - alert: ['1', '2', '3'], - save: jest.fn() - }, { - alert: ['4'], - save: jest.fn() - }] - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: { - get: jest.fn() - .mockReturnValueOnce({ - members: { - fetch: jest.fn() - .mockResolvedValueOnce({}) - .mockResolvedValueOnce({}) - .mockRejectedValueOnce({ code: 10007 }) - } - }) - .mockReturnValueOnce({ - members: { - fetch: jest.fn() - .mockRejectedValueOnce({ code: 10007 }) - } - }) - } - } - } - Profile.getAll.mockResolvedValue(profiles) - await pruneProfileAlerts(bot) - expect(profiles[0].alert).toEqual(['2', '3']) - expect(profiles[0].save).toHaveBeenCalled() - expect(profiles[1].save).toHaveBeenCalled() - }) - it('works on relevant error codes', async function () { - const profiles = [{ - alert: ['1', '2', '3'], - save: jest.fn() - }] - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: { - get: jest.fn() - .mockReturnValueOnce({ - members: { - fetch: jest.fn() - .mockRejectedValueOnce({ code: 50035 }) - .mockRejectedValueOnce({ code: 10013 }) - .mockRejectedValueOnce({ code: 10007 }) - } - }) - } - } - } - Profile.getAll.mockResolvedValue(profiles) - await pruneProfileAlerts(bot) - expect(profiles[0].alert).toEqual([]) - expect(profiles[0].save).toHaveBeenCalled() - }) - it('throws on unknown error', async function () { - const profiles = [{ - alert: ['1'], - save: jest.fn() - }] - const error = new Error('hsedg') - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: { - get: jest.fn() - .mockReturnValueOnce({ - members: { - fetch: jest.fn() - .mockRejectedValue(error) - } - }) - } - } - } - Profile.getAll.mockResolvedValue(profiles) - await expect(pruneProfileAlerts(bot)) - .rejects.toThrow(error) - expect(profiles[0].alert).toEqual(['1']) - expect(profiles[0].save).not.toHaveBeenCalled() - }) - it('rejects when profile get all fails', async function () { - const error = new Error('wsetg') - Profile.getAll.mockRejectedValue(error) - const bot = { - shard: { - ids: [] - } - } - await expect(pruneProfileAlerts(bot)) - .rejects.toThrow(error) - }) -}) diff --git a/services/bot/src/tests/maintenance/unit_pruneProfiles.test.js b/services/bot/src/tests/maintenance/unit_pruneProfiles.test.js deleted file mode 100644 index 3502d961b..000000000 --- a/services/bot/src/tests/maintenance/unit_pruneProfiles.test.js +++ /dev/null @@ -1,52 +0,0 @@ -process.env.TEST_ENV = true -const Profile = require('../../structs/db/Profile.js') -const pruneProfiles = require('../../maintenance/pruneProfiles.js') - -jest.mock('../../structs/db/Profile.js') - -describe('Unit::maintenance/pruneProfiles', function () { - beforeEach(function () { - jest.restoreAllMocks() - }) - it('deletes the guilds that are not in guildIds', async function () { - const profiles = [{ - _id: 'a', - delete: jest.fn() - }, { - _id: 'b', - delete: jest.fn() - }, { - _id: 'foo', - delete: jest.fn() - }, { - _id: 'c', - delete: jest.fn() - }] - const guildIds = new Map([['a', 0], ['c', 2], ['z', 1]]) - Profile.getAll.mockResolvedValue(profiles) - await pruneProfiles(guildIds) - expect(profiles[0].delete).not.toHaveBeenCalled() - expect(profiles[1].delete).toHaveBeenCalledTimes(1) - expect(profiles[2].delete).toHaveBeenCalledTimes(1) - expect(profiles[3].delete).not.toHaveBeenCalled() - }) - it('returns the number of deleted guilds', async function () { - const profiles = [{ - _id: 'a', - delete: jest.fn() - }, { - _id: 'b', - delete: jest.fn() - }, { - _id: 'foo', - delete: jest.fn() - }, { - _id: 'c', - delete: jest.fn() - }] - const guildIds = new Map([['a', 0], ['c', 1], ['f', 2]]) - Profile.getAll.mockResolvedValue(profiles) - const result = await pruneProfiles(guildIds) - expect(result).toEqual(2) - }) -}) diff --git a/services/bot/src/tests/maintenance/unit_pruneSubscribers.test.js b/services/bot/src/tests/maintenance/unit_pruneSubscribers.test.js deleted file mode 100644 index ef71f5296..000000000 --- a/services/bot/src/tests/maintenance/unit_pruneSubscribers.test.js +++ /dev/null @@ -1,306 +0,0 @@ -process.env.TEST_ENV = true -const Subscriber = require('../../structs/db/Subscriber.js') -const pruneSubscribers = require('../../maintenance/pruneSubscribers.js') - -jest.mock('../../structs/db/Subscriber.js') - -Subscriber.TYPES = { - USER: 'user', - ROLE: 'role' -} - -describe('Unit::maintenance/pruneSubscribers', function () { - beforeEach(function () { - jest.restoreAllMocks() - }) - afterEach(function () { - Subscriber.getAll.mockReset() - }) - it('skips subscribers whose feed does not exist', async function () { - const subscribers = [{ - id: 'id1', - type: 'user', - feed: 'feedA', - delete: jest.fn() - }, { - id: 'id2', - type: 'role', - feed: 'feedA', - delete: jest.fn() - }, { - id: 'id3', - type: 'user', - feed: 'feedB', - delete: jest.fn() - }] - const feeds = [{ - _id: 'feedB', - guild: 'guildA' - }] - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: new Map([['guildA', { - members: { - fetch: async () => ({}) - }, - roles: { - fetch: async () => ({}) - } - }]]) - }, - users: { - fetch: async () => true - } - } - Subscriber.getAll.mockResolvedValue(subscribers) - await pruneSubscribers.pruneSubscribers(bot, feeds) - expect(subscribers[0].delete).not.toHaveBeenCalled() - expect(subscribers[1].delete).not.toHaveBeenCalled() - expect(subscribers[2].delete).not.toHaveBeenCalled() - }) - describe('feed exists', function () { - it('deletes subscribers if their type is invalid', async function () { - const subscribers = [{ - id: 'u1', - type: 'qwt4ry3e5', - feed: 'feedA', - delete: jest.fn() - }, { - id: 'u2', - type: 'qwt4ry3e5', - feed: 'feedA', - delete: jest.fn() - }] - const feeds = [{ - _id: 'feedA', - guild: 'guildA' - }] - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: new Map([['guildA', { - members: { - fetch: jest.fn() - } - }]]) - } - } - Subscriber.getAll.mockResolvedValue(subscribers) - await pruneSubscribers.pruneSubscribers(bot, feeds) - expect(subscribers[0].delete).toHaveBeenCalledTimes(1) - expect(subscribers[1].delete).toHaveBeenCalledTimes(1) - }) - it('does not fetch members of the same ID twice', async function () { - const subscribers = [{ - id: 'u1', - type: Subscriber.TYPES.USER, - feed: 'feedA', - delete: jest.fn() - }, { - id: 'u1', - type: Subscriber.TYPES.USER, - feed: 'feedB', - delete: jest.fn() - }] - const feeds = [{ - _id: 'feedA', - guild: 'guildA' - }, { - _id: 'feedB', - guild: 'guildA' - }] - const guildAMembersFetch = jest.fn() - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: new Map([ - ['guildA', { - members: { - fetch: guildAMembersFetch - } - }] - ]) - } - } - Subscriber.getAll.mockResolvedValue(subscribers) - await pruneSubscribers.pruneSubscribers(bot, feeds) - expect(guildAMembersFetch).toHaveBeenCalledTimes(1) - expect(guildAMembersFetch).toHaveBeenCalledWith(subscribers[0].id) - }) - it('prunes user if they do not exist in bot', async function () { - const subscribers = [{ - id: 'u1', - type: Subscriber.TYPES.USER, - feed: 'feedA', - delete: jest.fn() - }, { - id: 'u2', - type: Subscriber.TYPES.USER, - feed: 'feedA', - delete: jest.fn() - }] - const feeds = [{ - _id: 'feedA', - guild: 'guildA' - }] - const fetchError = { - code: 10013 - } - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: new Map([['guildA', { - members: { - fetch: jest.fn().mockImplementation(async (id) => { - if (id === subscribers[0].id) { - return {} - } else { - throw fetchError - } - }) - } - }]]) - } - } - Subscriber.getAll.mockResolvedValue(subscribers) - await pruneSubscribers.pruneSubscribers(bot, feeds) - expect(subscribers[0].delete).not.toHaveBeenCalled() - expect(subscribers[1].delete).toHaveBeenCalled() - }) - it('prunes roles if they are not in guild that exists in bot', async function () { - const subscribers = [{ - id: 'rsub1', - type: Subscriber.TYPES.ROLE, - feed: 'feedA', - delete: jest.fn() - }, { - id: 'rsub12', - type: Subscriber.TYPES.ROLE, - feed: 'feedA', - delete: jest.fn() - }] - const feeds = [{ - _id: 'feedA', - guild: 'guildA' - }] - const guildA = { - roles: { - cache: { - has: (id) => id === subscribers[0].id - } - } - } - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: new Map([['guildA', guildA]]) - } - } - Subscriber.getAll.mockResolvedValue(subscribers) - await pruneSubscribers.pruneSubscribers(bot, feeds) - expect(subscribers[0].delete).not.toHaveBeenCalled() - expect(subscribers[1].delete).toHaveBeenCalled() - }) - it('prunes roles and users if they both do not exist', async function () { - const subscribers = [{ - id: 'rsub1', - type: Subscriber.TYPES.ROLE, - feed: 'feedA', - delete: jest.fn() - }, { - id: 'usub12', - type: Subscriber.TYPES.USER, - feed: 'feedA', - delete: jest.fn() - }] - const feeds = [{ - _id: 'feedA', - guild: 'guildA' - }] - const memberFetchError = { - code: 10013 - } - const guildA = { - members: { - fetch: jest.fn() - .mockRejectedValue(memberFetchError) - }, - roles: { - cache: { - has: () => false - } - } - } - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: new Map([['guildA', guildA]]) - } - } - Subscriber.getAll.mockResolvedValue(subscribers) - await pruneSubscribers.pruneSubscribers(bot, feeds) - expect(subscribers[1].delete).toHaveBeenCalled() - expect(subscribers[0].delete).toHaveBeenCalled() - }) - it('handles member error codes correctly', async function () { - const subscribers = [{ - id: 'u1', - type: Subscriber.TYPES.USER, - feed: 'feedA', - delete: jest.fn() - }, { - id: 'u2', - type: Subscriber.TYPES.USER, - feed: 'feedA', - delete: jest.fn() - }] - const feeds = [{ - _id: 'feedA', - guild: 'guildA' - }] - const fetchErrorOne = { - code: 10013 - } - const fetchErrorTwo = { - code: 10007 - } - const bot = { - shard: { - ids: [] - }, - guilds: { - cache: new Map([['guildA', { - members: { - fetch: jest.fn() - .mockImplementation(async (id) => { - if (id === subscribers[0].id) { - throw fetchErrorOne - } else if (id === subscribers[1].id) { - throw fetchErrorTwo - } - }) - } - }]]) - - } - } - Subscriber.getAll.mockResolvedValue(subscribers) - await pruneSubscribers.pruneSubscribers(bot, feeds) - expect(subscribers[1].delete).toHaveBeenCalled() - expect(subscribers[0].delete).toHaveBeenCalled() - }) - }) -}) diff --git a/services/bot/src/tests/maintenance/unit_pruneWebhooks.test.js b/services/bot/src/tests/maintenance/unit_pruneWebhooks.test.js deleted file mode 100644 index 8f398da65..000000000 --- a/services/bot/src/tests/maintenance/unit_pruneWebhooks.test.js +++ /dev/null @@ -1,338 +0,0 @@ -process.env.TEST_ENV = true -const Supporter = require('../../structs/db/Supporter.js') -const Guild = require('../../structs/Guild.js') -const pruneWebhooks = require('../../maintenance/pruneWebhooks') - -jest.mock('../../structs/db/Supporter.js') -jest.mock('../../structs/Guild.js') - -describe('Unit::maintenance/pruneWebhooks', function () { - const bot = { - shard: { - ids: [] - }, - channels: { - cache: { - get: jest.fn().mockReturnValue({}) - } - } - } - afterEach(function () { - jest.restoreAllMocks() - Supporter.mockReset() - Supporter.enabled = false - bot.channels.cache.get.mockReturnValue({}) - }) - describe('pruneWebhooks', function () { - it('removes feeds that has reasons to remove', async function () { - const relevantFeeds = [{ - channel: 1, - webhook: { - name: 'a' - }, - save: jest.fn() - }, { - channel: 1, - webhook: { - name: 'b' - }, - save: jest.fn() - }, { - channel: 1, - webhook: { - name: 'c' - }, - save: jest.fn() - }] - const webhookFetchData = new Map([ - [1, {}] - ]) - jest.spyOn(pruneWebhooks, 'getRelevantFeeds') - .mockReturnValue(relevantFeeds) - jest.spyOn(pruneWebhooks, 'fetchChannelWebhooks') - .mockReturnValue(webhookFetchData) - jest.spyOn(pruneWebhooks, 'getRemoveReason') - .mockReturnValueOnce('reason1') - .mockReturnValueOnce('') - .mockReturnValueOnce('reason2') - jest.spyOn(pruneWebhooks, 'getDisableReason') - .mockResolvedValueOnce('') - await pruneWebhooks.pruneWebhooks(bot, []) - expect(relevantFeeds[0].webhook).toBeUndefined() - expect(relevantFeeds[0].save).toHaveBeenCalledTimes(1) - expect(relevantFeeds[1].webhook).toBeDefined() - expect(relevantFeeds[1].save).toHaveBeenCalledTimes(0) - expect(relevantFeeds[2].webhook).toBeUndefined() - expect(relevantFeeds[2].save).toHaveBeenCalledTimes(1) - }) - it('disables the webhooks that should be disabled', async () => { - const relevantFeeds = [{ - channel: 1, - webhook: { - name: 'a' - }, - save: jest.fn() - }, { - channel: 1, - webhook: { - name: 'b' - }, - save: jest.fn() - }] - const webhookFetchData = new Map([ - [1, {}] - ]) - jest.spyOn(pruneWebhooks, 'getRelevantFeeds') - .mockReturnValue(relevantFeeds) - jest.spyOn(pruneWebhooks, 'fetchChannelWebhooks') - .mockReturnValue(webhookFetchData) - jest.spyOn(pruneWebhooks, 'getRemoveReason') - .mockReturnValueOnce('') - .mockReturnValueOnce('') - jest.spyOn(pruneWebhooks, 'getDisableReason') - .mockResolvedValueOnce('') - .mockResolvedValueOnce('disablereason2') - await pruneWebhooks.pruneWebhooks(bot, []) - expect(relevantFeeds[0].webhook.disabled).toBeUndefined() - expect(relevantFeeds[0].save).toHaveBeenCalledTimes(0) - expect(relevantFeeds[1].webhook.disabled).toEqual(true) - expect(relevantFeeds[1].save).toHaveBeenCalledTimes(1) - }) - it('enables the webhooks that should be enabled', async () => { - const relevantFeeds = [{ - channel: 1, - webhook: { - name: 'a', - disabled: true - }, - save: jest.fn() - }, { - channel: 1, - webhook: { - name: 'b', - disabled: true - }, - save: jest.fn() - }] - const webhookFetchData = new Map([ - [1, {}] - ]) - jest.spyOn(pruneWebhooks, 'getRelevantFeeds') - .mockReturnValue(relevantFeeds) - jest.spyOn(pruneWebhooks, 'fetchChannelWebhooks') - .mockReturnValue(webhookFetchData) - jest.spyOn(pruneWebhooks, 'getRemoveReason') - .mockReturnValueOnce('') - .mockReturnValueOnce('') - jest.spyOn(pruneWebhooks, 'getDisableReason') - .mockResolvedValueOnce('') - .mockResolvedValueOnce('') - await pruneWebhooks.pruneWebhooks(bot, []) - expect(relevantFeeds[0].webhook.disabled).toBeUndefined() - expect(relevantFeeds[0].save).toHaveBeenCalledTimes(1) - expect(relevantFeeds[1].webhook.disabled).toBeUndefined() - expect(relevantFeeds[1].save).toHaveBeenCalledTimes(1) - }) - }) - describe('getRelevantFeeds', function () { - it('excludes feeds with no webhooks', function () { - const feeds = [{ - _id: 1, - channel: 'a' - }, { - _id: 2, - webhook: {}, - channel: 'b' - }, { - _id: 3, - channel: 'c' - }] - bot.channels.cache.get - .mockReturnValue({}) - const relevantFeeds = pruneWebhooks.getRelevantFeeds(bot, feeds) - expect(relevantFeeds).toEqual([ - feeds[1] - ]) - }) - it('excludes feeds with missing channel', function () { - const feeds = [{ - _id: 1, - webhook: {}, - channel: 'a' - }, { - _id: 2, - webhook: {}, - channel: 'b' - }, { - _id: 3, - webhook: {}, - channel: 'c' - }] - bot.channels.cache.get - .mockReturnValueOnce({}) - .mockReturnValueOnce({}) - .mockReturnValueOnce() - const relevantFeeds = pruneWebhooks.getRelevantFeeds(bot, feeds) - expect(relevantFeeds).toEqual([ - feeds[0], - feeds[1] - ]) - }) - }) - describe('fetchChannelWebhooks', function () { - it('fetches all webhooks', async function () { - const relevantFeeds = [1, 2, 3] - const channels = [{ - id: 7, - fetchWebhooks: jest.fn().mockResolvedValue('abc') - }, { - id: 8, - fetchWebhooks: jest.fn().mockResolvedValue('def') - }, { - id: 9, - fetchWebhooks: jest.fn().mockResolvedValue('ghi') - }] - bot.channels.cache.get - .mockReturnValueOnce(channels[0]) - .mockReturnValueOnce(channels[1]) - .mockReturnValueOnce(channels[2]) - const returned = await pruneWebhooks.fetchChannelWebhooks(bot, relevantFeeds) - expect(returned.get(7)).toEqual(expect.objectContaining({ - value: 'abc' - })) - expect(returned.get(8)).toEqual(expect.objectContaining({ - value: 'def' - })) - expect(returned.get(9)).toEqual(expect.objectContaining({ - value: 'ghi' - })) - }) - it('returns a map', async function () { - const relevantFeeds = [1] - const channels = [{ - id: 7, - fetchWebhooks: jest.fn().mockResolvedValue('abc') - }] - bot.channels.cache.get - .mockReturnValueOnce(channels[0]) - const returned = await pruneWebhooks.fetchChannelWebhooks(bot, relevantFeeds) - expect(returned).toBeInstanceOf(Map) - }) - it('does not fetch the same channel multiple times', async function () { - const relevantFeeds = [1, 2, 3] - const channel = { - id: 7, - fetchWebhooks: jest.fn().mockResolvedValue('abc') - } - bot.channels.cache.get - .mockReturnValue(channel) - await pruneWebhooks.fetchChannelWebhooks(bot, relevantFeeds) - expect(channel.fetchWebhooks).toHaveBeenCalledTimes(1) - }) - }) - describe('getRemoveReason', function () { - it('returns a populated string for missing webhook', async function () { - const webhookID = 'qwte' - const feed = { - _id: 'abc', - webhook: { - id: webhookID - } - } - const webhookFetchResult = { - status: 'fulfilled', - value: new Map() - } - const result = await pruneWebhooks.getRemoveReason(bot, feed, webhookFetchResult) - expect(result).toEqual('Removing missing webhook from feed abc') - }) - it('returns a populated string for unpermitted webhook', async function () { - const webhookID = 'qwte' - const feed = { - _id: 'abc', - webhook: { - id: webhookID - } - } - const webhookFetchResult = { - status: 'rejected', - reason: { - code: 50013 - } - } - const result = await pruneWebhooks.getRemoveReason(bot, feed, webhookFetchResult) - expect(result).toEqual('Removing unpermitted webhook from feed abc') - }) - it('returns an empty string for valid webhook', async function () { - const webhookID = 'qwte' - const feed = { - _id: 'abc', - webhook: { - id: webhookID - } - } - const webhookFetchResult = { - status: 'fulfilled', - value: new Map([[webhookID, {}]]) - } - const result = await pruneWebhooks.getRemoveReason(bot, feed, webhookFetchResult) - expect(result).toEqual('') - }) - }) - describe('getDisableReason', function () { - it('returns empty string if authorized', async () => { - Supporter.enabled = true - const webhookID = 'qwte' - const feed = { - _id: 'abc', - webhook: { - id: webhookID - } - } - bot.channels.cache.get.mockReturnValue({ - guild: { - id: 'whatever' - } - }) - Guild.prototype.hasSupporterOrSubscriber.mockResolvedValue(true) - const result = await pruneWebhooks.getDisableReason(bot, feed) - expect(result).toEqual('') - }) - it('returns the reason if unauthorized', async () => { - Supporter.enabled = true - const webhookID = 'qwte' - const feed = { - _id: 'abc', - webhook: { - id: webhookID - } - } - bot.channels.cache.get.mockReturnValue({ - guild: { - id: 'whatever' - } - }) - Guild.prototype.hasSupporterOrSubscriber.mockResolvedValue(false) - const result = await pruneWebhooks.getDisableReason(bot, feed) - expect(result).toEqual(`Disabling unauthorized supporter webhook from feed ${feed._id}`) - }) - it('returns empty string if supporter is not enabled', async () => { - Supporter.enabled = false - const webhookID = 'qwte' - const feed = { - _id: 'abc', - webhook: { - id: webhookID - } - } - bot.channels.cache.get.mockReturnValue({ - guild: { - id: 'whatever' - } - }) - Guild.prototype.hasSupporterOrSubscriber.mockResolvedValue(false) - const result = await pruneWebhooks.getDisableReason(bot, feed) - expect(result).toEqual('') - }) - }) -}) diff --git a/services/bot/src/tests/models/int_FilteredFormat.test.js b/services/bot/src/tests/models/int_FilteredFormat.test.js deleted file mode 100644 index 8fb3aeefc..000000000 --- a/services/bot/src/tests/models/int_FilteredFormat.test.js +++ /dev/null @@ -1,73 +0,0 @@ -process.env.TEST_ENV = true -const mongoose = require('mongoose') -const FilteredFormatModel = require('../../models/FilteredFormat.js') -const initialize = require('../../initialization/index.js') -const dbName = 'test_int_filteredformat' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -describe('Int::models/FilteredFormat', function () { - /** @type {import('mongoose').Connection} */ - let con - /** @type {import('mongoose').Collection} */ - let collection - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await con.db.dropDatabase() - await initialize.setupModels(con) - collection = con.db.collection('filtered_formats') - }) - it('saves with filters', async function () { - const feedID = new mongoose.Types.ObjectId() - await con.db.collection('feeds').insertOne({ - _id: feedID - }) - const data = { - feed: feedID, - text: 'hello', - filters: { - title: ['hello', 'world'] - } - } - const filteredFormat = new FilteredFormatModel.Model(data) - await filteredFormat.save() - const found = await collection.findOne({ feed: feedID }) - expect(found).toBeDefined() - expect(found).toEqual(expect.objectContaining(data)) - }) - it('allows multiple filtered formats with same feed', async function () { - const feedID = new mongoose.Types.ObjectId() - await con.db.collection('feeds').insertOne({ - _id: feedID - }) - const data = { - feed: feedID, - text: 'hello', - filters: { - title: ['hello', 'world'] - } - } - const data2 = { - feed: feedID, - text: 'hello2', - filters: { - title: ['hello2', 'world2'] - } - } - const filteredFormat = new FilteredFormatModel.Model(data) - const filteredFormat2 = new FilteredFormatModel.Model(data2) - await Promise.all([ - filteredFormat.save(), - filteredFormat2.save() - ]) - const found = await collection.find({ feed: feedID }).toArray() - expect(found).toHaveLength(2) - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/models/middleware/int_Feed.test.js b/services/bot/src/tests/models/middleware/int_Feed.test.js deleted file mode 100644 index 10989ed92..000000000 --- a/services/bot/src/tests/models/middleware/int_Feed.test.js +++ /dev/null @@ -1,39 +0,0 @@ -const FeedModel = require('../../../models/Feed.js') -const intitialize = require('../../../initialization/index.js') -const mongoose = require('mongoose') - -const dbName = 'test_int_middleware_feed' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -describe('Int::models/middleware/Feed', function () { - let con - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await con.db.dropDatabase() - await intitialize.setupModels(con) - }) - it('throws an error if feed tries to change guild', async function () { - const id = 'wq23etr54ge5hu' - const guildId = new mongoose.Types.ObjectId() - const newGuildId = new mongoose.Types.ObjectId() - await con.db.collection('feeds').insertOne({ - id, - title: 'aedsg', - channel: 'sewry', - url: 'asedwt', - guild: guildId - }) - const feed = await FeedModel.Model.findOne({ id }).exec() - feed.guild = newGuildId.toHexString() - await expect(feed.save()) - .rejects.toThrow('Guild cannot be changed') - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/models/middleware/int_FilteredFormat.test.js b/services/bot/src/tests/models/middleware/int_FilteredFormat.test.js deleted file mode 100644 index c72913b41..000000000 --- a/services/bot/src/tests/models/middleware/int_FilteredFormat.test.js +++ /dev/null @@ -1,56 +0,0 @@ -const FilteredFormatModel = require('../../../models/FilteredFormat.js') -const initialize = require('../../../initialization/index.js') -const mongoose = require('mongoose') - -const dbName = 'test_int_middleware_format' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -describe('Int::models/middleware/FilteredFormat', function () { - let con - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await initialize.setupModels(con) - }) - beforeEach(async () => { - await con.db.dropDatabase() - }) - it('throws an error if the feed does not exist', async function () { - const format = new FilteredFormatModel.Model({ - text: 'ase', - feed: new mongoose.Types.ObjectId().toHexString() - }) - - await expect(format.save()) - .rejects.toThrowError(/specified feed/) - }) - it('throws an error if format tries to change feed', async function () { - const filteredFormatID = new mongoose.Types.ObjectId() - const feedId = new mongoose.Types.ObjectId() - const newFeedId = new mongoose.Types.ObjectId() - await con.db.collection('filtered_formats').insertOne({ - _id: filteredFormatID, - text: 'abc', - feed: feedId - }) - await con.db.collection('feeds').insertOne({ - _id: feedId - }) - await con.db.collection('feeds').insertOne({ - _id: newFeedId - }) - - const doc = await FilteredFormatModel.Model.findOne({ _id: filteredFormatID }) - const format = new FilteredFormatModel.Model(doc, true) - format.feed = newFeedId.toHexString() - await expect(format.save()) - .rejects.toThrow('Feed cannot be changed') - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/models/middleware/int_Subscriber.test.js b/services/bot/src/tests/models/middleware/int_Subscriber.test.js deleted file mode 100644 index f22fe491d..000000000 --- a/services/bot/src/tests/models/middleware/int_Subscriber.test.js +++ /dev/null @@ -1,57 +0,0 @@ -const SubscriberModel = require('../../../models/Subscriber.js') -const initialize = require('../../../initialization/index.js') -const mongoose = require('mongoose') - -const dbName = 'test_int_middleware_subscriber' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -describe('Int::models/middleware/Subscriber', function () { - let con - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await con.db.dropDatabase() - await initialize.setupModels(con) - }) - it('throws an error if the feed does not exist', async function () { - const subscriber = new SubscriberModel.Model({ - id: 'asd', - type: 'role', - feed: new mongoose.Types.ObjectId().toHexString() - }) - - await expect(subscriber.save()) - .rejects.toThrowError(/specified feed/) - }) - it('throws an error if subscriber tries to change feed', async function () { - const id = 'wq23etr54ge5hu' - const feedId = new mongoose.Types.ObjectId() - const newFeedId = new mongoose.Types.ObjectId() - await Promise.all([ - con.db.collection('subscribers').insertOne({ - id, - type: 'role', - feed: feedId - }), - con.db.collection('feeds').insertOne({ - _id: feedId - }), - con.db.collection('feeds').insertOne({ - _id: newFeedId - }) - ]) - - const doc = await SubscriberModel.Model.findOne({ id }) - const subscriber = new SubscriberModel.Model(doc, true) - subscriber.feed = newFeedId.toHexString() - await expect(subscriber.save()) - .rejects.toThrow('Feed cannot be changed') - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/models/middleware/unit_Feed.test.js b/services/bot/src/tests/models/middleware/unit_Feed.test.js deleted file mode 100644 index 97dc9e6e8..000000000 --- a/services/bot/src/tests/models/middleware/unit_Feed.test.js +++ /dev/null @@ -1,37 +0,0 @@ -const middleware = require('../../../models/middleware/Feed.js') - -describe('Unit::models/middleware/Feed', function () { - describe('validate', function () { - it('calls the right Model', async function () { - const model = jest.fn(() => ({ - findById: () => ({ exec: jest.fn(() => 1) }) - })) - await middleware.validate({ model })() - expect(model).toHaveBeenCalledWith('feed') - }) - it('throws an error if guild tries to change', async function () { - const guild = 'wte4ry' - const exec = jest.fn(() => ({ guild })) - const model = jest.fn(() => ({ - findById: () => ({ exec }) - })) - const Doc = { - guild: guild + 1 - } - await expect(middleware.validate({ model }).bind(Doc)()) - .rejects.toThrowError('Guild cannot be changed') - }) - it('does not throw an error for all correct conditions', async function () { - const guild = 'wte4ry' - const exec = jest.fn(async () => ({ guild })) - const model = jest.fn(() => ({ - findById: () => ({ exec }) - })) - const Doc = { - guild - } - await expect(middleware.validate({ model }).bind(Doc)()) - .resolves.toBeUndefined() - }) - }) -}) diff --git a/services/bot/src/tests/models/middleware/unit_Subscriber.test.js b/services/bot/src/tests/models/middleware/unit_Subscriber.test.js deleted file mode 100644 index 43334dd2f..000000000 --- a/services/bot/src/tests/models/middleware/unit_Subscriber.test.js +++ /dev/null @@ -1,56 +0,0 @@ -const middleware = require('../../../models/middleware/Subscriber.js') - -describe('Unit::models/middleware/Subscriber', function () { - describe('validate', function () { - it('calls the right Model', async function () { - const feed = { - equals: () => true - } - const model = jest.fn(() => ({ - findById: () => ({ exec: jest.fn(() => ({ feed })) }) - })) - await middleware.validate({ model })() - expect(model).toHaveBeenCalledWith('feed') - expect(model).toHaveBeenCalledWith('subscriber') - }) - it('throws an error if profile not found', function () { - const model = jest.fn(() => ({ - findById: () => ({ exec: () => null }) - })) - const Doc = { - _id: 123, - feed: 'abc' - } - return expect(middleware.validate({ model }).bind(Doc)()) - .rejects.toThrowError(new Error(`Subscriber's specified feed ${Doc.feed} was not found`)) - }) - it('throws an error if feed tries to change', async function () { - const feed = { - equals: () => false - } - const exec = jest.fn(() => ({ feed })) - const model = jest.fn(() => ({ - findById: () => ({ exec }) - })) - const Doc = { - feed: 'irrelevant' - } - await expect(middleware.validate({ model }).bind(Doc)()) - .rejects.toThrowError('Feed cannot be changed') - }) - it('does not throw an error for all correct conditions', async function () { - const feed = { - equals: () => true - } - const exec = jest.fn(async () => ({ feed })) - const model = jest.fn(() => ({ - findById: () => ({ exec }) - })) - const Doc = { - feed: 'irrelevant' - } - await expect(middleware.validate({ model }).bind(Doc)()) - .resolves.toBeUndefined() - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/__mocks__/BasicBase.js b/services/bot/src/tests/structs/db/__mocks__/BasicBase.js deleted file mode 100644 index b68d625d5..000000000 --- a/services/bot/src/tests/structs/db/__mocks__/BasicBase.js +++ /dev/null @@ -1,10 +0,0 @@ -const Base = require('../../../../structs/db/Base.js') -const MockModel = require('./MockModel.js') - -class BasicBase extends Base { - static get Model () { - return MockModel - } -} - -module.exports = BasicBase diff --git a/services/bot/src/tests/structs/db/__mocks__/Foobar.js b/services/bot/src/tests/structs/db/__mocks__/Foobar.js deleted file mode 100644 index 840ed0741..000000000 --- a/services/bot/src/tests/structs/db/__mocks__/Foobar.js +++ /dev/null @@ -1,16 +0,0 @@ -const mongoose = require('mongoose') - -const FoobarSchema = new mongoose.Schema({ - foo: String, - baz: Number, - undefinedField: String, - array: [String], - object: { - key: String - }, - objectId: mongoose.Types.ObjectId -}) - -const Foobar = mongoose.model('Foobar', FoobarSchema) - -module.exports = Foobar diff --git a/services/bot/src/tests/structs/db/__mocks__/FoobarClass.js b/services/bot/src/tests/structs/db/__mocks__/FoobarClass.js deleted file mode 100644 index 9f1ac0840..000000000 --- a/services/bot/src/tests/structs/db/__mocks__/FoobarClass.js +++ /dev/null @@ -1,30 +0,0 @@ -const Foobar = require('./Foobar.js') -const Base = require('../../../../structs/db/Base.js') - -class FoobarClass extends Base { - constructor (data, _saved) { - super(data, _saved) - - this.foo = this.getField('foo') - this.baz = this.getField('baz', 2) - this.undefinedField = this.getField('undefinedField') - this.object = this.getField('object') - this.array = this.getField('array', []) - } - - toObject () { - return { - foo: this.foo, - baz: this.baz, - undefinedField: this.undefinedField, - object: this.object, - array: this.array - } - } - - static get Model () { - return Foobar - } -} - -module.exports = FoobarClass diff --git a/services/bot/src/tests/structs/db/__mocks__/FoobarFilters.js b/services/bot/src/tests/structs/db/__mocks__/FoobarFilters.js deleted file mode 100644 index 1612697d4..000000000 --- a/services/bot/src/tests/structs/db/__mocks__/FoobarFilters.js +++ /dev/null @@ -1,12 +0,0 @@ -const mongoose = require('mongoose') -const FilterBase = require('../../../../models/common/FilterBase.js') - -const FoobarSchema = new mongoose.Schema({ - foo: String -}) - -FoobarSchema.add(FilterBase) - -const Foobar = mongoose.model('FoobarFilters', FoobarSchema) - -module.exports = Foobar diff --git a/services/bot/src/tests/structs/db/__mocks__/FoobarFiltersClass.js b/services/bot/src/tests/structs/db/__mocks__/FoobarFiltersClass.js deleted file mode 100644 index 670e07907..000000000 --- a/services/bot/src/tests/structs/db/__mocks__/FoobarFiltersClass.js +++ /dev/null @@ -1,23 +0,0 @@ -const FoobarFilters = require('./FoobarFilters.js') -const FilterBase = require('../../../../structs/db/FilterBase.js') - -class FoobarClass extends FilterBase { - constructor (data, _saved) { - super(data, _saved) - - this.foo = this.getField('foo') - } - - toObject () { - return { - ...super.toObject(), - foo: this.foo - } - } - - static get Model () { - return FoobarFilters - } -} - -module.exports = FoobarClass diff --git a/services/bot/src/tests/structs/db/__mocks__/MockModel.js b/services/bot/src/tests/structs/db/__mocks__/MockModel.js deleted file mode 100644 index ea05848b5..000000000 --- a/services/bot/src/tests/structs/db/__mocks__/MockModel.js +++ /dev/null @@ -1,13 +0,0 @@ -const MockModel = jest.fn() -MockModel.prototype.save = jest.fn() -MockModel.findOne = jest.fn(() => ({ exec: async () => Promise.resolve() })) -MockModel.findByIdAndUpdate = jest.fn(() => ({ exec: async () => Promise.resolve() })) -MockModel.findById = jest.fn(() => ({ exec: async () => Promise.resolve() })) -MockModel.find = jest.fn(() => ({ exec: async () => Promise.resolve() })) -MockModel.deleteOne = jest.fn(() => ({ exec: async () => Promise.resolve() })) -MockModel.deleteMany = jest.fn(() => ({ exec: async () => Promise.resolve() })) -MockModel.collection = { - collectionName: 123 -} - -module.exports = MockModel diff --git a/services/bot/src/tests/structs/db/int_BannedFeed.database.test.js b/services/bot/src/tests/structs/db/int_BannedFeed.database.test.js deleted file mode 100644 index 77b924e2a..000000000 --- a/services/bot/src/tests/structs/db/int_BannedFeed.database.test.js +++ /dev/null @@ -1,74 +0,0 @@ -process.env.TEST_ENV = true -const mongoose = require('mongoose') -const initialize = require('../../../initialization/index.js') -const BannedFeed = require('../../../structs/db/BannedFeed.js') -const dbName = 'test_int_bannedfeeds' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - } - }) -})) - -describe('Int::structs/db/BannedFeed Database', function () { - /** @type {import('mongoose').Connection} */ - let con - /** @type {import('mongoose').Collection} */ - let collection - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await con.db.dropDatabase() - await initialize.setupModels(con) - collection = con.db.collection('banned_feeds') - await collection.createIndex({ - urlPattern: 'text' - }) - }) - - describe('findForUrl', () => { - it('does not return a record for a url that matches but for a guild that does not apply', async () => { - const url = 'https://www.reddit.com/r/' - await collection.insertOne({ - url, - guildIds: ['123'] - }) - const record = await BannedFeed.findForUrl(url, '456') - expect(record).toBeNull() - }) - - it('does not return a record if the url does not match', async () => { - const url = 'a' - const guildId = 'guild-id' - await collection.insertOne({ - url: 'b', - guildIds: [] - }) - const record = await BannedFeed.findForUrl(url, guildId) - expect(record).toBeNull() - }) - - it('returns the record for exact matches', async () => { - const url = 'https://www.reddit.com/r/' - const guildId = 'guild-id' - await collection.insertOne({ - url, - guildIds: [guildId] - }) - const record = await BannedFeed.findForUrl(url, guildId) - console.log(await collection.find().toArray()) - expect(record).not.toBeNull() - }) - }) - - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/db/int_Base.database.test.js b/services/bot/src/tests/structs/db/int_Base.database.test.js deleted file mode 100644 index 5f2cd33a5..000000000 --- a/services/bot/src/tests/structs/db/int_Base.database.test.js +++ /dev/null @@ -1,214 +0,0 @@ -process.env.TEST_ENV = true -const Foobar = require('./__mocks__/Foobar.js') -const FoobarClass = require('./__mocks__/FoobarClass.js') -const mongoose = require('mongoose') -const dbName = 'test_int_base' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - } - }) -})) - -describe('Int::structs/db/Base Database', function () { - const collectionName = Foobar.collection.collectionName - beforeAll(async function () { - await mongoose.connect(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - }) - beforeEach(async () => { - await mongoose.connection.db.dropDatabase() - }) - describe('getAll', () => { - it('works', async () => { - const docs = [{ - foo: '1' - }, { - foo: '2' - }, { - foo: '3' - }, { - foo: '4' - }, { - foo: '5' - }] - await mongoose.connection.collection(collectionName).insertMany(docs) - const found = await FoobarClass.getAll() - expect(found.length).toEqual(5) - for (const doc of docs) { - expect(found.findIndex(f => f.foo === doc.foo)).toBeGreaterThan(-1) - } - }) - }) - describe('getAllByPagination', () => { - it.only('works', async () => { - const docs = [{ - foo: '1' - }, { - foo: '2' - }, { - foo: '3' - }, { - foo: '4' - }, { - foo: '5' - }] - await mongoose.connection.collection(collectionName).insertMany(docs) - const found = await FoobarClass.getAllByPagination(2) - expect(found.length).toEqual(5) - for (const doc of docs) { - expect(found.findIndex(f => f.foo === doc.foo)).toBeGreaterThan(-1) - } - }) - }) - it('initializes correctly', async function () { - const initData = { foo: 'qgfdew4' } - const initFoobar = new Foobar(initData) - const doc = await initFoobar.save() - const foobar = new FoobarClass(doc) - expect(foobar.data).toEqual(JSON.parse(JSON.stringify(doc.toObject()))) - expect(foobar.document).toBeInstanceOf(mongoose.Model) - expect(foobar._saved).toEqual(false) - await doc.remove() - }) - it('saves', async function () { - const data = { - foo: '349y5hjt', - baz: 666 - } - const foobar = new FoobarClass(data) - expect(foobar._saved).toEqual(false) - await foobar.save() - expect(foobar.document).toBeInstanceOf(mongoose.Model) - expect(foobar._saved).toEqual(true) - const found = Foobar.findOne(data).exec() - expect(found).toBeDefined() - }) - it('gets', async function () { - const initData = { foo: 'q352tew4', baz: 235 } - const initFoobar = new Foobar(initData) - const doc = await initFoobar.save() - const foobar = await FoobarClass.get(doc._id.toHexString()) - expect(foobar.document).toBeInstanceOf(mongoose.Model) - expect(foobar.data).toEqual(JSON.parse(JSON.stringify(doc.toObject()))) - for (const key in initData) { - expect(foobar[key]).toEqual(initData[key]) - } - }) - it('getsBy', async function () { - const initData1 = { - foo: 'baz', - baz: 1 - } - const initData2 = { - foo: 'bfgjz', - baz: 2 - } - const initData3 = { - foo: 'bfgjz', - baz: 3 - } - await new Foobar(initData1).save() - await new Foobar(initData2).save() - await new Foobar(initData3).save() - const found = await FoobarClass.getBy('foo', 'bfgjz') - expect(found.data).toEqual(expect.objectContaining(initData2)) - }) - it('deletes', async function () { - const initFoobar = new Foobar({ foo: 'abc' }) - const doc = await initFoobar.save() - const foobar = new FoobarClass(doc, true) - await foobar.delete() - const queried = await Foobar.findById(doc._id.toHexString()) - expect(queried).toBeNull() - }) - it('gets many', async function () { - const a = new Foobar({ foo: 'a' }) - const b = new Foobar({ foo: 'b' }) - const saves = await Promise.all([a.save(), b.save()]) - const [id1, id2] = saves.map(doc => doc._id.toHexString()) - const classes = await FoobarClass.getMany([id1, id2]) - expect(classes.length).toEqual(2) - for (const item of classes) { - expect(item).toBeInstanceOf(FoobarClass) - } - expect(classes[0].foo).toEqual('a') - expect(classes[1].foo).toEqual('b') - }) - it('updates', async function () { - const initData = { foo: 'exquisite' } - const initFoobar = new Foobar(initData) - const doc = await initFoobar.save() - const foobar = new FoobarClass(doc, true) - const newFooValue = 'changzz' - foobar.foo = newFooValue - await foobar.save() - const found = await Foobar.findById(initFoobar.id) - expect(found.foo).toEqual(newFooValue) - }) - it('deletes a key on undefined', async function () { - const initData = { foo: 'w49ti093u4j', baz: 987 } - const initFoobar = new Foobar(initData) - const doc = await initFoobar.save() - const foobar = new FoobarClass(doc, true) - foobar.foo = undefined - await foobar.save() - const found = await Foobar.findById(initFoobar.id).lean().exec() - expect(Object.keys(found)).not.toContain('foo') - }) - it('doesn\'t add keys after update', async function () { - const initData = { foo: 'w49t4qwej', baz: 976 } - const foobar = new FoobarClass(initData) - const saved = await foobar.save() - foobar.foo = 'abc' - await foobar.save() - const found = await Foobar.findById(saved._id).lean().exec() - expect(found.nullField).toBeUndefined() - }) - it('doesn\'t set object field when undefined', async function () { - const initData = { foo: 'w44jk', baz: 135749 } - const foobar = new FoobarClass(initData) - const saved = await foobar.save() - const found = await Foobar.findById(saved._id).lean().exec() - expect(Object.keys(found)).not.toContain('object') - }) - it('doesn\'t set object field when undefined after update', async function () { - const initData = { foo: 'w44zj', baz: 136679 } - const foobar = new FoobarClass(initData) - const saved = await foobar.save() - foobar.foo = 'zack' - await foobar.save() - const found = await Foobar.findById(saved._id).lean().exec() - expect(Object.keys(found)).not.toContain('object') - }) - it('sets default empty array', async function () { - const initData = { foo: 'w4h4j', baz: 13111 } - const foobar = new FoobarClass(initData) - expect(foobar.array).toBeInstanceOf(Array) - expect(foobar.array).toHaveLength(0) - }) - it('doesn\'t remove the array when updated', async function () { - const initData = { foo: 'wf44j', baz: 53579 } - const foobar = new FoobarClass(initData) - const saved = await foobar.save() - foobar.foo = 'qwe' - await foobar.save() - const found = await Foobar.findById(saved._id).lean().exec() - expect(Object.keys(found)).toContain('array') - }) - it('autocasts to ObjectId for strings', async function () { - const initData = { objectId: new mongoose.Types.ObjectId().toHexString() } - const foobar = new FoobarClass(initData) - expect(foobar.save()).resolves.toEqual(foobar) - }) - afterAll(async function () { - await mongoose.connection.db.dropDatabase() - await mongoose.connection.close() - }) -}) diff --git a/services/bot/src/tests/structs/db/int_Base.databaseless.test.js b/services/bot/src/tests/structs/db/int_Base.databaseless.test.js deleted file mode 100644 index e668a00a2..000000000 --- a/services/bot/src/tests/structs/db/int_Base.databaseless.test.js +++ /dev/null @@ -1,130 +0,0 @@ -process.env.TEST_ENV = true -const config = require('../../../config.js') -const fs = require('fs') -const path = require('path') -const util = require('util') -const fsReadFile = util.promisify(fs.readFile) -const fsReaddir = util.promisify(fs.readdir) -const fsWriteFile = util.promisify(fs.writeFile) -const fsUnlink = util.promisify(fs.unlink) -const fsRmdir = util.promisify(fs.rmdir) -const FoobarClass = require('./__mocks__/FoobarClass.js') -const Foobar = require('./__mocks__/Foobar.js') - -jest.mock('../../../config.js', () => ({ - get: () => ({ - database: { - uri: '___intbase_tests_' - } - }) -})) - -describe('Int::structs/db/Base Databaseless', function () { - let folderPath = '' - beforeAll(async function () { - folderPath = path.join(config.get().database.uri, Foobar.collection.collectionName) - }) - it('saves', async function () { - const data = { foo: 'zz', baz: 99 } - const foobar = new FoobarClass(data) - expect(foobar._saved).toEqual(false) - const returned = await foobar.save() - expect(foobar._saved).toEqual(true) - const fileName = returned._id - expect(fileName).toBeDefined() - const filePath = path.join(folderPath, `${fileName}.json`) - expect(fs.existsSync(filePath)).toEqual(true) - const read = JSON.parse(await fsReadFile(filePath)) - expect(read).toEqual(expect.objectContaining({ ...data, array: [] })) - await fsUnlink(filePath) - }) - it('saves multiple times correctly', async function () { - const data = { foo: 'z', baz: 'abc' } - const foobar = new FoobarClass(data) - foobar.foo = 'abc' - await foobar.save() - const fileName = foobar._id - const filePath = path.join(folderPath, `${fileName}.json`) - const read = JSON.parse(await fsReadFile(filePath)) - expect(read).toEqual(expect.objectContaining({ - foo: 'abc', - baz: data.baz, - array: [] - })) - }) - it('deletes', async function () { - const data = { foo: 'zzx', baz: 999 } - const _id = 'ghj23tgrehtrgf' - const filePath = path.join(folderPath, `${_id}.json`) - await fsWriteFile(filePath, JSON.stringify(data, null, 2)) - expect(fs.existsSync(filePath)).toEqual(true) - const foobar = new FoobarClass({ ...data, _id }, true) - await foobar.delete() - expect(fs.existsSync(filePath)).toEqual(false) - }) - it('gets many', async function () { - const data = { foo: 'zzx', baz: 999 } - const data2 = { foo: 'zxccb' } - const _id = 'ghj23tgrehtrgf' - const _id2 = 'aedgswrhft' - const filePath = path.join(folderPath, `${_id}.json`) - const filePath2 = path.join(folderPath, `${_id2}.json`) - await Promise.all([ - fsWriteFile(filePath, JSON.stringify(data, null, 2)), - fsWriteFile(filePath2, JSON.stringify(data2, null, 2)) - ]) - expect(fs.existsSync(filePath)).toEqual(true) - expect(fs.existsSync(filePath2)).toEqual(true) - const foobar = new FoobarClass({ ...data, _id }) - const foobar2 = new FoobarClass({ ...data2, _id: _id2 }) - const foobars = await FoobarClass.getMany([_id, _id2]) - expect(foobars).toHaveLength(2) - for (const key in data) { - expect(foobar[key]).toEqual(data[key]) - } - for (const key in data2) { - expect(foobar2[key]).toEqual(data2[key]) - } - await Promise.all([ - fsUnlink(filePath), - fsUnlink(filePath2) - ]) - }) - describe('getsBy', function () { - it('getsBy works', async function () { - const data = { foo: 'zzx', baz: 999 } - const data2 = { foo: 'zxccb' } - const data3 = { foo: 'zxch', jig: 'cc' } - const _id = 'ghj23tgrehtrgf' - const _id2 = 'aedgswrhft' - const _id3 = 'wseg' - const filePath = path.join(folderPath, `${_id}.json`) - const filePath2 = path.join(folderPath, `${_id2}.json`) - const filePath3 = path.join(folderPath, `${_id3}.json`) - await Promise.all([ - fsWriteFile(filePath, JSON.stringify(data, null, 2)), - fsWriteFile(filePath2, JSON.stringify(data2, null, 2)), - fsWriteFile(filePath3, JSON.stringify(data3, null, 2)) - ]) - const result = await FoobarClass.getBy('foo', 'zxch') - expect(result).toBeInstanceOf(FoobarClass) - await Promise.all([ - fsUnlink(filePath), - fsUnlink(filePath2), - fsUnlink(filePath3) - ]) - }) - it('getsBy returns null when not found', async function () { - const result = await FoobarClass.getBy('foFGJNFo', 'zdtjgxch') - expect(result).toBeNull() - }) - }) - afterAll(async function () { - const files = await fsReaddir(folderPath) - if (files.length > 0) { - await Promise.all(files.map(fileName => fsUnlink(path.join(folderPath, fileName)))) - } - await fsRmdir(folderPath) - await fsRmdir(config.get().database.uri) - }) -}) diff --git a/services/bot/src/tests/structs/db/int_Blacklist.database.test.js b/services/bot/src/tests/structs/db/int_Blacklist.database.test.js deleted file mode 100644 index b581b322a..000000000 --- a/services/bot/src/tests/structs/db/int_Blacklist.database.test.js +++ /dev/null @@ -1,62 +0,0 @@ -process.env.TEST_ENV = true -const mongoose = require('mongoose') -const Blacklist = require('../../../structs/db/Blacklist.js') -const initialize = require('../../../initialization/index.js') -const dbName = 'test_int_blacklists' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - } - }) -})) - -describe('Int::structs/db/Blacklist Database', function () { - /** @type {import('mongoose').Connection} */ - let con - /** @type {import('mongoose').Collection} */ - let collection - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await con.db.dropDatabase() - await initialize.setupModels(con) - collection = con.db.collection('blacklists') - }) - it('saves correctly', async function () { - const data = { - _id: '12436', - type: Blacklist.TYPES.USER, - name: 'ahhh' - } - const blacklist = new Blacklist(data) - await blacklist.save() - const doc = await collection.findOne({ _id: data._id }) - expect(doc).toBeDefined() - for (const key in data) { - expect(doc[key]).toEqual(data[key]) - } - }) - it('gets correctly', async function () { - const data = { - _id: 'foozxczdg', - type: Blacklist.TYPES.GUILD, - name: 'srfdetuj6y' - } - await collection.insertOne(data) - const blacklist = await Blacklist.get(data._id) - expect(blacklist).toBeDefined() - for (const key in data) { - expect(blacklist[key]).toEqual(data[key]) - } - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/db/int_FailRecord.database.test.js b/services/bot/src/tests/structs/db/int_FailRecord.database.test.js deleted file mode 100644 index 978716aa0..000000000 --- a/services/bot/src/tests/structs/db/int_FailRecord.database.test.js +++ /dev/null @@ -1,130 +0,0 @@ -process.env.TEST_ENV = true -const config = require('../../../config.js') -const mongoose = require('mongoose') -const FailRecord = require('../../../structs/db/FailRecord.js') -const initialize = require('../../../initialization/index.js') -const dbName = 'test_int_failcounter' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - }, - feeds: { - hoursUntilFail: 24 - } - }) -})) - -function getOldDate (hoursAgo) { - // https://stackoverflow.com/questions/1050720/adding-hours-to-javascript-date-object - const date = new Date() - date.setTime(date.getTime() - hoursAgo * 60 * 60 * 1000) - return date -} - -const oldDate = getOldDate(config.get().feeds.hoursUntilFail + 2) -const recentDate = getOldDate(config.get().feeds.hoursUntilFail - 1) - -describe('Int::structs/db/FailRecord Database', function () { - /** @type {import('mongoose').Connection} */ - let con - /** @type {import('mongoose').Collection} */ - let collection - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await con.db.dropDatabase() - await initialize.setupModels(con) - collection = con.db.collection('fail_records') - }) - describe('static record', function () { - it('creates the doc if url is new', async function () { - const url = 'wst34eygr5ht' - const reason = '23twe4gr' - const record = await FailRecord.record(url, reason) - const date = record.failedAt - const result = await collection.findOne({ - _id: url - }) - expect(result).toBeDefined() - expect(result.failedAt.toISOString()).toEqual(date) - expect(result.reason).toEqual(reason) - await collection.deleteOne({ url }) - }) - it('updates reason and alerted field if it exists for old date', async function () { - const url = 'incdocexist' - const reason = 'q23werf' - await collection.insertOne({ - _id: url, - failedAt: oldDate - }) - await FailRecord.record(url, reason) - const result = await collection.findOne({ - _id: url - }) - expect(result.reason).toEqual(reason) - await collection.deleteOne({ url }) - }) - it('does not change alerted status if not old date', async function () { - const url = 'incdocexistrecent' - await collection.insertOne({ - _id: url, - failedAt: recentDate, - alerted: false - }) - await FailRecord.record(url) - const result = await collection.findOne({ - _id: url - }) - expect(result.alerted).toEqual(false) - await collection.deleteOne({ url }) - }) - }) - describe('static reset', function () { - it('deletes the url if it exists', async function () { - const url = 'incdocreset' - collection.insertOne({ - _id: url - }) - expect(collection.findOne({ _id: url })) - .resolves.toBeDefined() - await FailRecord.reset(url) - expect(collection.findOne({ _id: url })) - .resolves.toBeNull() - }) - }) - describe('static hasFailed', function () { - it('returns true for failed urls', async function () { - const url = 'hasfailed' - await collection.insertOne({ - _id: url, - failedAt: oldDate - }) - await expect(FailRecord.hasFailed(url)) - .resolves.toEqual(true) - }) - it('returns false for not failed urls', async function () { - const url = 'hasnotfailed' - await collection.insertOne({ - _id: url, - count: recentDate - }) - await expect(FailRecord.hasFailed(url)) - .resolves.toEqual(false) - }) - it('returns false for nonexistent urls', async function () { - await expect(FailRecord.hasFailed('asd')) - .resolves.toEqual(false) - }) - }) - - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/db/int_Feed.database.test.js b/services/bot/src/tests/structs/db/int_Feed.database.test.js deleted file mode 100644 index 7355b17a8..000000000 --- a/services/bot/src/tests/structs/db/int_Feed.database.test.js +++ /dev/null @@ -1,337 +0,0 @@ -process.env.TEST_ENV = true -const Feed = require('../../../structs/db/Feed.js') -const Supporter = require('../../../structs/db/Supporter.js') -const FeedModel = require('../../../models/Feed.js') -const FilteredFormatModel = require('../../../models/FilteredFormat.js') -const SubscriberModel = require('../../../models/Subscriber.js') -const mongoose = require('mongoose') -const initialize = require('../../../initialization/index.js') -const dbName = 'test_int_feed' -const config = require('../../../config.js') -const Schedule = require('../../../structs/db/Schedule.js') -const Patron = require('../../../structs/db/Patron.js') -const Guild = require('../../../structs/Guild.js') -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true, - autoIndex: false -} - -jest.mock('../../../config.js', () => ({ - get: jest.fn() -})) - -describe('Int::structs/db/Feed Database', function () { - /** @type {import('mongoose').Connection} */ - let con - /** @type {import('mongoose').Collection} */ - let collection - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await initialize.setupModels(con) - collection = con.db.collection('feeds') - }) - beforeEach(async function () { - jest.resetAllMocks() - await con.db.dropDatabase() - config.get.mockReturnValue({ - database: { - uri: 'mongodb://' - } - }) - }) - describe('getSubscribers', function () { - it('works', async function () { - const feedId = new mongoose.Types.ObjectId() - const subscriberData = { - type: 'role', - id: '23', - feed: feedId - } - const subscriberData2 = { - type: 'role', - id: '234', - feed: feedId - } - const feedData = { - title: 'absgrfc', - url: 'asdffjy', - guild: 'asdfyghfj', - channel: 'sdxgdhj', - _id: feedId - } - await con.collection('subscribers').insertMany([ - subscriberData, - subscriberData2 - ]) - await con.db.collection('feeds').insertOne(feedData) - const feed = await Feed.get(feedId.toHexString()) - const subscribers = await feed.getSubscribers() - expect(subscribers).toHaveLength(2) - expect(subscribers[0].data).toEqual(expect.objectContaining(JSON.parse(JSON.stringify(subscriberData)))) - expect(subscribers[1].data).toEqual(expect.objectContaining(JSON.parse(JSON.stringify(subscriberData2)))) - }) - }) - describe('determineSchedule', function () { - it('returns supporter schedule for supporters', async function () { - config.get.mockReturnValue({ - _vip: true, - database: { - uri: 'mongodb://' - }, - apis: { - pledge: {} - }, - feeds: {} - }) - const guildID = 'w246y3r5eh' - const schedules = [{ - name: 'default', - refreshRateMinutes: 99 - }] - const supporters = [{ - _id: 'a', - guilds: [guildID] - }] - await con.db.collection(Schedule.Model.collection.name).insertMany(schedules) - await con.db.collection(Supporter.Model.collection.name).insertMany(supporters) - const feedData = { - url: 'asdf', - guild: guildID, - channel: 'sdxgdh' - } - const feed = new Feed(feedData) - const schedule = await feed.determineSchedule() - expect(schedule).toEqual(Supporter.schedule) - }) - it('does not returns supporter schedule for slow supporters', async function () { - config.get.mockReturnValue({ - _vip: true, - database: { - uri: 'mongodb://' - } - }) - const guildID = 'w246y3r5eh' - const schedules = [{ - name: 'default', - refreshRateMinutes: 99 - }] - const supporters = [{ - _id: 'a', - guilds: [guildID], - slowRate: true - }] - jest.spyOn(Guild.prototype, 'getSubscription').mockResolvedValue(null) - await con.db.collection(Schedule.Model.collection.name).insertMany(schedules) - await con.db.collection(Supporter.Model.collection.name).insertMany(supporters) - const feedData = { - url: 'asdf', - guild: guildID, - channel: 'sdxgdh' - } - const feed = new Feed(feedData) - const schedule = await feed.determineSchedule() - expect(schedule).toEqual(expect.objectContaining({ - name: 'default' - })) - }) - it('does not returns supporter schedule for ineligible patron', async function () { - config.get.mockReturnValue({ - _vip: true, - database: { - uri: 'mongodb://' - } - }) - const guildID = 'w246y3r5eh' - const schedules = [{ - name: 'default', - refreshRateMinutes: 99 - }] - const supporters = [{ - _id: 'a', - guilds: [guildID], - patron: true - }] - const patron = { - discord: supporters[0]._id, - status: Patron.STATUS.ACTIVE, - pledge: Patron.SLOW_THRESHOLD - 1, - pledgeLifetime: 0 - } - await con.db.collection(Schedule.Model.collection.name).insertMany(schedules) - await con.db.collection(Supporter.Model.collection.name).insertMany(supporters) - await con.db.collection(Patron.Model.collection.collectionName).insertOne(patron) - const feedData = { - url: 'asdf', - guild: guildID, - channel: 'sdxgdh' - } - const feed = new Feed(feedData) - const schedule = await feed.determineSchedule() - expect(schedule).toEqual(expect.objectContaining({ - name: 'default' - })) - }) - it('does not returns supporter schedule for eligible patron', async function () { - config.get.mockReturnValue({ - _vip: true, - database: { - uri: 'mongodb://' - } - }) - const guildID = 'w246y3r5eh' - const schedules = [{ - name: 'default', - refreshRateMinutes: 99 - }] - const supporters = [{ - _id: 'a', - guilds: [guildID], - patron: true - }] - const patron = { - discord: supporters[0]._id, - status: Patron.STATUS.ACTIVE, - pledge: Patron.SLOW_THRESHOLD, - pledgeLifetime: 0 - } - await con.db.collection(Schedule.Model.collection.name).insertMany(schedules) - await con.db.collection(Supporter.Model.collection.name).insertMany(supporters) - await con.db.collection(Patron.Model.collection.collectionName).insertOne(patron) - const feedData = { - url: 'asdf', - guild: guildID, - channel: 'sdxgdh' - } - const feed = new Feed(feedData) - const schedule = await feed.determineSchedule() - expect(schedule).toEqual(Supporter.schedule) - }) - it('returns supporter schedule if supporter guilds are given', async function () { - config.get.mockReturnValue({ - _vip: true, - database: { - uri: 'mongodb://' - } - }) - const guildID = 'w246y3r5eh' - const schedules = [{ - name: 'default', - refreshRateMinutes: 99 - }] - const supporterGuilds = new Set([guildID]) - await con.db.collection(Schedule.Model.collection.name).insertMany(schedules) - const feedData = { - url: 'asdf', - guild: guildID, - channel: 'sdxgdh' - } - const feed = new Feed(feedData) - const schedule = await feed.determineSchedule(undefined, supporterGuilds) - expect(schedule).toEqual(Supporter.schedule) - }) - it('returns the right schedule with keywords', async function () { - const feedURL = 'hello world' - const schedules = [{ - name: 'default', - refreshRateMinutes: 99 - }, { - name: 'bloopy', - refreshRateMinutes: 22, - keywords: ['hello'] - }] - await con.db.collection(Schedule.Model.collection.name).insertMany([...schedules]) - const feedData = { - url: feedURL, - guild: 'guildID', - channel: 'sdxgdh' - } - const feed = new Feed(feedData) - const schedule = await feed.determineSchedule() - expect(schedule).toEqual(expect.objectContaining({ - name: 'bloopy' - })) - }) - }) - it('saves and updates with filters', async function () { - const guild = 'swrye57' - await con.db.collection('guilds').insertOne({ - _id: guild - }) - const feedData = { - title: 'abc', - url: 'asdf', - guild, - channel: 'sdxgdh', - filters: { - title: ['a', 'b'] - } - } - const feed = new Feed(feedData) - await feed.save() - const found = await collection.findOne({ - _id: new mongoose.Types.ObjectId(feed._id) - }) - expect(found.filters).toEqual(feedData.filters) - feed.filters.description = [] - feed.filters.description.push('a') - delete feed.filters.title - await feed.save() - const foundAgain = await collection.findOne({ - _id: new mongoose.Types.ObjectId(feed._id) - }) - expect(foundAgain.filters).toEqual({ - description: ['a'] - }) - }) - it('deletes associated format and subscribers on delete', async function () { - const guildId = new mongoose.Types.ObjectId() - const feedId = new mongoose.Types.ObjectId() - const db = con.db - await Promise.all([ - db.collection('guilds').insertOne({ - _id: guildId.toHexString() - }), - db.collection('filtered_formats').insertOne({ - text: 'sde', - feed: feedId - }), - db.collection('subscribers').insertOne({ - id: 'af', - type: 'role', - feed: feedId - }), - db.collection('subscribers').insertOne({ - id: 'aed', - type: 'role', - feed: feedId - }), - collection.insertOne({ - _id: feedId, - guild: guildId.toHexString(), - title: 'asd', - channel: 'se', - url: 'srfhy' - }) - ]) - - const doc = await FeedModel.Model.findById(feedId).exec() - await expect(FilteredFormatModel.Model.find({ feed: feedId.toHexString() })) - .resolves.toHaveLength(1) - await expect(SubscriberModel.Model.find({ feed: feedId.toHexString() })) - .resolves.toHaveLength(2) - const feed = new Feed(doc, true) - await feed.delete() - await expect(db.collection('subscribers').find({ feed: feedId }) - .toArray()) - .resolves.toHaveLength(0) - await expect(db.collection('filtered_formats').find({ feed: feedId }) - .toArray()) - .resolves.toHaveLength(0) - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/db/int_FilterBase.database.test.js b/services/bot/src/tests/structs/db/int_FilterBase.database.test.js deleted file mode 100644 index c05e9d71b..000000000 --- a/services/bot/src/tests/structs/db/int_FilterBase.database.test.js +++ /dev/null @@ -1,73 +0,0 @@ -process.env.TEST_ENV = true -const mongoose = require('mongoose') -const FoobarFiltersModel = require('./__mocks__/FoobarFilters.js') -const FoobarFilters = require('./__mocks__/FoobarFiltersClass.js') -const dbName = 'test_int_filterbase' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - } - }) -})) - -describe('Int::structs/db/FilterBase Database', function () { - beforeAll(async function () { - await mongoose.connect(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - }) - beforeEach(async function () { - await mongoose.connection.db.dropDatabase() - }) - describe('constructor', function () { - it('initializes filters as an empty object', function () { - const foobar = new FoobarFilters() - expect(foobar.filters).toEqual({}) - }) - }) - describe('toObject', function () { - it('adds filters to the object as a map', function () { - const filters = { - be: [1, 2, 3], - good: ['a', 'b'] - } - const initialize = { - foo: 'abc', - filters - } - const foobar = new FoobarFilters(initialize) - const returned = foobar.toObject() - expect(returned.foo).toEqual(initialize.foo) - expect(returned.filters).toBeInstanceOf(Map) - returned.filters.forEach((value, key) => { - expect(value).toEqual(filters[key]) - }) - }) - }) - it('saves filters correctly', async function () { - const filters = { - title: ['abc'], - description: ['1', '2', '3'] - } - const foobar = new FoobarFilters({ filters }) - const saved = await foobar.save() - const found = await FoobarFiltersModel.findById(saved._id).lean().exec() - expect(found.filters).toEqual(filters) - foobar.filters.title = [] - foobar.filters.description.splice(1, 1) - await foobar.save() - const foundNew = await FoobarFiltersModel.findById(saved._id).lean().exec() - expect(foundNew.filters).toEqual({ - description: ['1', '3'] - }) - }) - afterAll(async function () { - await mongoose.connection.db.dropDatabase() - await mongoose.connection.close() - }) -}) diff --git a/services/bot/src/tests/structs/db/int_KeyValue.database.test.js b/services/bot/src/tests/structs/db/int_KeyValue.database.test.js deleted file mode 100644 index f6ce72e4f..000000000 --- a/services/bot/src/tests/structs/db/int_KeyValue.database.test.js +++ /dev/null @@ -1,81 +0,0 @@ -process.env.TEST_ENV = true -const mongoose = require('mongoose') -const KeyValue = require('../../../structs/db/KeyValue.js') -const initialize = require('../../../initialization/index.js') -const dbName = 'test_int_keyvalue' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true, - autoIndex: false -} - -jest.mock('../../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - } - }) -})) - -describe('Int::structs/db/KeyValue Database', function () { - /** @type {import('mongoose').Connection} */ - let con - /** @type {import('mongoose').Collection} */ - let collection - let collectionName - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await initialize.setupModels(con) - collectionName = KeyValue.Model.collection.collectionName - collection = con.db.collection(collectionName) - }) - beforeEach(async function () { - await con.db.dropDatabase() - }) - it('saves correctly', async function () { - const data = { - _id: '12436', - value: 'ahhh' - } - const kv = new KeyValue(data) - await kv.save() - const doc = await collection.findOne({ _id: data._id }) - expect(doc).toBeDefined() - for (const key in data) { - expect(doc[key]).toEqual(data[key]) - } - }) - it('does not allow keys to be written over', async function () { - const data = { - _id: '12436', - value: 'ahhh' - } - const kv = new KeyValue(data) - await kv.save() - const data2 = { - _id: data._id, - value: 'ahh2' - } - const kv2 = new KeyValue(data2) - await expect(kv2.save()).rejects.toThrow(expect.objectContaining({ - message: expect.stringContaining('duplicate key') - })) - }) - it('gets correctly', async function () { - const data = { - _id: 'foozxczdg', - value: 'srfdetuj6y' - } - await collection.insertOne(data) - const kv = await KeyValue.get(data._id) - expect(kv).toBeDefined() - for (const key in data) { - expect(kv[key]).toEqual(data[key]) - } - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/db/int_Patron.database.test.js b/services/bot/src/tests/structs/db/int_Patron.database.test.js deleted file mode 100644 index cc3a637cd..000000000 --- a/services/bot/src/tests/structs/db/int_Patron.database.test.js +++ /dev/null @@ -1,80 +0,0 @@ -process.env.TEST_ENV = true -const mongoose = require('mongoose') -const Patron = require('../../../structs/db/Patron.js') -const initialize = require('../../../initialization/index.js') -const dbName = 'test_int_patrons' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - } - }) -})) - -describe('Int::structs/db/Patron Database', function () { - /** @type {import('mongoose').Connection} */ - let con - /** @type {import('mongoose').Collection} */ - let collection - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await initialize.setupModels(con) - collection = con.db.collection('patrons') - }) - beforeEach(async function () { - await con.db.dropDatabase() - }) - describe('isActive', function () { - it('returns true for active', async function () { - const data = { - _id: 'isactivetrue', - status: Patron.STATUS.ACTIVE, - pledge: 1, - pledgeLifetime: 2 - } - await collection.insertOne(data) - const patron = await Patron.get(data._id) - expect(patron.isActive()).toEqual(true) - }) - it('returns false for former', async function () { - const data = { - _id: 'isactivefalse', - status: Patron.STATUS.FORMER, - pledge: 1, - pledgeLifetime: 2 - } - await collection.insertOne(data) - const patron = await Patron.get(data._id) - expect(patron.isActive()).toEqual(false) - }) - it('returns false for unknown status', async function () { - const data = { - _id: 'isactivenull', - status: null, - pledge: 1, - pledgeLifetime: 2 - } - const data2 = { - _id: 'isactiveunknown', - status: 'abc', - pledge: 1, - pledgeLifetime: 2 - } - await collection.insertMany([data, data2]) - const patron = await Patron.get(data._id) - const patron2 = await Patron.get(data2._id) - expect(patron.isActive()).toEqual(false) - expect(patron2.isActive()).toEqual(false) - }) - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/db/int_Subscriber.database.test.js b/services/bot/src/tests/structs/db/int_Subscriber.database.test.js deleted file mode 100644 index 0df2b70c4..000000000 --- a/services/bot/src/tests/structs/db/int_Subscriber.database.test.js +++ /dev/null @@ -1,56 +0,0 @@ -process.env.TEST_ENV = true -const Subscriber = require('../../../structs/db/Subscriber.js') -const initialize = require('../../../initialization/index.js') -const mongoose = require('mongoose') -const dbName = 'test_int_subscriber' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - } - }) -})) - -describe('Int::structs/db/subscriber Database', function () { - /** @type {import('mongoose').Connection} */ - let con - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await initialize.setupModels(con) - }) - beforeEach(async function () { - await con.db.dropDatabase() - }) - it('saves properly', async function () { - const feedId = new mongoose.Types.ObjectId() - const feedData = { - _id: feedId - } - await con.db.collection('feeds').insertOne(feedData) - const subData = { - feed: feedId.toHexString(), - id: '3etwg', - type: 'role', - filters: { - title: ['hzz', 'hg'], - de: ['e4', 'sgd'] - } - } - const subscriber = new Subscriber(subData) - await subscriber.save() - const found = await con.db.collection('subscribers').findOne({ - feed: feedId - }) - expect(JSON.parse(JSON.stringify(found))).toEqual(expect.objectContaining(subData)) - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/db/int_Supporter.database.test.js b/services/bot/src/tests/structs/db/int_Supporter.database.test.js deleted file mode 100644 index 3868712c6..000000000 --- a/services/bot/src/tests/structs/db/int_Supporter.database.test.js +++ /dev/null @@ -1,524 +0,0 @@ -process.env.TEST_ENV = true -const config = require('../../../config.js') -const mongoose = require('mongoose') -const Supporter = require('../../../structs/db/Supporter.js') -const Patron = require('../../../structs/db/Patron.js') -const initialize = require('../../../initialization/index.js') -const dbName = 'test_int_supporters' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../../config.js') - -describe('Int::structs/db/Supporter Database', function () { - /** @type {import('mongoose').Connection} */ - let con - /** @type {import('mongoose').Collection} */ - let collection - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await initialize.setupModels(con) - collection = con.db.collection('supporters') - }) - beforeEach(async function () { - await con.db.dropDatabase() - config.get.mockReturnValue({ - _vip: true, - database: { - uri: 'mongodb://' - }, - feeds: { - max: 5 - } - }) - }) - describe('static getGuilds', function () { - it('returns guilds of all valid supporters', async function () { - const tenDaysAgo = new Date() - tenDaysAgo.setDate(tenDaysAgo.getDate() - 10) - const tenDaysFuture = new Date() - tenDaysFuture.setDate(tenDaysFuture.getDate() + 10) - // Invalid non-patron, expired - const supporter1 = { - _id: 'w4e3yr5h', - expireAt: tenDaysAgo.toISOString(), - patron: false - } - // Valid patron, active - const supporter2 = { - _id: 'supporter2discord', - patron: true - } - const patron2 = { - discord: supporter2._id, - status: Patron.STATUS.ACTIVE, - pledge: 1, - pledgeLifetime: 1 - } - // Invalid patron, former - const supporter3 = { - _id: 'supporter3discord', - patron: true - } - const patron3 = { - discord: supporter3._id, - status: Patron.STATUS.FORMER, - pledge: 1, - pledgeLifetime: 1 - } - // Valid non-patron, not expired - const supporter4 = { - _id: 'w34e5rtu', - patron: false, - expireAt: tenDaysFuture.toISOString() - } - await collection.insertMany([ - supporter1, - supporter2, - supporter3, - supporter4 - ]) - await con.db.collection('patrons').insertMany([ - patron2, - patron3 - ]) - const returned = await Supporter.getValidSupporters() - expect(returned.length).toEqual(2) - expect(returned[0]).toBeInstanceOf(Supporter) - expect(returned[0].toObject()).toEqual(expect.objectContaining(supporter2)) - expect(returned[1]).toBeInstanceOf(Supporter) - expect(returned[1].toObject()).toEqual(expect.objectContaining(supporter4)) - }) - }) - describe('getValidFastGuilds', function () { - it('returns correctly', async () => { - const slowDiscordId = 'weryhdt' - const slowPatronData = { - discord: slowDiscordId, - status: Patron.STATUS.ACTIVE, - pledge: Patron.SLOW_THRESHOLD - 1, - pledgeLifetime: 0 - } - const slowSupporterData = { - _id: slowDiscordId, - patron: true, - slowRate: true, - guilds: ['a', 'b'] - } - const fastDiscordId = 'w4ery5e3uthjr' - const fastPatronData = { - discord: fastDiscordId, - status: Patron.STATUS.ACTIVE, - pledge: Patron.SLOW_THRESHOLD + 1, - pledgeLifetime: 0 - } - const fastDiscordId2 = 'weasdfg' - const fastPatron2Data = { - ...fastPatronData, - discord: fastDiscordId2 - } - const fastSupporterData = { - _id: fastDiscordId, - patron: true, - guilds: ['c', 'd'] - } - const fastSupporter2Data = { - ...fastSupporterData, - _id: fastDiscordId2, - guilds: ['e', 'f'] - } - await con.db.collection('patrons').insertMany([ - slowPatronData, - fastPatronData, - fastPatron2Data - ]) - await con.db.collection(Supporter.Model.collection.collectionName) - .insertMany([ - slowSupporterData, - fastSupporterData, - fastSupporter2Data - ]) - const guildIds = await Supporter.getValidFastGuilds() - expect(guildIds).toEqual(['c', 'd', 'e', 'f']) - }) - }) - describe('getValidSupporterOfGuild', function () { - it('returns supporters who did not expire yet', async function () { - config.get.mockReturnValue({ - _vip: true, - database: { - uri: 'mongodb://' - } - }) - const past = new Date(new Date().getTime() - (1000 * 60)) - const future = new Date(new Date().getTime() + (1000 * 60 * 60 * 60 * 24)) - const guildID = 'guild1' - const supporters = [{ - _id: 'a', - guilds: [guildID], - expireAt: past.toISOString() - }, { - _id: 'b', - guilds: [guildID], - expireAt: future.toISOString() - }, { - _id: 'c', - guilds: [guildID] - }, { - _id: 'd', - guilds: [guildID + '2'], - expireAt: future.toISOString() - }] - await con.db.collection(Supporter.Model.collection.name).insertMany(supporters) - const returned = await Supporter.getValidSupporterOfGuild('guild1') - expect(returned).toEqual(expect.objectContaining(supporters[1])) - }) - it('works with patrons', async function () { - config.get.mockReturnValue({ - _vip: true, - database: { - uri: 'mongodb://' - } - }) - const guildID = 'qw234et6r' - const patrons = [{ - discord: 'a', - status: Patron.STATUS.DECLINED, - lastCharge: undefined, - pledgeLifetime: 1, - pledge: 1 - }, { - discord: 'b', - status: Patron.STATUS.ACTIVE, - pledgeLifetime: 1, - pledge: 1 - }] - const supporters = [{ - _id: patrons[0].discord, - guilds: [guildID], - patron: true - }, { - _id: 'nopatron', - patron: true - }, { - _id: patrons[1].discord, - guilds: [guildID], - patron: true - }] - await con.db.collection(Patron.Model.collection.name).insertMany(patrons) - await con.db.collection(Supporter.Model.collection.name).insertMany(supporters) - const returned = await Supporter.getValidSupporterOfGuild(guildID) - expect(returned).toEqual(expect.objectContaining(supporters[2])) - }) - }) - describe('getValidSupporters', function () { - it('returns all valid supporters', async function () { - config.get.mockReturnValue({ - _vip: true, - database: { - uri: 'mongodb://' - } - }) - const past = new Date(new Date().getTime() - (1000 * 60)) - const future = new Date(new Date().getTime() + (1000 * 60 * 60 * 60 * 24)) - const supporters = [{ - _id: 'a', - expireAt: past.toISOString() - }, { - _id: 'b', - expireAt: future.toISOString() - }, { - _id: 'c' - }, { - _id: 'd', - expireAt: future.toISOString() - }] - await con.db.collection(Supporter.Model.collection.name).insertMany(supporters) - const returned = await Supporter.getValidSupporters() - expect(returned).toHaveLength(3) - expect(returned).toEqual([ - expect.objectContaining(supporters[1]), - expect.objectContaining(supporters[2]), - expect.objectContaining(supporters[3]) - ]) - }) - it('works with patrons', async function () { - config.get.mockReturnValue({ - _vip: true, - database: { - uri: 'mongodb://' - } - }) - const patrons = [{ - discord: 'a', - status: Patron.STATUS.DECLINED, - lastCharge: undefined, - pledgeLifetime: 1, - pledge: 1 - }, { - discord: 'b', - status: Patron.STATUS.ACTIVE, - pledgeLifetime: 1, - pledge: 1 - }, { - discord: 'c', - status: Patron.STATUS.ACTIVE, - pledgeLifetime: 1, - pledge: 1 - }] - const supporters = [{ - _id: patrons[0].discord, - patron: true - }, { - _id: 'nopatron', - patron: true - }, { - _id: patrons[1].discord, - patron: true - }, { - _id: patrons[2].discord, - patron: true - }] - await con.db.collection(Patron.Model.collection.name).insertMany(patrons) - await con.db.collection(Supporter.Model.collection.name).insertMany(supporters) - const returned = await Supporter.getValidSupporters() - expect(returned).toEqual([ - expect.objectContaining(supporters[2]), - expect.objectContaining(supporters[3]) - ]) - }) - }) - describe('isValid', function () { - describe('no patron', function () { - it('returns true if no expireAt', async function () { - const data = { - _id: 'isvalidnopatron' - } - await collection.insertOne(data) - const supporter = await Supporter.get(data._id) - await expect(supporter.isValid()).resolves.toEqual(true) - }) - it('returns true if now is past expire date', async function () { - const expireAt = new Date() - expireAt.setDate(expireAt.getDate() - 1) - const data = { - _id: 'isvalidnopatronpastexpire', - expireAt - } - await collection.insertOne(data) - const supporter = await Supporter.get(data._id) - await expect(supporter.isValid()).resolves.toEqual(false) - }) - }) - describe('is patron', function () { - it('returns false for no patron in database', async function () { - const data = { - _id: 'isvalidpatronnoexist', - patron: true - } - await collection.insertOne(data) - const supporter = await Supporter.get(data._id) - await expect(supporter.isValid()).resolves.toEqual(false) - }) - it('returns the active status of the patron if they exist', async function () { - const discordId = 'great id' - const data = { - _id: discordId, - patron: true - } - await collection.insertOne(data) - const patronData = { - discord: discordId, - status: Patron.STATUS.ACTIVE, - pledgeLifetime: 11, - pledge: 45 - } - await con.db.collection('patrons').insertOne(patronData) - const supporter = await Supporter.get(data._id) - await expect(supporter.isValid()).resolves.toEqual(true) - }) - it('returns the former status of the patron if they exist', async function () { - const discordId = 'former id' - const data = { - _id: discordId, - patron: true - } - await collection.insertOne(data) - const patronData = { - discord: discordId, - status: Patron.STATUS.FORMER, - pledgeLifetime: 11, - pledge: 45 - } - await con.db.collection('patrons').insertOne(patronData) - const supporter = await Supporter.get(data._id) - await expect(supporter.isValid()).resolves.toEqual(false) - }) - }) - }) - describe('getMaxFeeds', function () { - it('returns max feeds correctly for patron via method', async function () { - const discordId = 'getmaxfeeds max feeds patron' - const data = { - _id: discordId, - patron: true - } - await collection.insertOne(data) - const patronData = { - discord: discordId, - status: Patron.STATUS.ACTIVE, - pledge: 1250, - pledgeLifetime: 10000 - } - await con.db.collection('patrons').insertOne(patronData) - const supporter = await Supporter.get(data._id) - await expect(supporter.getMaxFeeds()).resolves.toEqual(70) - }) - it('returns default max feeds for former patron via method', async function () { - const discordId = 'getmaxfeeds max feeds former patron' - const data = { - _id: discordId, - patron: true - } - await collection.insertOne(data) - const patronData = { - discord: discordId, - status: Patron.STATUS.FORMER, - pledge: 1250, - pledgeLifetime: 10000 - } - await con.db.collection('patrons').insertOne(patronData) - const supporter = await Supporter.get(data._id) - await expect(supporter.getMaxFeeds()).resolves.toEqual(config.get().feeds.max) - }) - }) - describe('getMaxGuilds', function () { - it('returns max guilds correctly for patron via method', async function () { - const discordId = 'getmaxfeeds max guilds patron' - const data = { - _id: discordId, - patron: true - } - await collection.insertOne(data) - const patronData = { - discord: discordId, - status: Patron.STATUS.ACTIVE, - pledge: 1, - pledgeLifetime: 1600 - } - await con.db.collection('patrons').insertOne(patronData) - const supporter = await Supporter.get(data._id) - await expect(supporter.getMaxGuilds()).resolves.toEqual(3) - }) - it('returns 1 for inactive patron via method', async function () { - const discordId = 'getmaxfeeds max guilds former patron' - const data = { - _id: discordId, - patron: true - } - await collection.insertOne(data) - const patronData = { - discord: discordId, - status: Patron.STATUS.FORMER, - pledge: 1, - pledgeLifetime: 1600 - } - await con.db.collection('patrons').insertOne(patronData) - const supporter = await Supporter.get(data._id) - await expect(supporter.getMaxGuilds()).resolves.toEqual(1) - }) - }) - describe('getWebhookAccess', function () { - it('returns webhook access for unqualified patron via method', async function () { - const discordId = 'getwebhookaccess unqualified patron' - const data = { - _id: discordId, - patron: true - } - await collection.insertOne(data) - const patronData = { - discord: discordId, - status: Patron.STATUS.ACTIVE, - pledge: 1, - pledgeLifetime: 1600 - } - await con.db.collection('patrons').insertOne(patronData) - const supporter = await Supporter.get(data._id) - await expect(supporter.getWebhookAccess()).resolves.toEqual(false) - }) - it('returns webhook access for qualified patron via method', async function () { - const discordId = 'getwebhookaccess qualified patron' - const data = { - _id: discordId, - patron: true - } - await collection.insertOne(data) - const patronData = { - discord: discordId, - status: Patron.STATUS.ACTIVE, - pledge: 1001, - pledgeLifetime: 1600 - } - await con.db.collection('patrons').insertOne(patronData) - const supporter = await Supporter.get(data._id) - await expect(supporter.getWebhookAccess()).resolves.toEqual(true) - }) - }) - describe('hasSlowRate', () => { - it('returns true correctly for patron', async () => { - const discordId = 'hasslowrate patron' - const data = { - _id: discordId, - patron: true - } - await collection.insertOne(data) - const patronData = { - discord: discordId, - status: Patron.STATUS.ACTIVE, - pledge: Patron.SLOW_THRESHOLD + 1, - pledgeLifetime: 0 - } - await con.db.collection('patrons').insertOne(patronData) - const supporter = await Supporter.get(data._id) - await expect(supporter.hasSlowRate()) - .resolves.toEqual(false) - }) - it('returns false correctly for patron', async () => { - const discordId = 'hasslowrate patron' - const data = { - _id: discordId, - patron: true - } - await collection.insertOne(data) - const patronData = { - discord: discordId, - status: Patron.STATUS.ACTIVE, - pledge: Patron.SLOW_THRESHOLD - 1, - pledgeLifetime: 0 - } - await con.db.collection('patrons').insertOne(patronData) - const supporter = await Supporter.get(data._id) - await expect(supporter.hasSlowRate()) - .resolves.toEqual(true) - }) - it('returns true correctly for patron not found', async () => { - const discordId = 'hasslowrate patron' - const data = { - _id: discordId, - patron: true, - slowRate: true - } - await collection.insertOne(data) - const supporter = await Supporter.get(data._id) - await expect(supporter.hasSlowRate()) - .resolves.toEqual(true) - }) - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_Base.test.js b/services/bot/src/tests/structs/db/unit_Base.test.js deleted file mode 100644 index 0aab64f97..000000000 --- a/services/bot/src/tests/structs/db/unit_Base.test.js +++ /dev/null @@ -1,926 +0,0 @@ -process.env.TEST_ENV = true -const mongoose = require('mongoose') -const Base = require('../../../structs/db/Base.js') -const config = require('../../../config.js') -const MockModel = require('./__mocks__/MockModel.js') -const BasicBase = require('./__mocks__/BasicBase.js') -const path = require('path') -const fs = require('fs') -const fsPromises = fs.promises -const fsReadFileSync = fs.readFileSync -const fsWriteFileSync = fs.writeFileSync -const fsExistsSync = fs.existsSync -const fsReaddirSync = fs.readdirSync -const fsMkdirSync = fs.mkdirSync -const fsUnlinkSync = fs.unlinkSync -const fsRmdirSync = fs.rmdirSync -const fsPromisesReaddir = fsPromises.readdir -const fsPromisesUnlink = fsPromises.unlink -const fsPromisesRmdir = fsPromises.rmdir - -jest.mock('mongoose') -jest.mock('../../../config.js', () => ({ - get: jest.fn() -})) - -describe('Unit::structs/db/Base', function () { - beforeEach(function () { - config.get.mockReturnValue({ - database: {} - }) - }) - afterEach(function () { - jest.restoreAllMocks() - MockModel.mockClear() - MockModel.findOne.mockClear() - MockModel.findByIdAndUpdate.mockClear() - MockModel.find.mockClear() - MockModel.findById.mockClear() - MockModel.deleteOne.mockClear() - }) - describe('constructor', function () { - it('throws an error when Model is not implemented', function () { - const expectedError = new Error('Model static get method must be implemented by subclasses') - expect(() => new Base()).toThrowError(expectedError) - }) - it('doesn\'t throw an error when Model is implemented in subclass', function () { - const spy = jest.spyOn(Base, 'Model', 'get').mockImplementationOnce(() => {}) - expect(() => new Base()).not.toThrowError() - spy.mockReset() - }) - it('sets this.data', function () { - const data = 'wr4y3e5tuj' - const base = new BasicBase(data) - expect(base.data).toEqual(data) - }) - it('sets this.data to an empty object by default', function () { - const base = new BasicBase() - expect(base.data).toEqual({}) - }) - it('sets this._id', function () { - const init = { _id: 'we34tryh' } - const base = new BasicBase({ ...init }) - expect(base._id).toEqual(init._id) - }) - }) - describe('static get isMongoDatabase', function () { - it('calls startsWith', function () { - const startsWith = jest.fn() - config.get.mockReturnValue({ - database: { - uri: { - startsWith - } - } - }) - // eslint-disable-next-line no-void - void BasicBase.isMongoDatabase - expect(startsWith).toHaveBeenCalled() - }) - }) - describe('static getFolderPaths', function () { - it('returns correctly', function () { - const databaseURI = 'abc' - config.get.mockReturnValue({ - database: { - uri: databaseURI - } - }) - const collectionName = 'def' - const spy = jest.spyOn(BasicBase, 'Model', 'get').mockReturnValue({ - collection: { - collectionName - } - }) - const result = BasicBase.getFolderPaths() - expect(result).toEqual([ - databaseURI, - path.join(databaseURI, collectionName) - ]) - spy.mockRestore() - }) - }) - describe('static getField', function () { - it('returns the data from plain object', function () { - const base = new BasicBase() - const field = 'we4ryhdt' - const value = 'sw34rye5htd' - base.data = { [field]: value } - const returnValue = base.getField(field) - expect(returnValue).toEqual(value) - }) - it('returns undefined if not found in either', function () { - const base = new BasicBase() - base.data = {} - expect(base.getField('abc')).toEqual(undefined) - base.data = new mongoose.Model() - expect(base.getField('abc')).toEqual(undefined) - }) - }) - describe('static resolveObject', function () { - it('returns undefined for empty object', function () { - expect(Base.resolveObject({})).toBeUndefined() - }) - it('returns the webhook if defined', function () { - const data = { - foo: 'baz', - id: '123' - } - expect(Base.resolveObject({ ...data })).toEqual(data) - }) - }) - describe('toObject', function () { - it('throws an error when unimplemented', function () { - const base = new BasicBase() - expect(() => base.toObject()).toThrowError(new Error('Method must be implemented by subclasses')) - }) - it('doesn\'t throw an error when implemented', function () { - const base = new BasicBase() - const spy = jest.spyOn(BasicBase.prototype, 'toObject').mockImplementation(() => {}) - expect(() => base.toObject()).not.toThrowError() - spy.mockReset() - }) - }) - describe('toJSON', function () { - it('returns toObject by default', function () { - const base = new BasicBase() - const toObjectValue = 3456 - jest.spyOn(base, 'toObject').mockReturnValue(toObjectValue) - expect(base.toJSON()).toEqual(toObjectValue) - }) - }) - describe('get', function () { - it('throws an error for undefined id', function () { - return expect(BasicBase.get()).rejects.toThrowError(new Error('Undefined id')) - }) - it('throws an error for non-string id', function () { - return expect(BasicBase.get(123)).rejects.toThrowError(new Error('id must be a string')) - }) - describe('from database', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValue(true) - }) - it('uses findOne for database', async function () { - const id = '3w4e5rytu' - await BasicBase.get(id) - expect(MockModel.findById).toHaveBeenCalledWith(id) - }) - it('returns a new Basic Base for database', async function () { - const id = '12qw34r' - const execReturnValue = 'w24r3' - MockModel.findById.mockReturnValue(({ exec: () => Promise.resolve(execReturnValue) })) - const result = await BasicBase.get(id) - expect(result).toBeInstanceOf(BasicBase) - expect(result.data).toEqual(execReturnValue) - }) - it('returns null if not found', async function () { - MockModel.findById.mockReturnValue(({ exec: () => Promise.resolve(null) })) - const result = await BasicBase.get('asdewtgr') - expect(result).toBeNull() - }) - }) - describe('from databaseless', function () { - beforeEach(function () { - fs.readFileSync = jest.fn() - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValue(false) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a', 'b']) - }) - afterEach(function () { - fs.readFileSync = fsReadFileSync - fs.existsSync = fsExistsSync - }) - it('returns null if path does not exist', async function () { - fs.existsSync = jest.fn(() => false) - const returnValue = await BasicBase.get('1') - expect(returnValue).toBeNull() - fs.existsSync = fsExistsSync - }) - it('checks and reads the right file path', async function () { - fs.existsSync = jest.fn(() => true) - await BasicBase.get('abc') - expect(fs.existsSync) - .toHaveBeenCalledWith(path.join('b', 'abc.json')) - }) - it('returns the a new instance correctly', async function () { - const jsonString = '{"foo": "bar"}' - fs.existsSync = jest.fn(() => true) - fs.readFileSync = jest.fn(() => jsonString) - const returnValue = await BasicBase.get('1') - expect(returnValue).toBeInstanceOf(BasicBase) - expect(returnValue.data).toEqual({ foo: 'bar' }) - }) - it('returns null when JSON parse fails', async function () { - const jsonString = '{"foo": bar ;}' - fs.existsSync = jest.fn(() => true) - fs.readFileSync = jest.fn(() => jsonString) - const returnValue = await BasicBase.get('1') - expect(returnValue).toBeNull() - }) - }) - }) - describe('getByQuery', function () { - describe('from database', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValue(true) - }) - it('calls findOne correctly', async function () { - const field = '234wt6r' - const value = 'w23et43' - const query = { - [field]: value - } - await BasicBase.getByQuery(query) - expect(MockModel.findOne).toHaveBeenCalledWith(query, BasicBase.FIND_PROJECTION) - }) - it('returns a new instance correctly', async function () { - const doc = { - hello: 'world' - } - const exec = jest.fn(() => doc) - jest.spyOn(MockModel, 'findOne').mockReturnValue({ exec }) - const returnValue = await BasicBase.getByQuery({}) - expect(returnValue).toBeInstanceOf(BasicBase) - }) - it('returns null correctly', async function () { - const exec = jest.fn(() => null) - jest.spyOn(MockModel, 'findOne').mockReturnValue({ exec }) - const returnValue = await BasicBase.getByQuery({}) - expect(returnValue).toBeNull() - }) - }) - describe('from databaseless', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValue(false) - }) - it('returns a new isntance', async function () { - jest.spyOn(BasicBase, 'getManyByQuery').mockResolvedValue([1]) - await expect(BasicBase.getByQuery()).resolves.toBeInstanceOf(BasicBase) - }) - it('returns null when none found', async function () { - jest.spyOn(BasicBase, 'getManyByQuery').mockResolvedValue([]) - await expect(BasicBase.getByQuery()).resolves.toBeNull() - }) - }) - }) - describe('getBy', function () { - it('calls getByQuery correctly', async function () { - const spy = jest.spyOn(BasicBase, 'getByQuery').mockResolvedValue() - const field = 'w2346tyer5thujg' - const value = 'swe4t69ruyghj' - await BasicBase.getBy(field, value) - expect(spy).toHaveBeenCalledTimes(1) - expect(spy).toHaveBeenCalledWith({ [field]: value }) - }) - }) - describe('getManyByQuery', function () { - describe('from database', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockResolvedValue(true) - }) - it('calls find correctly', async function () { - const query = { - assrfh: 'srhyedhy', - joe: 1, - ahaaa: [] - } - jest.spyOn(MockModel, 'find').mockReturnValue({ exec: () => [] }) - await BasicBase.getManyByQuery(query) - expect(MockModel.find).toHaveBeenCalledWith(query, BasicBase.FIND_PROJECTION) - }) - it('returns a new array correctly', async function () { - const exec = jest.fn(() => [{}, {}]) - jest.spyOn(MockModel, 'find').mockReturnValue({ exec }) - const returnValues = await BasicBase.getManyByQuery() - expect(returnValues).toBeInstanceOf(Array) - expect(returnValues).toHaveLength(2) - for (const val of returnValues) { - expect(val).toBeInstanceOf(BasicBase) - } - }) - it('returns empty array correctly', async function () { - const exec = jest.fn(() => []) - jest.spyOn(MockModel, 'find').mockReturnValue({ exec }) - const returnValues = await BasicBase.getManyByQuery() - expect(returnValues).toHaveLength(0) - }) - }) - describe('from databaseless', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValue(false) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['1']) - fs.existsSync = jest.fn() - fs.readFileSync = jest.fn() - fsPromises.readdir = jest.fn() - }) - afterEach(function () { - fs.existsSync = fsExistsSync - fsPromises.readdir = fsPromisesReaddir - fs.readFileSync = fsReadFileSync - }) - it('returns empty array if path does not exist', async function () { - fs.existsSync.mockReturnValue(false) - const returned = await BasicBase.getManyByQuery({}) - expect(returned).toEqual([]) - }) - it('returns a single match', async function () { - const read1 = JSON.stringify({ key1: 'abcgfd' }) - const readFail = '{wetfrg:}' - const read2 = JSON.stringify({ key1: 'gh', key2: 123 }) - const read3 = JSON.stringify({ key1: 'gh', key: '2' }) - fsPromises.readdir.mockResolvedValue(['1', '2', '3']) - fs.existsSync.mockReturnValue(true) - fs.readFileSync - .mockReturnValueOnce(read1) - .mockReturnValueOnce(readFail) - .mockReturnValueOnce(read2) - .mockReturnValueOnce(read3) - const returned = await BasicBase.getManyByQuery({ - key1: 'gh', - key2: 123 - }) - expect(returned).toHaveLength(1) - expect(returned[0]).toBeInstanceOf(BasicBase) - await expect(BasicBase.getManyBy('random', 'key')) - .resolves.toEqual([]) - }) - it('returns multiple matches', async function () { - const read1Object = { key: 'rwse4yhg', george: 1, fal: 6 } - const read2Object = { key: read1Object.key, lucas: 1, fal: read1Object.fal } - const read1 = JSON.stringify(read1Object) - const read2 = JSON.stringify(read2Object) - fsPromises.readdir.mockResolvedValue(['1', '2']) - fs.existsSync.mockReturnValue(true) - fs.readFileSync - .mockReturnValueOnce(read1) - .mockReturnValueOnce(read2) - const returned = await BasicBase.getManyByQuery({ - key: read1Object.key, - fal: read1Object.fal - }) - expect(returned).toHaveLength(2) - }) - }) - }) - describe('getManyBy', function () { - it('calls getManyByQuery correctly', async function () { - const spy = jest.spyOn(BasicBase, 'getManyByQuery').mockResolvedValue() - const field = 'wt4ryhedt' - const value = 'srye57thr' - await BasicBase.getManyBy(field, value) - expect(spy).toHaveBeenCalledTimes(1) - expect(spy).toHaveBeenCalledWith({ [field]: value }) - }) - }) - describe('getMany', function () { - it('returns correctly', async function () { - const ids = [1, 2, 3, 4, 5] - const spy = jest.spyOn(BasicBase, 'get').mockReturnValue(1) - const returnValue = await BasicBase.getMany(ids) - const expected = ids.map(() => 1) - expect(returnValue).toEqual(expected) - spy.mockReset() - }) - }) - describe('getAll', function () { - describe('from database', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValueOnce(true) - }) - it('calls find with {}', async function () { - MockModel.find.mockReturnValue(({ exec: () => Promise.resolve([]) })) - await BasicBase.getAll() - expect(MockModel.find).toHaveBeenCalledWith({}, BasicBase.FIND_PROJECTION) - }) - it('returns correctly', async function () { - const documents = [1, 2, 3, 4, 5] - MockModel.find.mockReturnValue(({ exec: () => Promise.resolve(documents) })) - const returnValues = await BasicBase.getAll() - expect(returnValues).toBeInstanceOf(Array) - for (let i = 0; i < documents.length; ++i) { - const value = returnValues[i] - const docValue = documents[i] - expect(value).toBeInstanceOf(BasicBase) - expect(value.data).toEqual(docValue) - } - }) - }) - describe('from databaseless', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValueOnce(false) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue([]) - }) - afterEach(function () { - fs.existsSync = fsExistsSync - fs.readdirSync = fsReaddirSync - }) - it('checks the right path', async function () { - const folderPaths = ['a', path.join('a', 'b')] - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(folderPaths) - fs.existsSync = jest.fn(() => false) - await BasicBase.getAll() - expect(fs.existsSync).toHaveBeenCalledWith(folderPaths[1]) - }) - it('reads the right path', async function () { - const folderPaths = ['a', path.join('a', 'b')] - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(folderPaths) - fs.existsSync = jest.fn(() => true) - fs.readdirSync = jest.fn(() => []) - await BasicBase.getAll() - expect(fs.readdirSync).toHaveBeenCalledWith(folderPaths[1]) - }) - it('returns an empty array when the path does not exist', async function () { - fs.existsSync = jest.fn(() => false) - const returnValue = await BasicBase.getAll() - expect(returnValue).toEqual([]) - }) - it('ignores non-json files', async function () { - const fileNames = ['a.json', 'b.json', 'c.json'] - fs.existsSync = jest.fn(() => true) - fs.readdirSync = jest.fn(() => fileNames) - const spy = jest.spyOn(BasicBase, 'get').mockResolvedValue() - await BasicBase.getAll() - expect(spy).toHaveBeenCalledTimes(fileNames.length) - expect(spy).toHaveBeenCalledWith('a') - expect(spy).toHaveBeenCalledWith('b') - expect(spy).toHaveBeenCalledWith('c') - }) - it('calls get correctly', async function () { - const fileNames = ['a.json', 'b.json', 'c.json'] - fs.existsSync = jest.fn(() => true) - fs.readdirSync = jest.fn(() => fileNames) - const spy = jest.spyOn(BasicBase, 'get').mockResolvedValue() - await BasicBase.getAll() - expect(spy).toHaveBeenCalledTimes(fileNames.length) - expect(spy).toHaveBeenCalledWith('a') - expect(spy).toHaveBeenCalledWith('b') - expect(spy).toHaveBeenCalledWith('c') - }) - it('returns correctly', async function () { - const fileNames = ['1.json', '1.json', '1.json'] - const resolveValue = 6 - const getResolves = fileNames.map(() => resolveValue) - fs.existsSync = jest.fn(() => true) - fs.readdirSync = jest.fn(() => fileNames) - jest.spyOn(BasicBase, 'get').mockResolvedValue(resolveValue) - const returnValue = await BasicBase.getAll() - expect(returnValue).toEqual(getResolves) - }) - }) - }) - describe('static deleteAll', function () { - describe('from database', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValue(true) - }) - it('calls deleteMany correctly', async function () { - await BasicBase.deleteAll() - expect(MockModel.deleteMany).toHaveBeenCalledTimes(1) - expect(MockModel.deleteMany).toHaveBeenCalledWith({}) - }) - }) - describe('from databaseless', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValue(false) - fs.rmdirSync = jest.fn() - fs.existsSync = jest.fn() - fsPromises.readdir = jest.fn() - fsPromises.unlink = jest.fn() - fsPromises.rmdir = jest.fn() - }) - afterEach(function () { - fs.rmdirSync = fsRmdirSync - fs.existsSync = fsExistsSync - fsPromises.readdir = fsPromisesReaddir - fsPromises.unlink = fsPromisesUnlink - fsPromises.rmdir = fsPromisesRmdir - }) - it('doesn\'t call rmdir if the folder doesn\'t exist', async function () { - fs.existsSync.mockReturnValue(false) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a', 'b']) - await BasicBase.deleteAll() - expect(fs.existsSync).toHaveBeenCalledWith('b') - expect(fs.rmdirSync).not.toHaveBeenCalled() - }) - it('calls rmdir if the folder exists', async function () { - fs.existsSync.mockReturnValue(true) - fsPromises.readdir.mockResolvedValue(['file1.json', 'file2.json']) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a', 'b']) - await BasicBase.deleteAll() - expect(fs.existsSync).toHaveBeenCalledWith('b') - expect(fsPromises.rmdir).toHaveBeenCalledWith('b') - }) - it('deletes files within the directory', async function () { - fs.existsSync.mockReturnValue(true) - fsPromises.readdir.mockResolvedValue(['file1.json', 'file2.json']) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a', 'b']) - await BasicBase.deleteAll() - expect(fsPromises.unlink).toHaveBeenCalledTimes(2) - expect(fsPromises.unlink).toHaveBeenCalledWith(path.join('b', 'file1.json')) - expect(fsPromises.unlink).toHaveBeenCalledWith(path.join('b', 'file2.json')) - }) - }) - }) - describe('delete', function () { - it('throws an error if unsaved', function () { - const base = new BasicBase() - base._saved = false - return expect(base.delete()).rejects.toThrowError(new Error('Data has not been saved')) - }) - describe('from database', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValueOnce(true) - }) - it('calls remove', async function () { - const data = { remove: jest.fn() } - const base = new BasicBase() - base._saved = true - base.document = data - await base.delete() - expect(data.remove).toHaveBeenCalledTimes(1) - }) - }) - describe('from databaseless', function () { - beforeEach(function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValueOnce(false) - }) - afterEach(function () { - fs.existsSync = fsExistsSync - fs.unlinkSync = fsUnlinkSync - }) - it('checks the right path', async function () { - const folderPaths = ['a', path.join('a', 'b')] - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(folderPaths) - fs.existsSync = jest.fn(() => false) - const id = 'wr43yeht' - const base = new BasicBase() - base._saved = true - base._id = id - await base.delete() - expect(fs.existsSync).toHaveBeenCalledWith(path.join(folderPaths[1], `${id}.json`)) - }) - it('doesn\'t call unlink if path doesn\'t exist', async function () { - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a']) - fs.existsSync = jest.fn(() => false) - fs.unlinkSync = jest.fn(() => {}) - const base = new BasicBase() - base._saved = true - await base.delete() - expect(fs.unlinkSync).not.toHaveBeenCalled() - }) - it('calls unlink if path exists', async function () { - const folderPaths = ['a', path.join('a', 'b')] - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(folderPaths) - fs.existsSync = jest.fn(() => true) - fs.unlinkSync = jest.fn() - const id = 'qe3tw4ryhdt' - const base = new BasicBase() - base._saved = true - base._id = id - await base.delete() - expect(fs.unlinkSync).toHaveBeenCalledTimes(1) - expect(fs.unlinkSync).toHaveBeenCalledWith(path.join(folderPaths[1], `${id}.json`)) - }) - }) - }) - describe('save', function () { - it('branches correctly for mongodb', async function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValue(true) - const spy = jest.spyOn(BasicBase.prototype, 'saveToDatabase').mockImplementation() - const base = new BasicBase() - await base.save() - expect(spy).toHaveBeenCalledTimes(1) - }) - it('branches correctly for databaseless', async function () { - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValue(false) - const spy = jest.spyOn(BasicBase.prototype, 'saveToFile').mockImplementation() - const base = new BasicBase() - await base.save() - expect(spy).toHaveBeenCalledTimes(1) - }) - }) - describe('saveToDatabase', function () { - beforeEach(function () { - jest.spyOn(BasicBase.prototype, 'toObject').mockReturnValue({}) - jest.spyOn(BasicBase, 'isMongoDatabase', 'get').mockReturnValue(true) - jest.spyOn(MockModel.prototype, 'save').mockResolvedValue({ - toJSON: jest.fn(() => ({})) - }) - }) - describe('unsaved', function () { - it('calls save correctly', async function () { - const base = new BasicBase() - base._saved = false - await base.saveToDatabase() - expect(MockModel.mock.instances).toHaveLength(1) - expect(MockModel.mock.instances[0].save).toHaveBeenCalledTimes(1) - }) - it('overwrites this.document with the document', async function () { - const document = { - foo: 'bazd', - toJSON: () => ({}) - } - jest.spyOn(MockModel.prototype, 'save').mockResolvedValue({ ...document }) - const base = new BasicBase() - base._saved = false - await base.saveToDatabase() - expect(base.document).toEqual({ ...document }) - }) - it('overwrites this.data with the doc data', async function () { - const data = { - foo: 21 - } - const document = { - foo: 'bazd', - toJSON: () => data - } - jest.spyOn(MockModel.prototype, 'save').mockResolvedValue({ ...document }) - const base = new BasicBase() - base._saved = false - await base.saveToDatabase() - expect(base.data).toEqual(data) - }) - it('returns this', async function () { - const base = new BasicBase() - base._saved = false - const returnValue = await base.saveToDatabase() - expect(returnValue).toEqual(base) - }) - it('deletes undefined keys', async function () { - const base = new BasicBase() - const toSave = { - foo: 12345, - bar: undefined, - buck: undefined - } - const expectedSave = { - foo: 12345 - } - const id = 123 - base._id = id - base._saved = false - jest.spyOn(base, 'toObject').mockReturnValue(toSave) - await base.saveToDatabase() - expect(MockModel.mock.calls[0][0]).toEqual(expectedSave) - }) - it('sets save property properly', async function () { - const base = new BasicBase() - base._saved = false - await base.saveToDatabase() - expect(base._saved).toEqual(true) - }) - }) - describe('saved', function () { - const savedDocumentMock = { - toJSON: jest.fn(() => ({})) - } - it('calls data.set on all keys properly', async function () { - const toObjectValue = { - fo: 1, - fq: 'az', - gsd: 'frdbhg' - } - jest.spyOn(BasicBase.prototype, 'toObject').mockReturnValue(toObjectValue) - const base = new BasicBase() - base._saved = true - base.document = { - save: jest.fn(() => savedDocumentMock), - set: jest.fn() - } - await base.saveToDatabase() - for (const key in toObjectValue) { - expect(base.document.set).toHaveBeenCalledWith(key, toObjectValue[key]) - } - }) - it('calls save', async function () { - const base = new BasicBase() - base._saved = true - base.document = { - set: jest.fn(), - save: jest.fn(() => savedDocumentMock) - } - await base.saveToDatabase() - expect(base.document.save).toHaveBeenCalled() - }) - it('updates this.data', async function () { - const base = new BasicBase() - base._saved = true - const serializedDoc = { foo: 'baz', a: 2 } - const savedDoc = { - toJSON: jest.fn(() => serializedDoc) - } - base.document = { - save: jest.fn(() => savedDoc) - } - await base.saveToDatabase() - expect(savedDoc.toJSON).toHaveBeenCalled() - expect(base.data).toEqual(JSON.parse(JSON.stringify(serializedDoc))) - }) - it('returns this', async function () { - const base = new BasicBase() - base._saved = true - base.document = { - save: jest.fn(() => savedDocumentMock) - } - const returnValue = await base.saveToDatabase() - expect(returnValue).toEqual(base) - }) - it('updates this class data', async function () { - const toSave = { - is: '35u4', - good: 'wet4' - } - jest.spyOn(BasicBase.prototype, 'toObject').mockReturnValue(toSave) - const base = new BasicBase() - base._saved = true - const savedDocumentObject = { - random: 'key', - is: 1, - good: 1234 - } - const savedDocument = { - toJSON: jest.fn(() => savedDocumentObject) - } - base.document = { - save: jest.fn(() => savedDocument), - set: jest.fn() - } - await base.saveToDatabase() - expect(base.is).toEqual(savedDocumentObject.is) - expect(base.good).toEqual(savedDocumentObject.good) - expect(base.random).toBeUndefined() - }) - }) - }) - describe('saveToFile', function () { - beforeEach(function () { - jest.spyOn(BasicBase.prototype, 'toJSON').mockReturnValue({}) - fs.writeFileSync = jest.fn() - fs.mkdirSync = jest.fn() - }) - afterEach(function () { - fs.writeFileSync = fsWriteFileSync - fs.existsSync = fsExistsSync - fs.mkdirSync = fsMkdirSync - }) - it('checks all the paths', async function () { - const folderPaths = ['a', path.join('a', 'b'), path.join('a', 'b', 'c')] - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(folderPaths) - fs.existsSync = jest.fn(() => true) - const base = new BasicBase() - await base.saveToFile() - expect(fs.existsSync).toHaveBeenCalledTimes(folderPaths.length) - for (let i = 0; i < folderPaths.length; ++i) { - expect(fs.existsSync.mock.calls[i]).toEqual([folderPaths[i]]) - } - }) - it('makes the appropriate dirs', async function () { - const folderPaths = ['a', path.join('a', 'b'), path.join('a', 'b', 'c')] - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(folderPaths) - fs.existsSync = jest.fn() - .mockReturnValueOnce(true) - .mockReturnValueOnce(false) - .mockReturnValueOnce(false) - fs.mkdirSync = jest.fn() - const base = new BasicBase() - await base.saveToFile() - expect(fs.mkdirSync).toHaveBeenCalledTimes(2) - expect(fs.mkdirSync.mock.calls[0]).toEqual([folderPaths[1]]) - expect(fs.mkdirSync.mock.calls[1]).toEqual([folderPaths[2]]) - }) - it('deletes undefined keys', async function () { - const toSave = { - foo: '123', - baz: undefined, - bar: undefined - } - const expectedSave = { - foo: toSave.foo - } - jest.spyOn(BasicBase.prototype, 'toJSON').mockReturnValue(toSave) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a']) - const base = new BasicBase() - await base.saveToFile() - expect(fs.writeFileSync.mock.calls[0][1]).toEqual(JSON.stringify(expectedSave, null, 2)) - }) - describe('_saved is true', function () { - it('writes the data', async function () { - const folderPaths = ['q', path.join('q', 'w'), path.join('q', 'w', 'e')] - const data = { fudge: 'popsicle' } - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(folderPaths) - fs.existsSync = jest.fn(() => true) - jest.spyOn(BasicBase.prototype, 'toJSON').mockReturnValue(data) - const id = 'q3etwgjrhnft' - const base = new BasicBase() - base._saved = true - base._id = id - await base.saveToFile() - const writePath = path.join(folderPaths[2], `${id}.json`) - expect(fs.writeFileSync).toHaveBeenCalledWith(writePath, JSON.stringify(data, null, 2)) - }) - it('returns this', async function () { - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a']) - const base = new BasicBase() - base._saved = true - const returnValue = await base.saveToFile() - expect(returnValue).toEqual(base) - }) - it('deletes undefined keys', async function () { - const toSave = { - foo: '123', - baz: undefined, - bar: undefined - } - const expectedSave = { - foo: toSave.foo - } - jest.spyOn(BasicBase.prototype, 'toJSON').mockReturnValue(toSave) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a']) - const base = new BasicBase() - base._saved = true - await base.saveToFile() - expect(fs.writeFileSync.mock.calls[0][1]).toEqual(JSON.stringify(expectedSave, null, 2)) - }) - }) - describe('_saved is false', function () { - beforeEach(function () { - jest.spyOn(mongoose.Types, 'ObjectId').mockImplementation(() => ({ - toHexString: jest.fn(() => 1) - })) - }) - it('writes the data', async function () { - const generatedId = '2343635erygbh5' - jest.spyOn(mongoose.Types, 'ObjectId').mockImplementation(() => ({ - toHexString: jest.fn(() => generatedId) - })) - const folderPaths = ['q', path.join('q', 'w'), path.join('q', 'w', 'f')] - const data = { fudgead: 'popsicle' } - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(folderPaths) - fs.existsSync = jest.fn(() => true) - jest.spyOn(BasicBase.prototype, 'toJSON').mockReturnValue(data) - const base = new BasicBase() - base._saved = false - await base.saveToFile() - expect(fs.writeFileSync) - .toHaveBeenCalledWith(path.join(folderPaths[2], `${generatedId}.json`), JSON.stringify(data, null, 2)) - }) - it('saves the _id to this', async function () { - const generatedId = '2343635erh5' - jest.spyOn(mongoose.Types, 'ObjectId').mockImplementation(() => ({ - toHexString: jest.fn(() => generatedId) - })) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a']) - fs.existsSync = jest.fn(() => true) - const base = new BasicBase() - base._saved = false - await base.saveToFile() - expect(base._id).toEqual(generatedId) - }) - it('returns this', async function () { - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a']) - fs.existsSync = jest.fn(() => true) - const base = new BasicBase() - base._saved = false - const returnValue = await base.saveToFile() - expect(returnValue).toEqual(base) - }) - it('sets saved property', async function () { - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a']) - fs.existsSync = jest.fn(() => true) - const base = new BasicBase() - base._saved = false - await base.saveToFile() - expect(base._saved).toEqual(true) - }) - it('adds _id to the file if it doesn\'t already exists', async function () { - const newId = 'qa3et54wry' - jest.spyOn(mongoose.Types, 'ObjectId').mockImplementation(() => ({ - toHexString: jest.fn(() => newId) - })) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a']) - fs.existsSync = jest.fn(() => true) - const base = new BasicBase() - base._saved = false - await base.saveToFile() - const writtenTo = fs.writeFileSync.mock.calls[0][0] - const written = fs.writeFileSync.mock.calls[0][1] - expect(JSON.parse(written)._id).toEqual(newId) - expect(writtenTo).toEqual(path.join('a', `${newId}.json`)) - }) - it('uses the current _id and adds it to the file if already exists', async function () { - const _id = 'heasdz' - jest.spyOn(BasicBase.prototype, 'toJSON').mockReturnValue({ - _id - }) - jest.spyOn(BasicBase, 'getFolderPaths').mockReturnValue(['a']) - fs.existsSync = jest.fn(() => true) - const base = new BasicBase() - base._saved = false - await base.saveToFile() - const writtenTo = fs.writeFileSync.mock.calls[0][0] - const written = fs.writeFileSync.mock.calls[0][1] - expect(JSON.parse(written)._id).toEqual(_id) - expect(writtenTo).toEqual(path.join('a', `${_id}.json`)) - }) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_Blacklist.test.js b/services/bot/src/tests/structs/db/unit_Blacklist.test.js deleted file mode 100644 index dff7b6d2c..000000000 --- a/services/bot/src/tests/structs/db/unit_Blacklist.test.js +++ /dev/null @@ -1,70 +0,0 @@ -process.env.TEST_ENV = true -const Blacklist = require('../../../structs/db/Blacklist.js') - -jest.mock('../../../config.js') - -describe('Unit::structs/db/Blacklist', function () { - describe('constructor', function () { - it('throws for undefined id', function () { - const data = { - type: 123, - name: 'gfh' - } - expect(() => new Blacklist(data)) - .toThrow(new TypeError('_id is undefined')) - }) - it('throws for undefined type', function () { - const data = { - _id: 'asd', - name: 'gfh' - } - expect(() => new Blacklist(data)) - .toThrow(new TypeError('type is undefined')) - }) - it('throws for NaN type', function () { - const data = { - _id: 'asd', - name: 'gfh', - type: 'he' - } - expect(() => new Blacklist(data)) - .toThrow(new TypeError('type is not a number')) - }) - it('does not throw for missing name', function () { - const data = { - _id: 'asd', - type: 2 - } - expect(() => new Blacklist(data)) - .not.toThrow() - }) - }) - describe('toObject', function () { - it('returns correctly', function () { - const data = { - _id: 'srfh', - name: '3e45y', - type: 5 - } - const blacklist = new Blacklist({ ...data }) - const returned = blacklist.toObject() - expect(returned).toEqual(data) - }) - }) - describe('static get TYPES', function () { - it('returns correctly', function () { - expect(Blacklist.TYPES.GUILD).toEqual(1) - expect(Blacklist.TYPES.USER).toEqual(0) - }) - }) - describe('get id', function () { - it('gets the _id', function () { - const data = { - _id: 'aedgs', - type: Blacklist.TYPES.GUILD - } - const blacklist = new Blacklist(data) - expect(blacklist.id).toEqual(data._id) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_FailRecord.test.js b/services/bot/src/tests/structs/db/unit_FailRecord.test.js deleted file mode 100644 index 486819e30..000000000 --- a/services/bot/src/tests/structs/db/unit_FailRecord.test.js +++ /dev/null @@ -1,177 +0,0 @@ -process.env.TEST_ENV = true -const FailRecord = require('../../../structs/db/FailRecord.js') - -function getOldDate (hoursAgo) { - // https://stackoverflow.com/questions/1050720/adding-hours-to-javascript-date-object - const date = new Date() - date.setTime(date.getTime() - hoursAgo * 60 * 60 * 1000) - return date -} - -describe('Unit::structs/db/FailRecord', function () { - afterEach(function () { - jest.restoreAllMocks() - }) - describe('constructor', function () { - it('initializes default values', function () { - const data = { - _id: 'aesdgdf' - } - const record = new FailRecord(data) - expect(record.failedAt).toBeDefined() - expect(record.reason).toBeUndefined() - }) - it('initializes with given values', function () { - const data = { - _id: 'aesdgdf', - reason: 'helaz' - } - const record = new FailRecord(data) - expect(record._id).toEqual(data._id) - expect(record.reason).toEqual(data.reason) - }) - }) - describe('static record', function () { - it('finds the right model', async function () { - const spy = jest.spyOn(FailRecord, 'get').mockResolvedValue({ - save: jest.fn(), - hasFailed: jest.fn().mockReturnValue(false) - }) - const url = 'srfyhed' - await FailRecord.record(url) - expect(spy).toHaveBeenCalledWith(url) - }) - it('updates the reason the model if it exists', async function () { - const reason = 'ewstr4ydh' - const found = { - save: jest.fn(), - hasFailed: jest.fn().mockReturnValue(false), - reason: reason + 'abc' - } - jest.spyOn(FailRecord, 'get').mockResolvedValue(found) - await FailRecord.record('', reason) - expect(found.save).toHaveBeenCalledWith() - expect(found.reason).toEqual(reason) - }) - it('returns the record if it exists', async function () { - const reason = 'rrr' - const found = { - save: jest.fn(), - hasFailed: jest.fn().mockReturnValue(false), - reason, - alerted: true - } - jest.spyOn(FailRecord, 'get').mockResolvedValue(found) - const returned = await FailRecord.record('', reason) - expect(returned).toEqual(found) - }) - }) - describe('static reset', function () { - it('finds the right model', async function () { - const spy = jest.spyOn(FailRecord, 'get').mockResolvedValue({ - delete: jest.fn() - }) - const url = 'srfyhed' - await FailRecord.reset(url) - expect(spy).toHaveBeenCalledWith(url) - }) - it('deletes the found model', async function () { - const found = { - delete: jest.fn() - } - jest.spyOn(FailRecord, 'get').mockResolvedValue(found) - const url = 'srfyhed' - await FailRecord.reset(url) - expect(found.delete).toHaveBeenCalled() - }) - }) - describe('static hasFailed', function () { - it('returns false if getBy returns null', async function () { - jest.spyOn(FailRecord, 'get').mockResolvedValue(null) - const returned = await FailRecord.hasFailed() - expect(returned).toEqual(false) - }) - it('return the value of protoype.hasFailed if found', async function () { - const hasFailed = jest.fn(() => true) - const found = { - hasFailed - } - jest.spyOn(FailRecord, 'get').mockResolvedValue(found) - await expect(FailRecord.hasFailed()) - .resolves.toEqual(true) - hasFailed.mockReturnValue(false) - await expect(FailRecord.hasFailed()) - .resolves.toEqual(false) - }) - }) - describe('pastCutoff', function () { - const cutoff = 2 - const oldDate = getOldDate(cutoff + 1) - const atDate = getOldDate(cutoff) - const recentDate = getOldDate(cutoff - 1) - beforeEach(function () { - jest.spyOn(FailRecord, 'cutoff', 'get').mockReturnValue(cutoff) - }) - it('returns false if cutoff is 0', function () { - jest.spyOn(FailRecord, 'cutoff', 'get').mockReturnValue(0) - const data = { - _id: 'sg' - } - const record = new FailRecord(data) - expect(record.pastCutoff()).toEqual(false) - }) - it('returns false if failedAt is recent', function () { - const data = { - _id: 'sg' - } - const record = new FailRecord(data) - record.failedAt = recentDate.toISOString() - expect(record.pastCutoff()).toEqual(false) - }) - it('returns true if count at or above limit', function () { - const data = { - _id: 'sg' - } - const record = new FailRecord(data) - record.failedAt = atDate.toISOString() - expect(record.pastCutoff()).toEqual(true) - record.failedAt = oldDate.toISOString() - expect(record.pastCutoff()).toEqual(true) - }) - }) - describe('hasFailed', function () { - it('returns pastCutoff func value', function () { - const data = { - _id: 'wseg' - } - const record = new FailRecord(data) - const pastCutoff = 32546 - jest.spyOn(record, 'pastCutoff').mockReturnValue(pastCutoff) - const returned = record.hasFailed() - expect(returned).toEqual(pastCutoff) - }) - }) - describe('toObject', function () { - it('returns correctly', function () { - const data = { - _id: 'aetgswr' - } - const record = new FailRecord(data) - const url = 'w49y6huie' - const reason = 'jackzzz' - const failedAt = 'q3w24t6ery5tu6' - const alerted = true - record._id = url - record.reason = reason - record.failedAt = failedAt - record.alerted = alerted - const returned = record.toObject() - expect(returned).toEqual({ - _id: url, - reason, - failedAt, - alerted - }) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_Feed.test.js b/services/bot/src/tests/structs/db/unit_Feed.test.js deleted file mode 100644 index 0a32e96db..000000000 --- a/services/bot/src/tests/structs/db/unit_Feed.test.js +++ /dev/null @@ -1,359 +0,0 @@ -process.env.TEST_ENV = true -const Feed = require('../../../structs/db/Feed.js') -const Schedule = require('../../../structs/db/Schedule.js') -const Supporter = require('../../../structs/db/Supporter.js') -const FilteredFormat = require('../../../structs/db/FilteredFormat.js') -const Guild = require('../../../structs/Guild.js') - -jest.mock('../../../structs/db/FilteredFormat.js') -jest.mock('../../../structs/db/Schedule.js') -jest.mock('../../../structs/db/Supporter.js') -jest.mock('../../../config.js') - -describe('Unit::structs/db/Feed', function () { - const keys = [ - 'checkDates', - 'formatTables', - 'imgLinksExistence', - 'imgPreviews', - 'text', - 'ncomparisons', - 'pcomparisons' - ] - const necessaryInit = { - title: 'e5rh', - channel: 'e5rhy', - url: 'swr4y', - guild: 'wa4eyh' - } - afterEach(function () { - jest.restoreAllMocks() - }) - describe('constructor', function () { - it('throws an error when channel is missing', function () { - expect(() => new Feed({ title: 1, url: 1, guild: 1 })) - .toThrowError(new Error('Undefined channel')) - }) - it('throws an error when url is missing', function () { - expect(() => new Feed({ title: 1, channel: 1, guild: 1 })) - .toThrowError(new Error('Undefined url')) - }) - it('throws an error when guild is missing', function () { - expect(() => new Feed({ title: 1, url: 1, channel: 1 })) - .toThrowError(new Error('Undefined guild')) - }) - it('sets defined values from arg', function () { - const init = { - ...necessaryInit - } - let val = 'awsfde' - for (const key of keys) { - val += 'r' - init[key] = val - } - init.webhook = { - id: 'asb', - avatar: 'adsef', - name: 'adesgrf' - } - init._id = 'abc' - init.embeds = [{ a: 1, b: 2 }] - init.disabled = true - init.ncomparisons = ['a', 'b'] - init.pcomparisons = ['c', 'd'] - const feed = new Feed({ ...init }) - for (const key in init) { - expect(feed[key]).toEqual(init[key]) - } - expect(feed._id).toEqual(init._id) - expect(feed.webhook).toEqual(init.webhook) - expect(feed.embeds).toEqual(init.embeds) - expect(feed.disabled).toEqual(init.disabled) - expect(feed.ncomparisons).toEqual(init.ncomparisons) - expect(feed.pcomparisons).toEqual(init.pcomparisons) - }) - }) - describe('toObject', function () { - it('exports the _id if it exists', function () { - const feed = new Feed({ ...necessaryInit, _id: 123 }) - const exported = feed.toObject() - expect(exported._id).toEqual(123) - }) - it('returns a plain with the right keys', function () { - const feed = new Feed({ ...necessaryInit }) - const exported = feed.toObject() - expect(Object.prototype.toString.call(exported) === '[object Object]').toEqual(true) - for (const key of keys) { - expect(exported[key]).toEqual(feed[key]) - } - for (const key in necessaryInit) { - expect(exported[key]).toEqual(necessaryInit[key]) - } - expect(exported).not.toHaveProperty('_id') - }) - it('adds the _id if it exists', function () { - const _id = 'W34REY5' - const feed = new Feed({ - ...necessaryInit, - _id - }) - const exported = feed.toObject() - expect(exported._id).toEqual(_id) - }) - it('returns returns regexOps as a map', function () { - const feed = new Feed({ ...necessaryInit }) - const regexOps = { - description: [{ - name: 'swrgf', - search: { - regex: 'awsf' - } - }, { - name: 'swrgf2', - search: { - regex: 'awsf' - } - }] - } - feed.regexOps = regexOps - const exported = feed.toObject() - const expected = new Map() - expected.set('description', regexOps.description) - expect(exported.regexOps).toBeInstanceOf(Map) - expect(exported.regexOps).toEqual(expected) - }) - }) - describe('toJSON', function () { - it('exports the _id if it exists', function () { - const feed = new Feed({ ...necessaryInit, _id: 123 }) - const exported = feed.toJSON() - expect(exported._id).toEqual(123) - }) - it('returns regexOps as plain object', function () { - const feed = new Feed({ ...necessaryInit }) - const regexOps = { - description: [{ - name: 'swrgf', - search: { - regex: 'awsf' - } - }, { - name: 'swrgf2', - search: { - regex: 'awsf' - } - }] - } - feed.regexOps = { ...regexOps } - const exported = feed.toJSON() - expect(exported.regexOps).not.toBeInstanceOf(Map) - expect(exported.regexOps).toEqual(regexOps) - }) - it('returns filters as objects', function () { - const feed = new Feed({ ...necessaryInit }) - feed.filters = { - title: ['1'] - } - feed.rfilters = { - description: ['erdg'] - } - const exported = feed.toJSON() - expect(exported.filters).not.toBeInstanceOf(Map) - expect(exported.rfilters).not.toBeInstanceOf(Map) - }) - it('returns the correct keys', function () { - const feed = new Feed({ ...necessaryInit }) - for (const key of keys) { - feed[key] = 1 - } - const exported = feed.toJSON() - for (const key of keys) { - expect(exported[key]).toEqual(feed[key]) - } - }) - }) - describe('set webhook', function () { - it('sets correctly', function () { - const feed = new Feed({ ...necessaryInit }) - const webhook = { - id: 123, - name: 'aszdfe', - avatar: 'ewstrg', - george: 'costanza' - } - feed.webhook = webhook - expect(feed._webhook).toEqual(webhook) - }) - }) - describe('set split', function () { - it('sets correctly', function () { - const feed = new Feed({ ...necessaryInit }) - const split = { - id: 123, - hawa: 'asdf' - } - feed.webhook = split - expect(feed._webhook).toEqual(split) - }) - }) - describe('getFilteredFormats', function () { - it('calls correctly', async function () { - const _id = 'q23tw45erey5h' - const feed = new Feed({ ...necessaryInit }) - feed._id = _id - await feed.getFilteredFormats() - expect(FilteredFormat.getManyBy).toHaveBeenCalledWith('feed', _id) - }) - }) - describe('enable', function () { - it('calls this.save', async function () { - const feed = new Feed({ ...necessaryInit }) - feed.disabled = 'hura' - const spy = jest.spyOn(feed, 'save').mockResolvedValue() - await feed.enable() - expect(spy).toHaveBeenCalled() - }) - it('sets this.disabled to undefined', async function () { - const feed = new Feed({ ...necessaryInit }) - feed.disabled = 'hoopa dooop' - jest.spyOn(feed, 'save').mockResolvedValue() - await feed.enable() - expect(feed.disabled).toEqual(undefined) - }) - }) - describe('disable', function () { - it('calls this.save', async function () { - const feed = new Feed({ ...necessaryInit }) - const spy = jest.spyOn(feed, 'save').mockResolvedValue() - await feed.disable() - expect(spy).toHaveBeenCalled() - }) - it('sets this.disabled to the reason given', async function () { - const feed = new Feed({ ...necessaryInit }) - jest.spyOn(feed, 'save').mockResolvedValue() - const reason = 'hoopa doop' - await feed.disable(reason) - expect(feed.disabled).toEqual(reason) - }) - it('sets this.disabled to the default reason if none given', async function () { - const feed = new Feed({ ...necessaryInit }) - jest.spyOn(feed, 'save').mockResolvedValue() - await feed.disable() - expect(feed.disabled).toEqual('No reason specified') - }) - }) - describe('hasFastSupporterSchedule', function () { - it('returns correctly if set of guilds passed in', async function () { - const feed = new Feed({ ...necessaryInit }) - const supporterGuilds = new Set(['a', 'b', 'c']) - feed.guild = 'a' - await expect(feed.hasFastSupporterSchedule(supporterGuilds)) - .resolves.toEqual(true) - feed.guild = 'd' - await expect(feed.hasFastSupporterSchedule(supporterGuilds)) - .resolves.toEqual(false) - }) - it('returns correctly if guild was found and has slow rate', async function () { - jest.spyOn(Guild.prototype, 'getSubscription') - .mockResolvedValue({ - slowRate: true - }) - const feed = new Feed({ ...necessaryInit }) - await expect(feed.hasFastSupporterSchedule()).resolves.toEqual(false) - }) - it('returns correctly if guild was found and does not have slow rate', async function () { - jest.spyOn(Guild.prototype, 'getSubscription') - .mockResolvedValue({ - slowRate: false - }) - const feed = new Feed({ ...necessaryInit }) - await expect(feed.hasFastSupporterSchedule()).resolves.toEqual(true) - }) - it('returns correctly for supporter if nothing passed in', async function () { - jest.spyOn(Guild.prototype, 'getSubscription') - .mockResolvedValue(null) - jest.spyOn(Guild.prototype, 'getSupporter') - .mockResolvedValueOnce({ hasSlowRate: async () => false }) - .mockResolvedValueOnce({ hasSlowRate: async () => true }) - const feed = new Feed({ ...necessaryInit }) - await expect(feed.hasFastSupporterSchedule()).resolves.toEqual(true) - await expect(feed.hasFastSupporterSchedule()).resolves.toEqual(false) - }) - }) - describe('determineSchedule', function () { - const schedules = [{ - name: 'default' - }, { - name: 'sched1', - feeds: ['aa', 'bb', 'cc'], - keywords: ['key1', 'key2'] - }, { - name: 'sched2', - feeds: ['id1', 'id2', 'id3'], - keywords: ['yek1', 'yek2'] - }] - beforeEach(function () { - Schedule.getAll.mockResolvedValue([]) - Supporter.getValidSupporterOfGuild.mockResolvedValue([]) - }) - afterEach(function () { - Schedule.getAll.mockReset() - Supporter.getValidSupporterOfGuild.mockReset() - }) - it('calls Schedule.getAll if it is not passed in', async function () { - const feed = new Feed({ ...necessaryInit }) - await feed.determineSchedule(undefined, []) - expect(Schedule.getAll).toHaveBeenCalledTimes(1) - }) - it('returns the supporter schedule if supported', async function () { - const origSchedule = Supporter.schedule - Supporter.schedule = { - foo: 'bar' - } - Supporter.enabled = true - const feed = new Feed({ ...necessaryInit }) - jest.spyOn(feed, 'hasFastSupporterSchedule') - .mockResolvedValue(true) - const determined = await feed.determineSchedule([]) - expect(determined).toEqual(Supporter.schedule) - Supporter.schedule = origSchedule - Supporter.enabled = false - }) - it('does not return the supporter schedule if supported', async function () { - const origSchedule = Supporter.schedule - Supporter.schedule = { - foo: 'bar' - } - Supporter.enabled = true - const feed = new Feed({ ...necessaryInit }) - feed.url = 'feed43' - jest.spyOn(feed, 'hasFastSupporterSchedule') - .mockResolvedValue(true) - const determined = await feed.determineSchedule([]) - expect(determined).not.toEqual(Supporter.schedule) - Supporter.schedule = origSchedule - Supporter.enabled = false - }) - it('returns the schedule that has the feed\'s id', async function () { - const feed = new Feed({ ...necessaryInit }) - feed._id = 'id1' - feed.url = 'no match' - const schedule = await feed.determineSchedule(schedules, []) - expect(schedule).toEqual(schedules[2]) - }) - it('returns the schedule if it has a keyword in the feed\'s url', async function () { - const feed = new Feed({ ...necessaryInit }) - feed._id = 'no match' - feed.url = 'dun yek2 haz' - const schedule = await feed.determineSchedule(schedules, []) - expect(schedule).toEqual(schedules[2]) - }) - it('returns the default schedule if it matches no schedules', async function () { - const feed = new Feed({ ...necessaryInit }) - feed._id = 'no match' - feed.url = 'no match' - const schedule = await feed.determineSchedule(schedules, []) - expect(schedule).toEqual(schedules[0]) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_FilterBase.test.js b/services/bot/src/tests/structs/db/unit_FilterBase.test.js deleted file mode 100644 index c8f28de10..000000000 --- a/services/bot/src/tests/structs/db/unit_FilterBase.test.js +++ /dev/null @@ -1,364 +0,0 @@ -process.env.TEST_ENV = true -const FilterBase = require('../../../structs/db/FilterBase.js') - -jest.mock('../../../config.js') - -class FilterClass extends FilterBase { - static get Model () { - - } -} - -class FilterClassWithFoo extends FilterBase { - constructor (data, _saved) { - super(data, _saved) - this.foo = this.getField('foo') - } - - toObject () { - return { - foo: this.foo - } - } - - static get Model () { - - } -} - -describe('Unit::structs/db/FilterBase', function () { - afterEach(function () { - jest.restoreAllMocks() - }) - describe('constructor', function () { - it('initializes correctly', function () { - const base = new FilterClass() - expect(base.filters).toEqual({}) - expect(base.rfilters).toEqual({}) - }) - }) - describe('toObject', function () { - it('converts the filters into a map', function () { - const base = new FilterClass() - const filters = { - title: ['ab', 'gf'], - dasdge: [1, 2, 3] - } - base.filters = filters - const returned = base.toObject() - expect(returned.filters).toBeInstanceOf(Map) - for (const key in filters) { - expect(returned.filters.get(key)).toEqual(filters[key]) - } - }) - it('converts rfilters into a map', function () { - const base = new FilterClass() - const rfilters = { - title: 'swrhyetg', - dasdge: '46treht5ru' - } - base.rfilters = rfilters - const returned = base.toObject() - expect(returned.rfilters).toBeInstanceOf(Map) - for (const key in rfilters) { - expect(returned.rfilters.get(key)).toEqual(rfilters[key]) - } - }) - }) - describe('toJSON', function () { - it('returns plain object', function () { - const filters = { - a: ['fdg'], - b: [1, 2, 6] - } - const rfilters = { - a: 'qewts', - c: 'asedgrfh', - d: 'wse4yr5' - } - const base = new FilterClassWithFoo() - base.filters = filters - base.rfilters = rfilters - base.foo = 'helloa world' - const returned = base.toJSON() - expect(returned).toEqual({ - foo: base.foo, - filters, - rfilters - }) - expect(returned.filters).not.toBeInstanceOf(Map) - // expect(returned.filters).toEqual(filters) - }) - }) - describe('pruneFilters', function () { - it('deletes invalid filters', function () { - const filters = { - a: [], - b: 0, - good: ['a', 'bc', 'de'], - c: null, - d: undefined, - e: '3', - f: 1 - } - const base = new FilterClass() - base.filters = { ...filters } - base.pruneFilters() - const keys = Object.keys(base.filters) - expect(keys).toHaveLength(1) - expect(base.filters.good).toEqual(filters.good) - }) - }) - describe('getFilterIndex', function () { - it('works', function () { - const filters = { - skadoosh: ['a', 'b', 'c', 'd'], - honk: ['z', 'g', 'e'] - } - const base = new FilterClass() - base.filters = filters - expect(base.getFilterIndex('skadoosh', 'c')).toEqual(2) - expect(base.getFilterIndex('skadoosh', 'B')).toEqual(1) - expect(base.getFilterIndex('skadoosh', 'h')).toEqual(-1) - }) - }) - describe('removeFilter', function () { - beforeEach(function () { - jest.spyOn(FilterClass.prototype, 'save').mockReturnValue() - }) - it('throws an error if it does not exist', function () { - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(-1) - const base = new FilterClass() - return expect(base.removeFilter('', 'abc')) - .rejects.toThrowError(new Error('"abc" does not exist')) - }) - it('splices the value if it exists', async function () { - const filters = { - foo: ['a', 'g', 'facts'] - } - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(1) - const base = new FilterClass() - base.filters = filters - await base.removeFilter('foo', '') - expect(base.filters).toEqual({ - foo: ['a', 'facts'] - }) - }) - it('calls this.save', async function () { - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(0) - const base = new FilterClass() - const spy = jest.spyOn(base, 'save') - base.filters = { - foo: ['abc'] - } - await base.removeFilter('foo', 'abc') - expect(spy).toHaveBeenCalledTimes(1) - }) - }) - describe('addFilter', function () { - beforeEach(function () { - jest.spyOn(FilterClass.prototype, 'save').mockReturnValue() - }) - it('throws an error if it exists', function () { - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(1) - const base = new FilterClass() - return expect(base.addFilter('', 'abc')) - .rejects.toThrowError(new Error('"abc" already exists')) - }) - it('adds the value', async function () { - const filters = { - foo: ['a'] - } - const toAdd = 'ju' - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(-1) - const base = new FilterClass() - base.filters = filters - await base.addFilter('foo', toAdd) - await base.addFilter('fad', toAdd) - expect(base.filters).toEqual({ - foo: ['a', toAdd], - fad: [toAdd] - }) - }) - it('lowercase and trims the added value', async function () { - const filters = { - foo: ['a'] - } - const toAdd = ' JA ' - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(-1) - const base = new FilterClass() - base.filters = filters - await base.addFilter('foo', toAdd) - expect(base.filters).toEqual({ - foo: ['a', toAdd.toLowerCase().trim()] - }) - }) - it('calls this.save', async function () { - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(-1) - const base = new FilterClass() - const spy = jest.spyOn(base, 'save') - base.filters = { - foo: ['abc'] - } - await base.addFilter('foo', 'abc') - expect(spy).toHaveBeenCalledTimes(1) - }) - }) - describe('addFilters', function () { - beforeEach(function () { - jest.spyOn(FilterClass.prototype, 'save').mockReturnValue() - }) - it('throws an error if a filter already exists', function () { - jest.spyOn(FilterClass.prototype, 'getFilterIndex') - .mockReturnValueOnce(-1) - .mockReturnValue(1) - const base = new FilterClass() - base.filters = { - h: ['da', 'abc'] - } - return expect(base.addFilters('h', ['zzz', 'asdf'])) - .rejects.toThrowError(new Error('"asdf" already exists')) - }) - it('adds the values', async function () { - const filters = { - foo: ['a'] - } - const toAdd = ['fun', 'two', 'ne', 'zz'] - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(-1) - const base = new FilterClass() - base.filters = filters - await base.addFilters('foo', toAdd) - await base.addFilters('bar', toAdd) - expect(base.filters).toEqual({ - foo: ['a'].concat(toAdd), - bar: toAdd - }) - }) - it('lowercase and trims the added values', async function () { - const filters = { - foo: ['a'] - } - const toAdd = [' JA ', '43W E'] - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(-1) - const base = new FilterClass() - base.filters = filters - await base.addFilters('foo', toAdd) - expect(base.filters).toEqual({ - foo: ['a'].concat(toAdd.map(s => s.trim().toLowerCase())) - }) - }) - it('calls this.save', async function () { - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(-1) - const base = new FilterClass() - const spy = jest.spyOn(base, 'save') - base.filters = { - foo: ['abc'] - } - await base.addFilters('foo', []) - expect(spy).toHaveBeenCalledTimes(1) - }) - }) - describe('removeFilters', function () { - beforeEach(function () { - jest.spyOn(FilterClass.prototype, 'save').mockReturnValue() - }) - it('removes the values', async function () { - const filters = { - foo: ['a', 'ae', 'wsr', 'swrg'] - } - jest.spyOn(FilterClass.prototype, 'getFilterIndex') - .mockReturnValueOnce(2) - .mockReturnValueOnce(1) - const base = new FilterClass() - base.filters = filters - await base.removeFilters('foo', ['a', 'b']) - expect(base.filters).toEqual({ - foo: ['a', 'swrg'] - }) - }) - it('calls this.save if removed', async function () { - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(0) - const base = new FilterClass() - const spy = jest.spyOn(base, 'save') - base.filters = { - foo: ['abc'] - } - await base.removeFilters('foo', ['a']) - expect(spy).toHaveBeenCalledTimes(1) - }) - it('does not call this.save if not removed', async function () { - jest.spyOn(FilterClass.prototype, 'getFilterIndex').mockReturnValue(-1) - const base = new FilterClass() - const spy = jest.spyOn(base, 'save') - base.filters = { - foo: ['abc'] - } - await base.removeFilters('foo', ['a']) - expect(spy).not.toHaveBeenCalled() - }) - it('removes the values if indexes are not in order', async function () { - const filters = { - foo: ['a', 'ae', 'wsr', 'swrg'] - } - jest.spyOn(FilterClass.prototype, 'getFilterIndex') - .mockReturnValueOnce(1) - .mockReturnValueOnce(2) - const base = new FilterClass() - base.filters = filters - await base.removeFilters('foo', ['a', 'b']) - expect(base.filters).toEqual({ - foo: ['a', 'swrg'] - }) - }) - }) - describe('removeAllFilters', function () { - beforeEach(function () { - jest.spyOn(FilterClass.prototype, 'save').mockReturnValue() - }) - it('sets this.filters to an empty object', async function () { - const base = new FilterClass() - base.filters = { - foo: 'bar', - jack: 1 - } - await base.removeAllFilters() - expect(base.filters).toEqual({}) - }) - it('calls this.save', async function () { - const base = new FilterClass() - const spy = jest.spyOn(base, 'save') - await base.removeAllFilters() - expect(spy).toHaveBeenCalled() - }) - }) - describe('validate', function () { - it('calls pruneFilters', function () { - const base = new FilterClass() - const spy = jest.spyOn(base, 'pruneFilters').mockReturnValue() - base.validate() - expect(spy).toHaveBeenCalled() - }) - }) - describe('hasFilters', function () { - it('returns correctly', function () { - const base = new FilterClass() - base.filters = {} - expect(base.hasFilters()).toEqual(false) - base.filters = { - title: ['ha'] - } - expect(base.hasFilters()).toEqual(true) - }) - }) - describe('hasRFilters', function () { - it('returns correctly', function () { - const base = new FilterClass() - base.rfilters = {} - expect(base.hasRFilters()).toEqual(false) - base.rfilters = { - title: 'halo' - } - expect(base.hasRFilters()).toEqual(true) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_FilteredFormat.test.js b/services/bot/src/tests/structs/db/unit_FilteredFormat.test.js deleted file mode 100644 index e261aef8f..000000000 --- a/services/bot/src/tests/structs/db/unit_FilteredFormat.test.js +++ /dev/null @@ -1,157 +0,0 @@ -process.env.TEST_ENV = true -const FilteredFormatModel = require('../../../models/FilteredFormat.js') -const FilteredFormat = require('../../../structs/db/FilteredFormat.js') -const embedSchema = require('../../../models/common/Embed.js') - -jest.mock('../../../config.js') - -describe('Unit::structs/db/FilteredFormat', function () { - const keys = Object.keys(embedSchema) - keys.splice(keys.indexOf('_id'), 1) - keys.splice(keys.indexOf('fields'), 1) - const initData = { - feed: '123b', - text: 'qawed' - } - beforeEach(() => { - jest.restoreAllMocks() - }) - describe('constructor', function () { - it('throws for missing feed', function () { - expect(() => new FilteredFormat({})).toThrow('feed is undefined') - }) - it('does not throw for populated text', function () { - const data = { - feed: '1234345y', - text: 'asedg' - } - expect(() => new FilteredFormat(data)).not.toThrow() - }) - it('does not throw for populated embed', function () { - const data = { - feed: '1234345y', - embeds: [{ - description: 'sewgt' - }] - } - expect(() => new FilteredFormat(data)).not.toThrow() - }) - it('does not throw for unpopulated text and embed', function () { - const data = { - feed: '1234345y', - embeds: [] - } - expect(() => new FilteredFormat(data)).toThrow('text or embeds must be populated') - }) - }) - describe('static isPopulatedEmbedField', function () { - afterEach(function () { - jest.restoreAllMocks() - }) - it('returns false for no field', function () { - expect(FilteredFormat.isPopulatedEmbedField()) - .toBeFalsy() - }) - it('false for no name or value', function () { - expect(FilteredFormat.isPopulatedEmbedField({ name: 1 })) - .toBeFalsy() - expect(FilteredFormat.isPopulatedEmbedField({ value: 1 })) - .toBeFalsy() - expect(FilteredFormat.isPopulatedEmbedField({})) - .toBeFalsy() - }) - it('returns true when both exists', function () { - expect(FilteredFormat.isPopulatedEmbedField({ name: 1, value: 1 })) - .toBeTruthy() - }) - }) - describe('static isPopulatedEmbed', function () { - it('returns true for filled embeds', function () { - for (const key of keys) { - const embed = { - [key]: 'abc', - fields: [] - } - expect(FilteredFormat.isPopulatedEmbed(embed)) - .toBeTruthy() - } - const embedWithFields = { - fields: [{}] - } - jest.spyOn(FilteredFormat, 'isPopulatedEmbedField') - .mockReturnValueOnce(true) - expect(FilteredFormat.isPopulatedEmbed(embedWithFields)) - .toBeTruthy() - }) - it('returns false for unfilled embeds', function () { - expect(FilteredFormat.isPopulatedEmbed({})) - .toBeFalsy() - expect(FilteredFormat.isPopulatedEmbed({ fields: [] })) - .toBeFalsy() - }) - it('splices the correct fields', function () { - const embed = { - fields: [{ - foo: 1 - }, { - bar: 2 - }, { - baz: 3 - }] - } - jest.spyOn(FilteredFormat, 'isPopulatedEmbedField') - .mockReturnValueOnce(false) - .mockReturnValueOnce(false) - .mockReturnValueOnce(true) - FilteredFormat.isPopulatedEmbed(embed, true) - expect(embed.fields).toHaveLength(1) - expect(embed.fields[0]).toEqual({ foo: 1 }) - }) - }) - describe('pruneEmbeds', function () { - it('splices correct embeds', function () { - const embeds = [{}, {}, {}, { jack: 1 }] - const format = new FilteredFormat({ ...initData }) - format.embeds = embeds - jest.spyOn(FilteredFormat, 'isPopulatedEmbed') - .mockReturnValueOnce(true) - .mockReturnValueOnce(false) - .mockReturnValueOnce(false) - .mockReturnValueOnce(false) - FilteredFormat.pruneEmbeds(format.embeds) - expect(format.embeds).toEqual([{ jack: 1 }]) - }) - }) - describe('validate', function () { - it('calls pruneEmbeds', async function () { - const format = new FilteredFormat({ - ...initData - }) - format.embeds = [] - const spy = jest.spyOn(FilteredFormat, 'pruneEmbeds').mockReturnValue() - await format.validate() - expect(spy).toHaveBeenCalledTimes(1) - }) - it('does not throw if no embeds specified', async function () { - const format = new FilteredFormat({ - ...initData - }) - format.embeds = undefined - const spy = jest.spyOn(FilteredFormat, 'pruneEmbeds').mockReturnValue() - await format.validate() - expect(spy).toHaveBeenCalledTimes(0) - }) - it('throws an error for invalid timestamp in an embed', function () { - const format = new FilteredFormat({ ...initData }) - format.embeds = [{ timestamp: 'o' }] - jest.spyOn(FilteredFormat, 'pruneEmbeds').mockReturnValue() - return expect(format.validate()) - .rejects.toThrowError(new Error('Timestamp can only be article or now')) - }) - }) - describe('static get Model', function () { - it('returns the right model', function () { - expect(FilteredFormat.Model).toEqual(FilteredFormatModel.Model) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_KeyValue.test.js b/services/bot/src/tests/structs/db/unit_KeyValue.test.js deleted file mode 100644 index 8bbe75480..000000000 --- a/services/bot/src/tests/structs/db/unit_KeyValue.test.js +++ /dev/null @@ -1,34 +0,0 @@ -process.env.TEST_ENV = true -const KeyValue = require('../../../structs/db/KeyValue.js') - -jest.mock('../../../config.js') - -describe('Unit::structs/db/KeyValue', function () { - describe('constructor', function () { - it('throws for undefined id', function () { - const data = { - value: 123 - } - expect(() => new KeyValue(data)) - .toThrow(new TypeError('_id is undefined')) - }) - it('throws for undefined value', function () { - const data = { - _id: 'asd' - } - expect(() => new KeyValue(data)) - .toThrow(new TypeError('value is undefined')) - }) - }) - describe('toObject', function () { - it('returns correctly', function () { - const data = { - _id: 'srfh', - value: '3e45y' - } - const kv = new KeyValue({ ...data }) - const returned = kv.toObject() - expect(returned).toEqual(data) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_Patron.test.js b/services/bot/src/tests/structs/db/unit_Patron.test.js deleted file mode 100644 index 7e77accd8..000000000 --- a/services/bot/src/tests/structs/db/unit_Patron.test.js +++ /dev/null @@ -1,233 +0,0 @@ -process.env.TEST_ENV = true -const Patron = require('../../../structs/db/Patron.js') -const config = require('../../../config.js') - -jest.mock('../../../config.js', () => ({ - get: () => ({ - feeds: { - max: 3.5 - } - }) -})) - -describe('Unit::structs/db/Patron', function () { - afterEach(function () { - jest.restoreAllMocks() - }) - const initData = { - _id: '123s4', - status: '32hj5', - lastCharge: 'sdhb', - pledgeLifetime: 123, - pledge: 123, - name: 'q3e2w64yre7', - email: 'szadgrf' - } - describe('constructor', function () { - it('throws for undefined _id', function () { - const data = { - status: 'ased', - pledgeLifetime: 123, - pledge: 123, - name: 'easdg', - email: 'segdt' - } - expect(() => new Patron(data)) - .toThrow(new TypeError('_id is undefined')) - }) - it('throws for undefined pledgeLifetime', function () { - const data = { - _id: '1234', - status: '325', - pledge: 123, - name: 'easdg', - email: 'segdt' - } - expect(() => new Patron(data)) - .toThrow(new TypeError('pledgeLifetime is undefined')) - }) - it('throws for undefined pledge', function () { - const data = { - _id: '1234', - status: '325', - lastCharge: 'szfxrhd', - pledgeLifetime: 123, - name: 'easdg', - email: 'segdt' - } - expect(() => new Patron(data)) - .toThrow(new TypeError('pledge is undefined')) - }) - it('does not throws for undefined name, email, lastCharge and status', function () { - const data = { - _id: '1234', - pledgeLifetime: 123, - pledge: 123 - } - expect(() => new Patron(data)) - .not.toThrow() - }) - }) - describe('toObject', function () { - it('returns correctly', function () { - const patron = new Patron({ ...initData }) - expect(patron.toObject()).toEqual(initData) - }) - }) - describe('determineMaxFeeds', function () { - describe('inactive patron', function () { - it('returns config.feeds.max', function () { - jest.spyOn(Patron.prototype, 'isActive').mockReturnValue(false) - const patron = new Patron({ ...initData }) - const max = config.get().feeds.max - expect(patron.determineMaxFeeds()).toEqual(max) - }) - }) - describe('active patron', function () { - beforeEach(function () { - jest.spyOn(Patron.prototype, 'isActive').mockReturnValue(true) - }) - it('returns 140 for >= 2000 for pledge', function () { - const patron = new Patron({ ...initData }) - patron.pledge = 2111 - expect(patron.determineMaxFeeds()).toEqual(140) - patron.pledge = 2000 - expect(patron.determineMaxFeeds()).toEqual(140) - }) - it('returns 70 for >= 1000 for pledge', function () { - const patron = new Patron({ ...initData }) - patron.pledge = 1100 - expect(patron.determineMaxFeeds()).toEqual(70) - patron.pledge = 1000 - expect(patron.determineMaxFeeds()).toEqual(70) - }) - it('returns 35 for >= 500 for pledge', function () { - const patron = new Patron({ ...initData }) - patron.pledge = 511 - expect(patron.determineMaxFeeds()).toEqual(35) - patron.pledge = 500 - expect(patron.determineMaxFeeds()).toEqual(35) - }) - it('returns 15 for >= 250 for pledge', function () { - const patron = new Patron({ ...initData }) - patron.pledge = 255 - expect(patron.determineMaxFeeds()).toEqual(15) - patron.pledge = 250 - expect(patron.determineMaxFeeds()).toEqual(15) - }) - it('returns default for < 250 for pledge', function () { - const patron = new Patron({ ...initData }) - patron.pledge = 249 - const max = config.get().feeds.max - expect(patron.determineMaxFeeds()).toEqual(max) - }) - }) - }) - describe('determineMaxGuilds', function () { - describe('inactive patron', function () { - it('returns 1', function () { - jest.spyOn(Patron.prototype, 'isActive').mockReturnValue(false) - const patron = new Patron({ ...initData }) - expect(patron.determineMaxGuilds()).toEqual(1) - }) - }) - describe('active patron', function () { - beforeEach(function () { - jest.spyOn(Patron.prototype, 'isActive').mockReturnValue(true) - }) - it('returns 4 for >= 2500 for pledgeLifetime', function () { - const patron = new Patron({ ...initData }) - patron.pledgeLifetime = 2511 - expect(patron.determineMaxGuilds()).toEqual(4) - patron.pledgeLifetime = 2500 - expect(patron.determineMaxGuilds()).toEqual(4) - }) - it('returns 3 for >= 1500 for pledgeLifetime', function () { - const patron = new Patron({ ...initData }) - patron.pledgeLifetime = 1511 - expect(patron.determineMaxGuilds()).toEqual(3) - patron.pledgeLifetime = 1500 - expect(patron.determineMaxGuilds()).toEqual(3) - }) - it('returns 2 for >= 500 for pledgeLifetime', function () { - const patron = new Patron({ ...initData }) - patron.pledgeLifetime = 511 - expect(patron.determineMaxGuilds()).toEqual(2) - patron.pledgeLifetime = 500 - expect(patron.determineMaxGuilds()).toEqual(2) - }) - it('returns 1 for < 500 for pledgeLifetime', function () { - const patron = new Patron({ ...initData }) - patron.pledgeLifetime = 499 - expect(patron.determineMaxGuilds()).toEqual(1) - }) - }) - }) - describe('isActive', function () { - it('returns true for active status', function () { - const patron = new Patron({ ...initData }) - patron.status = Patron.STATUS.ACTIVE - expect(patron.isActive()).toEqual(true) - }) - it('returns false for unknown status', function () { - const patron = new Patron({ ...initData }) - patron.status = 'drshtrj' - expect(patron.isActive()).toEqual(false) - }) - it('returns false for former status', function () { - const patron = new Patron({ ...initData }) - patron.status = Patron.STATUS.FORMER - expect(patron.isActive()).toEqual(false) - }) - describe('declined status', function () { - it('returns false for > 3 days ago', function () { - const longAgo = new Date(new Date().toUTCString()) - longAgo.setDate(longAgo.getDate() - 4) - const patron = new Patron({ ...initData }) - patron.status = Patron.STATUS.DECLINED - patron.lastCharge = longAgo.toString() - expect(patron.isActive()).toEqual(false) - }) - it('returns true for <= 3 days ago', function () { - const longAgo = new Date(new Date().toUTCString()) - longAgo.setDate(longAgo.getDate() - 3) - const patron = new Patron({ ...initData }) - patron.status = Patron.STATUS.DECLINED - patron.lastCharge = longAgo.toString() - expect(patron.isActive()).toEqual(true) - }) - it('returns false for missing charge date', function () { - const patron = new Patron({ ...initData }) - patron.status = Patron.STATUS.DECLINED - patron.lastCharge = undefined - expect(patron.isActive()).toEqual(false) - }) - }) - }) - describe('determineWebhook', function () { - describe('inactive patron', function () { - it('returns false', function () { - jest.spyOn(Patron.prototype, 'isActive').mockReturnValue(false) - const patron = new Patron({ ...initData }) - expect(patron.determineWebhook()).toEqual(false) - }) - }) - describe('active patron', function () { - beforeEach(function () { - jest.spyOn(Patron.prototype, 'isActive').mockReturnValue(true) - }) - it('returns true for >= 100', function () { - const patron = new Patron({ ...initData }) - patron.pledge = 101 - expect(patron.determineWebhook()).toEqual(true) - patron.pledge = 100 - expect(patron.determineWebhook()).toEqual(true) - }) - it('returns false for < 100', function () { - const patron = new Patron({ ...initData }) - patron.pledge = 99 - expect(patron.determineWebhook()).toEqual(false) - }) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_Profile.test.js b/services/bot/src/tests/structs/db/unit_Profile.test.js deleted file mode 100644 index 123955bd7..000000000 --- a/services/bot/src/tests/structs/db/unit_Profile.test.js +++ /dev/null @@ -1,171 +0,0 @@ -process.env.TEST_ENV = true -const Profile = require('../../../structs/db/Profile.js') -const Supporter = require('../../../structs/db/Supporter.js') - -jest.mock('../../../structs/db/Supporter.js') -jest.mock('../../../config.js', () => ({ - get: () => ({ - bot: { - prefix: 'sdf' - }, - feeds: { - max: 22 - } - }) -})) - -describe('Unit::structs/db/Profile', function () { - const necessaryInit = { - _id: 'sr', - name: 'sedg' - } - afterEach(function () { - jest.restoreAllMocks() - Supporter.getValidSupporterOfGuild.mockReset() - }) - describe('constructor', function () { - it('throws an error if id is not set', function () { - expect(() => new Profile({ name: 1 })) - .toThrowError(new Error('Undefined _id')) - }) - it('throws an error if name is not set', function () { - expect(() => new Profile({ _id: 1 })) - .toThrowError(new Error('Undefined name')) - }) - it('does not throw with correct info', function () { - expect(() => new Profile({ _id: 1, name: 1 })) - .not.toThrowError() - }) - it('sets defined values from arg', function () { - const initialize = { - _id: 1, - name: 1, - dateFormat: '123', - dateLanguage: '452', - locale: '2344', - prefix: 'aws', - timezone: 'as' - } - const profile = new Profile(initialize) - for (const key in initialize) { - expect(profile[key]).toEqual(initialize[key]) - } - expect(profile.alert).toEqual([]) - }) - }) - describe('toObject', function () { - it('returns a plain with the right keys', function () { - const initialize = { - _id: 1, - name: 1, - dateFormat: '123', - dateLanguage: '452', - locale: '2344', - prefix: 'aws', - timezone: 'as' - } - const profile = new Profile(initialize) - const exported = profile.toObject() - expect(Object.prototype.toString.call(exported)).toEqual('[object Object]') - for (const key in initialize) { - expect(exported[key]).toEqual(profile[key]) - } - }) - }) - describe('static setPrefix', function () { - afterEach(function () { - Profile.prefixes = new Map() - }) - it('sets the new prefix', function () { - const guildID = 'qwt4ery' - const prefix = 'w4rye5t' - Profile.setPrefix(guildID, prefix) - expect(Profile.prefixes.get(guildID)) - .toEqual(prefix) - }) - }) - describe('static getPrefix', function () { - afterEach(function () { - Profile.prefixes = new Map() - }) - it('gets the prefix', function () { - const guildID = 'qwt4ery' - const prefix = 'w4rye5t' - Profile.prefixes.set(guildID, prefix) - expect(Profile.getPrefix(guildID)) - .toEqual(prefix) - }) - }) - describe('static populatePrefixes', function () { - it('populates properly', async function () { - const profiles = [{ - _id: 1, - prefix: 'a' - }, { - _id: 2 - }, { - _id: 3, - prefix: 'b' - }] - jest.spyOn(Profile, 'getAll') - .mockResolvedValue(profiles) - const setPrefix = jest.spyOn(Profile, 'setPrefix') - .mockImplementation() - await Profile.populatePrefixes() - expect(setPrefix).toHaveBeenCalledWith(profiles[0]._id, profiles[0].prefix) - expect(setPrefix).not.toHaveBeenCalledWith(profiles[1]._id, profiles[1].prefix) - expect(setPrefix).toHaveBeenCalledWith(profiles[2]._id, profiles[2].prefix) - }) - it('clears prefixes', async function () { - Profile.prefixes = new Map([['1', '2'], ['3', '4']]) - jest.spyOn(Profile, 'getAll') - .mockResolvedValue([]) - jest.spyOn(Profile, 'setPrefix') - .mockImplementation() - await Profile.populatePrefixes() - expect(Profile.prefixes.size).toEqual(0) - }) - }) - describe('setPrefixAndSave', function () { - it('saves', async function () { - const prefix = 'qa3et4wr' - const profile = new Profile({ ...necessaryInit }) - const save = jest.spyOn(profile, 'save') - .mockImplementation() - await profile.setPrefixAndSave(prefix) - expect(save).toHaveBeenCalledTimes(1) - }) - it('sets the prefix', async function () { - const prefix = 'qa3et4wr' - const profile = new Profile({ ...necessaryInit }) - jest.spyOn(profile, 'save') - .mockImplementation() - await profile.setPrefixAndSave(prefix) - expect(profile.prefix).toEqual(prefix) - }) - it('updates the cache', async function () { - const prefix = 'qa3et4wr' - const profileID = 'w4rey57tu6' - const profile = new Profile({ ...necessaryInit }) - profile._id = profileID - const setPrefix = jest.spyOn(Profile, 'setPrefix') - .mockImplementation() - jest.spyOn(profile, 'save') - .mockImplementation() - await profile.setPrefixAndSave(prefix) - expect(setPrefix).toHaveBeenCalledWith(profileID, prefix) - }) - it('deletes from cache if undefined', async function () { - const prefix = undefined - const profileID = 'w4rey57tu6' - const profile = new Profile({ ...necessaryInit }) - profile._id = profileID - const deletePrefix = jest.spyOn(Profile, 'deletePrefix') - .mockImplementation() - jest.spyOn(profile, 'save') - .mockImplementation() - await profile.setPrefixAndSave(prefix) - expect(deletePrefix).toHaveBeenCalledWith(profileID) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_Schedule.test.js b/services/bot/src/tests/structs/db/unit_Schedule.test.js deleted file mode 100644 index 51142cc5e..000000000 --- a/services/bot/src/tests/structs/db/unit_Schedule.test.js +++ /dev/null @@ -1,58 +0,0 @@ -process.env.TEST_ENV = true -const Schedule = require('../../../structs/db/Schedule.js') - -jest.mock('../../../config.js') - -describe('Unit::structs/db/Schedule', function () { - describe('constructor', function () { - it('throws if name is missing', function () { - expect(() => new Schedule({ - refreshRateMinutes: 34, - keywords: ['ab'], - feeds: ['asd'] - })).toThrow('name is undefined') - }) - it('throws if refreshRateMinutes is missing', function () { - expect(() => new Schedule({ - name: 'hello', - keywords: ['ab'], - feeds: ['asd'] - })).toThrow('refreshRateMinutes is undefined') - }) - it('throws if refreshRateMinutes is not a number', function () { - expect(() => new Schedule({ - refreshRateMinutes: 'jackalope', - name: 'hello', - keywords: ['ab'], - feeds: ['asd'] - })).toThrow('refreshRateMinutes must be a number') - }) - it('does not throw if name and refresh rate is defined', function () { - expect(() => new Schedule({ - name: 'hello', - refreshRateMinutes: 12, - keywords: ['ab'], - feeds: ['asd'] - })).not.toThrow() - }) - it('does not throw if keywords and feeds is undefined', function () { - expect(() => new Schedule({ - name: 'hello', - refreshRateMinutes: 12 - })).not.toThrow() - }) - }) - describe('toObject', function () { - it('returns correctly', function () { - const data = { - name: 'hello', - refreshRateMinutes: 12, - keywords: ['ab'], - feeds: ['asd'] - } - const schedule = new Schedule(data) - const returned = schedule.toObject() - expect(returned).toEqual(data) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_ShardStats.test.js b/services/bot/src/tests/structs/db/unit_ShardStats.test.js deleted file mode 100644 index 676348511..000000000 --- a/services/bot/src/tests/structs/db/unit_ShardStats.test.js +++ /dev/null @@ -1,29 +0,0 @@ -process.env.TEST_ENV = true -const ScheduleStats = require('../../../structs/db/ScheduleStats.js') - -jest.mock('../../../config.js') - -describe('Unit::structs/db/ScheduleStats', function () { - describe('constructor', function () { - it('throws an error if _id is undefined', function () { - expect(() => new ScheduleStats()).toThrowError('Undefined _id') - }) - }) - describe('toObject', function () { - it('returns correctly', function () { - const data = { - _id: 'q23rew', - feeds: 1, - cycleTime: 2, - cycleFails: 3, - cycleURLs: 4, - lastUpdated: 'q3ew2t4r' - } - const stats = new ScheduleStats(data) - for (const key in data) { - stats[key] = data[key] - } - expect(stats.toObject()).toEqual(data) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_Subscriber.test.js b/services/bot/src/tests/structs/db/unit_Subscriber.test.js deleted file mode 100644 index c440a5aa2..000000000 --- a/services/bot/src/tests/structs/db/unit_Subscriber.test.js +++ /dev/null @@ -1,110 +0,0 @@ -process.env.TEST_ENV = true -const Subscriber = require('../../../structs/db/Subscriber.js') - -jest.mock('../../../config.js') - -describe('Unit::structs/db/Subscriber', function () { - const initData = { - feed: 'abc', - id: '123', - type: 'role' - } - describe('constructor', function () { - it('throws an error for missing feed', function () { - const data = { - id: 'awnb', - type: 'role' - } - expect(() => new Subscriber(data)) - .toThrow('feed is undefined') - }) - it('throws an error for missing id', function () { - const data = { - type: 'role', - feed: '123' - } - expect(() => new Subscriber(data)) - .toThrow('id is undefined') - }) - it('throws an error for invalid type', function () { - const data = { - id: '123', - type: 'fgh', - feed: '123' - } - expect(() => new Subscriber(data)) - .toThrow('type must be "user" or "role"') - }) - it('does not throw error for proper data', function () { - const data = { - id: '123', - type: 'role', - feed: '123' - } - expect(() => new Subscriber(data)).not.toThrow() - data.type = 'user' - expect(() => new Subscriber(data)).not.toThrow() - }) - }) - describe('static get TYPES', function () { - it('returns correctly', function () { - expect(Subscriber.TYPES).toEqual({ - USER: 'user', - ROLE: 'role' - }) - }) - }) - describe('toObject', function () { - it('returns correctly', function () { - const subscriber = new Subscriber({ ...initData }) - expect(subscriber.toObject()).toEqual({ - filters: new Map(), - rfilters: new Map(), - ...initData - }) - }) - }) - describe('toJSON', function () { - it('returns correctly', function () { - const subscriber = new Subscriber({ ...initData }) - expect(subscriber.toJSON()).toEqual({ - filters: {}, - rfilters: {}, - ...initData - }) - }) - }) - describe('validate', function () { - it('throws an error if type is not user or role', function () { - const subscriber = new Subscriber({ ...initData }) - subscriber.type = 'haa' - return expect(subscriber.validate()) - .rejects.toThrow('type must be "user" or "role"') - }) - it('does not throw for user or role type', async function () { - const subscriber = new Subscriber({ ...initData }) - subscriber.type = 'role' - await expect(subscriber.validate()).resolves.toEqual(undefined) - subscriber.type = 'user' - await expect(subscriber.validate()).resolves.toEqual(undefined) - }) - }) - describe('getMentionText', function () { - it('returns user mention string correctly', function () { - const subscriber = new Subscriber({ ...initData }) - subscriber.id = '54eu6ryi' - subscriber.type = Subscriber.TYPES.USER - const expected = `<@${subscriber.id}>` - expect(subscriber.getMentionText()) - .toEqual(expected) - }) - it('returns role mention string correctly', function () { - const subscriber = new Subscriber({ ...initData }) - subscriber.id = '54eu6ryi' - subscriber.type = Subscriber.TYPES.ROLE - const expected = `<@&${subscriber.id}>` - expect(subscriber.getMentionText()) - .toEqual(expected) - }) - }) -}) diff --git a/services/bot/src/tests/structs/db/unit_Supporter.test.js b/services/bot/src/tests/structs/db/unit_Supporter.test.js deleted file mode 100644 index 5dc73f413..000000000 --- a/services/bot/src/tests/structs/db/unit_Supporter.test.js +++ /dev/null @@ -1,391 +0,0 @@ -process.env.TEST_ENV = true -const Supporter = require('../../../structs/db/Supporter.js') -const Patron = require('../../../structs/db/Patron.js') -const config = require('../../../config.js') - -jest.mock('../../../config.js', () => ({ - get: jest.fn(() => ({ - _vipRefreshRateMinutes: 1234, - feeds: { - max: 444 - } - })) -})) - -describe('Unit::structs/db/Supporter', function () { - const initData = { - _id: '123' - } - afterEach(function () { - jest.restoreAllMocks() - }) - describe('constructor', function () { - it('throws an error for missing _id', function () { - const data = { - patron: 12, - webhook: true, - maxGuilds: 1, - maxFeeds: 1, - guilds: [], - expireAt: '3er', - comment: '123', - slowRate: true, - discord: '3rte3y' - } - expect(() => new Supporter(data)) - .toThrow(new TypeError('_id is undefined')) - }) - it('does not throw errors for any values other than _id', function () { - const data = { - _id: '123' - } - expect(() => new Supporter(data)).not.toThrow() - }) - it('initializes correctly', function () { - const supporter = new Supporter({ ...initData }) - expect(supporter.guilds).toEqual([]) - }) - }) - describe('static get schedule', function () { - it('returns the right object', function () { - expect(Supporter.schedule).toEqual({ - name: 'supporter', - refreshRateMinutes: 1234 - }) - }) - }) - describe('static get enabled', function () { - it('returns if config._vip is true', function () { - const oValue = config.get() - config.get.mockReturnValue({ - _vip: true - }) - expect(Supporter.enabled).toEqual(true) - config.get.mockReturnValue({ - _vip: false - }) - expect(Supporter.enabled).toEqual(false) - config.get.mockReturnValue({ - _vip: 'abcdf' - }) - expect(Supporter.enabled).toEqual(false) - config.get.mockReturnValue(oValue) - }) - }) - describe('static getValidSupporters', function () { - it('returns empty array if supporters not enabled', async function () { - jest.spyOn(Supporter, 'enabled', 'get').mockReturnValue(false) - expect(Supporter.getValidSupporters()).resolves.toEqual([]) - }) - it('returns all guilds of all valid supporters in 1 array', async function () { - jest.spyOn(Supporter, 'enabled', 'get').mockReturnValue(true) - const supporters = [{ - _id: 1, - isValid: () => Promise.resolve(true) - }, { - _id: 2, - isValid: () => Promise.resolve(false) - }, { - _id: 3, - isValid: () => Promise.resolve(true) - }, { - _id: 4, - isValid: () => Promise.resolve(true) - }] - jest.spyOn(Supporter, 'getAll').mockResolvedValue(supporters) - const guilds = await Supporter.getValidSupporters() - const expected = [supporters[0], supporters[2], supporters[3]] - expect(guilds).toEqual(expected) - }) - }) - describe('static getValidGuilds', function () { - it('returns all guilds from valid supporters in 1 array', async function () { - const validSupporters = [{ - guilds: [1, 2, 3] - }, { - guilds: [] - }, { - guilds: [4, 5, 6] - }] - jest.spyOn(Supporter, 'getValidSupporters').mockResolvedValue(validSupporters) - const returned = await Supporter.getValidGuilds() - expect(returned).toEqual([1, 2, 3, 4, 5, 6]) - }) - }) - describe('static getValidSupporterOfGuild', function () { - beforeEach(() => { - jest.spyOn(Supporter, 'enabled', 'get') - .mockReturnValue(true) - }) - it('returns empty null if not not enabled', async function () { - jest.spyOn(Supporter, 'enabled', 'get') - .mockReturnValue(false) - await expect(Supporter.getValidSupporterOfGuild()) - .resolves.toEqual(null) - }) - it('runs the right query', async function () { - const getManyByQuery = jest.spyOn(Supporter, 'getManyByQuery') - .mockResolvedValue([]) - const guildID = 'w34etryh' - await Supporter.getValidSupporterOfGuild(guildID) - expect(getManyByQuery).toHaveBeenCalledWith({ - guilds: { - $in: [guildID] - } - }) - }) - it('returns correctly', async function () { - const guildId = 'q23wt5' - const supporters = [{ - _id: 'a', - isValid: async () => false - }, { - _id: 'b', - isValid: async () => true - }, { - _id: 'c', - isValid: async () => false - }] - jest.spyOn(Supporter, 'getManyByQuery') - .mockResolvedValue(supporters) - return expect(Supporter.getValidSupporterOfGuild(guildId)) - .resolves.toEqual(supporters[1]) - }) - it('returns null if no supporter found', async function () { - const guildId = 'q23wt5' - const supporters = [{ - _id: 'a', - isValid: async () => false - }, { - _id: 'b', - isValid: async () => false - }] - jest.spyOn(Supporter, 'getManyByQuery') - .mockResolvedValue(supporters) - return expect(Supporter.getValidSupporterOfGuild(guildId)) - .resolves.toBeNull() - }) - }) - describe('static hasValidGuild', function () { - it('returns whether valid guilds have the id', async function () { - jest.spyOn(Supporter, 'getValidGuilds').mockResolvedValue(['a', 'b', 'c']) - await expect(Supporter.hasValidGuild('b')) - .resolves.toEqual(true) - await expect(Supporter.hasValidGuild('z')) - .resolves.toEqual(false) - }) - }) - describe('findActivePatron', function () { - it('returns the first active patron', async () => { - const supporter = new Supporter({ ...initData }) - const patrons = [{ - id: 1, - isActive: jest.fn().mockReturnValue(false) - }, { - id: 2, - isActive: jest.fn().mockReturnValue(true) - }] - jest.spyOn(Patron, 'getManyBy').mockResolvedValue(patrons) - await expect(supporter.findActivePatron()) - .resolves.toEqual(patrons[1]) - }) - it('returns undefined if no active patron', async () => { - const supporter = new Supporter({ ...initData }) - const patrons = [{ - id: 1, - isActive: jest.fn().mockReturnValue(false) - }, { - id: 2, - isActive: jest.fn().mockReturnValue(false) - }] - jest.spyOn(Patron, 'getManyBy').mockResolvedValue(patrons) - await expect(supporter.findActivePatron()) - .resolves.toBeUndefined() - }) - }) - describe('getMaxGuilds', function () { - it('returns the result from patron method if patron', async function () { - const supporter = new Supporter({ ...initData }) - supporter.patron = true - const maxGuilds = 5555 - const patron = { - determineMaxGuilds: jest.fn(() => maxGuilds) - } - jest.spyOn(supporter, 'findActivePatron').mockResolvedValue(patron) - const returned = await supporter.getMaxGuilds() - expect(returned).toEqual(maxGuilds) - }) - it('returns 1 if maxGuilds is undefined, or maxGuilds if defined', async function () { - const supporter = new Supporter({ ...initData }) - const maxGuilds = 999 - supporter.patron = false - supporter.maxGuilds = 999 - await expect(supporter.getMaxGuilds()).resolves.toEqual(maxGuilds) - supporter.maxGuilds = undefined - await expect(supporter.getMaxGuilds()).resolves.toEqual(1) - }) - }) - describe('getMaxFeeds', function () { - it('returns result from patron determineMaxFeeds if patron', async function () { - const supporter = new Supporter({ ...initData }) - supporter.patron = true - const maxFeeds = 1231 - const patron = { - determineMaxFeeds: jest.fn(() => maxFeeds) - } - jest.spyOn(supporter, 'findActivePatron').mockResolvedValue(patron) - const returned = await supporter.getMaxFeeds() - expect(returned).toEqual(maxFeeds) - }) - describe('not a patron', function () { - it('returns the default config max feeds if no maxFeeds set', async function () { - const supporter = new Supporter({ ...initData }) - supporter.patron = false - supporter.maxFeeds = undefined - await expect(supporter.getMaxFeeds()) - .resolves.toEqual(config.get().feeds.max) - }) - it('returns the default config max feeds if it is bigger than maxFeeds', async function () { - const supporter = new Supporter({ ...initData }) - supporter.patron = false - supporter.maxFeeds = config.get().feeds.max - 1 - await expect(supporter.getMaxFeeds()) - .resolves.toEqual(config.get().feeds.max) - }) - it('returns maxFeeds if bigger than default config max feeds', async function () { - const supporter = new Supporter({ ...initData }) - supporter.patron = false - supporter.maxFeeds = config.get().feeds.max + 1 - await expect(supporter.getMaxFeeds()) - .resolves.toEqual(supporter.maxFeeds) - }) - }) - }) - describe('getWebhookAccess', function () { - it('returns the patron determineWebhook return value if patron', async function () { - const supporter = new Supporter({ ...initData }) - supporter.patron = true - const patron = { - determineWebhook: jest.fn(() => 5553) - } - jest.spyOn(supporter, 'findActivePatron').mockResolvedValue(patron) - const returned = await supporter.getWebhookAccess() - expect(returned).toEqual(5553) - }) - it('returns this.webhook if not a patron', async function () { - const supporter = new Supporter({ ...initData }) - const value = 'booadg' - supporter.patron = false - supporter.webhook = value - const returned = await supporter.getWebhookAccess() - expect(returned).toEqual(value) - }) - }) - describe('toObject', function () { - it('returns correctly', function () { - const data = { - _id: 'abc', - patron: 'q3et', - webhook: true, - guilds: [1, 2, 3], - expireAt: 'abc', - comment: '123', - slowRate: true, - maxGuilds: 435, - maxFeeds: 23 - } - const supporter = new Supporter({ ...data }) - expect(supporter.toObject()).toEqual(data) - }) - }) - describe('isValid', function () { - describe('is patron', function () { - it('returns true if at least one patron is active', async function () { - const supporter = new Supporter({ ...initData }) - supporter.patron = 'abc' - const patrons = [{ - isActive: jest.fn().mockReturnValue(false) - }, { - isActive: jest.fn().mockReturnValue(true) - }] - jest.spyOn(Patron, 'getManyBy').mockResolvedValue(patrons) - await expect(supporter.isValid()).resolves.toEqual(true) - }) - it('returns false if no patron found', async function () { - const supporter = new Supporter({ ...initData }) - supporter.patron = 'abc' - jest.spyOn(Patron, 'getManyBy').mockResolvedValue([{ - isActive: jest.fn().mockReturnValue(false) - }]) - await expect(supporter.isValid()).resolves.toEqual(false) - }) - }) - describe('is not patron', function () { - it('returns true if there is no expireAt', async function () { - const supporter = new Supporter({ ...initData }) - supporter.expireAt = undefined - await expect(supporter.isValid()).resolves.toEqual(true) - }) - it('returns false if expireAt is before now', async function () { - const supporter = new Supporter({ ...initData }) - const longAgo = new Date() - longAgo.setDate(longAgo.getDate() - 1) - supporter.expireAt = longAgo.toString() - await expect(supporter.isValid()).resolves.toEqual(false) - }) - it('returns false if expireAt is after now', async function () { - const supporter = new Supporter({ ...initData }) - const longAgo = new Date() - longAgo.setDate(longAgo.getDate() + 1) - supporter.expireAt = longAgo.toString() - await expect(supporter.isValid()).resolves.toEqual(true) - }) - }) - }) - describe('hasSlowRate', () => { - it('returns correctly for non-patron', async () => { - const supporter = new Supporter({ ...initData }) - supporter.slowRate = true - supporter.patron = false - await expect(supporter.hasSlowRate()) - .resolves.toEqual(true) - supporter.slowRate = false - await expect(supporter.hasSlowRate()) - .resolves.toEqual(false) - }) - it('returns correctly for patrons', async () => { - const threshold = 5 - jest.spyOn(Patron, 'SLOW_THRESHOLD', 'get') - .mockReturnValue(threshold) - const supporter = new Supporter({ ...initData }) - supporter.patron = true - jest.spyOn(supporter, 'findActivePatron') - .mockResolvedValue({ - pledge: threshold - 1 - }) - await expect(supporter.hasSlowRate()) - .resolves.toEqual(true) - jest.spyOn(supporter, 'findActivePatron') - .mockResolvedValue({ - pledge: threshold + 1 - }) - await expect(supporter.hasSlowRate()) - .resolves.toEqual(false) - }) - it('returns correctly if patron not found', async () => { - const threshold = 5 - jest.spyOn(Patron, 'SLOW_THRESHOLD', 'get') - .mockReturnValue(threshold) - const supporter = new Supporter({ ...initData }) - supporter.patron = true - supporter.slowRate = true - jest.spyOn(supporter, 'findActivePatron') - .mockResolvedValue(null) - await expect(supporter.hasSlowRate()) - .resolves.toEqual(true) - supporter.slowRate = false - await expect(supporter.hasSlowRate()) - .resolves.toEqual(false) - }) - }) -}) diff --git a/services/bot/src/tests/structs/int_ArticleMessageRateLimiter.test.js b/services/bot/src/tests/structs/int_ArticleMessageRateLimiter.test.js deleted file mode 100644 index 0589ed96f..000000000 --- a/services/bot/src/tests/structs/int_ArticleMessageRateLimiter.test.js +++ /dev/null @@ -1,145 +0,0 @@ -const mongoose = require('mongoose') -const config = require('../../config.js') -const initialize = require('../../initialization/index.js') -const GeneralStats = require('../../models/GeneralStats.js') -const DeliveryRecord = require('../../models/DeliveryRecord.js') -const ArticleRateLimiter = require('../../structs/ArticleMessageRateLimiter.js') -const dbName = 'test_int_articlemessageratelimiter' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../config.js') - -describe('Unit::structs/DeliveryPipeline', function () { - /** @type {import('mongoose').Connection} */ - let con - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await initialize.setupModels(con) - }) - beforeEach(async () => { - jest.resetAllMocks() - jest.useFakeTimers() - await con.db.dropDatabase() - jest.spyOn(config, 'get') - .mockReturnValue({ - database: { - uri: 'mongodb://' - }, - feeds: { - articleDailyChannelLimit: 2 - } - }) - }) - afterEach(() => { - jest.clearAllTimers() - jest.useRealTimers() - ArticleRateLimiter.sent = 0 - ArticleRateLimiter.blocked = 0 - }) - describe('updateArticlesBlocked', () => { - it('inserts correctly', async () => { - ArticleRateLimiter.blocked = 10 - await ArticleRateLimiter.updateArticlesBlocked() - const found = await con.db.collection(GeneralStats.Model.collection.name).findOne({ - _id: GeneralStats.TYPES.ARTICLES_BLOCKED - }) - expect(found).toBeDefined() - expect(found.data).toEqual(10) - expect(found.addedAt).toBeDefined() - }) - it('updates correctly', async () => { - ArticleRateLimiter.blocked = 2 - await con.db.collection(GeneralStats.Model.collection.name).insertOne({ - _id: GeneralStats.TYPES.ARTICLES_BLOCKED, - data: 2 - }) - await ArticleRateLimiter.updateArticlesBlocked() - const found = await con.db.collection(GeneralStats.Model.collection.name).findOne({ - _id: GeneralStats.TYPES.ARTICLES_BLOCKED - }) - expect(found).toBeDefined() - expect(found.data).toEqual(4) - }) - }) - describe('isAtDailyLimit', () => { - it('returns true correctly', async () => { - const channelID = 'channelid' - const rateLimiter = new ArticleRateLimiter(channelID) - await con.db.collection(DeliveryRecord.Model.collection.name).insertMany([{ - channel: channelID, - delivered: true, - addedAt: new Date() - }, { - channel: channelID, - delivered: true, - addedAt: new Date() - }]) - await expect(rateLimiter.isAtDailyLimit()) - .resolves.toEqual(true) - }) - it('returns false correctly', async () => { - const channelID = 'channelid' - const rateLimiter = new ArticleRateLimiter(channelID) - const yesterday = new Date() - yesterday.setDate(yesterday.getDate() - 1) - await con.db.collection(DeliveryRecord.Model.collection.name).insertMany([{ - channel: channelID, - delivered: true, - addedAt: yesterday - }, { - channel: channelID, - delivered: true, - addedAt: yesterday - }]) - await expect(rateLimiter.isAtDailyLimit()) - .resolves.toEqual(false) - }) - it('returns false if no limit is set', async () => { - jest.spyOn(config, 'get') - .mockReturnValue({ - database: { - uri: 'mongodb://' - }, - feeds: { - articleDailyChannelLimit: 0 - } - }) - const channelID = 'channelid' - const rateLimiter = new ArticleRateLimiter(channelID) - await con.db.collection(DeliveryRecord.Model.collection.name).insertMany([{ - channel: channelID, - delivered: true, - addedAt: new Date() - }]) - await expect(rateLimiter.isAtDailyLimit()) - .resolves.toEqual(false) - }) - it('returns false if limiter has increased limits', async () => { - const channelID = 'channelid' - const rateLimiter = new ArticleRateLimiter(channelID, true) - await con.db.collection(DeliveryRecord.Model.collection.name).insertMany([{ - channel: channelID, - delivered: true, - addedAt: new Date() - }, { - channel: channelID, - delivered: true, - addedAt: new Date() - }, { - channel: channelID, - delivered: true, - addedAt: new Date() - }]) - await expect(rateLimiter.isAtDailyLimit()) - .resolves.toEqual(false) - }) - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/int_ArticleQueue.test.js b/services/bot/src/tests/structs/int_ArticleQueue.test.js deleted file mode 100644 index 34b044720..000000000 --- a/services/bot/src/tests/structs/int_ArticleQueue.test.js +++ /dev/null @@ -1,72 +0,0 @@ -const mongoose = require('mongoose') -const config = require('../../config.js') -const initialize = require('../../initialization/index.js') -const GeneralStats = require('../../models/GeneralStats.js') -const ArticleQueue = require('../../structs/ArticleQueue.js') -const dbName = 'test_int_articlequeue' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../config.js') - -describe('Int::structs/ArticleQueue', function () { - /** @type {import('mongoose').Connection} */ - let con - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await initialize.setupModels(con) - jest.useFakeTimers() - }) - beforeEach(async () => { - await con.db.dropDatabase() - jest.resetAllMocks() - jest.useFakeTimers() - jest.spyOn(config, 'get') - .mockReturnValue({ - database: { - uri: 'mongodb://' - }, - feeds: { - articleDailyChannelLimit: 2 - } - }) - }) - afterEach(() => { - jest.clearAllTimers() - jest.useRealTimers() - ArticleQueue.sent = 0 - ArticleQueue.blocked = 0 - }) - describe('updateArticlesSent', () => { - it('inserts correctly', async () => { - ArticleQueue.sent = 10 - await ArticleQueue.updateArticlesSent() - const found = await con.db.collection(GeneralStats.Model.collection.name).findOne({ - _id: GeneralStats.TYPES.ARTICLES_SENT - }) - expect(found).toBeDefined() - expect(found.data).toEqual(10) - expect(found.addedAt).toBeDefined() - }) - it('updates correctly', async () => { - ArticleQueue.sent = 2 - await con.db.collection(GeneralStats.Model.collection.name).insertOne({ - _id: GeneralStats.TYPES.ARTICLES_SENT, - data: 2 - }) - await ArticleQueue.updateArticlesSent() - const found = await con.db.collection(GeneralStats.Model.collection.name).findOne({ - _id: GeneralStats.TYPES.ARTICLES_SENT - }) - expect(found).toBeDefined() - expect(found.data).toEqual(4) - }) - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/int_DeliveryPipeline.test.js b/services/bot/src/tests/structs/int_DeliveryPipeline.test.js deleted file mode 100644 index 03400bded..000000000 --- a/services/bot/src/tests/structs/int_DeliveryPipeline.test.js +++ /dev/null @@ -1,132 +0,0 @@ -const DeliveryPipeline = require('../../structs/DeliveryPipeline.js') -const Feed = require('../../structs/db/Feed.js') -const config = require('../../config.js') -const DeliveryRecord = require('../../models/DeliveryRecord.js') -const ArticleRateLimiter = require('../../structs/ArticleMessageRateLimiter.js') - -jest.mock('../../config.js') -jest.mock('../../structs/FeedData.js') -jest.mock('../../structs/db/Feed.js') -jest.mock('../../structs/ArticleMessageRateLimiter.js') -jest.mock('../../structs/ArticleMessage.js') - -Feed.isMongoDatabase = true - -const Bot = () => ({ - shard: { - ids: [] - } -}) - -describe('Unit::structs/DeliveryPipeline', function () { - const originalModel = DeliveryRecord.Model - const modelSave = jest.fn() - beforeEach(() => { - DeliveryRecord.Model = jest.fn() - .mockReturnValue({ - save: modelSave - }) - jest.spyOn(config, 'get') - .mockReturnValue({ - log: {}, - apis: { - pledge: {}, - discordHttpGateway: {} - } - }) - }) - afterEach(() => { - DeliveryRecord.Model = originalModel - jest.restoreAllMocks() - modelSave.mockReset() - }) - describe('deliver', function () { - const channel = { - send: jest.fn() - } - const newArticle = { - article: { - _id: 'some id' - }, - feedObject: { - channel: 'w4yrh5e', - url: 'someurl' - } - } - beforeEach(() => { - jest.spyOn(DeliveryPipeline.prototype, 'getChannel') - .mockReturnValue(channel) - }) - afterEach(() => { - channel.send.mockReset() - }) - it('records filter blocked', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const articleMessage = { - foo: 'bar', - passedFilters: () => false - } - jest.spyOn(pipeline, 'createArticleMessage') - .mockReturnValue(articleMessage) - await pipeline.deliver(newArticle) - expect(DeliveryRecord.Model).toHaveBeenCalledWith({ - articleID: newArticle.article._id, - feedURL: newArticle.feedObject.url, - channel: newArticle.feedObject.channel, - delivered: false, - comment: 'Blocked by filters' - }) - expect(modelSave).toHaveBeenCalledTimes(1) - }) - it('records failure', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const articleMessage = { - foo: 'bar', - passedFilters: () => true - } - jest.spyOn(pipeline, 'createArticleMessage') - .mockReturnValue(articleMessage) - const error = new Error('basfdgrf') - jest.spyOn(ArticleRateLimiter, 'assertWithinLimits') - .mockRejectedValue(error) - await pipeline.deliver(newArticle) - expect(DeliveryRecord.Model).toHaveBeenCalledWith({ - articleID: newArticle.article._id, - feedURL: newArticle.feedObject.url, - channel: newArticle.feedObject.channel, - delivered: false, - comment: error.message - }) - expect(modelSave).toHaveBeenCalledTimes(1) - }) - it('sends the article', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const articleMessage = { - foo: 'bar', - passedFilters: () => true - } - jest.spyOn(pipeline, 'createArticleMessage') - .mockReturnValue(articleMessage) - const sendNewArticle = jest.spyOn(pipeline, 'sendNewArticle') - await pipeline.deliver(newArticle) - expect(sendNewArticle).toHaveBeenCalledWith(newArticle, articleMessage, undefined) - }) - it('rejects if failure handling throws', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const articleMessage = { - foo: 'bar', - passedFilters: () => true - } - jest.spyOn(pipeline, 'createArticleMessage') - .mockReturnValue(articleMessage) - const error = new Error('basfdgrf') - jest.spyOn(ArticleRateLimiter, 'assertWithinLimits') - .mockRejectedValue(error) - const handleFailureError = new Error('handle failure error') - jest.spyOn(pipeline, 'handleArticleFailure') - .mockRejectedValue(handleFailureError) - await expect(pipeline.deliver(newArticle)) - .rejects.toThrow(handleFailureError) - }) - }) -}) diff --git a/services/bot/src/tests/structs/int_Filter.test.js b/services/bot/src/tests/structs/int_Filter.test.js deleted file mode 100644 index 7b6646382..000000000 --- a/services/bot/src/tests/structs/int_Filter.test.js +++ /dev/null @@ -1,57 +0,0 @@ -const Filter = require('../../structs/Filter.js') - -describe('Int::structs/Filter', function () { - it('returns passes correctly for broad', function () { - const filter = new Filter('~hello world') - const string = 'jack hello worlddunkeh' - const string2 = 'jack hel dun' - expect(filter.passes(string)).toEqual(true) - expect(filter.passes(string2)).toEqual(false) - }) - it('returns passes correctly for unmodified', function () { - const filter = new Filter('hello world') - const string = 'jack hello worlddunkeh' - const string2 = 'jack hello world ha' - expect(filter.passes(string)).toEqual(false) - expect(filter.passes(string2)).toEqual(true) - }) - it('returns passes correctly for inverted', function () { - const filter = new Filter('!hello world') - const string = 'jack hello world dunkeh' - const string2 = 'jack hello ha' - expect(filter.passes(string)).toEqual(false) - expect(filter.passes(string2)).toEqual(true) - }) - it('returns passes correctly for inverted+broad', function () { - const filter = new Filter('!~hello world') - const sameFilter = new Filter('~!hello world') - const string = 'jack hello worlddunkeh' - const string2 = 'jack hello ha' - expect(filter.passes(string)).toEqual(false) - expect(sameFilter.passes(string)).toEqual(false) - expect(filter.passes(string2)).toEqual(true) - expect(sameFilter.passes(string2)).toEqual(true) - }) - it('does not care about case', function () { - const filter = new Filter('HELLO WORLD') - const string = 'hello WoRlD' - expect(filter.passes(string)).toEqual(true) - }) - it('does not care about case for broad', function () { - const filter = new Filter('~world') - const string = 'hello WoRlD' - expect(filter.passes(string)).toEqual(true) - }) - it('does not care about case for negated', function () { - const filter = new Filter('!world') - const string = 'hello WoRlD' - expect(filter.passes(string)).toEqual(false) - }) - it('does not care about case for broad and negated', function () { - const filter = new Filter('!~rld') - const filter2 = new Filter('~!rld') - const string = 'hello WoRlD' - expect(filter.passes(string)).toEqual(false) - expect(filter2.passes(string)).toEqual(false) - }) -}) diff --git a/services/bot/src/tests/structs/int_GuildData.test.js b/services/bot/src/tests/structs/int_GuildData.test.js deleted file mode 100644 index db1f04fc1..000000000 --- a/services/bot/src/tests/structs/int_GuildData.test.js +++ /dev/null @@ -1,222 +0,0 @@ -process.env.TEST_ENV = true -const mongoose = require('mongoose') -const GuildData = require('../../structs/GuildData.js') -const initialize = require('../../initialization/index.js') -const dbName = 'test_int_guilddata' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - } - }) -})) - -describe('Int::structs/GuildData Database', function () { - /** @type {import('mongoose').Connection} */ - let con - beforeAll(async function () { - con = await mongoose.createConnection(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - await initialize.setupModels(con) - }) - beforeEach(async function () { - await con.db.dropDatabase() - }) - it('restores properly', async function () { - const feedID1 = new mongoose.Types.ObjectId() - const feedID2 = new mongoose.Types.ObjectId() - const profile = { - _id: '123', - name: 'whaa' - } - const feeds = [{ - _id: feedID1, - title: 'ha', - url: 'url', - guild: '123', - channel: 'abc', - embeds: [] - }, { - _id: feedID2, - title: 'ha2', - url: 'url2', - guild: '123', - channel: 'abc2', - embeds: [] - }] - const filteredFormats = [{ - feed: feedID1, - text: 'abc', - embeds: [] - }, { - feed: feedID2, - text: 'hozz', - embeds: [] - }] - const subscribers = [{ - feed: feedID1, - id: 's1', - type: 'user' - }, { - feed: feedID1, - id: 's2', - type: 'role' - }] - const data = { - profile, - feeds, - filteredFormats, - subscribers - } - const guildData = new GuildData(JSON.parse(JSON.stringify(data))) - await guildData.restore() - const db = con.db - const [ - foundProfile, - foundFeed1, - foundFeed2, - foundFormat1, - foundFormat2, - foundSubscriber1, - foundSubscriber2 - ] = await Promise.all([ - db.collection('profiles').findOne(profile), - db.collection('feeds').findOne(feeds[0]), - db.collection('feeds').findOne(feeds[1]), - db.collection('filtered_formats').findOne(filteredFormats[0]), - db.collection('filtered_formats').findOne(filteredFormats[1]), - db.collection('subscribers').findOne(subscribers[0]), - db.collection('subscribers').findOne(subscribers[1]) - ]) - expect(foundProfile).toEqual(expect.objectContaining(profile)) - expect(foundFeed1).toEqual(expect.objectContaining(feeds[0])) - expect(foundFeed2).toEqual(expect.objectContaining(feeds[1])) - expect(foundFormat1).toEqual(expect.objectContaining(filteredFormats[0])) - expect(foundFormat2).toEqual(expect.objectContaining(filteredFormats[1])) - expect(foundSubscriber1).toEqual(expect.objectContaining(subscribers[0])) - expect(foundSubscriber2).toEqual(expect.objectContaining(subscribers[1])) - await con.db.dropDatabase() - }) - it('gets', async function () { - const feedID1 = new mongoose.Types.ObjectId() - const feedID2 = new mongoose.Types.ObjectId() - const profile = { - _id: 'getguildid', - name: 'whaa' - } - const feeds = [{ - _id: feedID1, - title: 'ha', - url: 'url', - guild: 'getguildid', - channel: 'abc', - embeds: [] - }, { - _id: feedID2, - title: 'ha2', - url: 'url2', - guild: 'getguildid', - channel: 'abc2', - embeds: [] - }] - const filteredFormats = [{ - feed: feedID1, - text: 'abc', - embeds: [] - }, { - feed: feedID2, - text: 'hozz', - embeds: [] - }] - const subscribers = [{ - feed: feedID1, - id: 's1', - type: 'user' - }, { - feed: feedID1, - id: 's2', - type: 'role' - }] - const data = JSON.parse(JSON.stringify({ - profile, - feeds, - filteredFormats, - subscribers - })) - const db = con.db - await Promise.all([ - db.collection('profiles').insertOne(profile), - db.collection('feeds').insertMany(feeds), - db.collection('filtered_formats').insertMany(filteredFormats), - db.collection('subscribers').insertMany(subscribers) - ]) - const guildData = await GuildData.get(profile._id) - expect(guildData.profile).toEqual(expect.objectContaining(data.profile)) - expect(guildData.feeds[0]).toEqual(expect.objectContaining(data.feeds[0])) - expect(guildData.feeds[1]).toEqual(expect.objectContaining(data.feeds[1])) - expect(guildData.filteredFormats[0]).toEqual(expect.objectContaining(data.filteredFormats[0])) - expect(guildData.filteredFormats[1]).toEqual(expect.objectContaining(data.filteredFormats[1])) - expect(guildData.subscribers[0]).toEqual(expect.objectContaining(data.subscribers[0])) - expect(guildData.subscribers[1]).toEqual(expect.objectContaining(data.subscribers[1])) - con.db.dropDatabase() - }) - it('restores without profile correctly', async function () { - const feedID1 = new mongoose.Types.ObjectId() - const feeds = [{ - _id: feedID1, - title: 'ha', - url: 'url', - guild: '123', - channel: 'abc', - embeds: [] - }] - const data = { - feeds, - filteredFormats: [], - subscribers: [] - } - const guildData = new GuildData(JSON.parse(JSON.stringify(data))) - await guildData.restore() - const db = con.db - const foundFeed1 = await db.collection('feeds').findOne(feeds[0]) - expect(foundFeed1).toEqual(expect.objectContaining(feeds[0])) - await con.db.dropDatabase() - }) - it('restores with conflicting _ids in database', async function () { - const feedID = new mongoose.Types.ObjectId() - const profile = { - _id: 'conflictingids', - name: 'conflicter' - } - const feeds = [{ - _id: feedID, - title: 'ha', - url: 'url', - guild: profile._id, - channel: 'abc', - embeds: [] - }] - const data = { - profile, - feeds, - filteredFormats: [], - subscribers: [] - } - const db = con.db - await Promise.all([ - db.collection('profiles').insertOne(profile), - db.collection('feeds').insertMany(feeds) - ]) - const guildData = new GuildData(JSON.parse(JSON.stringify(data))) - await guildData.restore() - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/structs/int_LinkLogic.test.js b/services/bot/src/tests/structs/int_LinkLogic.test.js deleted file mode 100644 index fee97129a..000000000 --- a/services/bot/src/tests/structs/int_LinkLogic.test.js +++ /dev/null @@ -1,551 +0,0 @@ -process.env.TEST_ENV = true -const mongoose = require('mongoose') -const LinkLogic = require('../../structs/LinkLogic.js') -const dbName = 'test_int_linklogic' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true -} - -jest.mock('../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - } - }) -})) - -describe('Int::structs/LinkLogic Database', function () { - beforeAll(async function () { - await mongoose.connect(`mongodb://localhost:27017/${dbName}`, CON_OPTIONS) - }) - beforeEach(async function () { - await mongoose.connection.db.dropDatabase() - }) - it('sends new articles if new ID', async function () { - const articleList = [{ - _id: 'a', - guid: 'a', - title: 't1' - }, { - _id: 'b', - guid: 'b' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: [], - ncomparisons: [] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - id: 'b', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: {} - }] - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run(docs) - expect(newArticles).toHaveLength(1) - expect(newArticles[0]).toEqual({ - article: articleList[0], - feedObject: rssList.feedid1 - }) - }) - it('does not send new articles if old ID', async function () { - const articleList = [{ - _id: 'b', - guid: 'b' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: [], - ncomparisons: [] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - id: 'b', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: {} - }] - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run(docs) - expect(newArticles).toHaveLength(0) - }) - it('sends new articles when id exists in DB but pass pcomparison', async function () { - const articleList = [{ - _id: 'a', - guid: 'a', - title: 't1' - }, { - _id: 'b', - guid: 'b', - title: 't2' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: ['title'], - ncomparisons: [] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - id: 'a', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: { - title: 't1' - } - }] - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run(docs) - expect(newArticles).toHaveLength(1) - expect(newArticles[0]).toEqual({ - article: articleList[1], - feedObject: rssList.feedid1 - }) - }) - it('sends new articles when id exists in DB but pass pcomparison with nested value', async function () { - const articleList = [{ - _id: 'a', - guid: 'a', - my: { - property: 't1' - } - }, { - _id: 'b', - guid: 'b', - my: { - property: 't2' - } - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: ['my.property'], - ncomparisons: [] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - id: 'a', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: { - title: 't1' - } - }] - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run(docs) - expect(newArticles).toHaveLength(1) - expect(newArticles[0]).toEqual({ - article: articleList[1], - feedObject: rssList.feedid1 - }) - }) - it('does not send articles when id is new but ncomparison blocks', async function () { - const articleList = [{ - _id: 'a', - guid: 'a', - title: 't1' - }, { - _id: 'b', - guid: 'b', - title: 't2' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: [], - ncomparisons: ['title'] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - id: 'b', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: { - title: 't1' - } - }] - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run(docs) - expect(newArticles).toHaveLength(0) - }) - it('does not send new articles when no articles have been stored', async function () { - const articleList = [{ - _id: 'a', - guid: 'a', - title: 't1' - }, { - _id: 'b', - guid: 'b', - title: 't2' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: [], - ncomparisons: [] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run([]) - expect(newArticles).toHaveLength(0) - }) - it('does not send new article with old ID but contains a pcomparison value of recently sent article', async function () { - /** - * This is when the IDs are within the database. This - * case is when IDs are seen in DB. - */ - const articleList = [{ - /** - * This one should not send since the previous - * article (index 1) already has this title - */ - _id: 'a', - guid: 'a', - title: 't2' - }, { - _id: 'b', - guid: 'b', - title: 't2' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: ['title'], - ncomparisons: [] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - id: 'a', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: { - title: 'shffgh' - } - }, { - id: 'b', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: {} - }] - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run(docs) - expect(newArticles).toHaveLength(1) - expect(newArticles[0].article).toEqual(expect.objectContaining(articleList[1])) - }) - it('does not send new article with new IDs when ncomparison blocked a recent new article', async function () { - /** - * The article list is handled in *reverse*. This case - * is when IDs are not seen in DB - */ - const articleList = [{ - /** - * This one should not send, despite the new - * guid since the previous article (index 1) has same title - */ - _id: 'aa', - guid: 'aa', - title: 'ta' - }, { - _id: 'bb', - guid: 'bb', - title: 'ta' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: [], - ncomparisons: ['title'] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - _id: 'a', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: { - title: 't' - } - }] - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run(docs) - expect(newArticles).toHaveLength(1) - expect(newArticles[0].article).toEqual(expect.objectContaining(articleList[1])) - }) - it('sends when at least 1 pcomparison passes if others do not pass', async function () { - const articleList = [{ - _id: 'aa', - guid: 'aa', - title: 't', - description: 'hola' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: ['title', 'description'], - ncomparisons: [] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - _id: 'a', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: { - title: 't', - description: 'holano' - } - }] - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run(docs) - expect(newArticles).toHaveLength(1) - expect(newArticles[0].article).toEqual(expect.objectContaining(articleList[0])) - }) - it('blocks when at least 1 ncomparison blocks if others pass', async function () { - const articleList = [{ - _id: 'aa', - guid: 'aa', - title: 't', - description: 'hola' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: [], - ncomparisons: ['title', 'description'] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - _id: 'a', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: { - title: 't', - description: 'holano' - } - }] - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run(docs) - expect(newArticles).toHaveLength(0) - }) - it('does not send articles with old guids that pass pcomparisons if property is uninitialized', async function () { - const articleList = [{ - guid: 'a', - title: 't1' - }, { - guid: 'b', - title: 't2' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: ['title'], - ncomparisons: [] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - id: 'a', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: {} - }, { - id: 'b', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: {} - }] - const logic = new LinkLogic(logicData) - const { newArticles } = await logic.run(docs) - expect(newArticles).toHaveLength(0) - }) - it('deletes irrelevant stored properties', async function () { - const articleList = [{ - guid: 'a', - title: 't1' - }] - const rssList = { - feedid1: { - _id: 'feedid1', - pcomparisons: [], - ncomparisons: [] - } - } - const logicData = { - link: 'https://www.example.com', - shardID: 1, - scheduleName: 'default', - config: { - feeds: {} - }, - articleList, - rssList, - useIdType: 'guid' - } - const docs = [{ - id: 'a', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: { - useless1: 'a', - useless2: 'b' - } - }, { - id: 'b', - feedURL: logicData.link, - shardID: logicData.shardID, - scheduleName: logicData.scheduleName, - properties: { - useless1: 'a' - } - }] - const logic = new LinkLogic(logicData) - await logic.run(docs) - const all = await mongoose.connection.collection('articles').find().toArray() - for (const item of all) { - expect(item).toEqual(expect.objectContaining({ - properties: {} - })) - } - }) - afterAll(async function () { - await mongoose.connection.db.dropDatabase() - await mongoose.connection.close() - }) -}) diff --git a/services/bot/src/tests/structs/unit_Article.test.js b/services/bot/src/tests/structs/unit_Article.test.js deleted file mode 100644 index 6ac3ac607..000000000 --- a/services/bot/src/tests/structs/unit_Article.test.js +++ /dev/null @@ -1,223 +0,0 @@ -const Article = require('../../structs/Article.js') - -describe('Unit::structs/Article', function () { - const baseArticle = { - meta: {} - } - const feedData = { - feed: {} - } - describe('testFilters', function () { - it('passes with no filters', function () { - const article = new Article(baseArticle, feedData) - article.fullTitle = 'my sentence is this' - const filters = {} - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(true) - }) - it('works with regular filters', function () { - const article = new Article(baseArticle, feedData) - article.fullTitle = 'my sentence is this' - const filters = { - title: ['foo', 'sentence'] - } - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(true) - }) - it('blocks when regular filters are not found', function () { - const article = new Article(baseArticle, feedData) - article.fullTitle = 'my cahones' - const filters = { - title: ['foo', 'sentence'] - } - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(false) - }) - it('works with negated filters', function () { - const article = new Article(baseArticle, feedData) - article.fullTitle = 'my sentence is this' - const filters = { - title: ['!sentence'] - } - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(false) - }) - it('passes when negated filters are not found', function () { - const article = new Article(baseArticle, feedData) - article.fullTitle = 'my cajones is this' - const filters = { - title: ['!sentence'] - } - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(true) - }) - it('works with broad filters', function () { - const article = new Article(baseArticle, feedData) - article.fullTitle = 'my sentence is this' - const filters = { - title: ['~ence'] - } - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(true) - }) - it('blocks when broad filters are not found', function () { - const article = new Article(baseArticle, feedData) - article.fullTitle = 'is this' - const filters = { - title: ['~ence'] - } - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(false) - }) - it('works with regular and negated filters', function () { - const article = new Article(baseArticle, feedData) - article.fullTitle = 'my sentence is this' - const filters = { - title: ['!sentence', 'my'] - } - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(false) - }) - it('works with broad and negated filters', function () { - const article = new Article(baseArticle, feedData) - article.fullTitle = 'my sentence is this' - const filters = { - title: ['!sentence', '~ence'] - } - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(false) - }) - it('all types works together', function () { - const filters = { - title: [ - '(free/100% off)', - '(free / 100% off)', - '100% off', - '$0.99', - '~100%', - '!~itch.io', - '!boogeyman' - ] - } - const article = new Article(baseArticle, feedData) - article.fullTitle = '[Steam] Key x Sekai Project Publisher Weekend (Planetarian $4.49/55%, Re;Lord $6.99/30%, Clannad Complete $34.40/60%, Maitetsu $8.99/40% and more)' - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(false) - }) - it('blocks for non-existent article properties', function () { - const filters = { - Title: [ - 'Blah' - ] - } - const article = new Article(baseArticle, feedData) - article.title = 'Blah george' - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(false) - }) - it('works with filters across multiple categories', function () { - const filters = { - title: [ - 'Blah' - ], - description: [ - 'Boh' - ] - } - const article = new Article(baseArticle, feedData) - article.title = 'Blah george' - article.description = 'hoder' - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(true) - }) - it('blocks when one filter blocks with filters in multiple categories', function () { - const filters = { - title: [ - 'Blah' - ], - description: [ - '!Boh' - ], - author: [ - 'Bang' - ] - } - const article = new Article(baseArticle, feedData) - article.title = 'Blah Blahs' - article.description = 'Ban Boh' - article.author = 'Bang Bang' - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(false) - }) - it('blocks when using broad filters and negated filters in different categories', function () { - const filters = { - title: [ - '~campaig' - ], - guid: [ - '!60097a6bf64cf135e3323184' - ] - } - const article = new Article(baseArticle, feedData) - article.title = 'Sirius and Utopia Compete to Host Galactic Summit' - article.guid = '600accd53eb598007a0385f8' - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(false) - }) - it('passes with negated and regular in one category with regular in another, and the other matches', function () { - const filters = { - title: [ - '!software', - 'srfdhetgfj' - ], - author: [ - 'huntermc' - ] - } - const article = new Article(baseArticle, feedData) - article.title = "[UPDATE] Hunter's Harem [v0.4.3.2a]" - article.author = 'huntermc' - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(true) - }) - it('passes for negated filters if the property does not exist', () => { - const filters = { - title: [ - '!holla-go' - ] - } - const article = new Article(baseArticle, feedData) - article.description = 'unrelated' - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(true) - }) - it('passes properly for negated filters on non-existent property and regular filters on existent property', () => { - const filters = { - title: [ - '!holla-go' - ], - description: [ - 'unrelated' - ] - } - const article = new Article(baseArticle, feedData) - article.description = 'unrelated' - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(true) - }) - it('blocks properly for negated filters on non-existent property and regular filters on existent property', () => { - const filters = { - title: [ - '!holla-go' - ], - description: [ - '!unrelated' - ] - } - const article = new Article(baseArticle, feedData) - article.description = 'unrelated' - const returned = article.testFilters(filters) - expect(returned.passed).toEqual(false) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_ArticleIDResolver.test.js b/services/bot/src/tests/structs/unit_ArticleIDResolver.test.js deleted file mode 100644 index c4a94d87b..000000000 --- a/services/bot/src/tests/structs/unit_ArticleIDResolver.test.js +++ /dev/null @@ -1,115 +0,0 @@ -const ArticleIDResolver = require('../../structs/ArticleIDResolver.js') - -describe('Unit::ArticleIDResolver', function () { - const spy = jest.spyOn(ArticleIDResolver, 'ID_TYPE_NAMES', 'get') - const idTypeNames = ['a', 'b', 'c'] - const expectedIDTypes = ['a', 'b', 'c', 'a,b', 'a,c', 'b,c'] - describe('constructor', function () { - beforeEach(function () { - spy.mockReturnValueOnce(idTypeNames) - }) - it('adds all id types to this.useIdTypes', function () { - const idResolver = new ArticleIDResolver() - expect(idResolver.useIdTypes.size).toEqual(expectedIDTypes.length) - for (const item of expectedIDTypes) { - expect(idResolver.useIdTypes.has(item)).toEqual(true) - } - }) - it('adds all id types as an empty set in this.idsRecorded', function () { - const idResolver = new ArticleIDResolver() - for (const item of expectedIDTypes) { - expect(idResolver.idsRecorded[item]).toBeInstanceOf(Set) - } - }) - it('adds the merged id types to this.mergedTypeNames', function () { - const idResolver = new ArticleIDResolver() - const expectedMergedTypeNames = ['a,b', 'a,c', 'b,c'] - expect(idResolver.mergedTypeNames).toEqual(expectedMergedTypeNames) - }) - }) - describe('getIDType()', function () { - beforeEach(function () { - spy.mockReturnValue(idTypeNames) - }) - it('returns the first valid id type', function () { - const idResolver = new ArticleIDResolver() - idResolver.mergedTypeNames = ['lswikedgjowir', 'rjhgyn;bkjdn'] - expect(idResolver.getIDType()).toEqual(idTypeNames[0]) - }) - it('returns the first merged id type if there are invalids', function () { - const idResolver = new ArticleIDResolver() - for (const idType of idTypeNames) { - idResolver.useIdTypes.delete(idType) - } - expect(idResolver.getIDType()).toEqual(expectedIDTypes[3]) - }) - it('returns the last failed id type if there are no valid id types', function () { - const idResolver = new ArticleIDResolver() - idResolver.useIdTypes.clear() - const failedType = 'aedsgwtdrfkhjnb' - idResolver.failedTypeNames.push(failedType) - expect(idResolver.getIDType()).toEqual(failedType) - }) - }) - describe('static getIDTypeValue()', function () { - it('returns the article value for non-merged id type', function () { - const article = { a: 'b', dingus: 'berry' } - expect(ArticleIDResolver.getIDTypeValue(article, 'a')).toEqual(article.a) - }) - it('returns the article values joined for a merged id type', function () { - const article = { joe: 'poe', doe: 'koe' } - expect(ArticleIDResolver.getIDTypeValue(article, 'doe,joe')).toEqual('koepoe') - }) - }) - describe('recordArticle()', function () { - const mockArticleValue = 'adegtrhfnj' - const spy = jest.spyOn(ArticleIDResolver, 'getIDTypeValue') - beforeAll(function () { - spy.mockReturnValue(mockArticleValue) - }) - afterAll(function () { - spy.mockRestore() - }) - it('adds the articles values to their respective id type in this.idsRecorded', function () { - const idResolver = new ArticleIDResolver() - idResolver.useIdTypes = new Set(['a', 'b']) - idResolver.idsRecorded.a = new Set() - idResolver.idsRecorded.b = new Set() - idResolver.recordArticle({}) // This can be empty since article values are accessed with ArticleIDResolve.getIDTypeValue - expect(idResolver.idsRecorded.a.has(mockArticleValue)).toEqual(true) - expect(idResolver.idsRecorded.b.has(mockArticleValue)).toEqual(true) - }) - it('deletes the id type from this.useIdTypes if there is no article value', function () { - const idResolver = new ArticleIDResolver() - idResolver.useIdTypes = new Set(['a']) - idResolver.idsRecorded.a = new Set() - spy.mockReturnValueOnce(null) - idResolver.recordArticle({}) - expect(idResolver.useIdTypes.has('a')).toEqual(false) - }) - it('adds the id type from this.failedTypeNames if there is no article value', function () { - const idResolver = new ArticleIDResolver() - idResolver.useIdTypes = new Set(['a']) - idResolver.idsRecorded.a = new Set() - spy.mockReturnValueOnce(null) - idResolver.recordArticle({}) - expect(idResolver.failedTypeNames).toContain('a') - }) - it('deletes the id type from this.useIdTypes if the article value was seen before', function () { - const idResolver = new ArticleIDResolver() - idResolver.useIdTypes = new Set(['a']) - idResolver.idsRecorded.a = new Set() - idResolver.recordArticle({}) - idResolver.recordArticle({}) - expect(idResolver.useIdTypes.has('a')).toEqual(false) - }) - it('adds the id type from this.failedTypeNames if there is no article value', function () { - const idResolver = new ArticleIDResolver() - idResolver.useIdTypes = new Set(['a']) - idResolver.idsRecorded.a = new Set() - idResolver.recordArticle({}) - idResolver.recordArticle({}) - expect(idResolver.failedTypeNames).toContain('a') - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_ArticleMessage.test.js b/services/bot/src/tests/structs/unit_ArticleMessage.test.js deleted file mode 100644 index 5ffdaf5be..000000000 --- a/services/bot/src/tests/structs/unit_ArticleMessage.test.js +++ /dev/null @@ -1,343 +0,0 @@ -process.env.TEST_ENV = true -const Discord = require('discord.js') -const ArticleMessage = require('../../structs/ArticleMessage.js') -const Article = require('../../structs/Article.js') - -jest.mock('discord.js') -jest.mock('../../structs/Article.js') - -describe('Unit::ArticleMessage', function () { - const baseFeedData = { - feed: {} - } - afterEach(function () { - jest.restoreAllMocks() - }) - describe('constructor', function () { - const baseArticle = {} - it('defines the correct properties for this.parsedArticle', function () { - const parsedArticle = { - foo: 'barbara' - } - Article.mockImplementationOnce(() => parsedArticle) - const feedData = { - foo: 'baz', - feed: 'boobaz', - filteredFormats: 'asrfdeuj' - } - const debug = true - const m = new ArticleMessage(baseArticle, feedData, debug) - expect(m.article).toEqual(baseArticle) - expect(m.feed).toEqual(feedData.feed) - expect(m.filteredFormats).toEqual(feedData.filteredFormats) - expect(m.parsedArticle).toEqual(parsedArticle) - expect(m.debug).toEqual(debug) - }) - }) - describe('passedFilters', function () { - it('returns the tested filters for regex if it exists', function () { - const feedData = { - feed: { - rfilters: { - title: 'myregex' - } - } - } - const testFilters = jest.fn() - .mockReturnValue({ - passed: 'what is here' - }) - const parsedArticle = { - testFilters - } - const m = new ArticleMessage({}, feedData) - m.parsedArticle = parsedArticle - expect(m.passedFilters()).toEqual('what is here') - expect(testFilters).toHaveBeenCalledWith(feedData.feed.rfilters) - }) - it('returns the tested filters for non-regex if regex does not exist', function () { - const feedData = { - feed: { - filters: { - title: ['a', 'b'] - }, - rfilters: {} - } - } - const testFilters = jest.fn() - .mockReturnValue({ - passed: 'water' - }) - const parsedArticle = { - testFilters - } - const m = new ArticleMessage({}, feedData) - m.parsedArticle = parsedArticle - expect(m.passedFilters()).toEqual('water') - expect(testFilters).toHaveBeenCalledWith(feedData.feed.filters) - }) - }) - describe('getWebhookNameAvatar', function () { - it('returns default name if no custom settings', function () { - const webhook = { - name: 'abc' - } - const feedData = { - ...baseFeedData, - feed: { - webhook: {} - } - } - const m = new ArticleMessage({}, feedData) - expect(m.getWebhookNameAvatar(webhook)) - .toEqual({ - username: webhook.name - }) - }) - it('returns the custom name if it exists', function () { - const webhook = { - name: 'abc' - } - const feedData = { - ...baseFeedData, - feed: { - webhook: { - name: 'bqa wsedtgrf' - } - } - } - const customName = 'we4r5ytu6j546ut7ryji5e6rr7irkmkw34ry54' - const m = new ArticleMessage({}, feedData) - m.parsedArticle = { - convertKeywords: () => customName - } - expect(m.getWebhookNameAvatar(webhook)) - .toEqual({ - username: customName.slice(0, 32) - }) - }) - it('returns the custom avatar if it exists', function () { - const webhook = { - name: 'abc' - } - const feedData = { - ...baseFeedData, - feed: { - webhook: { - avatar: 'my avatar {placeholders}' - } - } - } - const customAvatar = 'we4r5ytu6j546ut7ryji5e6rr7irkmkw34ry54' - const m = new ArticleMessage({}, feedData) - m.parsedArticle = { - convertImgs: () => customAvatar - } - expect(m.getWebhookNameAvatar(webhook)) - .toEqual({ - username: webhook.name, - avatarURL: customAvatar - }) - }) - }) - describe('send()', function () { - const rawArticle = { - _feed: { - filters: {}, - rfilters: {}, - filteredFormats: [], - embeds: [] - } - } - let medium - beforeEach(function () { - const generatedMessage = { text: 'awszf', embeds: [1, 2] } - medium = { - send: jest.fn() - } - jest.spyOn(ArticleMessage.prototype, 'getWebhook') - .mockImplementation() - jest.spyOn(ArticleMessage.prototype, 'createTextAndOptions') - .mockImplementation(() => generatedMessage) - jest.spyOn(ArticleMessage.prototype, 'getMedium') - .mockReturnValue(medium) - jest.spyOn(ArticleMessage.prototype, 'passedFilters') - .mockReturnValue(true) - jest.spyOn(ArticleMessage.prototype, 'generateMessage') - .mockReturnValue({}) - }) - afterEach(function () { - jest.restoreAllMocks() - }) - it('throws an error if missing channel', function () { - const m = new ArticleMessage(rawArticle, baseFeedData) - jest.spyOn(m, 'getMedium') - .mockResolvedValue() - return expect(m.send()).rejects.toBeInstanceOf(Error) - }) - it('throws the same error that channel.send throws ', async function () { - const m = new ArticleMessage(rawArticle, baseFeedData) - const error = new Error('hello world') - error.code = 5555 - medium.send.mockRejectedValue(error) - await expect(m.send()).rejects.toThrow(error) - }) - it('does not retry if errorCode is 50013', async function () { - const m = new ArticleMessage(rawArticle, baseFeedData) - const error = new Error('hello world') - error.code = 50013 - medium.send.mockRejectedValue(error) - await expect(m.send()).rejects.toThrow(Error) - expect(medium.send).toHaveBeenCalledTimes(1) - }) - it('retries a maximum of 4 times with an unrecognized error', async function () { - const m = new ArticleMessage(rawArticle, baseFeedData) - const error = new Error('hello world') - medium.send.mockRejectedValue(error) - await expect(m.send()).rejects.toThrow(Error) - expect(medium.send).toHaveBeenCalledTimes(4) - }) - }) - describe('createOptions', function () { - const rawArticle = {} - beforeEach(function () { - jest.spyOn(ArticleMessage.prototype, 'getWebhookNameAvatar') - .mockReturnValue({}) - }) - it('parses roles, users, everyone', function () { - const m = new ArticleMessage(rawArticle, baseFeedData) - const embeds = [] - const options = m.createOptions(embeds) - expect(options.allowedMentions.parse) - .toEqual(expect.arrayContaining(['roles', 'users', 'everyone'])) - }) - it('sets the username and avatar URL if the medium is a webhook', function () { - const m = new ArticleMessage(rawArticle, baseFeedData) - const webhookSettings = { - username: 'fooby', - avatarURL: 'urlhere' - } - jest.spyOn(m, 'getWebhookNameAvatar') - .mockReturnValue(webhookSettings) - const webhook = new Discord.Webhook() - const options = m.createOptions([], webhook) - expect(options.username).toEqual(webhookSettings.username) - expect(options.avatarURL).toEqual(webhookSettings.avatarURL) - }) - it('returns the first embed in a list of embeds if there is no webhook', function () { - const m = new ArticleMessage(rawArticle, baseFeedData) - const embeds = [1, 2, 3] - const data = m.createOptions(embeds) - expect(data.embed).toEqual(embeds[0]) - expect(data.embeds).toBeUndefined() - }) - it('returns all the embeds in a list there is a webhook', function () { - const m = new ArticleMessage(rawArticle, baseFeedData) - const embeds = [1, 2, 3] - const webhook = new Discord.Webhook() - const data = m.createOptions(embeds, webhook) - expect(data.embed).toBeUndefined() - expect(data.embeds).toEqual(embeds) - }) - it('attaches user split options for non-test-message', function () { - const feedData = { - ...baseFeedData, - feed: { - split: { - a: 'b' - } - } - } - const m = new ArticleMessage(rawArticle, feedData) - const data = m.createOptions([]) - expect(data.split).toEqual(feedData.feed.split) - }) - }) - describe('createAPIPayloads', function () { - it('returns one payload for content with <2000 characters', () => { - const mockAPIPayload = { - foo: 'bar', - content: 'hello world' - } - jest.spyOn(ArticleMessage.prototype, 'createAPIPayload') - .mockReturnValue(mockAPIPayload) - const m = new ArticleMessage({}, baseFeedData) - expect(m.createAPIPayloads()) - .toEqual([mockAPIPayload]) - }) - it('returns one payload if split is disabled when content is >= 20000 characters', () => { - const mockAPIPayload = { - foo: 'bar', - content: ''.padEnd(2100) - } - jest.spyOn(ArticleMessage.prototype, 'createAPIPayload') - .mockReturnValue(mockAPIPayload) - const m = new ArticleMessage({}, baseFeedData) - expect(m.createAPIPayloads()) - .toEqual([mockAPIPayload]) - }) - it('it attaches the first embed to the last message after split for non-webhook', () => { - const mockAPIPayload = { - foo: 'bar', - content: ''.padEnd(2001, 'b'), - split: { - maxLength: 2000, - char: '\n', - prepend: '', - append: '' - }, - embed: { foo: 1 } - } - Discord.Util.splitMessage.mockReturnValue(['a', 'b']) - jest.spyOn(ArticleMessage.prototype, 'createAPIPayload') - .mockReturnValue(mockAPIPayload) - const m = new ArticleMessage({}, baseFeedData) - const payloads = m.createAPIPayloads() - expect(payloads).toHaveLength(2) - expect(payloads[1]).toEqual(expect.objectContaining({ - foo: 'bar', - content: 'b', - embed: { foo: 1 } - })) - }) - it('it attaches the embeds to the last message after split for webhooks', () => { - const mockAPIPayload = { - foo: 'bar', - content: ''.padEnd(2001, 'b'), - split: { - maxLength: 2000, - char: '\n', - prepend: '', - append: '' - }, - embeds: [{ foo: 1 }, { foo: 2 }] - } - const webhook = new Discord.Webhook({}) - Discord.Util.splitMessage.mockReturnValue(['a', 'b']) - jest.spyOn(ArticleMessage.prototype, 'createAPIPayload') - .mockReturnValue(mockAPIPayload) - const m = new ArticleMessage({}, baseFeedData) - const payloads = m.createAPIPayloads(webhook) - expect(payloads).toHaveLength(2) - expect(payloads[1]).toEqual(expect.objectContaining({ - foo: 'bar', - content: 'b', - embeds: mockAPIPayload.embeds - })) - }) - }) - describe('createTextAndOptions', function () { - beforeEach(function () { - jest.spyOn(ArticleMessage.prototype, 'generateMessage') - .mockReturnValue({}) - jest.spyOn(ArticleMessage.prototype, 'createOptions') - .mockImplementation() - }) - it('returns text and options', function () { - const m = new ArticleMessage({}, baseFeedData) - const data = m.createTextAndOptions() - expect(data).toHaveProperty('text') - expect(data).toHaveProperty('options') - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_ArticleMessageRateLimiter.test.js b/services/bot/src/tests/structs/unit_ArticleMessageRateLimiter.test.js deleted file mode 100644 index 34275b70d..000000000 --- a/services/bot/src/tests/structs/unit_ArticleMessageRateLimiter.test.js +++ /dev/null @@ -1,137 +0,0 @@ -const ArticleRateLimiter = require('../../structs/ArticleMessageRateLimiter.js') -const config = require('../../config.js') - -jest.mock('../../config.js') - -describe('create', () => { - beforeEach(() => { - jest.resetAllMocks() - ArticleRateLimiter.limiters = new Map() - jest.useFakeTimers() - }) - afterEach(() => { - jest.clearAllTimers() - jest.useRealTimers() - }) - describe('hasLimiter', () => { - it('returns correctly', () => { - const channelID = 'w4rte36y5' - ArticleRateLimiter.limiters.set(channelID, 1) - expect(ArticleRateLimiter.hasLimiter(channelID)) - .toEqual(true) - expect(ArticleRateLimiter.hasLimiter(channelID + 'b')) - .toEqual(false) - }) - }) - describe('getLimiter', () => { - it('returns the limiter if it exists', function () { - const channelID = 'w34r6te5y' - const limiter = { - foo: 'bar' - } - ArticleRateLimiter.limiters.set(channelID, limiter) - expect(ArticleRateLimiter.getLimiter(channelID)) - .toEqual(limiter) - }) - it('returns a created limiter if it does not exist', () => { - const channelID = 'w43y6yhy' - const limiter = { - foo: 'baz' - } - jest.spyOn(ArticleRateLimiter, 'create') - .mockReturnValue(limiter) - expect(ArticleRateLimiter.getLimiter(channelID)) - .toEqual(limiter) - }) - }) - describe('assertWithinLimits', () => { - it('rejects if no channel', async () => { - const articleMessage = { - getChannel: () => null - } - const limiter = { - isAtLimit: () => false, - send: jest.fn() - } - jest.spyOn(ArticleRateLimiter, 'getLimiter') - .mockReturnValue(limiter) - await expect(ArticleRateLimiter.assertWithinLimits(articleMessage)) - .rejects.toThrowError() - }) - it('rejects if rate limited', async () => { - const articleMessage = { - getChannel: () => ({}) - } - const limiter = { - isAtLimit: () => true, - send: jest.fn() - } - jest.spyOn(ArticleRateLimiter, 'getLimiter') - .mockReturnValue(limiter) - await expect(ArticleRateLimiter.assertWithinLimits(articleMessage)) - .rejects.toThrow(Error) - }) - it('rejects if at daily limit', async () => { - const articleMessage = { - getChannel: () => ({}) - } - const limiter = { - isAtLimit: () => false, - isAtDailyLimit: async () => true, - send: jest.fn() - } - jest.spyOn(ArticleRateLimiter, 'getLimiter') - .mockReturnValue(limiter) - await expect(ArticleRateLimiter.assertWithinLimits(articleMessage)) - .rejects.toThrow(Error) - }) - }) - describe('isAtLimit', () => { - beforeEach(() => { - config.get.mockReturnValue({ - feeds: {} - }) - }) - it('returns false if no article limit', () => { - const limiter = new ArticleRateLimiter() - limiter.articlesLimit = 0 - expect(limiter.isAtLimit()).toEqual(false) - }) - it('returns true if articles remaining is 0', () => { - const limiter = new ArticleRateLimiter() - limiter.articlesLimit = 99 - limiter.articlesRemaining = 0 - expect(limiter.isAtLimit()).toEqual(true) - }) - it('returns false if there are articles remaining', () => { - const limiter = new ArticleRateLimiter() - limiter.articlesLimit = 99 - limiter.articlesRemaining = 1 - expect(limiter.isAtLimit()).toEqual(false) - }) - }) - describe('constructor', () => { - const configRefreshRate = 4536745 - const configArticlesLimit = 436745 - beforeEach(() => { - config.get.mockReturnValue({ - feeds: { - refreshRateMinutes: configRefreshRate, - articleRateLimit: configArticlesLimit - } - }) - }) - it('sets the relevant properties', () => { - const channelID = '34wr6te5y' - const limiter = new ArticleRateLimiter(channelID) - expect(limiter.channelID).toEqual(channelID) - expect(limiter.articlesLimit).toEqual(configArticlesLimit) - expect(limiter.articlesRemaining).toEqual(configArticlesLimit) - }) - it('creates the timer', () => { - // eslint-disable-next-line no-unused-vars - const limiter = new ArticleRateLimiter() - expect(setInterval).toHaveBeenCalledWith(expect.any(Function), 1000 * 60 * configRefreshRate) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_ArticleQueue.test.js b/services/bot/src/tests/structs/unit_ArticleQueue.test.js deleted file mode 100644 index c94299d2d..000000000 --- a/services/bot/src/tests/structs/unit_ArticleQueue.test.js +++ /dev/null @@ -1,111 +0,0 @@ -const ArticleQueue = require('../../structs/ArticleQueue.js') -const configuration = require('../../config.js') - -jest.mock('../../config.js') - -const bot = { - shard: { - ids: [1] - } -} - -describe('Unit::structs/ArticleQueue', function () { - beforeEach(async () => { - configuration.get.mockReturnValue({ - feeds: { - articleDequeueRate: 0.01 - } - }) - jest.spyOn(ArticleQueue.prototype, '_logDebug') - .mockImplementation() - jest.useFakeTimers() - }) - describe('enqueue', () => { - it('adds the data to the queue', () => { - const newArticle = { - foo: 'baz' - } - const articleMessage = { - foh: 'ban' - } - const queue = new ArticleQueue({ ...bot }) - queue.enqueue(newArticle, articleMessage) - expect(queue.queue).toEqual([{ - newArticle, - articleMessage - }]) - }) - }) - describe('dequeue', () => { - beforeEach(() => { - jest.spyOn(ArticleQueue.prototype, 'send') - .mockImplementation() - }) - it('sends the right article messages', async () => { - const articleData1 = { - id: 1 // for debugging - } - const articleData2 = { - id: 2 - } - const articleData3 = { - id: 3 - } - const articleDataQueue = [ - articleData1, - articleData2, - articleData3 - ] - const queue = new ArticleQueue({ ...bot }) - queue.queue = articleDataQueue - queue.send = jest.fn() - await queue.dequeue(queue.queue, 2) - expect(queue.send) - .toHaveBeenCalledTimes(2) - expect(queue.send) - .toHaveBeenCalledWith(articleData1) - expect(queue.send) - .toHaveBeenCalledWith(articleData2) - expect(queue.send) - .not.toHaveBeenCalledWith(articleData3) - }) - }) - describe('constructor', () => { - it('creates the interval correctly for rate of <1', () => { - configuration.get.mockReturnValue({ - feeds: { - articleDequeueRate: 0.2 - } - }) - // eslint-disable-next-line no-new - const queue = new ArticleQueue({ ...bot }) - queue.dequeue = jest.fn() - queue.queue = [1, 3, 4] - queue.serviceBacklogQueue = [] - const expectedDequeueRate = 1 - const expectedInterval = 5000 - expect(setInterval) - .toHaveBeenCalledWith(expect.any(Function), expectedInterval) - expect(setInterval).toHaveBeenCalledTimes(1) - jest.advanceTimersByTime(expectedInterval) - expect(queue.dequeue).toHaveBeenCalledWith(queue.queue, expectedDequeueRate) - }) - it('creates interval correctly for rate of >1', () => { - configuration.get.mockReturnValue({ - feeds: { - articleDequeueRate: 5 - } - }) - const queue = new ArticleQueue({ ...bot }) - queue.queue = [4, 5, 6] - queue.dequeue = jest.fn() - const expectedDequeueRate = 5 - const expectedInterval = 1000 - expect(setInterval) - .toHaveBeenCalledWith(expect.any(Function), expectedInterval) - expect(setInterval).toHaveBeenCalledTimes(1) - jest.advanceTimersByTime(expectedInterval) - expect(queue.dequeue).toHaveBeenCalledWith(queue.queue, expectedDequeueRate) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_Command.test.js b/services/bot/src/tests/structs/unit_Command.test.js deleted file mode 100644 index 889a4cd90..000000000 --- a/services/bot/src/tests/structs/unit_Command.test.js +++ /dev/null @@ -1,527 +0,0 @@ -const Discord = require('discord.js') -const Command = require('../../structs/Command.js') -const Profile = require('../../structs/db/Profile.js') -const DiscordPromptRunner = require('discord.js-prompts').DiscordPromptRunner -const config = require('../../config.js') -const fsPromises = require('fs').promises - -jest.mock('../../config.js') -jest.mock('discord.js') -jest.mock('discord.js-prompts') -jest.mock('../../structs/db/Profile.js') - -describe('Unit::structs/Command', function () { - afterEach(function () { - Command.enabled = true - jest.restoreAllMocks() - DiscordPromptRunner.mockReset() - config.get.mockReset() - Profile.mockReset() - }) - describe('enable', function () { - it('enalbes commands', function () { - Command.enable() - expect(Command.enabled).toEqual(true) - }) - }) - describe('disable', function () { - it('disables commands', function () { - Command.disable() - expect(Command.enabled).toEqual(false) - }) - }) - describe('isBlacklistedID', function () { - beforeEach(function () { - Command.blacklistCache = { - users: new Set(), - guilds: new Set() - } - }) - afterEach(function () { - Command.blacklistCache = undefined - }) - it('returns correctly for blacklisted user', function () { - const blacklistedUserID = 'q3e5rw2t4ry5t' - Command.blacklistCache.users.add(blacklistedUserID) - expect(Command.isBlacklistedID(blacklistedUserID)) - .toEqual(true) - expect(Command.isBlacklistedID(blacklistedUserID + 'eads')) - .toEqual(false) - }) - it('returns correctly for blacklisted guild', function () { - const blacklistedGuildID = 'q3e5rw2t4ry5t' - Command.blacklistCache.guilds.add(blacklistedGuildID) - expect(Command.isBlacklistedID(blacklistedGuildID)) - .toEqual(true) - expect(Command.isBlacklistedID(blacklistedGuildID + 'eads')) - .toEqual(false) - }) - }) - describe('shouldIgnore', function () { - const log = { - debug: jest.fn(), - trace: jest.fn() - } - const client = { - user: { - id: 'clientid' - } - } - const baseMessage = { - client, - channel: {} - } - it('returns true if no guild', function () { - const message = { - ...baseMessage, - author: { - id: client.user.id + 'diff' - } - } - jest.spyOn(DiscordPromptRunner, 'isActiveChannel') - .mockReturnValue(false) - jest.spyOn(Command, 'isBlacklistedID') - .mockReturnValue(false) - expect(Command.shouldIgnore(message, log)) - .toEqual(true) - }) - it('returns true if message from self', function () { - const message = { - ...baseMessage, - author: { - id: client.user.id - }, - guild: {} - } - jest.spyOn(DiscordPromptRunner, 'isActiveChannel') - .mockReturnValue(false) - jest.spyOn(Command, 'isBlacklistedID') - .mockReturnValue(false) - expect(Command.shouldIgnore(message, log)) - .toEqual(true) - }) - it('returns true if active channel', function () { - const message = { - ...baseMessage, - author: { - id: client.user.id + 'abc' - }, - guild: {} - } - jest.spyOn(DiscordPromptRunner, 'isActiveChannel') - .mockReturnValue(true) - jest.spyOn(Command, 'isBlacklistedID') - .mockReturnValue(false) - expect(Command.shouldIgnore(message, log)) - .toEqual(true) - }) - it('returns true if blacklisted guild', function () { - const message = { - ...baseMessage, - author: { - id: client.user.id + 'abc' - }, - guild: { - id: 'guildid' - } - } - jest.spyOn(DiscordPromptRunner, 'isActiveChannel') - .mockReturnValue(false) - jest.spyOn(Command, 'isBlacklistedID') - .mockImplementation((id) => { - if (id === message.guild.id) { - return true - } - }) - expect(Command.shouldIgnore(message, log)) - .toEqual(true) - }) - it('returns true if blacklisted guild', function () { - const message = { - ...baseMessage, - author: { - id: client.user.id + 'abc' - }, - guild: {} - } - jest.spyOn(DiscordPromptRunner, 'isActiveChannel') - .mockReturnValue(false) - jest.spyOn(Command, 'isBlacklistedID') - .mockImplementation((id) => { - if (id === message.author.id) { - return true - } - }) - expect(Command.shouldIgnore(message, log)) - .toEqual(true) - }) - it('returns false correctly', function () { - const message = { - ...baseMessage, - author: { - id: client.user.id + 'abcd' - }, - guild: { - id: 'guildidhere' - } - } - jest.spyOn(DiscordPromptRunner, 'isActiveChannel') - .mockReturnValue(false) - jest.spyOn(Command, 'isBlacklistedID') - .mockReturnValue(false) - expect(Command.shouldIgnore(message, log)) - .toEqual(false) - }) - }) - describe('isOwnerID', function () { - it('returns correctly', function () { - const ownerIDs = ['a', 'b'] - config.get.mockReturnValue({ - bot: { - ownerIDs - } - }) - expect(Command.isOwnerID(ownerIDs[0])) - .toEqual(true) - expect(Command.isOwnerID(ownerIDs[0] + 'whatever')) - .toEqual(false) - }) - }) - describe('readCommands', function () { - const originalReaddir = fsPromises.readdir - beforeEach(function () { - fsPromises.readdir = jest.fn() - }) - afterEach(function () { - fsPromises.readdir = originalReaddir - }) - it('returns command names', async function () { - const fileNames = ['a.js', 'c.js', 'folder', 'blah.txt', 'b.js'] - fsPromises.readdir.mockResolvedValue(fileNames) - await expect(Command.readCommands(false)).resolves - .toEqual(['a', 'c', 'b']) - }) - }) - describe('getDefaultPrefix', function () { - it('returns correctly', function () { - const prefix = 'afd' - config.get.mockReturnValue({ - bot: { - prefix - } - }) - expect(Command.getDefaultPrefix()) - .toEqual(prefix) - }) - }) - describe('getPrefix', function () { - it('returns profile prefix if it exists', function () { - const prefix = 'aszdf' - Profile.getPrefix.mockReturnValue(prefix) - expect(Command.getPrefix()) - .toEqual(prefix) - }) - it('returns default prefix if no profile prefix', function () { - const prefix = 'aszdf' - Profile.getPrefix.mockReturnValue(undefined) - jest.spyOn(Command, 'getDefaultPrefix') - .mockReturnValue(prefix) - expect(Command.getPrefix()) - .toEqual(prefix) - }) - }) - describe('parseForName', function () { - it('returns the string without the prefix', function () { - const string = 'abcmycomm' - const prefix = 'abc' - expect(Command.parseForName(string, prefix)) - .toEqual('mycomm') - }) - it('returns an empty string if no prefix found', function () { - const string = 'bcmycomm' - const prefix = 'abc' - expect(Command.parseForName(string, prefix)) - .toEqual('') - }) - it('handles args correctly', function () { - const string = 'abcmycomm arg1 arg2' - const prefix = 'abc' - expect(Command.parseForName(string, prefix)) - .toEqual('mycomm') - }) - }) - describe('tryGetCommand', function () { - const log = { - debug: jest.fn(), - trace: jest.fn() - } - const message = { - guild: {} - } - it('returns the command if profile prefix found', function () { - const foundCommand = { - foo: 'bar' - } - jest.spyOn(Command, 'getPrefix').mockImplementation() - jest.spyOn(Command, 'parseForName').mockImplementation() - jest.spyOn(Command, 'get').mockReturnValue(foundCommand) - expect(Command.tryGetCommand(message, log)) - .toEqual(foundCommand) - }) - it('returns the command if no profile prefix but with default prefix', function () { - const foundCommand = { - foo: 'bar' - } - jest.spyOn(Command, 'getPrefix').mockImplementation() - jest.spyOn(Command, 'parseForName').mockImplementation() - jest.spyOn(Command, 'getDefaultPrefix').mockImplementation() - jest.spyOn(Command, 'get') - .mockReturnValueOnce(undefined) - .mockReturnValueOnce(foundCommand) - expect(Command.tryGetCommand(message, log)) - .toEqual(foundCommand) - }) - }) - describe('has', function () { - const orig = Command.commands - afterEach(function () { - Command.commands = orig - }) - it('returns the command if found', function () { - const commandName = 'hello' - Command.commands = new Map() - Command.commands.set(commandName, {}) - expect(Command.has(commandName)) - .toEqual(true) - expect(Command.has(commandName + 'abc')) - .toEqual(false) - }) - }) - describe('get', function () { - it('gets the command if found', function () { - const commandName = 'hello' - const command = { - fat: 'tony' - } - Command.commands = new Map() - Command.commands.set(commandName, command) - expect(Command.get(commandName)) - .toEqual(command) - expect(Command.get(commandName + 'abc')) - .toEqual(undefined) - }) - }) - describe('getMemberPermission', function () { - it('returns the member permissions', function () { - const commandName = 'qaetswr' - const permissions = [4445, 223] - const command = new Command(commandName) - const userPermissions = { - [commandName]: permissions - } - jest.spyOn(Command, 'USER_PERMISSIONS', 'get') - .mockReturnValue(userPermissions) - expect(command.getMemberPermission()) - .toEqual(permissions) - }) - it('returns the default manage channels perms if no specific perms', function () { - const commandName = 'qaetswr' - const command = new Command(commandName) - expect(command.getMemberPermission()) - .toEqual([Discord.Permissions.FLAGS.MANAGE_CHANNELS]) - }) - }) - describe('getBotPermissions', function () { - it('returns the bot permissions', function () { - const commandName = 'qaetswr' - const permissions = [4445, 223] - const command = new Command(commandName) - const botPermissions = { - [commandName]: permissions - } - jest.spyOn(Command, 'BOT_PERMISSIONS', 'get') - .mockReturnValue(botPermissions) - expect(command.getBotPermissions()) - .toEqual([ - ...permissions, - Discord.Permissions.FLAGS.SEND_MESSAGES - ]) - }) - it('returns the send message perm array if no specific perms', function () { - const commandName = 'qaetswr' - const command = new Command(commandName) - expect(command.getBotPermissions()) - .toEqual([Discord.Permissions.FLAGS.SEND_MESSAGES]) - }) - }) - describe('hasMemberPermission', function () { - beforeEach(function () { - jest.spyOn(Command, 'isOwnerID') - .mockReturnValue(false) - }) - it('returns whether if member is owner if owner command', function () { - const command = new Command('', {}, true) - command.owner = true - const message = { - member: { - user: {} - }, - channel: {} - } - const isOwnerID = true - jest.spyOn(Command, 'isOwnerID') - .mockReturnValue(isOwnerID) - expect(command.hasMemberPermission(message)) - .toEqual(isOwnerID) - jest.spyOn(Command, 'isOwnerID') - .mockReturnValue(!isOwnerID) - expect(command.hasMemberPermission(message)) - .toEqual(!isOwnerID) - }) - it('returns the resolved permissons', function () { - const command = new Command() - command.owner = false - const has = jest.fn() - const fetchedMember = { - user: {}, - permissionsIn: jest.fn().mockReturnValue({ - has - }) - } - const message = { - member: fetchedMember, - channel: {} - } - has.mockReturnValue(true) - expect(command.hasMemberPermission(message)) - .toEqual(true) - has.mockReturnValue(false) - expect(command.hasMemberPermission(message)) - .toEqual(false) - }) - it('returns true if member is owner', function () { - const command = new Command('', {}, true) - command.owner = false - const message = { - member: { - user: {} - }, - channel: {} - } - const isOwnerID = true - jest.spyOn(Command, 'isOwnerID') - .mockReturnValue(isOwnerID) - expect(command.hasMemberPermission(message)) - .toEqual(true) - }) - }) - describe('hasBotPermission', function () { - it('returns the resolved permissions', function () { - const command = new Command() - jest.spyOn(command, 'getBotPermissions') - .mockReturnValue([1, 1]) - const hasChannelPermission = jest.fn() - const hasServerPermission = jest.fn() - const message = { - guild: { - me: { - permissionsIn: () => ({ has: hasChannelPermission }), - hasPermission: hasServerPermission - } - }, - channel: {} - } - hasChannelPermission - .mockReturnValueOnce(true) - .mockReturnValueOnce(false) - hasServerPermission - .mockReturnValueOnce(false) - .mockReturnValueOnce(false) - expect(command.hasBotPermission(message)) - .toEqual(false) - hasChannelPermission.mockReset() - hasServerPermission.mockReset() - hasChannelPermission - .mockReturnValueOnce(false) - .mockReturnValueOnce(false) - hasServerPermission - .mockReturnValueOnce(false) - .mockReturnValueOnce(true) - hasChannelPermission.mockReset() - hasServerPermission.mockReset() - expect(command.hasBotPermission(message)) - .toEqual(false) - hasChannelPermission.mockReset() - hasServerPermission.mockReset() - hasChannelPermission - .mockReturnValueOnce(true) - .mockReturnValueOnce(true) - hasServerPermission - .mockReturnValueOnce(false) - .mockReturnValueOnce(false) - expect(command.hasBotPermission(message)) - .toEqual(true) - }) - it('handles manage roles correctly', async () => { - const command = new Command() - jest.spyOn(command, 'getBotPermissions') - .mockReturnValue([Discord.Permissions.FLAGS.MANAGE_ROLES, 1]) - const hasChannelPermission = jest.fn() - const hasServerPermission = jest.fn() - const message = { - guild: { - me: { - permissionsIn: () => ({ has: hasChannelPermission }), - hasPermission: hasServerPermission - } - }, - channel: {} - } - hasChannelPermission - .mockReturnValueOnce(false) - .mockReturnValueOnce(true) - hasServerPermission - .mockReturnValueOnce(true) - .mockReturnValueOnce(false) - expect(command.hasBotPermission(message)) - .toEqual(true) - }) - }) - describe('run', function () { - it('calls the func', async function () { - const command = new Command() - command.name = 'hello world' - command.func = jest.fn() - const message = { - channel: {} - } - await command.run(message) - expect(command.func) - .toHaveBeenCalledWith(message, command.name) - }) - it('adds to the active channel', async function () { - const command = new Command() - command.func = jest.fn() - const message = { - channel: { - id: 'abawc' - } - } - await command.run(message) - expect(DiscordPromptRunner.addActiveChannel) - .toHaveBeenCalledWith(message.channel.id) - }) - it('deletes the active channel upon completion', async function () { - const command = new Command() - command.func = jest.fn() - const message = { - channel: { - id: 'abawc' - } - } - await command.run(message) - expect(DiscordPromptRunner.deleteActiveChannel) - .toHaveBeenCalledWith(message.channel.id) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_DeliveryPipeline.test.js b/services/bot/src/tests/structs/unit_DeliveryPipeline.test.js deleted file mode 100644 index 95f42473b..000000000 --- a/services/bot/src/tests/structs/unit_DeliveryPipeline.test.js +++ /dev/null @@ -1,327 +0,0 @@ -const DeliveryPipeline = require('../../structs/DeliveryPipeline.js') -const config = require('../../config.js') -const DeliveryRecord = require('../../models/DeliveryRecord.js') -const ArticleMessage = require('../../structs/ArticleMessage.js') -const Feed = require('../../structs/db/Feed.js') - -jest.mock('../../config.js') -jest.mock('../../structs/FeedData.js') -jest.mock('../../structs/db/Feed.js') -jest.mock('../../structs/ArticleMessageRateLimiter.js') -jest.mock('../../structs/ArticleMessage.js') -// jest.mock('../../models/DeliveryRecord.js') - -const Bot = () => ({ - shard: { - ids: [] - } -}) - -const NewArticle = () => ({ - feedObject: {}, - article: {} -}) - -Feed.isMongoDatabase = true - -describe('Unit::structs/DeliveryPipeline', function () { - beforeAll(() => { - DeliveryRecord.Model = jest.fn() - }) - afterEach(() => { - jest.restoreAllMocks() - }) - describe('constructor', function () { - it('sets the fields', function () { - const bot = { - foo: 'bar', - shard: { - ids: [] - } - } - jest.spyOn(config, 'get') - .mockReturnValue({ - log: { - unfiltered: true - }, - apis: { - pledge: {}, - discordHttpGateway: {} - } - }) - const pipeline = new DeliveryPipeline(bot) - expect(pipeline.bot).toEqual(bot) - expect(pipeline.logFiltered).toEqual(true) - }) - }) - describe('getChannel', () => { - it('returns the channel', () => { - const newArticle = { - feedObject: {} - } - const pipeline = new DeliveryPipeline(Bot()) - const get = jest.fn() - pipeline.bot = { - channels: { - cache: { - get - } - } - } - const channel = { - bla: 'dah' - } - get.mockReturnValue(channel) - expect(pipeline.getChannel(newArticle)) - .toEqual(channel) - }) - }) - describe('createArticleMessage', () => { - it('returns the created article message', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const createdMessage = { - foo: 'baz' - } - jest.spyOn(ArticleMessage, 'create') - .mockReturnValue(createdMessage) - await expect(pipeline.createArticleMessage({})) - .resolves.toEqual(createdMessage) - }) - }) - describe('deliver', () => { - beforeEach(() => { - jest.spyOn(DeliveryPipeline.prototype, 'getChannel') - .mockReturnValue({}) - jest.spyOn(DeliveryPipeline.prototype, 'createArticleMessage') - .mockResolvedValue() - jest.spyOn(DeliveryPipeline.prototype, 'handleArticleBlocked') - .mockResolvedValue() - jest.spyOn(DeliveryPipeline.prototype, 'sendNewArticle') - .mockResolvedValue() - jest.spyOn(DeliveryPipeline.prototype, 'handleArticleFailure') - .mockResolvedValue() - }) - it('does not send if channel is not found', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const sendNewArticle = jest.spyOn(pipeline, 'sendNewArticle') - jest.spyOn(pipeline, 'getChannel') - .mockReturnValue(undefined) - await pipeline.deliver(NewArticle()) - expect(sendNewArticle).not.toHaveBeenCalled() - }) - it('does not send article if it does not pass filters', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const sendNewArticle = jest.spyOn(pipeline, 'sendNewArticle') - jest.spyOn(pipeline, 'createArticleMessage') - .mockResolvedValue({ - passedFilters: () => false - }) - await pipeline.deliver(NewArticle()) - expect(sendNewArticle).not.toHaveBeenCalled() - }) - it('sends the article for delivery', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const articleMessage = { - passedFilters: () => true - } - const sendNewArticle = jest.spyOn(pipeline, 'sendNewArticle') - jest.spyOn(pipeline, 'createArticleMessage') - .mockResolvedValue(articleMessage) - await pipeline.deliver(NewArticle()) - expect(sendNewArticle) - .toHaveBeenCalled() - }) - it('handles errors', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const articleMessage = { - passedFilters: () => true - } - const error = new Error('deadgf') - jest.spyOn(pipeline, 'sendNewArticle') - .mockRejectedValue(error) - const handleArticleFailure = jest.spyOn(pipeline, 'handleArticleFailure') - jest.spyOn(pipeline, 'createArticleMessage') - .mockResolvedValue(articleMessage) - await pipeline.deliver(NewArticle()) - expect(handleArticleFailure) - .toHaveBeenCalled() - }) - }) - describe('handleArticleBlocked', () => { - beforeEach(() => { - config.get.mockReturnValue({ - log: { - unfiltered: false - }, - apis: { - pledge: {}, - discordHttpGateway: {} - } - }) - }) - it('records the filter block', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const recordFilterBlock = jest.spyOn(pipeline, 'recordFilterBlock') - const newArticle = NewArticle() - await pipeline.handleArticleBlocked(newArticle) - expect(recordFilterBlock).toHaveBeenCalledWith(newArticle) - }) - }) - describe('handleArticleFailure', () => { - beforeEach(() => { - jest.spyOn(DeliveryPipeline.prototype, 'getChannel') - .mockReturnValue({}) - }) - it('records the failure', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const recordFailure = jest.spyOn(pipeline, 'recordFailure') - const newArticle = NewArticle() - await pipeline.handleArticleFailure(newArticle, new Error('atwegq')) - expect(recordFailure).toHaveBeenCalledTimes(1) - }) - it('sends the error if error code 50035', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const channel = { - send: jest.fn() - } - jest.spyOn(pipeline, 'getChannel') - .mockReturnValue(channel) - const newArticle = NewArticle() - const error = new Error('srfx') - error.code = 50035 - await pipeline.handleArticleFailure(newArticle, error) - expect(channel.send).toHaveBeenCalledTimes(1) - }) - }) - describe('sendNewArticle', () => { - const enqueue = jest.fn() - beforeEach(() => { - jest.spyOn(DeliveryPipeline.prototype, 'getQueueForChannel') - .mockReturnValue({ - enqueue - }) - enqueue.mockReset() - }) - it('enqueues the article if medium is found', async () => { - const bot = Bot() - const pipeline = new DeliveryPipeline(bot) - const newArticle = NewArticle() - const articleMessage = { - foo: 'baz', - getMedium: jest.fn().mockResolvedValue({}) - } - - await pipeline.sendNewArticle(newArticle, articleMessage) - expect(enqueue) - .toHaveBeenCalledWith(newArticle, articleMessage) - }) - it('does not enqueue the article if medium is missing', async () => { - const bot = Bot() - const pipeline = new DeliveryPipeline(bot) - const newArticle = NewArticle() - const articleMessage = { - foo: 'baz', - getMedium: jest.fn().mockResolvedValue(null) - } - - await pipeline.sendNewArticle(newArticle, articleMessage) - expect(enqueue).not.toHaveBeenCalled() - }) - }) - describe('record functions', () => { - const original = DeliveryRecord.Model - const modelSave = jest.fn() - beforeEach(() => { - DeliveryRecord.Model = jest.fn() - .mockReturnValue({ - save: modelSave - }) - modelSave.mockReset() - }) - afterEach(() => { - DeliveryRecord.Model = original - }) - describe('recordFailure', () => { - it('creates and saves the model', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const newArticle = { - article: { - _id: 'abc' - }, - feedObject: { - channel: 'abaa', - url: 'someurl' - } - } - const errorMessage = '53e47yu' - await pipeline.recordFailure(newArticle, errorMessage) - expect(DeliveryRecord.Model).toHaveBeenCalledWith({ - articleID: newArticle.article._id, - feedURL: newArticle.feedObject.url, - channel: newArticle.feedObject.channel, - delivered: false, - comment: errorMessage - }) - expect(modelSave).toHaveBeenCalledTimes(1) - }) - it('does not create model if not mongodb', async () => { - Feed.isMongoDatabase = false - const pipeline = new DeliveryPipeline(Bot()) - const newArticle = { - article: { - _id: 'abc' - }, - feedObject: { - url: 'bla', - channel: 'abaa' - } - } - const error = new Error('sewtryd') - await pipeline.recordFailure(newArticle, error) - expect(DeliveryRecord.Model).not.toHaveBeenCalled() - expect(modelSave).not.toHaveBeenCalled() - Feed.isMongoDatabase = true - }) - }) - describe('recordFilterBlock', () => { - it('works', async () => { - const pipeline = new DeliveryPipeline(Bot()) - const newArticle = { - article: { - _id: 'abc' - }, - feedObject: { - channel: 'abaa', - url: 'feedurl' - } - } - await pipeline.recordFilterBlock(newArticle) - expect(DeliveryRecord.Model).toHaveBeenCalledWith({ - articleID: newArticle.article._id, - feedURL: newArticle.feedObject.url, - channel: newArticle.feedObject.channel, - delivered: false, - comment: 'Blocked by filters' - }) - expect(modelSave).toHaveBeenCalledTimes(1) - }) - it('does not create model if not mongodb', async () => { - Feed.isMongoDatabase = false - const pipeline = new DeliveryPipeline(Bot()) - const newArticle = { - article: { - _id: 'abc' - }, - feedObject: { - channel: 'abaa', - url: 'feedurl' - } - } - await pipeline.recordFilterBlock(newArticle) - expect(DeliveryRecord.Model).not.toHaveBeenCalled() - expect(modelSave).not.toHaveBeenCalled() - Feed.isMongoDatabase = true - }) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_FeedData.test.js b/services/bot/src/tests/structs/unit_FeedData.test.js deleted file mode 100644 index d07eccf62..000000000 --- a/services/bot/src/tests/structs/unit_FeedData.test.js +++ /dev/null @@ -1,252 +0,0 @@ -process.env.TEST_ENV = true -const FeedData = require('../../structs/FeedData.js') -const Profile = require('../../structs/db/Profile.js') -const Feed = require('../../structs/db/Feed.js') - -jest.mock('../../structs/db/Profile.js') -jest.mock('../../structs/db/Feed.js') -jest.mock('../../config.js') - -const INIT_DATA = { - feed: 1 -} - -describe('Unit::structs/FeedData', function () { - beforeEach(function () { - jest.restoreAllMocks() - Profile.get.mockReset() - Feed.prototype.getSubscribers.mockReset() - Feed.prototype.getFilteredFormats.mockReset() - }) - describe('constructor', function () { - it('throws an error if feed is missing', function () { - expect(() => new FeedData({})) - .toThrowError('Missing feed for FeedData') - }) - it('initializes correctly', function () { - const data = { - feed: 1, - profile: 2, - subscribers: 3, - filteredFormats: 4 - } - const feedData = new FeedData(data) - for (const key in data) { - expect(feedData[key]).toEqual(data[key]) - } - }) - }) - describe('toObject', function () { - it('returns the toObject of every piece of data', function () { - const feedData = new FeedData({ ...INIT_DATA }) - const feedToObj = { - key: 'abc' - } - feedData.feed = { - toObject: jest.fn(() => feedToObj) - } - const profileToObj = 2 - feedData.profile = { - toObject: jest.fn(() => profileToObj) - } - const subscriber1ToObj = 3 - const subscriber2ToObj = 4 - feedData.subscribers = [{ - toObject: jest.fn(() => subscriber1ToObj) - }, { - toObject: jest.fn(() => subscriber2ToObj) - }] - const filteredFormat1ToObj = 5 - const filteredFormat2ToObj = 6 - feedData.filteredFormats = [{ - toObject: jest.fn(() => filteredFormat1ToObj) - }, { - toObject: jest.fn(() => filteredFormat2ToObj) - }] - const returned = feedData.toObject() - expect(returned).toEqual({ - ...feedToObj, - profile: 2, - subscribers: [3, 4], - filteredFormats: [5, 6] - }) - }) - }) - describe('toJSON', function () { - it('returns the toObject of every piece of data', function () { - const feedData = new FeedData({ ...INIT_DATA }) - const feedToJSON = { - key: 'anb' - } - feedData.feed = { - toJSON: jest.fn(() => feedToJSON) - } - const profileToJSON = 2 - feedData.profile = { - toJSON: jest.fn(() => profileToJSON) - } - const subscriber1ToJSON = 3 - const subscriber2ToJSON = 4 - feedData.subscribers = [{ - toJSON: jest.fn(() => subscriber1ToJSON) - }, { - toJSON: jest.fn(() => subscriber2ToJSON) - }] - const filteredFormat1ToJSON = 5 - const filteredFormat2ToJSON = 6 - feedData.filteredFormats = [{ - toJSON: jest.fn(() => filteredFormat1ToJSON) - }, { - toJSON: jest.fn(() => filteredFormat2ToJSON) - }] - const returned = feedData.toJSON() - expect(returned).toEqual({ - ...feedToJSON, - profile: 2, - subscribers: [3, 4], - filteredFormats: [5, 6] - }) - }) - }) - describe('static getFeedAssociations', function () { - it('returns the right data', async function () { - const profile = 99 - const subscribers = [1, 2] - const filteredFormats = [3, 4] - const feed = { - getSubscribers: jest.fn(() => subscribers), - getFilteredFormats: jest.fn(() => filteredFormats) - } - Profile.get.mockResolvedValue(profile) - const returned = await FeedData.getFeedAssociations(feed) - expect(returned).toEqual({ - profile, - subscribers, - filteredFormats - }) - }) - }) - describe('static get', function () { - it('returns null if no feed', async function () { - Feed.get.mockResolvedValue(null) - const returned = await FeedData.get('qr43ew') - expect(returned).toBeNull() - }) - it('returns the data if found', async function () { - const feed = 123 - Feed.get.mockResolvedValue(feed) - const feedAssociations = { - profile: 1, - subscribers: 2, - filteredFormats: 3 - } - const ofunc = FeedData.getFeedAssociations - FeedData.getFeedAssociations = jest.fn(() => feedAssociations) - const returned = await FeedData.get() - expect(returned).toEqual({ - feed, - ...feedAssociations - }) - FeedData.getFeedAssociations = ofunc - }) - }) - describe('static getManyBy', function () { - it('returns FeedData', async function () { - const feeds = [1, 2, 3] - Feed.getManyBy.mockResolvedValue(feeds) - const associations = [{ - profile: 'pro1', - subscribers: 'sub1', - filteredFormats: 'ff1' - }, { - profile: 'pro2', - subscribers: 'sub2', - filteredFormats: 'ff2' - }, { - profile: 'pro3', - subscribers: 'sub3', - filteredFormats: 'ff3' - }] - const oval = FeedData.getFeedAssociations - FeedData.getFeedAssociations = jest.fn() - .mockResolvedValueOnce(associations[0]) - .mockResolvedValueOnce(associations[1]) - .mockResolvedValueOnce(associations[2]) - const feedDatas = await FeedData.getManyBy() - expect(feedDatas).toHaveLength(feeds.length) - for (let i = 0; i < feedDatas.length; ++i) { - const feedData = feedDatas[i] - expect(feedData).toBeInstanceOf(FeedData) - } - FeedData.getFeedAssociations = oval - }) - it('calls Feed getManyBy correctly', async function () { - const field = 'q3re' - const value = 'wte4rsyh' - Feed.getManyBy.mockResolvedValue([]) - await FeedData.getManyBy(field, value) - expect(Feed.getManyBy).toHaveBeenCalledWith(field, value) - }) - it('returns empty array with no found feeds', async function () { - Feed.getManyBy.mockResolvedValue([]) - const returned = await FeedData.getManyBy() - expect(returned).toEqual([]) - }) - }) - describe('static getAll', function () { - it('returns FeedData', async function () { - const feeds = [1, 2, 3] - Feed.getAll.mockResolvedValue(feeds) - const associations = [{ - profile: 'pro1', - subscribers: 'sub1', - filteredFormats: 'ff1' - }, { - profile: 'pro2', - subscribers: 'sub2', - filteredFormats: 'ff2' - }, { - profile: 'pro3', - subscribers: 'sub3', - filteredFormats: 'ff3' - }] - const oval = FeedData.getFeedAssociations - FeedData.getFeedAssociations = jest.fn() - .mockResolvedValueOnce(associations[0]) - .mockResolvedValueOnce(associations[1]) - .mockResolvedValueOnce(associations[2]) - const feedDatas = await FeedData.getAll() - expect(feedDatas).toHaveLength(feeds.length) - for (let i = 0; i < feedDatas.length; ++i) { - const feedData = feedDatas[i] - expect(feedData).toBeInstanceOf(FeedData) - } - FeedData.getFeedAssociations = oval - }) - it('calls Feed getAll correctly', async function () { - Feed.getAll.mockResolvedValue([]) - await FeedData.getAll() - expect(Feed.getAll).toHaveBeenCalled() - }) - it('returns empty array with no found feeds', async function () { - Feed.getAll.mockResolvedValue([]) - const returned = await FeedData.getAll() - expect(returned).toEqual([]) - }) - }) - describe('ofFeed', function () { - it('returns correctly', async function () { - const associations = { - profile: 'b', - subscribers: 'd' - } - jest.spyOn(FeedData, 'getFeedAssociations') - .mockResolvedValue(associations) - const feed = { - foo: 'bar' - } - const returned = await FeedData.ofFeed(feed) - expect(returned).toBeInstanceOf(FeedData) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_Filter.test.js b/services/bot/src/tests/structs/unit_Filter.test.js deleted file mode 100644 index 597ab48ca..000000000 --- a/services/bot/src/tests/structs/unit_Filter.test.js +++ /dev/null @@ -1,129 +0,0 @@ -const Filter = require('../../structs/Filter.js') - -describe('Unit::structs/Filter', function () { - afterEach(function () { - jest.restoreAllMocks() - }) - describe('constructor', function () { - it('lowercases content', function () { - const broad = new Filter('HELO') - expect(broad.content).toEqual('helo') - }) - }) - describe('static escapeRegex', function () { - it('returns a string', function () { - expect(typeof Filter.escapeRegex('hellozz')) - .toEqual('string') - }) - }) - describe('get broad', function () { - it('returns correctly', function () { - const broad = new Filter('') - broad.content = '~heloa' - const notBroad = new Filter('') - notBroad.content = 'heloa' - expect(broad.broad).toEqual(true) - expect(notBroad.broad).toEqual(false) - }) - it('returns combined modifiers correctly', function () { - const filter = new Filter('') - filter.content = '!~heloa' - expect(filter.broad).toEqual(true) - filter.content = '~!heloa' - expect(filter.broad).toEqual(true) - }) - }) - describe('get inverted', function () { - it('returns correctly', function () { - const inverted = new Filter('') - inverted.content = '!heloa' - const notInverted = new Filter('') - notInverted.content = 'heloa' - expect(inverted.inverted).toEqual(true) - expect(notInverted.inverted).toEqual(false) - }) - it('returns combined modifiers correctly', function () { - const filter = new Filter('') - filter.content = '!~heloa' - expect(filter.inverted).toEqual(true) - filter.content = '~!heloa' - expect(filter.inverted).toEqual(true) - }) - }) - describe('parseWord', function () { - it('removes broad modifier', function () { - const filter = new Filter('') - filter.content = '~hallo' - expect(filter.parseWord()).toEqual('hallo') - }) - it('removes inverter modifier', function () { - const filter = new Filter('') - filter.content = '!hallo' - expect(filter.parseWord()).toEqual('hallo') - }) - it('removes combined modifiers', function () { - const filter = new Filter('') - filter.content = '~!hallo' - expect(filter.parseWord()).toEqual('hallo') - filter.content = '!~hallo' - expect(filter.parseWord()).toEqual('hallo') - }) - it('removes forward slash escape for broad modifier', function () { - const filter = new Filter('') - filter.content = '\\~hallo' - expect(filter.parseWord()).toEqual('~hallo') - }) - it('removes forward slash escape for inverter modifier', function () { - const filter = new Filter('') - filter.content = '\\!hallo' - expect(filter.parseWord()).toEqual('!hallo') - }) - }) - describe('foundIn', function () { - it('returns whether the search term is included, irrespective of space if broad', function () { - const filter = new Filter('') - jest.spyOn(filter, 'broad', 'get').mockReturnValue(true) - filter.searchTerm = 'foobar' - expect(filter.foundIn('asdsgfoobaradfsgl')).toEqual(true) - }) - it('returns whether the search term is included, with respect to space if not broad', function () { - const filter = new Filter('') - jest.spyOn(filter, 'broad', 'get').mockReturnValue(false) - filter.searchTerm = 'foobar' - jest.spyOn(Filter, 'escapeRegex').mockReturnValue(filter.searchTerm) - expect(filter.foundIn('asdsgfoobaradfsgl')).toEqual(false) - expect(filter.foundIn('foh foobar faz')).toEqual(true) - }) - it('does not care about case for broad', function () { - const filter = new Filter('') - jest.spyOn(filter, 'broad', 'get').mockReturnValue(true) - filter.searchTerm = 'foobar' - expect(filter.foundIn('Foobar')).toEqual(true) - }) - it('does not care about case for non-broad', function () { - const filter = new Filter('') - jest.spyOn(filter, 'broad', 'get').mockReturnValue(false) - filter.searchTerm = 'foobar' - jest.spyOn(Filter, 'escapeRegex').mockReturnValue(filter.searchTerm) - expect(filter.foundIn('Foobar')).toEqual(true) - }) - }) - describe('passes', function () { - it('returns not found if inverted', function () { - const filter = new Filter('') - jest.spyOn(filter, 'inverted', 'get').mockReturnValue(true) - jest.spyOn(filter, 'foundIn').mockReturnValue(true) - expect(filter.passes()).toEqual(false) - jest.spyOn(filter, 'foundIn').mockReturnValue(false) - expect(filter.passes()).toEqual(true) - }) - it('returns found if not inverted', function () { - const filter = new Filter('') - jest.spyOn(filter, 'inverted', 'get').mockReturnValue(false) - jest.spyOn(filter, 'foundIn').mockReturnValue(true) - expect(filter.passes()).toEqual(true) - jest.spyOn(filter, 'foundIn').mockReturnValue(false) - expect(filter.passes()).toEqual(false) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_FilterRegex.test.js b/services/bot/src/tests/structs/unit_FilterRegex.test.js deleted file mode 100644 index d6e22ede7..000000000 --- a/services/bot/src/tests/structs/unit_FilterRegex.test.js +++ /dev/null @@ -1,15 +0,0 @@ -const FilterRegex = require('../../structs/FilterRegex.js') - -describe('Unit::structs/FilterRegex', function () { - describe('passes', function () { - it('returns correctly', function () { - const filter = new FilterRegex('jo.*ey') - expect(filter.passes('jodfgey')).toEqual(true) - expect(filter.passes('zzzz')).toEqual(false) - }) - it('does not care about case', function () { - const filter = new FilterRegex('jo*ey') - expect(filter.passes('JOEY')).toEqual(true) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_Guild.test.js b/services/bot/src/tests/structs/unit_Guild.test.js deleted file mode 100644 index 01414f814..000000000 --- a/services/bot/src/tests/structs/unit_Guild.test.js +++ /dev/null @@ -1,169 +0,0 @@ -const Supporter = require('../../structs/db/Supporter.js') -const Guild = require('../../structs/Guild.js') -const GuildSubscription = require('../../structs/GuildSubscription.js') -const configuration = require('../../config') - -jest.mock('../../config.js', () => ({ - get: jest.fn(() => ({ - _vipRefreshRateMinutes: 1234, - feeds: { - max: 100 - } - })) -})) - -describe('Unit::structs/Guild', function () { - beforeEach(() => { - jest.spyOn(Supporter, 'enabled', 'get').mockReturnValue(true) - }) - afterEach(function () { - jest.restoreAllMocks() - }) - describe('static getFastSupporterAndSubscriberGuildIds', () => { - it('returns all guild IDs of valid supporters and subscriptions', async () => { - jest.spyOn(Supporter, 'getValidFastGuilds').mockResolvedValue([ - 'a', - 'b' - ]) - jest.spyOn(GuildSubscription, 'getAllSubscriptions').mockResolvedValue([{ - guildId: 'c' - }, { - guildId: 'd' - }, { - guildId: 'a' - }]) - const returned = await Guild.getFastSupporterAndSubscriberGuildIds() - expect(returned).toBeInstanceOf(Set) - expect(Array.from(returned)).toEqual(['a', 'b', 'c', 'd']) - }) - it('does not include subscriptions with slow rates', async () => { - jest.spyOn(Supporter, 'getValidFastGuilds').mockResolvedValue([ - 'a', - 'b' - ]) - jest.spyOn(GuildSubscription, 'getAllSubscriptions').mockResolvedValue([{ - guildId: 'c' - }, { - guildId: 'd', - slowRate: true - }, { - guildId: 'a' - }]) - const returned = await Guild.getFastSupporterAndSubscriberGuildIds() - expect(returned).toBeInstanceOf(Set) - expect(Array.from(returned)).toEqual(['a', 'b', 'c']) - }) - }) - describe('static getAllUniqueFeedLimits', function () { - it('returns guilds with their respsective supporter feed limits', async function () { - const validSupporters = [{ - getMaxFeeds: () => 99, - guilds: ['a', 'b'] - }, { - getMaxFeeds: () => 11, - guilds: ['c', 'd'] - }] - jest.spyOn(GuildSubscription, 'getAllSubscriptions').mockResolvedValue([{ - guildId: 'e', - maxFeeds: 12, - refreshRate: 10, - expireAt: new Date() - }, { - guildId: 'f', - maxFeeds: 13, - refreshRate: 10, - expireAt: new Date() - }]) - jest.spyOn(Supporter, 'getValidSupporters').mockResolvedValue(validSupporters) - const returned = await Guild.getAllUniqueFeedLimits() - expect(returned).toBeInstanceOf(Map) - expect(Array.from(returned.keys())).toEqual(['a', 'b', 'c', 'd', 'e', 'f']) - expect(returned.get('a')).toEqual(99) - expect(returned.get('b')).toEqual(99) - expect(returned.get('c')).toEqual(11) - expect(returned.get('d')).toEqual(11) - expect(returned.get('e')).toEqual(12) - expect(returned.get('f')).toEqual(13) - }) - }) - describe('getMaxFeeds', () => { - let configMaxFeeds - let guild - beforeEach(() => { - configMaxFeeds = configuration.get().feeds.max - guild = new Guild('id') - guild.getSupporter = jest.fn() - guild.getSubscription = jest.fn() - }) - describe('subscription exists but supporter does not', () => { - it('returns subscription max feeds', async () => { - const maxFeeds = configMaxFeeds + 100 - jest.spyOn(guild, 'getSubscription').mockResolvedValue({ - maxFeeds - }) - await expect(guild.getMaxFeeds()).resolves.toEqual(maxFeeds) - }) - it('returns the max of either subscription max feeds or default max feeds', async () => { - const maxFeeds = configMaxFeeds - 100 - jest.spyOn(guild, 'getSubscription').mockResolvedValue({ - maxFeeds - }) - await expect(guild.getMaxFeeds()).resolves.toEqual(configMaxFeeds) - }) - }) - describe('subscription does not exist but supporter does', () => { - beforeEach(() => { - jest.spyOn(Guild.prototype, 'getSubscription').mockResolvedValue(null) - }) - it('returns the supporter max feeds', async () => { - const maxFeeds = configMaxFeeds + 100 - jest.spyOn(guild, 'getSupporter').mockResolvedValue({ - getMaxFeeds: async () => maxFeeds - }) - await expect(guild.getMaxFeeds()).resolves.toEqual(maxFeeds) - }) - it('returns the max of either supporter max feeds or default max feeds', async () => { - const maxFeeds = configMaxFeeds - 100 - jest.spyOn(guild, 'getSupporter').mockResolvedValue({ - getMaxFeeds: async () => maxFeeds - }) - await expect(guild.getMaxFeeds()).resolves.toEqual(configMaxFeeds) - }) - }) - describe('subscription and supporter exists', () => { - it('returns subscription max feeds if its greater than supporter and config', async () => { - const supporterMaxFeeds = configMaxFeeds + 10 - const subscriptionMaxFeeds = configMaxFeeds + 100 - jest.spyOn(guild, 'getSubscription').mockResolvedValue({ - maxFeeds: subscriptionMaxFeeds - }) - jest.spyOn(guild, 'getSupporter').mockResolvedValue({ - getMaxFeeds: async () => supporterMaxFeeds - }) - await expect(guild.getMaxFeeds()).resolves.toEqual(subscriptionMaxFeeds) - }) - it('returns supporter max feeds if its greater than subscription and config', async () => { - const supporterMaxFeeds = configMaxFeeds + 100 - const subscriptionMaxFeeds = configMaxFeeds + 10 - jest.spyOn(Guild.prototype, 'getSubscription').mockResolvedValue({ - maxFeeds: subscriptionMaxFeeds - }) - jest.spyOn(guild, 'getSupporter').mockResolvedValue({ - getMaxFeeds: async () => supporterMaxFeeds - }) - await expect(guild.getMaxFeeds()).resolves.toEqual(supporterMaxFeeds) - }) - it('returns config max feeds if its greater than subscription and supporter', async () => { - const supporterMaxFeeds = configMaxFeeds - 10 - const subscriptionMaxFeeds = configMaxFeeds - 10 - jest.spyOn(Guild.prototype, 'getSubscription').mockResolvedValue({ - maxFeeds: subscriptionMaxFeeds - }) - jest.spyOn(guild, 'getSupporter').mockResolvedValue({ - getMaxFeeds: async () => supporterMaxFeeds - }) - await expect(guild.getMaxFeeds()).resolves.toEqual(configMaxFeeds) - }) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_GuildData.test.js b/services/bot/src/tests/structs/unit_GuildData.test.js deleted file mode 100644 index 80181db1c..000000000 --- a/services/bot/src/tests/structs/unit_GuildData.test.js +++ /dev/null @@ -1,406 +0,0 @@ -process.env.TEST_ENV = true -const GuildData = require('../../structs/GuildData.js') -const Profile = require('../../structs/db/Profile.js') -const Feed = require('../../structs/db/Feed.js') -const FilteredFormat = require('../../structs/db/FilteredFormat.js') -const Subscriber = require('../../structs/db/Subscriber.js') - -jest.mock('../../structs/db/Profile.js') -jest.mock('../../structs/db/Feed.js') -jest.mock('../../structs/db/Subscriber.js') -jest.mock('../../structs/db/FilteredFormat.js') - -describe('Unit::structs/GuildData', function () { - const initData = { - feeds: [], - filteredFormats: [], - subscribers: [] - } - afterEach(function () { - Profile.mockReset() - Profile.get.mockReset() - Feed.mockReset() - Feed.getManyBy.mockReset() - FilteredFormat.mockReset() - Subscriber.mockReset() - }) - describe('constructor', function () { - it('throws error if feeds don\'t match the guild', function () { - const profile = { - _id: 'id1' - } - const feeds = [{ - _id: 'abc', - guild: 'id2' - }] - const data = { - profile, - feeds - } - expect(() => new GuildData(data)) - .toThrow('Feed abc does not match profile') - }) - it('throws error if formats don\'t match any feeds', function () { - const feeds = [{ - _id: 'abc', - guild: 'id2' - }] - const filteredFormats = [{ - _id: 'whatever', - feed: 'abh' - }] - const data = { - feeds, - filteredFormats - } - expect(() => new GuildData(data)) - .toThrow('Format whatever does not match any given feeds') - }) - it('throws error if subscribers don\'t match any feeds', function () { - const feeds = [{ - _id: 'abc', - guild: 'id2' - }] - const subscribers = [{ - _id: 'whatever', - feed: 'abh' - }] - const data = { - feeds, - subscribers, - filteredFormats: [] - } - expect(() => new GuildData(data)) - .toThrow('Subscriber whatever does not match any given feeds') - }) - it('throws for multiple guild ids found for feeds', function () { - const feeds = [{ - guild: 'a' - }, { - guild: 'b' - }] - const data = { - feeds, - subscribers: [], - filteredFormats: [] - } - expect(() => new GuildData(data)) - .toThrow('Mismatched guild IDs found for feeds') - }) - it('sets the instance vars', function () { - const profile = { - _id: 'id2' - } - const feeds = [{ - _id: 'abc', - guild: 'id2' - }] - const subscribers = [{ - _id: 'whatever', - feed: 'abc' - }] - const filteredFormats = [{ - _id: 'formz', - feed: 'abc' - }] - const data = { - profile, - feeds, - subscribers, - filteredFormats - } - const guildData = new GuildData(data) - expect(guildData.id).toEqual('id2') - expect(guildData.profile).toEqual(profile) - expect(guildData.feeds).toEqual(feeds) - expect(guildData.subscribers).toEqual(subscribers) - expect(guildData.filteredFormats).toEqual(filteredFormats) - }) - }) - describe('toJSON', function () { - it('returns this.data', function () { - const guildData = new GuildData({ ...initData }) - guildData.data = 123 - expect(guildData.toJSON()).toEqual(123) - }) - }) - describe('static get', function () { - it('returns an instance of GuildData', async function () { - Profile.get.mockResolvedValue(null) - Feed.getManyBy.mockResolvedValue([]) - const returned = await GuildData.get() - expect(returned).toBeInstanceOf(GuildData) - }) - }) - describe('restore', function () { - it('calls delete first', async function () { - const guildData = new GuildData({ ...initData }) - const spy = jest.spyOn(guildData, 'delete') - await guildData.restore() - expect(spy).toHaveBeenCalledTimes(1) - }) - it('creates the relevant models', async function () { - const guildData = new GuildData({ ...initData }) - const profile = { - foo: 1 - } - const feeds = [{ - a: 1 - }, { - b: 2 - }] - const filteredFormats = [{ - f: 1 - }, { - g: 2 - }] - const subscribers = [{ - s: 1 - }, { - q: 2 - }] - guildData.profile = profile - guildData.feeds = feeds - guildData.filteredFormats = filteredFormats - guildData.subscribers = subscribers - await guildData.restore() - expect(Profile).toHaveBeenCalledWith(profile) - feeds.forEach(f => expect(Feed).toHaveBeenCalledWith(f)) - filteredFormats.forEach(f => expect(FilteredFormat).toHaveBeenCalledWith(f)) - subscribers.forEach(s => expect(Subscriber).toHaveBeenCalledWith(s)) - }) - it('does not create a Profile if profile does not exist', async function () { - const guildData = new GuildData({ ...initData }) - delete guildData.profile - const feeds = [{ - a: 1 - }, { - b: 2 - }] - guildData.feeds = feeds - await guildData.restore() - expect(Profile).not.toHaveBeenCalled() - }) - it('calls save on all relevant models', async function () { - const guildData = new GuildData({ ...initData }) - const profile = { - foo: 1 - } - const feeds = [{ - a: 1 - }, { - b: 2 - }] - const filteredFormats = [{ - f: 1 - }, { - g: 2 - }] - const subscribers = [{ - s: 1 - }, { - q: 2 - }] - guildData.profile = profile - guildData.feeds = feeds - guildData.filteredFormats = filteredFormats - guildData.subscribers = subscribers - await guildData.restore() - expect(Profile.mock.instances[0].save).toHaveBeenCalledTimes(1) - feeds.forEach((f, i) => expect(Feed.mock.instances[i].save).toHaveBeenCalledTimes(1)) - filteredFormats.forEach((f, i) => expect(FilteredFormat.mock.instances[i].save).toHaveBeenCalledTimes(1)) - subscribers.forEach((s, i) => expect(Subscriber.mock.instances[i].save).toHaveBeenCalledTimes(1)) - }) - it('calls delete on all models if some saves fail', async function () { - const guildData = new GuildData({ ...initData }) - const profile = { - foo: 1 - } - const feeds = [{ - a: 1 - }, { - b: 2 - }] - const filteredFormats = [{ - f: 1 - }, { - g: 2 - }] - const subscribers = [{ - s: 1 - }, { - q: 2 - }] - guildData.profile = profile - guildData.feeds = feeds - guildData.filteredFormats = filteredFormats - guildData.subscribers = subscribers - Subscriber.prototype.save.mockRejectedValue(new Error('save error')) - try { - await guildData.restore() - } catch (err) {} - expect(Profile.mock.instances[0].delete).toHaveBeenCalledTimes(1) - feeds.forEach((f, i) => expect(Feed.mock.instances[i].delete).toHaveBeenCalledTimes(1)) - filteredFormats.forEach((f, i) => expect(FilteredFormat.mock.instances[i].delete).toHaveBeenCalledTimes(1)) - subscribers.forEach((s, i) => expect(Subscriber.mock.instances[i].delete).toHaveBeenCalledTimes(1)) - Subscriber.prototype.save.mockRestore() - }) - it('throws the error from save if some save fails', async function () { - const guildData = new GuildData({ ...initData }) - const profile = { - foo: 1 - } - const feeds = [{ - a: 1 - }, { - b: 2 - }] - guildData.profile = profile - guildData.feeds = feeds - guildData.filteredFormats = [] - guildData.subscribers = [] - const error = new Error('save error') - Feed.prototype.save.mockRejectedValue(error) - await expect(guildData.restore()).rejects.toThrow(error) - Feed.prototype.save.mockRestore() - }) - it('throws save error if delete fails', async function () { - const guildData = new GuildData({ ...initData }) - const profile = { - foo: 1 - } - const feeds = [{ - a: 1 - }, { - b: 2 - }] - guildData.profile = profile - guildData.feeds = feeds - guildData.filteredFormats = [] - guildData.subscribers = [] - const saveError = new Error('save error') - Feed.prototype.save.mockRejectedValue(saveError) - Feed.prototype.delete.mockRejectedValue(new Error('delete error')) - await expect(guildData.restore()).rejects.toThrow(saveError) - Feed.prototype.save.mockRestore() - Feed.prototype.delete.mockRestore() - }) - }) - describe('isEmpty', function () { - it('returns true if there is no profile and no feeds', function () { - const guildData = new GuildData({ ...initData }) - guildData.profile = null - guildData.feeds = [] - expect(guildData.isEmpty()).toEqual(true) - }) - it('returns false for either populated feeds or populated profile', function () { - const guildData = new GuildData({ ...initData }) - guildData.profile = {} - guildData.feeds = [] - expect(guildData.isEmpty()).toEqual(false) - guildData.profie = null - guildData.feeds = [{}] - expect(guildData.isEmpty()).toEqual(false) - guildData.profie = {} - guildData.feeds = [{}] - expect(guildData.isEmpty()).toEqual(false) - }) - }) - describe('delete', function () { - it('calls delete on all found data', async function () { - const profile = { - _id: 'profile' - } - const feeds = [{ - _id: 1, - guild: 'profile' - }, { - _id: 2, - guild: 'profile' - }] - const subscribers = [{ - _id: 1, - feed: 1 - }, { - _id: 1, - feed: 1 - }] - const filteredFormats = [{ - _id: 1, - feed: 1 - }, { - _id: 1, - feed: 1 - }] - const foundProfile = { delete: jest.fn() } - const foundFeed1 = { delete: jest.fn() } - const foundFeed2 = { delete: jest.fn() } - const foundSubscriber1 = { delete: jest.fn() } - const foundSubscriber2 = { delete: jest.fn() } - const foundFilteredFormat1 = { delete: jest.fn() } - const foundFilteredFormat2 = { delete: jest.fn() } - Profile.get - .mockResolvedValue(foundProfile) - Feed.get - .mockResolvedValueOnce(foundFeed1) - .mockResolvedValueOnce(foundFeed2) - Subscriber.get - .mockResolvedValueOnce(foundSubscriber1) - .mockResolvedValueOnce(foundSubscriber2) - FilteredFormat.get - .mockResolvedValueOnce(foundFilteredFormat1) - .mockResolvedValueOnce(foundFilteredFormat2) - const guildData = new GuildData({ - profile, - feeds, - subscribers, - filteredFormats - }) - await guildData.delete() - expect(foundProfile.delete).toHaveBeenCalledTimes(1) - expect(foundFeed1.delete).toHaveBeenCalledTimes(1) - expect(foundFeed2.delete).toHaveBeenCalledTimes(1) - expect(foundFilteredFormat1.delete).toHaveBeenCalledTimes(1) - expect(foundFilteredFormat2.delete).toHaveBeenCalledTimes(1) - expect(foundSubscriber1.delete).toHaveBeenCalledTimes(1) - expect(foundSubscriber2.delete).toHaveBeenCalledTimes(1) - Profile.get.mockReset() - Feed.get.mockReset() - Subscriber.get.mockReset() - FilteredFormat.get.mockReset() - }) - it('calls delete on partially found data', async function () { - const profile = { - _id: 'profile' - } - const feeds = [{ - _id: 1, - guild: 'profile' - }] - const subscribers = [] - const filteredFormats = [{ - _id: 1, - feed: 1 - }] - const foundProfile = { delete: jest.fn() } - const foundFeed1 = { delete: jest.fn() } - const foundFormat1 = { delete: jest.fn() } - Profile.get - .mockResolvedValue(foundProfile) - Feed.get - .mockResolvedValueOnce(foundFeed1) - FilteredFormat.get - .mockResolvedValueOnce(foundFormat1) - const guildData = new GuildData({ - profile, - feeds, - subscribers, - filteredFormats - }) - await guildData.delete() - expect(foundProfile.delete).toHaveBeenCalledTimes(1) - expect(foundFeed1.delete).toHaveBeenCalledTimes(1) - expect(foundFormat1.delete).toHaveBeenCalledTimes(1) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_GuildSubscription.test.js b/services/bot/src/tests/structs/unit_GuildSubscription.test.js deleted file mode 100644 index 56a83b069..000000000 --- a/services/bot/src/tests/structs/unit_GuildSubscription.test.js +++ /dev/null @@ -1,194 +0,0 @@ -const GuildSubscription = require('../../structs/GuildSubscription.js') -const getConfig = require('../../config').get -const fetch = require('node-fetch') - -jest.mock('../../config.js', () => ({ - get: jest.fn(() => ({ - _vipRefreshRateMinutes: 1234, - feeds: { - max: 100, - refreshRateMinutes: 10 - } - })) -})) - -jest.mock('node-fetch') - -describe('Unit::structs/GuildSubscription', function () { - let mockResponse - let apiConfig - beforeEach(() => { - mockResponse = { - guild_id: 'abc', - extra_feeds: 100, - refresh_rate: 111, - expire_at: new Date('2029-09-09') - } - apiConfig = { - url: 'https://www.google.com', - accessToken: 'accesstoken', - enabled: true - } - }) - afterEach(function () { - jest.restoreAllMocks() - jest.spyOn(console, 'error').mockImplementation(() => {}) - }) - describe('static mapApiResponse', () => { - it('returns correctly', () => { - const config = getConfig() - expect(GuildSubscription.mapApiResponse(mockResponse)).toEqual({ - guildId: mockResponse.guild_id, - maxFeeds: config.feeds.max + mockResponse.extra_feeds, - refreshRate: mockResponse.refresh_rate / 60, - expireAt: mockResponse.expire_at, - slowRate: false - }) - }) - it('returns slow rate if ignore refresh rate is true', () => { - const config = getConfig() - mockResponse = { - guild_id: 'abc', - extra_feeds: 100, - refresh_rate: 111, - expire_at: new Date('2029-09-09'), - ignore_refresh_rate_benefit: true - } - expect(GuildSubscription.mapApiResponse(mockResponse)).toEqual({ - guildId: mockResponse.guild_id, - maxFeeds: config.feeds.max + mockResponse.extra_feeds, - refreshRate: mockResponse.refresh_rate / 60, - expireAt: mockResponse.expire_at, - slowRate: true - }) - }) - it('returns slow rate if response refresh rate is slower than config', () => { - const config = getConfig() - mockResponse = { - guild_id: 'abc', - extra_feeds: 100, - refresh_rate: config.feeds.refreshRateMinutes * 60 + 10, - expire_at: new Date('2029-09-09') - } - expect(GuildSubscription.mapApiResponse(mockResponse)).toEqual({ - guildId: mockResponse.guild_id, - maxFeeds: config.feeds.max + mockResponse.extra_feeds, - refreshRate: mockResponse.refresh_rate / 60, - expireAt: mockResponse.expire_at, - slowRate: true - }) - }) - it('does not return slow rate if response refresh rate is faster than config', () => { - const config = getConfig() - mockResponse = { - guild_id: 'abc', - extra_feeds: 100, - refresh_rate: config.feeds.refreshRateMinutes * 60 / 2, - expire_at: new Date('2029-09-09'), - ignore_refresh_rate_benefit: false - } - expect(GuildSubscription.mapApiResponse(mockResponse)).toEqual({ - guildId: mockResponse.guild_id, - maxFeeds: config.feeds.max + mockResponse.extra_feeds, - refreshRate: mockResponse.refresh_rate / 60, - expireAt: mockResponse.expire_at, - slowRate: false - }) - }) - }) - describe('static getSubscription', () => { - it('returns null if url is not configured', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue({}) - await expect(GuildSubscription.getSubscription()).resolves.toEqual(null) - }) - it('returns null if 404', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue(apiConfig) - fetch.mockResolvedValue({ - status: 404, - json: jest.fn() - }) - await expect(GuildSubscription.getSubscription()).resolves.toEqual(null) - }) - it('returns null if an error was thrown', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue(apiConfig) - const error = new Error('asdsdf') - fetch.mockRejectedValue(error) - await expect(GuildSubscription.getSubscription()).resolves.toEqual(null) - }) - it('returns null if disabled', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue({ - ...apiConfig, - enabled: false - }) - const error = new Error('asdsdf') - fetch.mockRejectedValue(error) - await expect(GuildSubscription.getSubscription()).resolves.toEqual(null) - }) - it('returns a GuildSubscription on success', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue(apiConfig) - fetch.mockResolvedValue({ - status: 200, - json: async () => mockResponse - }) - await expect(GuildSubscription.getSubscription()).resolves.toBeInstanceOf(GuildSubscription) - }) - it('calls the right url and options', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue(apiConfig) - fetch.mockResolvedValue({ - status: 200, - json: async () => mockResponse - }) - const guildId = '12345' - await GuildSubscription.getSubscription(guildId) - expect(fetch).toHaveBeenCalledWith(`${apiConfig.url}/guilds/${guildId}`, { - headers: { - Authorization: apiConfig.accessToken - } - }) - }) - }) - describe('static getAllSubscriptions', () => { - it('returns empty array if url is not configured', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue({}) - await expect(GuildSubscription.getAllSubscriptions()).resolves.toEqual([]) - }) - it('returns empty array if an error ocurred', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue(apiConfig) - const error = new Error('fetch err') - fetch.mockRejectedValue(error) - await expect(GuildSubscription.getAllSubscriptions()).resolves.toEqual([]) - }) - it('returns empty array if disabled', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue({ - ...apiConfig, - enabled: false - }) - const error = new Error('asdsdf') - fetch.mockRejectedValue(error) - await expect(GuildSubscription.getAllSubscriptions()).resolves.toEqual([]) - }) - it('returns guild subscriptions if successful', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue(apiConfig) - fetch.mockResolvedValue({ - status: 200, - json: () => [mockResponse, mockResponse] - }) - const returned = await GuildSubscription.getAllSubscriptions() - expect(returned).toHaveLength(2) - expect(returned.every(item => item instanceof GuildSubscription)).toEqual(true) - }) - it('calls the right url and options', async () => { - jest.spyOn(GuildSubscription, 'getApiConfig').mockReturnValue(apiConfig) - fetch.mockResolvedValue({ - status: 200, - json: async () => [mockResponse] - }) - await GuildSubscription.getAllSubscriptions() - expect(fetch).toHaveBeenCalledWith(`${apiConfig.url}/guilds`, { - headers: { - Authorization: apiConfig.accessToken - } - }) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_LinkLogic.test.js b/services/bot/src/tests/structs/unit_LinkLogic.test.js deleted file mode 100644 index bc872b032..000000000 --- a/services/bot/src/tests/structs/unit_LinkLogic.test.js +++ /dev/null @@ -1,362 +0,0 @@ -process.env.TEST_ENV = true -const NewArticle = require('../../structs/NewArticle.js') -const LinkLogic = require('../../structs/LinkLogic.js') - -jest.mock('../../structs/NewArticle.js') -jest.mock('../../config.js') - -const DEFAULT_DATA = { config: { feeds: {} } } - -describe('Unit::LinkLogic', function () { - beforeEach(function () { - jest.restoreAllMocks() - NewArticle.mockReset() - }) - describe('static getArticleProperty', function () { - it('returns correctly', function () { - const article = { - foo: { - bar: { - here: 1 - } - } - } - const accessor = 'foo_bar_here' - expect(LinkLogic.getArticleProperty(article, accessor)) - .toEqual(1) - }) - it('returns undefined on invalid property', function () { - const article = { - foo: { - bar: { - here: 1 - } - } - } - const accessor = 'foo_bar_here_doo_dat' - expect(LinkLogic.getArticleProperty(article, accessor)) - .toBeUndefined() - }) - }) - describe('getComparisonReferences', function () { - it('returns a map', function () { - const result = LinkLogic.getComparisonReferences([]) - expect(result).toBeInstanceOf(Map) - }) - it('returns all stored properties', function () { - const docs = [{ - properties: { - title: 't1', - description: 'd1' - } - }, { - properties: { - title: 't2' - } - }] - const result = LinkLogic.getComparisonReferences(docs) - expect(result.get('title')).toEqual(new Set(['t1', 't2'])) - expect(result.get('description')).toEqual(new Set(['d1'])) - }) - }) - describe('positiveComparisonPasses', function () { - it('returns false for no comparisons', function () { - const result = LinkLogic.positiveComparisonPasses({}, [], new Map(), new Map()) - expect(result).toEqual(false) - }) - it('returns true for unstored property', function () { - const dbReferences = new Map() - dbReferences.set('title', new Set(['t1'])) - dbReferences.set('description', new Set(['d1'])) - const article = { - title: 't1', - description: 'd2' - } - const result = LinkLogic.positiveComparisonPasses( - article, - ['title', 'description'], - dbReferences, - new Map()) - expect(result).toEqual(true) - }) - it('returns false for all stored properties', function () { - const dbReferences = new Map() - dbReferences.set('title', new Set(['t1'])) - dbReferences.set('description', new Set(['d1'])) - const article = { - title: 't1', - description: 'd1' - } - const result = LinkLogic.positiveComparisonPasses( - article, - ['title', 'description'], - dbReferences, - new Map()) - expect(result).toEqual(false) - }) - it('returns false for uninitialized properties', function () { - /** - * Returns false because other title values must be - * stored to show this was initialized. Uninitialized - * pcomparison properties will cause every article in - * the feed to be sent since every value would technically - * be new for a newly added pcomparison. - */ - const article = { - title: 't1' - } - const result = LinkLogic.positiveComparisonPasses( - article, - ['title'], - new Map(), - new Map()) - expect(result).toEqual(false) - }) - it('returns false for cached property', function () { - const sentRefs = new Map() - sentRefs.set('title', new Set(['t1'])) - sentRefs.set('description', new Set(['d1'])) - const dbReferences = new Map() - dbReferences.set('title', new Set(['srfdht'])) - dbReferences.set('description', new Set(['srfdhredht'])) - const article = { - title: 't1', - description: 'd1' - } - const result = LinkLogic.positiveComparisonPasses( - article, - ['title', 'description'], - dbReferences, - sentRefs) - expect(result).toEqual(false) - }) - }) - describe('negativeComparisonBlocks', function () { - it('returns false for no comparisons', function () { - const result = LinkLogic.negativeComparisonBlocks({}, [], new Map(), new Map()) - expect(result).toEqual(false) - }) - it('returns true for unstored property', function () { - const dbReferences = new Map() - dbReferences.set('description', new Set(['d1'])) - const article = { - description: 'd2' - } - const result = LinkLogic.negativeComparisonBlocks( - article, - ['title', 'description'], - dbReferences, - new Map()) - expect(result).toEqual(false) - }) - it('returns true for one stored properties', function () { - const dbReferences = new Map() - dbReferences.set('description', new Set(['d1'])) - const article = { - title: 't1', - description: 'd1' - } - const result = LinkLogic.negativeComparisonBlocks( - article, - ['title', 'description'], - dbReferences, - new Map()) - expect(result).toEqual(true) - }) - it('returns true for a cached property', function () { - const sentRefs = new Map() - sentRefs.set('title', new Set(['t1'])) - const article = { - title: 't1', - description: 'd1' - } - const result = LinkLogic.negativeComparisonBlocks( - article, - ['title', 'description'], - new Map(), - sentRefs) - expect(result).toEqual(true) - }) - }) - describe('isNewArticle', function () { - beforeEach(function () { - jest.spyOn(LinkLogic.prototype, 'storePropertiesToBuffer') - .mockReturnValue() - }) - describe('id is undefined', function () { - it('returns false', function () { - const logic = new LinkLogic({ ...DEFAULT_DATA }) - const article = { - _id: undefined - } - const feed = { - pcomparisons: [], - ncomparisons: [] - } - expect(logic.isNewArticle(new Set(), article, feed, false, new Map())) - .toEqual(false) - }) - }) - describe('id is not in database', function () { - const dbIDs = new Set(['b']) - const article = { - _id: 'a' - } - it('returns true with no blocked comparisons', function () { - jest.spyOn(LinkLogic, 'negativeComparisonBlocks') - .mockReturnValue(false) - const logic = new LinkLogic({ ...DEFAULT_DATA }) - expect(logic.isNewArticle(dbIDs, article, {}, false, new Map())) - .toEqual(true) - }) - it('returns false with blocked comaprisons', function () { - jest.spyOn(LinkLogic, 'negativeComparisonBlocks') - .mockReturnValue(true) - const logic = new LinkLogic({ ...DEFAULT_DATA }) - expect(logic.isNewArticle(dbIDs, article, {}, false, new Map())) - .toEqual(false) - }) - }) - describe('id is in database', function () { - const dbIDs = new Set(['a']) - const article = { - _id: 'a' - } - it('returns false with no passed comparisons', function () { - jest.spyOn(LinkLogic, 'positiveComparisonPasses') - .mockReturnValue(false) - const logic = new LinkLogic({ ...DEFAULT_DATA }) - expect(logic.isNewArticle(dbIDs, article, {}, false, new Map())) - .toEqual(false) - }) - it('returns true with passed comparisons', function () { - jest.spyOn(LinkLogic, 'positiveComparisonPasses') - .mockReturnValue(true) - const logic = new LinkLogic({ ...DEFAULT_DATA }) - expect(logic.isNewArticle(dbIDs, article, {}, false, new Map())) - .toEqual(true) - }) - }) - describe('date checks for any article that sends', function () { - const dbIDs = new Set(['b']) - const article = { - _id: 'a' - } - beforeEach(function () { - jest.spyOn(LinkLogic, 'negativeComparisonBlocks') - .mockReturnValue(false) - }) - it('does not send if no article date', function () { - const logic = new LinkLogic({ ...DEFAULT_DATA }) - expect(logic.isNewArticle(dbIDs, article, {}, true, new Map())) - .toEqual(false) - }) - it('does not send if article has invalid date', function () { - const invalidDateArticle = { - ...article, - pubdate: new Date('invalid date') - } - const logic = new LinkLogic({ ...DEFAULT_DATA }) - expect(logic.isNewArticle(dbIDs, invalidDateArticle, {}, true, new Map())) - .toEqual(false) - }) - it('sends for recent article with valid date', function () { - const validDateArticle = { - ...article, - pubdate: new Date() - } - const logic = new LinkLogic({ ...DEFAULT_DATA }) - expect(logic.isNewArticle(dbIDs, validDateArticle, {}, true, new Map())) - .toEqual(true) - }) - it('does not send for old date', function () { - const logicData = { - config: { - feeds: { - cycleMaxAge: 2 - } - } - } - const oldDate = new Date() - oldDate.setDate(oldDate.getDate() - 10) - const oldArticle = { - ...article, - pubdate: oldDate - } - const logic = new LinkLogic({ ...logicData }) - expect(logic.isNewArticle(dbIDs, oldArticle, {}, true, new Map())) - .toEqual(false) - }) - it('uses the feed article max age if available', function () { - const logicData = { - config: { - feeds: { - cycleMaxAge: 2 - } - } - } - const oldDate = new Date() - oldDate.setDate(oldDate.getDate() - 4) - const oldArticle = { - ...article, - pubdate: oldDate - } - const feed = { - articleMaxAge: 10 - } - const logic = new LinkLogic({ ...logicData }) - expect(logic.isNewArticle(dbIDs, oldArticle, feed, true, new Map())) - .toEqual(true) - }) - }) - }) - describe('static shouldCheckDates', function () { - it('should return config val if feed setting is not there', function () { - const config = { - feeds: { - checkDates: true - } - } - const feed = {} - expect(LinkLogic.shouldCheckDates(config, feed)) - .toEqual(true) - }) - it('should return feed val if it exists', function () { - const config = { - feeds: { - checkDates: false - } - } - const feed = { - checkDates: true - } - expect(LinkLogic.shouldCheckDates(config, feed)) - .toEqual(true) - }) - }) - describe('getNewArticlesOfFeed', function () { - it('returns the new articles', function () { - const articleList = [{ - _id: 'a' - }, { - _id: 'b' - }, { - _id: 'c' - }] - const logic = new LinkLogic({ ...DEFAULT_DATA }) - const formatted = { - foo: 'baz' - } - jest.spyOn(logic, 'isNewArticle') - .mockReturnValueOnce(false) - .mockReturnValueOnce(true) - .mockReturnValueOnce(false) - NewArticle.mockImplementation(() => { - return formatted - }) - const newArticles = logic.getNewArticlesOfFeed(new Set(), {}, articleList, new Map()) - expect(newArticles).toHaveLength(1) - expect(newArticles[0]).toEqual(formatted) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_ScheduleRun.test.js b/services/bot/src/tests/structs/unit_ScheduleRun.test.js deleted file mode 100644 index 5dbb9173d..000000000 --- a/services/bot/src/tests/structs/unit_ScheduleRun.test.js +++ /dev/null @@ -1,333 +0,0 @@ -const ScheduleRun = require('../../structs/ScheduleRun.js') -const Supporter = require('../../structs/db/Supporter.js') -const FailRecord = require('../../structs/db/FailRecord.js') -const maintenance = require('../../maintenance/index.js') - -jest.mock('../../structs/db/Schedule.js') -jest.mock('../../structs/db/Supporter.js') -jest.mock('../../structs/Guild.js') -jest.mock('../../structs/db/FailRecord.js') -jest.mock('../../maintenance/index.js') - -Supporter.schedule = { - name: 'default' -} - -describe('Unit::structs/ScheduleRun', function () { - afterEach(function () { - jest.restoreAllMocks() - FailRecord.mockReset() - }) - const basicSchedule = { - name: 'awsdefgr', - refreshRateMinutes: 55, - keywords: ['35'] - } - describe('updateFeedsStatus', function () { - it('runs the right func', async function () { - const run = new ScheduleRun(basicSchedule) - maintenance.checkLimits.limits - .mockResolvedValue({ - enabled: [1, 2], - disabled: [3, 4] - }) - const emit = jest.spyOn(run, 'emit') - await run.updateFeedsStatus([]) - expect(emit).toHaveBeenCalledWith('feedEnabled', 1) - expect(emit).toHaveBeenCalledWith('feedEnabled', 2) - expect(emit).toHaveBeenCalledWith('feedDisabled', 3) - expect(emit).toHaveBeenCalledWith('feedDisabled', 4) - }) - }) - describe('getFailRecordsMap', function () { - it('returns correctly', async function () { - const failRecords = [{ - _id: 'a', - key: '1' - }, { - _id: 'b', - key: '2' - }] - FailRecord.getManyByQuery.mockResolvedValue(failRecords) - const run = new ScheduleRun(basicSchedule) - await expect(run.getFailRecordsMap([])) - .resolves.toEqual(new Map([ - ['a', failRecords[0]], - ['b', failRecords[1]] - ])) - }) - }) - describe('getScheduleFeeds', function () { - it('returns all feeds of this schedule', async function () { - const scheduleName = 'abaesdgr' - const run = new ScheduleRun(basicSchedule) - run.name = scheduleName - const feeds = [{ - key: 1, - determineSchedule: async () => ({ name: scheduleName }) - }, { - key: 2, - determineSchedule: async () => ({ name: scheduleName + 'other' }) - }, { - key: 3, - determineSchedule: async () => ({ name: scheduleName }) - }] - const returned = await run.getScheduleFeeds(feeds) - expect(returned).toEqual([ - feeds[0], - feeds[2] - ]) - }) - }) - describe('getEligibleFeeds', function () { - beforeEach(function () { - jest.spyOn(ScheduleRun.prototype, 'isEligibleFeed') - .mockImplementation() - }) - it('excludes ineligible feeds', async function () { - const scheduleName = 'abaesdgr' - const run = new ScheduleRun(basicSchedule) - run.name = scheduleName - const feeds = [{ - key: 1 - }, { - key: 2 - }, { - key: 3 - }] - jest.spyOn(run, 'isEligibleFeed') - .mockReturnValueOnce(false) - .mockReturnValueOnce(true) - .mockReturnValueOnce(false) - const returned = await run.getEligibleFeeds(feeds, new Map(), new Set()) - expect(returned).toEqual([ - feeds[1] - ]) - }) - }) - describe('isEligibleFeed', function () { - it('returns false if feed is disabled', function () { - const run = new ScheduleRun(basicSchedule) - const feedObject = { - disabled: true - } - expect(run.isEligibleFeed(feedObject, new Map(), new Set())) - .toEqual(false) - }) - it('returns false if fail record has failed', function () { - const run = new ScheduleRun(basicSchedule) - const feedObject = { - disabled: false, - url: 'abc123' - } - const failRecordMap = new Map([ - [feedObject.url, { - hasFailed: jest.fn().mockReturnValue(true) - }] - ]) - expect(run.isEligibleFeed(feedObject, failRecordMap, new Set())) - .toEqual(false) - }) - it('returns true if eligible', function () { - const run = new ScheduleRun(basicSchedule) - const feedObject = { - disabled: false, - url: 'abc123' - } - const failRecordMap = new Map([ - [feedObject.url, { - hasFailed: jest.fn().mockReturnValue(false) - }] - ]) - expect(run.isEligibleFeed(feedObject, failRecordMap, new Set())) - .toEqual(true) - }) - }) - describe('convertFeedsToJSON', function () { - it('returns json of feeds', function () { - const run = new ScheduleRun(basicSchedule) - const feeds = [{ - toJSON: () => 1 - }, { - toJSON: () => 2 - }, { - toJSON: () => 3 - }] - const returned = run.convertFeedsToJSON(feeds) - expect(returned).toEqual([1, 2, 3]) - }) - }) - describe('mapFeedsByURL', function () { - it('maps correctly', function () { - const run = new ScheduleRun(basicSchedule) - const feedObjects = [{ - url: 'a', - _id: 'feed1' - }, { - url: 'b', - _id: 'feed2' - }, { - url: 'b', - _id: 'feed3' - }, { - url: 'c', - _id: 'feed4' - }] - const returned = run.mapFeedsByURL(feedObjects, new Set()) - const keys = Array.from(returned.keys()) - expect(keys).toHaveLength(3) - expect(keys).toEqual(expect.arrayContaining([ - 'a', - 'b', - 'c' - ])) - expect(returned.get('a')).toEqual({ - feed1: { - _id: 'feed1', - url: 'a' - } - }) - expect(returned.get('b')).toEqual({ - feed2: { - _id: 'feed2', - url: 'b' - }, - feed3: { - _id: 'feed3', - url: 'b' - } - }) - expect(returned.get('c')).toEqual({ - feed4: { - _id: 'feed4', - url: 'c' - } - }) - }) - }) - describe('createBatches', function () { - it('returns the batches', function () { - const run = new ScheduleRun(basicSchedule) - const url1Feeds = { - feed1: {}, - feed2: {} - } - const url2Feeds = { - feed3: {} - } - const url3Feeds = { - feed4: {}, - feed5: {} - } - const urlMap = new Map([ - ['url1', url1Feeds], - ['url2', url2Feeds], - ['url3', url3Feeds] - ]) - const returned = run.createBatches(urlMap, 2, new Set()) - expect(returned).toHaveLength(2) - expect(returned).toEqual([{ - url1: url1Feeds, - url2: url2Feeds - }, { - url3: url3Feeds - }]) - }) - }) - describe('createURLRecords', function () { - it('creates the records correctly', function () { - const run = new ScheduleRun(basicSchedule) - const batches = [ - { - url1: {}, - url2: {} - }, { - url3: {}, - url4: {} - }, { - url5: {}, - url6: {} - }, { - url7: {} - } - ] - run.createURLRecords(batches) - expect(run.urlBatchRecords).toEqual([ - new Set(['url1', 'url2']), - new Set(['url3', 'url4']), - new Set(['url5', 'url6']), - new Set(['url7']) - ]) - }) - it('creates counts correctly', function () { - const run = new ScheduleRun(basicSchedule) - const batchGroups = [ - { - url1: {}, - url2: {} - }, { - url3: {}, - url4: {} - }, { - url5: {}, - url6: {} - }, { - url7: {} - } - ] - run.createURLRecords(batchGroups) - expect(run.urlSizeRecords).toEqual([ - 2, 2, 2, 1 - ]) - }) - }) - describe('removeFromBatchRecords', function () { - it('removes correctly', function () { - const run = new ScheduleRun(basicSchedule) - run.urlBatchRecords = [ - new Set(['url1', 'url2']), - new Set(['url3', 'url4']), - new Set(['url5', 'url6']), - new Set(['url7']) - ] - run.removeFromBatchRecords(3, 'url7') - expect(run.urlBatchRecords).toEqual([ - new Set(['url1', 'url2']), - new Set(['url3', 'url4']), - new Set(['url5', 'url6']), - new Set() - ]) - run.removeFromBatchRecords(1, 'url3') - expect(run.urlBatchRecords).toEqual([ - new Set(['url1', 'url2']), - new Set(['url4']), - new Set(['url5', 'url6']), - new Set() - ]) - }) - }) - describe('getHungUpURLs', function () { - it('returns correctly for mid-progress batches', function () { - const run = new ScheduleRun(basicSchedule) - run.urlBatchRecords = [ - new Set(['url1', 'url2']), - new Set(['url3']), - new Set(['url5']), - new Set(['url7']) - ] - run.urlSizeRecords = [ - 2, 2, 2, 1 - ] - expect(run.getHungUpURLs()).toEqual({ - summary: [ - ['url3'], - ['url5'] - ], - remaining: [ - 2, 1, 1, 1 - ], - total: 5 - }) - }) - }) -}) diff --git a/services/bot/src/tests/structs/unit_Translator.test.js b/services/bot/src/tests/structs/unit_Translator.test.js deleted file mode 100644 index 126a02f97..000000000 --- a/services/bot/src/tests/structs/unit_Translator.test.js +++ /dev/null @@ -1,89 +0,0 @@ -process.env.TEST_ENV = true -const Translator = require('../../structs/Translator.js') - -jest.mock('../../config.js') - -describe('Unit::Translator', function () { - afterEach(function () { - jest.restoreAllMocks() - }) - describe('createLocaleTranslator', function () { - it('returns a function', function () { - expect(typeof Translator.createLocaleTranslator('eaa')).toEqual('function') - }) - }) - describe('createProfileTranslator', function () { - it('creates a locale translator correctly', function () { - const createLocaleTranslator = jest.spyOn(Translator, 'createLocaleTranslator') - .mockImplementation() - Translator.createProfileTranslator() - expect(createLocaleTranslator).toHaveBeenCalledWith(undefined) - createLocaleTranslator.mockReset() - Translator.createProfileTranslator({ - locale: 'qwerty' - }) - expect(createLocaleTranslator).toHaveBeenCalledWith('qwerty') - }) - }) - describe('hasLocale', function () { - it('returns whether this.LOCALES_DATA has a locale', function () { - const addLocale = 'awsofikerhsduighnbdefhnguerdoiedh' - Translator.LOCALES_DATA.set(addLocale, {}) - expect(Translator.hasLocale(addLocale)).toEqual(true) - Translator.LOCALES_DATA.delete(addLocale) - expect(Translator.hasLocale(addLocale)).toEqual(false) - }) - }) - describe('getLocales', function () { - const originalValue = Array.from(Translator.LOCALES_DATA) - const mockValues = [['z', 2], ['c', 1], ['b', 2], ['a', 3]] - - beforeEach(function () { - Translator.LOCALES_DATA.clear() - for (let i = 0; i < mockValues.length; ++i) { - Translator.LOCALES_DATA.set(mockValues[i][0], mockValues[i][1]) - } - }) - afterEach(function () { - Translator.LOCALES_DATA.clear() - for (let i = 0; i < originalValue.length; ++i) { - Translator.LOCALES_DATA.set(originalValue[i][0], originalValue[i][1]) - } - }) - it('returns the Map of locales as a sorted array', function () { - expect(Translator.getLocales()).toBeInstanceOf(Array) - }) - it('returns the array of locales as sorted', function () { - expect(Translator.getLocales()).toEqual(['a', 'b', 'c', 'z']) - }) - }) - describe('getCommandDescriptions', function () { - it('returns the .commandDescriptions property of a locale\'s data', function () { - const mockLocale = 'oiwe3rjtui8w9o32p4ehygnbediruhy8345' - const mockLocaleData = { commandDescriptions: 'abc123' } - Translator.LOCALES_DATA.set(mockLocale, mockLocaleData) - expect(Translator.getCommandDescriptions(mockLocale)).toEqual(mockLocaleData.commandDescriptions) - Translator.LOCALES_DATA.delete(mockLocale) - }) - }) - describe('translate', function () { - const testLocale = 'asfwsgikj' - const testLocaleData = { a: { b: { c: 1 }, arrayKey: [], numberKey: 1 } } - beforeAll(function () { - expect(Translator.LOCALES_DATA.has(testLocale)).toEqual(false) - Translator.LOCALES_DATA.set(testLocale, testLocaleData) - }) - afterAll(function () { - Translator.LOCALES_DATA.delete(testLocale) - }) - it.todo('throws an error if translate string is not a string') - it.todo('throws an error if locale is not a string') - it.todo('throws an error if the locale is unknown') - it.todo('throws an error if the specified locale is undefined at some point') - it.todo('throws an error if the specified locale is not a string') - it.todo('throws an error if the specified locale is an empty string but the reference locale does not exist') - it.todo('throws an error if the reference locale is undefined at some point') - it.todo('gives the reference locale string if the specified locale is an empty string') - it.todo('gives the specified locale if it exists') - }) -}) diff --git a/services/bot/src/tests/updates/int_6.0.0.test.js b/services/bot/src/tests/updates/int_6.0.0.test.js deleted file mode 100644 index 6677b4d0b..000000000 --- a/services/bot/src/tests/updates/int_6.0.0.test.js +++ /dev/null @@ -1,379 +0,0 @@ -process.env.TEST_ENV = true -const config = require('../../config.js') -const mongoose = require('mongoose') -const initialize = require('../../initialization/index.js') -const { updateProfiles, updateFailRecords } = require('../../../scripts/updates/6.0.0.js') -const dbName = 'test_int_v6_migrate' -const CON_OPTIONS = { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true, - /** - * This is needed to prevent the tests from running while - * the database has not finished building indexes. Only happens - * in this test suite for some reason. - */ - autoIndex: false -} - -jest.mock('../../config.js', () => ({ - get: () => ({ - database: { - uri: 'mongodb://' - }, - feeds: { - hoursUntilFail: 24 - } - }) -})) - -function getOldDate (hoursAgo) { - // https://stackoverflow.com/questions/1050720/adding-hours-to-javascript-date-object - const date = new Date() - date.setTime(date.getTime() - hoursAgo * 60 * 60 * 1000) - return date -} - -describe('Int::scripts/updates/6.0.0 Database', function () { - /** @type {import('mongoose').Connection} */ - let con - const uri = `mongodb://localhost/${dbName}` - beforeAll(async function () { - process.env.DRSS_DATABASE_URI = uri - con = await mongoose.createConnection(uri, CON_OPTIONS) - await initialize.setupModels(con) - }) - beforeEach(async function () { - await con.db.dropDatabase() - }) - describe('fail_records', function () { - it('restores', async function () { - const failedLink = { - link: 'https://www.google1.com', - count: 20, - failed: 'huzz' - } - await updateFailRecords(failedLink) - const record = await con.collection('fail_records').findOne({ - _id: failedLink.link - }) - const expected = { - _id: failedLink.link, - reason: failedLink.failed, - alerted: true - } - expect(record).toEqual(expect.objectContaining(expected)) - expect(record.failedAt).toBeInstanceOf(Date) - }) - it('sets a date before the cutoff for failed links', async function () { - const failedLink = { - link: 'https://www.google2.com', - count: 20, - failed: 'huzz' - } - await updateFailRecords(failedLink) - const record = await con.collection('fail_records').findOne({ - _id: failedLink.link - }) - const cutoff = getOldDate(config.get().feeds.hoursUntilFail) - expect(record.failedAt < cutoff) - }) - it('sets a new date for not-yet-failed links', async function () { - const failedLink = { - link: 'https://www.google3.com', - count: 20 - } - await updateFailRecords(failedLink) - const record = await con.collection('fail_records').findOne({ - _id: failedLink.link - }) - const cutoff = getOldDate(config.get().feeds.hoursUntilFail) - expect(record.failedAt > cutoff) - }) - }) - describe('profile', function () { - it('does restores profile', async function () { - const guildRss = { - id: '32qwet4ry', - name: 'azdsh', - prefix: '22' - } - await updateProfiles(guildRss) - const profile = await con.collection('profiles').findOne({ - _id: guildRss.id - }) - expect(profile).toBeDefined() - }) - it('does restores profile with alerts', async function () { - const guildRss = { - id: '32qwet4ry', - name: 'azdsh', - sendAlertsTo: ['22'] - } - await updateProfiles(guildRss) - const profile = await con.collection('profiles').findOne({ - _id: guildRss.id - }) - expect(profile).toBeDefined() - }) - it('deletes prefixes with empty spaces', async function () { - const guildRss = { - id: '32qwet4ry', - name: 'azdsh', - prefix: 'hello world' - } - await updateProfiles(guildRss) - const profile = await con.collection('profiles').findOne({ - _id: guildRss.id - }) - expect(profile).toBeNull() - }) - it('does not restore empty profile', async function () { - const guildRss = { - id: '32qwethk4ry', - name: 'azdsh', - sendAlertsTo: [] - } - await updateProfiles(guildRss) - const profile = await con.collection('profiles').findOne({ - _id: guildRss.id - }) - expect(profile).toBeNull() - }) - }) - describe('feeds', function () { - it('saves correctly', async function () { - const guildRss = { - id: '32qwet4ry', - name: 'azdsh', - sources: { - f1: { - title: 't1', - link: 'u1', - channel: 'q3wet4' - }, - f2: { - title: 't2', - link: 'u2', - channel: 'aq3wet4' - } - } - } - await updateProfiles(guildRss) - const feeds = await con.collection('feeds').find({ - guild: guildRss.id - }).toArray() - expect(feeds).toHaveLength(2) - }) - it('replaces old embed keys with new ones', async function () { - const embeds = [{ - thumbnail_url: '1', - authorUrl: '2', - footer_text: '3', - imageUrl: '4', - author_name: '5', - authorIconURL: '6', - authorIconUrl: '7', - fields: [{ - title: 'hello', - value: 'world' - }, { - title: '', - value: '' - }] - }] - const guildRss = { - id: '32qwet4ry', - name: 'azdsh', - sources: { - f1: { - title: 't1', - link: 'u1', - channel: 'q3wet4', - embeds - } - } - } - const expectedEmbeds = [{ - thumbnailURL: '1', - authorURL: '2', - footerText: '3', - imageURL: '4', - authorName: '5', - authorIconURL: '6', - fields: [{ - name: 'hello', - value: 'world' - }, { - name: '\u200b', - value: '\u200b' - }] - }] - await updateProfiles(guildRss) - const feed = await con.collection('feeds').findOne({ - guild: guildRss.id - }) - expect(feed.embeds).toEqual(expectedEmbeds) - }) - it('converts checkTitles to ncomparison', async function () { - const guildRss = { - id: '32qwet4ry', - name: 'azdsh', - sources: { - f1: { - title: 't1', - link: 'u1', - channel: 'q3wet4', - checkTitles: true - } - } - } - await updateProfiles(guildRss) - const feeds = await con.collection('feeds').find({ - guild: guildRss.id - }).toArray() - expect(feeds).toHaveLength(1) - expect(feeds[0].ncomparisons).toEqual(['title']) - }) - it('saves correctly with message', async function () { - const guildRss = { - id: '32qwet4ry', - name: 'azdsh', - sources: { - f1: { - title: 't1', - link: 'u1', - channel: 'q3wet4', - message: 'awr' - }, - f2: { - title: 't2', - link: 'u2', - channel: 'aq3wet4', - message: 'sergt' - } - } - } - await updateProfiles(guildRss) - const feeds = await con.collection('feeds').find({ - guild: guildRss.id - }).toArray() - expect(feeds).toContainEqual(expect.objectContaining({ - text: guildRss.sources.f1.message - })) - expect(feeds).toContainEqual(expect.objectContaining({ - text: guildRss.sources.f2.message - })) - }) - it('saves correctly with embeds', async function () { - const guildRss = { - id: '32qwet4ry', - name: 'azdsh', - sources: { - f1: { - title: 't1', - link: 'u1', - channel: 'q3wet4', - embeds: [{ - title: 'h' - }] - }, - f2: { - title: 't2', - link: 'u2', - channel: 'aq3wet4', - embeds: [{ - title: 'aegds' - }] - } - } - } - await updateProfiles(guildRss) - const feeds = await con.collection('feeds').find({ - guild: guildRss.id - }).toArray() - expect(feeds).toContainEqual(expect.objectContaining({ - embeds: expect.arrayContaining([ - expect.objectContaining(guildRss.sources.f1.embeds[0]) - ]) - })) - expect(feeds).toContainEqual(expect.objectContaining({ - embeds: expect.arrayContaining([ - expect.objectContaining(guildRss.sources.f1.embeds[0]) - ]) - })) - }) - }) - describe('subscribers', function () { - it('saves correctly', async function () { - const guildRss = { - id: '32qwet4ry', - name: 'azdsh', - sources: { - f1: { - title: 't1', - link: 'u1', - channel: 'q3wet4', - subscribers: [{ - id: 'are1', - type: 'role' - }, { - id: 'ees2', - type: 'user' - }] - }, - f2: { - title: 't2', - link: 'u2', - channel: 'aq3wet4', - subscribers: [{ - id: 'are3', - type: 'role' - }] - } - } - } - await updateProfiles(guildRss) - const subscribers = await con - .collection('subscribers').find({}).toArray() - expect(subscribers).toHaveLength(3) - }) - it('converts unknown subscriber types to role', async function () { - const guildRss = { - id: '32qwet4ry', - name: 'azdsh', - sources: { - f2: { - title: 't2', - link: 'u2', - channel: 'aq3wet4', - subscribers: [{ - id: 'are3' - // Missing type should be "role" - }, { - id: 'are4', - type: 'boogaloo' - }] - } - } - } - await updateProfiles(guildRss) - const subscribers = await con - .collection('subscribers').find({}).toArray() - expect(subscribers).toEqual(expect.arrayContaining([ - expect.objectContaining({ - id: 'are3', - type: 'role' - }), - expect.objectContaining({ - id: 'are4', - type: 'role' - }) - ])) - }) - }) - afterAll(async function () { - await con.db.dropDatabase() - await con.close() - }) -}) diff --git a/services/bot/src/tests/util/unit_FeedFetcher.test.js b/services/bot/src/tests/util/unit_FeedFetcher.test.js deleted file mode 100644 index 8b0144707..000000000 --- a/services/bot/src/tests/util/unit_FeedFetcher.test.js +++ /dev/null @@ -1,540 +0,0 @@ -process.env.TEST_ENV = true -const FeedFetcher = require('../../util/FeedFetcher.js') -const fetch = require('node-fetch') -const Article = require('../../structs/Article.js') -const RequestError = require('../../structs/errors/RequestError.js') -const AbortController = require('abort-controller').AbortController -const config = require('../../config.js') -const Readable = require('stream').Readable - -jest.mock('node-fetch') -jest.mock('../../config.js', () => ({ - get: jest.fn(() => ({ - _vip: false - })) -})) -jest.mock('../../structs/ArticleIDResolver.js') -jest.mock('../../structs/Article.js') -jest.mock('../../structs/DecodedFeedParser.js') -jest.mock('abort-controller') -jest.useFakeTimers() - -describe('Unit::FeedFetcher', function () { - afterEach(function () { - AbortController.mockRestore() - jest.restoreAllMocks() - fetch.mockReset() - config.get.mockReturnValue({ - _vip: false, - bot: { - feedParseTimeout: 10000 - } - }) - }) - it('throws an error if it is instantiated', function () { - expect(() => new FeedFetcher()).toThrowError() - }) - describe('static resolveUserAgent', function () { - it('adds GoogleBot if tumblr', function () { - const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0' - const url = 'https://whatever.tumblr.com' - const newUserAgent = FeedFetcher.resolveUserAgent(url, userAgent) - expect(newUserAgent).toEqual('Mozilla/5.0 GoogleBot (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0') - }) - it('returns the same string if not tumblr', function () { - const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0' - const url = 'https://www.google.com' - const newUserAgent = FeedFetcher.resolveUserAgent(url, userAgent) - expect(newUserAgent).toEqual(userAgent) - }) - }) - describe('static formatNodeFetchResponse', function () { - it('converts headers to lowercase', function () { - const headers = { - HELLO: 'world', - CAPITAL: 'punish' - } - const res = { - headers: { - raw: jest.fn(() => headers) - } - } - const expectedHeaders = { - hello: 'world', - capital: 'punish' - } - expect(FeedFetcher.formatNodeFetchResponse({ ...res }).headers) - .toEqual(expectedHeaders) - }) - it('converts array etag to string', function () { - const headers = { - etag: ['world'] - } - const res = { - headers: { - raw: jest.fn(() => headers) - } - } - const expectedHeaders = { - etag: headers.etag[0] - } - expect(FeedFetcher.formatNodeFetchResponse({ ...res }).headers) - .toEqual(expectedHeaders) - }) - it('converts array last-modified to string', function () { - const headers = { - 'last-modified': ['world'] - } - const res = { - headers: { - raw: jest.fn(() => headers) - } - } - const expectedHeaders = { - 'last-modified': headers['last-modified'][0] - } - expect(FeedFetcher.formatNodeFetchResponse({ ...res }).headers) - .toEqual(expectedHeaders) - }) - it('converts array content-type to string', function () { - const headers = { - 'content-type': ['world'] - } - const res = { - headers: { - raw: jest.fn(() => headers) - } - } - const expectedHeaders = { - 'content-type': headers['content-type'][0] - } - expect(FeedFetcher.formatNodeFetchResponse({ ...res }).headers) - .toEqual(expectedHeaders) - }) - it('returns status and headers object', function () { - const headers = { - jack: 'h', - fo: 'do' - } - const res = { - status: 200, - headers: { - raw: jest.fn(() => ({ ...headers })) - } - } - const expectedReturn = { - status: res.status, - headers - } - expect(FeedFetcher.formatNodeFetchResponse({ ...res })) - .toEqual(expectedReturn) - }) - }) - describe('createFetchOptions', function () { - const resolvedUserAgent = 'swry4e3' - const feedRequestTimeoutMs = 10000 - beforeEach(function () { - jest.spyOn(config, 'get') - .mockReturnValue({ - bot: { - feedRequestTimeoutMs - } - }) - jest.spyOn(FeedFetcher, 'resolveUserAgent') - .mockReturnValue(resolvedUserAgent) - }) - afterEach(function () { - jest.clearAllTimers() - }) - it('adds the request options with the default headers', function () { - const requestOptions = { - foo: 'bar', - bz: 'da', - headers: { - 'user-agent': resolvedUserAgent - } - } - const returned = FeedFetcher.createFetchOptions('url1', requestOptions) - expect(returned.options) - .toEqual(expect.objectContaining(requestOptions)) - }) - it('adds the passed in headers', function () { - const headers = { - foz: 'baz' - } - const requestOptions = { - headers - } - const returned = FeedFetcher.createFetchOptions('url2', requestOptions) - expect(returned.options.headers) - .toEqual(expect.objectContaining(headers)) - }) - it('adds the abort signal', function () { - const signal = jest.fn() - const abort = jest.fn() - const controller = { - signal, - abort - } - AbortController.mockReturnValue(controller) - const returned = FeedFetcher.createFetchOptions('asd', {}) - expect(returned.options.signal).toEqual(signal) - }) - it('aborts the signal in the correct amount of time', function () { - const signal = jest.fn() - const abort = jest.fn() - const controller = { - signal, - abort - } - AbortController.mockReturnValue(controller) - FeedFetcher.createFetchOptions('asd', {}) - jest.runAllTimers() - expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), feedRequestTimeoutMs) - expect(abort).toHaveBeenCalled() - }) - }) - describe('fetchURL', function () { - beforeEach(function () { - jest.spyOn(FeedFetcher, 'formatNodeFetchResponse').mockReturnValue({}) - jest.spyOn(FeedFetcher, 'createFetchOptions').mockReturnValue({ - options: { - headers: {} - } - }) - }) - describe('retried is false', function () { - it('throws an error if url is not defined', function () { - return expect(FeedFetcher.fetchURL()).rejects.toBeInstanceOf(Error) - }) - it('passes the options to fetch', async function () { - const reqOpts = { a: 'b', c: 'd', e: 'f' } - jest.spyOn(FeedFetcher, 'createFetchOptions') - .mockReturnValue({ - options: reqOpts - }) - fetch.mockResolvedValueOnce({ status: 200 }) - await FeedFetcher.fetchURL('abc', reqOpts) - const passedObject = fetch.mock.calls[0][1] - for (const key in reqOpts) { - expect(passedObject[key]).toEqual(reqOpts[key]) - } - }) - it('does no recursive call if fetch failed and retried is true', function (done) { - FeedFetcher.fetchURL('abc', {}, true) - .then(() => done(new Error('Promise resolved'))) - .catch(() => { - expect(fetch).toHaveBeenCalledTimes(1) - done() - }) - .catch(done) - }) - it('throws a RequestError if fetch throws an error', async function () { - const error = new Error('abc') - fetch.mockRejectedValueOnce(error) - await expect(FeedFetcher.fetchURL('abc')) - .rejects.toThrow(new RequestError(null, error.message)) - }) - it('returns the stream and response if the response status is 200', async function () { - const body = 'abc' - const response = { body, status: 200 } - const parsedResponse = { fa: 1 } - fetch.mockResolvedValueOnce(response) - jest.spyOn(FeedFetcher, 'formatNodeFetchResponse').mockReturnValue(parsedResponse) - const data = await FeedFetcher.fetchURL('abc') - expect(data).toEqual({ stream: body, response: parsedResponse }) - }) - it('returns the stream and response if the response status is 304 with If-Modified-Since and If-None-Match is in request headers', async function () { - const headers = { 'If-Modified-Since': 1, 'If-None-Match': 1 } - jest.spyOn(FeedFetcher, 'createFetchOptions') - .mockReturnValue({ - options: { - headers - } - }) - const body = 'abc' - const response = { body, status: 304 } - const parsedResponse = { a: 1 } - fetch.mockResolvedValueOnce(response) - jest.spyOn(FeedFetcher, 'formatNodeFetchResponse').mockReturnValue(parsedResponse) - const data = await FeedFetcher.fetchURL('abc', { headers }) - expect(data).toEqual({ stream: body, response: parsedResponse }) - }) - it('recursively calls again if res status is 403/400', async function () { - fetch - .mockResolvedValueOnce({ status: 403 }) - .mockResolvedValueOnce({ status: 200 }) - jest.spyOn(FeedFetcher, 'formatNodeFetchResponse') - .mockReturnValue({ status: 200 }) - const spy = jest.spyOn(FeedFetcher, 'fetchURL') - await FeedFetcher.fetchURL('abc') - expect(spy).toHaveBeenCalledTimes(2) - }) - it('recursively calls with empty user agent if res status is 403/400', async function () { - const headers = { a: 'b', c: 'd' } - const fetchURL = jest.spyOn(FeedFetcher, 'fetchURL') - fetch - .mockResolvedValueOnce({ status: 403 }) - .mockResolvedValueOnce({ status: 200 }) - jest.spyOn(FeedFetcher, 'formatNodeFetchResponse') - .mockReturnValue({ status: 200 }) - await FeedFetcher.fetchURL('abc', { headers }) - expect(fetchURL.mock.calls[1][1].headers['user-agent']).toEqual('') - }) - }) - describe('request failed and retried is true', function () { - it('throws a RequestError if res headers does not include cloudflare', async function () { - fetch.mockResolvedValueOnce({ status: 403, headers: { get: () => null } }) - return expect(FeedFetcher.fetchURL('abc', {}, true)).rejects.toBeInstanceOf(RequestError) - }) - it.skip('throws a RequestError with an unsupported Cloudflare message if cloudflare and config._vip is true', function (done) { - config.get.mockReturnValue({ - _vip: true - }) - fetch - .mockResolvedValueOnce({ status: 403, headers: { get: () => ['cloudflare'] } }) - FeedFetcher.fetchURL('abc', {}, true) - .then(() => done(new Error('Promise resolved'))) - .catch(err => { - expect(err).toBeInstanceOf(RequestError) - done() - }) - .catch(done) - }) - it.skip('attaches the error code to the error thrown if cloudflare and config._vip is true', function (done) { - config.get.mockReturnValue({ - _vip: true - }) - fetch - .mockResolvedValueOnce({ status: 403, headers: { get: () => ['cloudflare'] } }) - FeedFetcher.fetchURL('abc', {}, true) - .then(() => done(new Error('Promise resolved'))) - .catch(err => { - expect(err.code).toEqual(FeedFetcher.REQUEST_ERROR_CODE) - done() - }) - .catch(done) - }) - it('throws a cloudflare error', async function () { - FeedFetcher.fetchCloudScraper = jest.fn() - fetch - .mockResolvedValue({ status: 403, headers: { get: () => ['cloudflare'] } }) - await expect(FeedFetcher.fetchURL('a', {}, true)) - .rejects.toThrowError(expect.objectContaining({ - cloudflare: true - })) - }) - }) - }) - describe('parseStream', function () { - it('throws an error if no url is defined', function () { - return expect(FeedFetcher.parseStream({})).rejects.toBeInstanceOf(Error) - }) - it('rejects if the stream emits an error', function (done) { - const stream = new Readable() - const error = new Error('aszf') - stream.pipe = jest.fn(() => { - stream.emit('error', error) - }) - FeedFetcher.parseStream(stream, 'asd') - .then(() => { - done(new Error('Promise Resolved')) - }) - .catch(err => { - expect(err).toEqual(error) - done() - }) - .catch(done) - }) - it.todo('rejects with a FeedParserError if feedparser emits an error') - it.todo('rejects with the feedparser error code if the error is not a feed') - it.todo('attaches the ._id property to all articles') - it.todo('returns the article list with the id type') - }) - describe('fetchFeed', function () { - const origFetchURL = FeedFetcher.fetchURL - const origParseStream = FeedFetcher.parseStream - const fetchURLResults = { stream: 'abc' } - beforeEach(function () { - jest.spyOn(FeedFetcher, 'getCharsetFromResponse') - .mockImplementation() - FeedFetcher.fetchURL = jest.fn(() => fetchURLResults) - FeedFetcher.parseStream = jest.fn(() => ({})) - }) - afterEach(function () { - FeedFetcher.fetchURL = origFetchURL - FeedFetcher.parseStream = origParseStream - }) - it('passes the url, options and charset to fetchURL', async function () { - const url = 'abc' - const opts = { a: 'b', c: 1 } - await FeedFetcher.fetchFeed(url, opts) - expect(FeedFetcher.fetchURL).toHaveBeenCalledWith(url, opts) - }) - it('passes the stream from fetchURL and url to parseStream', async function () { - const url = 'abzz' - const charset = 'aedswry' - jest.spyOn(FeedFetcher, 'getCharsetFromResponse') - .mockReturnValue(charset) - await FeedFetcher.fetchFeed(url) - expect(FeedFetcher.parseStream).toHaveBeenCalledWith(fetchURLResults.stream, url, charset) - }) - it('returns the articleList and idType', async function () { - const results = await FeedFetcher.fetchFeed() - expect(Object.prototype.hasOwnProperty.call(results, 'articleList')).toEqual(true) - expect(Object.prototype.hasOwnProperty.call(results, 'idType')).toEqual(true) - }) - }) - describe('fetchFilteredFeed', function () { - it('returns the filtered feed', async function () { - const originalArticleList = [{ - a: 1 - }, { - a: 2 - }, { - a: 3 - }] - jest.spyOn(FeedFetcher, 'fetchFeed') - .mockResolvedValue({ - articleList: originalArticleList - }) - jest.spyOn(Article.prototype, 'testFilters') - .mockReturnValueOnce({ passed: true }) - .mockReturnValueOnce({ passed: false }) - .mockReturnValueOnce({ passed: false }) - const filtered = await FeedFetcher.fetchFilteredFeed() - expect(filtered).toEqual({ - articleList: [originalArticleList[0]] - }) - }) - }) - describe('fetchRandomArticle', function () { - const origMathRand = Math.random - const randNum = 0.7 - beforeEach(function () { - Math.random = jest.fn(() => randNum) - }) - afterEach(function () { - Math.random = origMathRand - }) - it('returns null if articleList length is 0', function () { - jest.spyOn(FeedFetcher, 'fetchFeed') - .mockResolvedValue({ - articleList: [] - }) - return expect(FeedFetcher.fetchRandomArticle()).resolves.toEqual(null) - }) - it('returns a random article with no filters', function () { - const articleList = [] - const articleCount = 150 - for (let i = 0; i < articleCount; ++i) { - articleList.push(i) - } - jest.spyOn(FeedFetcher, 'fetchFeed') - .mockResolvedValueOnce({ - articleList - }) - const expectedIndex = Math.round(randNum * (articleList.length - 1)) - return expect(FeedFetcher.fetchRandomArticle()) - .resolves.toEqual(articleList[expectedIndex]) - }) - it('returns null if there are no filtered articles if filters are passed in', function () { - jest.spyOn(FeedFetcher, 'fetchFilteredFeed') - .mockResolvedValueOnce({ - articleList: [] - }) - return expect(FeedFetcher.fetchRandomArticle('a', { a: 'b' })) - .resolves.toEqual(null) - }) - }) - describe('static fetchLatestArticle', function () { - it('returns the newest article', async function () { - const now = new Date() - const past = new Date(new Date().getTime() - 1000 * 60) - const future = new Date(new Date().getTime() + 1000 * 60) - const future2 = new Date(new Date().getTime() + 2000 * 60) - const data = { - articleList: [{ - pubDate: past - }, { - pubDate: future2 - }, { - pubDate: now - }, { - pubDate: future - }] - } - jest.spyOn(FeedFetcher, 'fetchFeed') - .mockResolvedValue(data) - await expect(FeedFetcher.fetchLatestArticle()) - .resolves.toEqual(data.articleList[1]) - }) - it('returns null if a date of one article is invalid', async function () { - const invalid = new Date('4e3wyr5tu') - const past = new Date(new Date().getTime() - 1000 * 60) - const future = new Date(new Date().getTime() + 1000 * 60) - const future2 = new Date(new Date().getTime() + 2000 * 60) - const data = { - articleList: [{ - pubDate: past - }, { - pubDate: future2 - }, { - pubDate: invalid - }, { - pubDate: future - }] - } - jest.spyOn(FeedFetcher, 'fetchFeed') - .mockResolvedValue(data) - await expect(FeedFetcher.fetchLatestArticle()) - .resolves.toEqual(null) - }) - it('returns null if not all articles have dates', async function () { - const invalid = new Date('4e3wyr5tu') - const past = new Date(new Date().getTime() - 1000 * 60) - const future = new Date(new Date().getTime() + 1000 * 60) - const data = { - articleList: [{ - pubDate: past - }, { - }, { - pubDate: invalid - }, { - pubDate: future - }] - } - jest.spyOn(FeedFetcher, 'fetchFeed') - .mockResolvedValue(data) - await expect(FeedFetcher.fetchLatestArticle()) - .resolves.toEqual(null) - }) - it('returns null if there are no articles', async function () { - const data = { - articleList: [] - } - jest.spyOn(FeedFetcher, 'fetchFeed') - .mockResolvedValue(data) - await expect(FeedFetcher.fetchLatestArticle()) - .resolves.toEqual(null) - }) - }) - describe('static getCharsetFromResponse', function () { - it('returns the charset', function () { - const response = { - headers: { - 'content-type': 'application/rss+xml; charset=ISO-8859-1' - } - } - expect(FeedFetcher.getCharsetFromResponse(response)) - .toEqual('ISO-8859-1') - }) - it('does not throw on incomplete headers', function () { - const response = { - headers: {} - } - expect(() => FeedFetcher.getCharsetFromResponse(response)) - .not.toThrow() - }) - }) -}) diff --git a/services/bot/src/tests/util/unit_database.test.js b/services/bot/src/tests/util/unit_database.test.js deleted file mode 100644 index e8a9a835c..000000000 --- a/services/bot/src/tests/util/unit_database.test.js +++ /dev/null @@ -1,359 +0,0 @@ -const databaseFuncs = require('../../util/database.js') -const Article = require('../../models/Article') - -jest.mock('../../models/Article', () => ({ - Model: { - updateOne: jest.fn(() => ({ - exec: jest.fn() - })), - find: jest.fn(() => ({ - lean: jest.fn(() => ({ - exec: jest.fn() - })) - })) - } -})) - -describe('Unit::util/database', function () { - afterEach(function () { - jest.restoreAllMocks() - }) - describe('formatArticleForDatabase', function () { - it('attaches the critical values', function () { - const meta = { - feedURL: 'abc', - scheduleName: 'ewstg' - } - const article = { - _id: 'abc' - } - const formatted = databaseFuncs.formatArticleForDatabase(article, [], meta) - expect(formatted).toEqual(expect.objectContaining({ - id: article._id, - ...meta - })) - }) - it('attaches properties', function () { - const article = { - title: 't1', - description: 'd1', - summary: 'abc', - date: new Date(), - author: null - } - const properties = ['title', 'description', 'date', 'author'] - const formatted = databaseFuncs.formatArticleForDatabase(article, properties, {}) - expect(formatted.properties).toEqual({ - title: article.title, - description: article.description - }) - }) - }) - describe('updatedDocumentForDatabase', function () { - beforeEach(function () { - jest.spyOn(databaseFuncs, 'prunedDocumentForDatabase') - .mockReturnValue(false) - }) - it('adds new properties when applicable', function () { - const article = { - title: 't1', - description: 'd1', - summary: 's1', - date: new Date(), - author: null - } - const document = { - properties: { - title: 't1' - } - } - const properties = ['title', 'description', 'date', 'author'] - databaseFuncs.updatedDocumentForDatabase(article, document, properties) - expect(document).toEqual({ - properties: { - title: 't1', - description: 'd1' - } - }) - }) - it('updates properties', function () { - const article = { - link: 'a', - date: new Date(), - author: null - } - const document = { - properties: { - link: 'b' - } - } - const properties = ['link'] - databaseFuncs.updatedDocumentForDatabase(article, document, properties) - expect(document).toEqual({ - properties: { - link: 'a' - } - }) - }) - it('returns false for no changes', function () { - const article = { - title: 't1' - } - const document = { - properties: { - title: 't1' - } - } - const properties = ['title'] - const changed = databaseFuncs.updatedDocumentForDatabase(article, document, properties) - expect(changed).toEqual(false) - }) - it('returns true for changes', function () { - const article = { - title: 't1' - } - const document = { - properties: {} - } - const properties = ['title'] - const changed = databaseFuncs.updatedDocumentForDatabase(article, document, properties) - expect(changed).toEqual(true) - }) - it('returns true if pruned', function () { - const article = {} - const document = { - properties: { - title: 't1', - holla: 'there', - bo: 'gh' - } - } - jest.spyOn(databaseFuncs, 'prunedDocumentForDatabase') - .mockReturnValue(true) - const properties = [] - const changed = databaseFuncs.updatedDocumentForDatabase(article, document, properties) - expect(changed).toEqual(true) - }) - }) - describe('getInsertsAndUpdates', function () { - const meta = { - feedURL: '1', - shardID: 2, - scheduleName: 'awsd' - } - it('returns the formatted articles for new insertions', function () { - const articleList = [{ - _id: 'foo' - }, { - _id: 'bar' - }] - const dbDocs = [{ - id: 'foo' - }] - const spy = jest.spyOn(databaseFuncs, 'formatArticleForDatabase') - .mockReturnValueOnce('abc') - const returned = databaseFuncs.getInsertsAndUpdates(articleList, dbDocs, [], meta) - expect(spy).toHaveBeenCalledTimes(1) - expect(spy.mock.calls[0][0]).toEqual(articleList[1]) - expect(returned.toInsert).toContain('abc') - }) - it('ignores articles with invalid IDs', function () { - const articleList = [{ - _id: null - }] - const dbDocs = [] - const spy = jest.spyOn(databaseFuncs, 'formatArticleForDatabase') - const returned = databaseFuncs.getInsertsAndUpdates(articleList, dbDocs, [], meta) - expect(spy).toHaveBeenCalledTimes(0) - expect(returned.toInsert).toEqual([]) - }) - it('returns the updated articles for existing insertions', function () { - const articleList = [{ - _id: 'foo' - }, { - _id: 'bar' - }] - const dbDocs = [{ - id: 'foo' - }, { - id: 'bar' - }] - const spy = jest.spyOn(databaseFuncs, 'updatedDocumentForDatabase') - .mockReturnValueOnce(false) - .mockReturnValueOnce(true) - const returned = databaseFuncs.getInsertsAndUpdates(articleList, dbDocs, [], meta) - expect(spy).toHaveBeenCalledTimes(2) - expect(spy.mock.calls[1][0]).toEqual(articleList[0]) - expect(spy.mock.calls[0][0]).toEqual(articleList[1]) - expect(returned.toUpdate).toHaveLength(1) - expect(returned.toUpdate).toContain(dbDocs[0]) - }) - }) - describe('insertDocuments', function () { - it('adds documents to memoryCollection if databaseless', async function () { - const memoryCollection = [{ - foo: 1 - }] - const documents = [{ - a: 1 - }, { - b: 2 - }] - await databaseFuncs.insertDocuments(documents, memoryCollection) - expect(memoryCollection).toEqual([{ - foo: 1 - }, { - a: 1 - }, { - b: 2 - }]) - }) - }) - describe('updateDocuments', function () { - it('calls db update with every document if mongodb', async function () { - const documents = [{ - _id: 'a', - whatever: 'asd' - }, { - _id: 'b', - whatever: 'aszdc' - }] - await databaseFuncs.updateDocuments(documents) - expect(Article.Model.updateOne).toHaveBeenCalledTimes(2) - expect(Article.Model.updateOne).toHaveBeenCalledWith({ - _id: documents[0]._id - }, { - $set: documents[0] - }) - expect(Article.Model.updateOne).toHaveBeenCalledWith({ - _id: documents[1]._id - }, { - $set: documents[1] - }) - }) - it('replaces data in memory collection if databaseless', async function () { - const memoryCollection = [{ - id: '1', - foo: 'foo1' - }, { - id: '2', - foo: 'foo2' - }] - const documents = [{ - id: '1', - jack: 'pot' - }] - await databaseFuncs.updateDocuments(documents, memoryCollection) - expect(memoryCollection).toEqual([{ - id: '1', - jack: 'pot' - }, { - id: '2', - foo: 'foo2' - }]) - }) - }) - describe('prunedDocumentForDatabase', function () { - it('deletes all irrelevant keys', function () { - const document = { - properties: { - a: '1', - b: '2', - title: 'hi' - } - } - const properties = ['title'] - databaseFuncs.prunedDocumentForDatabase(document, properties) - expect(document.a).toBeUndefined() - expect(document.b).toBeUndefined() - }) - it('returns true if deleted keys', function () { - const document = { - properties: { - a: '1', - b: '2', - title: 'hi' - } - } - const properties = ['title'] - const pruned = databaseFuncs.prunedDocumentForDatabase(document, properties) - expect(pruned).toEqual(true) - }) - it('returns true if no deleted keys', function () { - const document = { - properties: { - title: 'hi' - } - } - const properties = ['title'] - const pruned = databaseFuncs.prunedDocumentForDatabase(document, properties) - expect(pruned).toEqual(false) - }) - }) - describe('getAllDocuments', function () { - it('returns memory collection if memory', async function () { - const memoryCollection = { - foo: 'bar' - } - const returned = await databaseFuncs.getAllDocuments('sa', memoryCollection) - expect(returned).toEqual(memoryCollection) - }) - it('calls other function correctly', async function () { - const scheduleName = 'aqwrsefd' - const mappedResult = { - foo: 'aa' - } - const spy = jest.spyOn(databaseFuncs, 'mapArticleDocumentsToURL') - .mockReturnValue(mappedResult) - const documents = [{ - a: 1 - }] - Article.Model.find.mockReturnValue({ - lean: jest.fn(() => ({ - exec: jest.fn(() => documents) - })) - }) - const returned = await databaseFuncs.getAllDocuments(scheduleName) - expect(spy).toHaveBeenCalledWith(documents) - expect(returned).toEqual(mappedResult) - }) - it('uses the right query', async function () { - const scheduleName = 'aqwrsefd' - jest.spyOn(databaseFuncs, 'mapArticleDocumentsToURL') - .mockReturnValue() - const urls = ['a', 'b'] - await databaseFuncs.getAllDocuments(scheduleName, undefined, urls) - expect(Article.Model.find).toHaveBeenCalledWith({ - scheduleName, - feedURL: { - $in: urls - } - }) - }) - }) - describe('mapArticleDocumentsToURL', function () { - it('returns correctly', async function () { - const articles = [{ - feedURL: '1', - id: 1 - }, { - feedURL: '2', - id: 2 - }, { - feedURL: '1', - id: 3 - }] - const result = await databaseFuncs.mapArticleDocumentsToURL(articles) - expect(result).toEqual({ - 1: [{ - ...articles[2] - }, { - ...articles[0] - }], - 2: [{ - ...articles[1] - }] - }) - }) - }) -}) diff --git a/services/bot/src/tests/util/unit_ipc.test.js b/services/bot/src/tests/util/unit_ipc.test.js deleted file mode 100644 index 60da69893..000000000 --- a/services/bot/src/tests/util/unit_ipc.test.js +++ /dev/null @@ -1,44 +0,0 @@ -const ipc = require('../../util/ipc.js') - -describe('Unit::util/ipc.js', function () { - beforeEach(function () { - jest.restoreAllMocks() - }) - describe('static send', function () { - it('sends with the right data', function () { - const type = 'qawe3t46ry3' - const data = { - fo: 'z' - } - const expected = { - _drss: true, - _loopback: true, - type, - data - } - const spy = jest.spyOn(process, 'send').mockReturnValue() - ipc.send(type, data, true) - expect(spy).toHaveBeenCalledWith(expected) - spy.mockClear() - ipc.send(type, data) - expect(spy).toHaveBeenCalledWith({ - ...expected, - _loopback: false - }) - }) - }) - describe('isValid', function () { - it('returns the correct value', function () { - expect(ipc.isValid({ fo: 'b' })).toEqual(false) - expect(ipc.isValid({ _drss: true })).toEqual(true) - expect(ipc.isValid({ _drss: 1 })).toEqual(false) - }) - }) - describe('loopback', function () { - it('returns the correct value', function () { - expect(ipc.isLoopback({ fo: 'b' })).toEqual(false) - expect(ipc.isLoopback({ _loopback: true })).toEqual(true) - expect(ipc.isLoopback({ _loopback: 1 })).toEqual(false) - }) - }) -}) diff --git a/services/bot/src/util/FeedFetcher.js b/services/bot/src/util/FeedFetcher.js deleted file mode 100644 index 3de7be130..000000000 --- a/services/bot/src/util/FeedFetcher.js +++ /dev/null @@ -1,317 +0,0 @@ -const fetch = require('node-fetch') -const AbortController = require('abort-controller').AbortController -const RequestError = require('../structs/errors/RequestError.js') -const FeedParserError = require('../structs/errors/FeedParserError.js') -const DecodedFeedParser = require('../structs/DecodedFeedParser.js') -const ArticleIDResolver = require('../structs/ArticleIDResolver.js') -const Article = require('../structs/Article.js') -const configuration = require('../config.js') - -class FeedFetcher { - constructor () { - throw new Error('Cannot be instantiated') - } - - static get REQUEST_ERROR_CODE () { - return 50042 - } - - static get FEEDPARSER_ERROR_CODE () { - return 40002 - } - - /** - * @typedef {Object} FormattedResponse - * @property {number} status - * @property {Object} headers - */ - - /** - * @param {string} url - * @param {string} userAgent - */ - static resolveUserAgent (url, userAgent) { - if (url.includes('.tumblr.com')) { - // tumblr only allows GoogleBot to automatically view NSFW feeds - const tempParts = userAgent.split(' ') - tempParts.splice(1, 0, 'GoogleBot') - return tempParts.join(' ') - } else { - return userAgent - } - } - - /** - * Responses must be uniform - * @param {import('node-fetch').Response} res - * @returns {FormattedResponse} - */ - static formatNodeFetchResponse (res) { - const rawHeaders = res.headers.raw() - const headers = { - ...rawHeaders - } - // Normalize the headers - for (const key in headers) { - const val = headers[key] - delete headers[key] - headers[key.toLowerCase()] = val - } - // Sometimes it's an array for some reason - if (Array.isArray(headers.etag)) { - headers.etag = headers.etag[0] - } - if (Array.isArray(headers['last-modified'])) { - headers['last-modified'] = headers['last-modified'][0] - } - if (Array.isArray(headers['content-type'])) { - headers['content-type'] = headers['content-type'][0] - } - return { - status: res.status, - headers - } - } - - /** - * @param {string} url - * @param {Object} requestOptions - */ - static createFetchOptions (url, requestOptions = {}) { - const config = configuration.get() - const options = { - follow: 5, - ...requestOptions, - headers: { - 'user-agent': this.resolveUserAgent(url, config.bot.userAgent) - } - } - - if (requestOptions.headers) { - options.headers = { - ...options.headers, - ...requestOptions.headers - } - } - - const controller = new AbortController() - const timeout = setTimeout(() => { - controller.abort() - }, config.bot.feedRequestTimeoutMs) - - options.signal = controller.signal - - return { - options, - timeout - } - } - - /** - * @typedef {object} FetchResults - * @property {import('stream').Readable} stream - * @property {import('node-fetch').Response} response - */ - - /** - * Fetch a URL - * @param {string} url - URL to fetch - * @param {object} requestOptions - Options to directly pass to fetch - * @param {boolean} retried - If true, recursive retries will not be made - * @returns {FetchResults} - */ - static async fetchURL (url, requestOptions = {}, retried) { - if (!url) throw new Error('No url defined') - const { options, timeout } = this.createFetchOptions(url, requestOptions) - let endStatus - let res - - try { - res = await fetch(url, options) - } catch (err) { - throw new RequestError(null, err.message === 'The user aborted a request.' ? 'Connected timed out' : err.message) - } finally { - clearTimeout(timeout) - } - - endStatus = res.status - - // Fetch returns a 304 if the two properties below were passed in by the calling function to check if there are new feeds - if (res.status === 200 || (res.status === 304 && 'If-Modified-Since' in options.headers && 'If-None-Match' in options.headers)) { - return { - stream: res.body, - response: this.formatNodeFetchResponse(res) - } - } - if (!retried && (res.status === 403 || res.status === 400)) { - delete options.headers - const res2 = await this.fetchURL(url, { - ...options, - headers: { - ...options.headers, - 'user-agent': '' - } - }, true) - endStatus = res2.response.status - if (endStatus === 200) { - return res2 - } - } - - const serverHeaders = res.headers.get('server') - if (!serverHeaders || !serverHeaders.includes('cloudflare')) { - throw new RequestError(this.REQUEST_ERROR_CODE, `Bad status code (${endStatus})`) - } - - // Cloudflare errors - throw new RequestError(endStatus, `Bad Cloudflare status code (${endStatus})`, true) - } - /** - * @typedef {Object} CSResults - * @property {import('stream').Readable} stream - * @property {Object} response - */ - - /** - * @typedef {object} FeedData - * @property {object[]} articleList - Array of articles - * @property {string} idType - The ID type used for the article ._id property - */ - - /** - * Parse a stream and return the article list, and the article ID type used - * @param {object} stream - * @param {string} url - The fetched URL of this stream - * @param {charset} [encoding] - Response charset - * @returns {FeedData} - The article list and the id type used - */ - static async parseStream (stream, url, charset) { - if (!url) { - throw new Error('No url defined') - } - const feedparser = new DecodedFeedParser(null, url, charset) - const idResolver = new ArticleIDResolver() - const articleList = [] - - return new Promise((resolve, reject) => { - const config = configuration.get() - setTimeout(() => { - reject(new FeedParserError(null, 'Feed parsing took too long')) - }, config.bot.feedParseTimeoutMs || 10000) - - stream.on('error', err => { - // feedparser may not handle all errors such as incorrect headers. (feedparser v2.2.9) - reject(new FeedParserError(this.REQUEST_ERROR_CODE, err.message)) - }) - - feedparser.on('error', err => { - feedparser.removeAllListeners('end') - if (err.message === 'Not a feed') { - reject(new FeedParserError(this.FEEDPARSER_ERROR_CODE, 'That is a not a valid feed. Note that you cannot add just any link. You may check if it is a valid feed by using online RSS feed validators')) - } else { - reject(new FeedParserError(null, err.message)) - } - }) - - feedparser.on('readable', function () { - let item - do { - item = this.read() - if (item) { - idResolver.recordArticle(item) - articleList.push(item) - } - } while (item) - }) - - feedparser.on('end', () => { - if (articleList.length === 0) { - return resolve({ articleList }) - } - const idType = idResolver.getIDType() - for (const article of articleList) { - article._id = ArticleIDResolver.getIDTypeValue(article, idType) - } - resolve({ articleList, idType }) - }) - - stream.pipe(feedparser) - }) - } - - /** - * Fetch and parse results, and result the article list and id type - * @param {string} url - The URL to fetch - * @param {object} options - The options to pass to fetch - * @returns {FeedData} - The article list and the id type used - */ - static async fetchFeed (url, options) { - const { stream, response } = await this.fetchURL(url, options) - const charset = this.getCharsetFromResponse(response) - const { articleList, idType } = await this.parseStream(stream, url, charset) - return { articleList, idType } - } - - static async fetchFilteredFeed (url, filters) { - const { articleList, idType } = await this.fetchFeed(url) - const filtered = articleList.filter(article => { - const parsed = new Article(article, { feed: {} }) - return parsed.testFilters(filters).passed - }) - return { - articleList: filtered, - idType - } - } - - /** - * Get a random article in the feed - * @param {string} url - The URL to fetch - * @param {object} filters - * @returns {object|null} - Either null, or an article object - */ - static async fetchRandomArticle (url, filters) { - const { articleList } = filters - ? await this.fetchFilteredFeed(url, filters) - : await this.fetchFeed(url) - if (articleList.length === 0) { - return null - } - return articleList[Math.round(Math.random() * (articleList.length - 1))] - } - - static async fetchLatestArticle (url) { - const { articleList } = await this.fetchFeed(url) - if (articleList.length === 0) { - return null - } - const allHaveValidDates = articleList.every(article => { - const date = new Date(article.pubDate) - return !isNaN(date.getTime()) - }) - if (!allHaveValidDates) { - return null - } - return articleList.sort((a, b) => { - return new Date(b.pubDate) - new Date(a.pubDate) - })[0] - } - - /** - * @param {Object} response - */ - static getCharsetFromResponse (response) { - const headers = response.headers - const contentType = headers['content-type'] - if (!contentType) { - return null - } - const match = /charset(?:=?)(.*)(?:$|\s)/ig.exec(contentType) - if (match && match[1]) { - return match[1] - } - return null - } -} - -module.exports = FeedFetcher diff --git a/services/bot/src/util/config/schema.js b/services/bot/src/util/config/schema.js deleted file mode 100644 index 7285de76c..000000000 --- a/services/bot/src/util/config/schema.js +++ /dev/null @@ -1,138 +0,0 @@ -const Joi = require('@hapi/joi') -const decodeValidator = require('./validation/decode.js') -const timezoneValidator = require('./validation/timezone.js') -const localeValidator = require('./validation/locale.js') - -const logSchema = Joi.object({ - level: Joi.string().strict().valid('silent', 'trace', 'debug', 'info', 'owner', 'warn', 'error', 'fatal').default('info'), - destination: Joi.string().allow('').default(''), - linkErrs: Joi.bool().strict().default(true), - unfiltered: Joi.bool().strict().default(true), - failedFeeds: Joi.bool().strict().default(true), - rateLimitHits: Joi.bool().strict().default(true), - datadogApikey: Joi.string().allow('').default('') -}) - -const botSchema = Joi.object({ - clientId: Joi.string().strict().default(''), - token: Joi.string().strict().default(''), - locale: localeValidator.config().locale(), - enableCommands: Joi.bool().strict().default(true), - prefix: Joi.string().strict().default('rss.'), - status: Joi.string().valid('online', 'dnd', 'invisible', 'idle').default('online'), - activityType: Joi.string().valid('', 'PLAYING', 'STREAMING', 'LISTENING', 'WATCHING').default(''), - activityName: Joi.string().strict().allow('').default(''), - streamActivityURL: Joi.string().strict().allow('').default(''), - ownerIDs: Joi.array().items(Joi.string().strict()).default([]), - menuColor: Joi.number().strict().greater(0).default(5285609), - deleteMenus: Joi.bool().strict().default(true), - runSchedulesOnStart: Joi.bool().strict().default(true), - exitOnSocketIssues: Joi.bool().strict().default(true), - exitOnDatabaseDisconnect: Joi.bool().strict().default(false), - exitOnExcessRateLimits: Joi.bool().strict().default(true), - userAgent: Joi.string().strict().default('MonitoRSS (+https://github.com/synzen/MonitoRSS)'), - feedParseTimeoutMs: Joi.number().strict().default(10000), - feedRequestTimeoutMs: Joi.number().strict().default(15000) -}) - -const databaseSchema = Joi.object({ - uri: Joi.string().strict().default('mongodb://localhost:27017/rss'), - redis: Joi.string().strict().allow('').default(''), - connection: Joi.object().default({}), - articlesExpire: Joi.number().strict().greater(-1).default(14), - deliveryRecordsExpire: Joi.number().strict().greater(-1).default(2) -}) - -const feedsSchema = Joi.object({ - refreshRateMinutes: Joi.number().strict().greater(0).default(10), - articleDequeueRate: Joi.number().strict().greater(0).default(1), - articleRateLimit: Joi.number().strict().greater(-1).default(0), - articleDailyChannelLimit: Joi.number().strict().greater(-1).default(0), - timezone: timezoneValidator.config().timezone(), - dateFormat: Joi.string().strict().default('ddd, D MMMM YYYY, h:mm A z'), - dateLanguage: Joi.string().strict().default('en'), - dateLanguageList: Joi.array().items(Joi.string().strict()).min(1).default(['en']), - dateFallback: Joi.bool().strict().default(false), - timeFallback: Joi.bool().strict().default(false), - max: Joi.number().strict().greater(-2).default(0), - hoursUntilFail: Joi.number().strict().default(0), - notifyFail: Joi.bool().strict().default(true), - sendFirstCycle: Joi.bool().strict().default(true), - cycleMaxAge: Joi.number().strict().default(1), - defaultText: Joi.string().default(':newspaper: | **{title}**\n\n{link}\n\n{subscribers}'), - imgPreviews: Joi.bool().strict().default(true), - imgLinksExistence: Joi.bool().strict().default(true), - checkDates: Joi.bool().strict().default(true), - formatTables: Joi.bool().strict().default(false), - directSubscribers: Joi.bool().strict().default(false), - decode: decodeValidator.config().encoding() -}) - -const advancedSchema = Joi.object({ - shards: Joi.number().greater(-1).strict().default(0), - batchSize: Joi.number().greater(0).strict().default(400), - parallelBatches: Joi.number().greater(0).strict().default(1), - parallelRuns: Joi.number().greater(0).strict().default(1) -}) - -const pledgeApiSchema = Joi.object({ - enabled: Joi.bool().strict().default(false), - url: Joi.string().allow('').default(''), - accessToken: Joi.string().strict().allow('').default('').when('url', { - is: Joi.string().strict().min(1), - then: Joi.string().strict().required(), - otherwise: Joi.string().strict().allow('') - }) -}) - -const discordHttpGateway = Joi.object({ - enabled: Joi.bool().strict().default(false), - redisUri: Joi.string().strict().allow('').default(''), - rabbitmqUri: Joi.string().strict().allow('').default('') -}) - -const apisSchema = Joi.object({ - pledge: pledgeApiSchema.default(pledgeApiSchema.validate({}).value), - discordHttpGateway: discordHttpGateway.default(discordHttpGateway.validate({}).value) -}) - -const schema = Joi.object({ - apis: apisSchema.default(apisSchema.validate({}).value), - dev: Joi.number().strict().greater(-1), - _vip: Joi.bool().strict(), - _vipRestricted: Joi.bool().strict().default(false), - _vipRefreshRateMinutes: Joi.number().strict(), - log: logSchema.default(logSchema.validate({}).value), - bot: botSchema.default(botSchema.validate({}).value), - database: databaseSchema.default(databaseSchema.validate({}).value), - feeds: feedsSchema.default(feedsSchema.validate({}).value), - advanced: advancedSchema.default(advancedSchema.validate({}).value), - webURL: Joi.string().strict().allow('').allow('').default(''), - discordSupportURL: Joi.string().uri().strict().allow(''), - disableFeedCycles: Joi.bool().strict().default(false) -}) - -module.exports = { - schemas: { - apis: apisSchema, - log: logSchema, - bot: botSchema, - database: databaseSchema, - feeds: feedsSchema, - advanced: advancedSchema, - config: schema - }, - defaults: schema.validate({}).value, - validate: config => { - const results = schema.validate(config, { - abortEarly: false - }) - if (results.error) { - const str = results.error.details - .map(d => d.message) - .join('\n') - - throw new TypeError(`Bot config validation failed\n\n${str}\n`) - } - } -} diff --git a/services/bot/src/util/config/validation/decode.js b/services/bot/src/util/config/validation/decode.js deleted file mode 100644 index d6e67ba7d..000000000 --- a/services/bot/src/util/config/validation/decode.js +++ /dev/null @@ -1,33 +0,0 @@ -const Joi = require('@hapi/joi') -const iconv = require('iconv-lite') - -const custom = Joi.extend(joi => { - return { - base: joi.object().pattern(/^/, joi.string()).default({}), - type: 'config', - messages: { - encoding: '{{#label}} has items with invalid encodings: {{#invalid}}' - }, - rules: { - encoding: { - validate (value, helpers, args, options) { - const invalids = new Set() - for (const url in value) { - const encoding = value[url] - if (!iconv.encodingExists(encoding)) { - invalids.add(encoding) - } - } - if (invalids.size > 0) { - return helpers.error('encoding', { - invalid: Array.from(invalids).join(',') - }) - } - return value - } - } - } - } -}) - -module.exports = custom diff --git a/services/bot/src/util/config/validation/locale.js b/services/bot/src/util/config/validation/locale.js deleted file mode 100644 index db365972c..000000000 --- a/services/bot/src/util/config/validation/locale.js +++ /dev/null @@ -1,31 +0,0 @@ -const fs = require('fs') -const path = require('path') -const Joi = require('@hapi/joi') -const fileList = fs.readdirSync(path.join(__dirname, '..', '..', '..', 'locales')) -const localesData = new Map() -for (const file of fileList) { - const read = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', '..', 'locales', file))) - localesData.set(file.replace('.json', ''), read) -} - -const custom = Joi.extend(joi => { - return { - base: joi.string().strict().default('en-US'), - type: 'config', - messages: { - locale: '{{#label}} needs to be a supported locale' - }, - rules: { - locale: { - validate (value, helpers, args, options) { - if (!localesData.has(value)) { - return helpers.error('locale') - } - return value - } - } - } - } -}) - -module.exports = custom diff --git a/services/bot/src/util/config/validation/timezone.js b/services/bot/src/util/config/validation/timezone.js deleted file mode 100644 index 99b334950..000000000 --- a/services/bot/src/util/config/validation/timezone.js +++ /dev/null @@ -1,24 +0,0 @@ -const Joi = require('@hapi/joi') -const moment = require('moment-timezone') - -const custom = Joi.extend(joi => { - return { - base: joi.string().strict().default('UTC'), - type: 'config', - messages: { - timezone: '{{#label}} needs to be a valid timezone' - }, - rules: { - timezone: { - validate (value, helpers, args, options) { - if (!moment.tz.zone(value)) { - return helpers.error('timezone') - } - return value - } - } - } - } -}) - -module.exports = custom diff --git a/services/bot/src/util/connectDatabase.js b/services/bot/src/util/connectDatabase.js deleted file mode 100644 index 035b4c747..000000000 --- a/services/bot/src/util/connectDatabase.js +++ /dev/null @@ -1,32 +0,0 @@ -const fs = require('fs') -const mongoose = require('mongoose') -const BUFFER_CONFIGS = ['sslCA', 'sslCRL', 'sslCert', 'sslKey'] - -function readBuffers (connectionSettings) { - const buffers = {} - for (const name of BUFFER_CONFIGS) { - if (connectionSettings[name]) { - buffers[name] = fs.readFileSync(connectionSettings[name]) - } - } - return buffers -} - -module.exports = async (uri, userOptions) => { - const connectionSettings = userOptions || {} - let buffers = {} - if (Object.keys(connectionSettings).length > 0) { - buffers = readBuffers(connectionSettings) - } - const options = { - useCreateIndex: true, - useFindAndModify: false, - useUnifiedTopology: true, - useNewUrlParser: true, - socketTimeoutMS: 90000, - ...userOptions, - ...connectionSettings, - ...buffers - } - return mongoose.createConnection(uri, options) -} diff --git a/services/bot/src/util/database.js b/services/bot/src/util/database.js deleted file mode 100644 index c2c08860d..000000000 --- a/services/bot/src/util/database.js +++ /dev/null @@ -1,213 +0,0 @@ -const Article = require('../models/Article.js') -const LinkLogic = require('../structs/LinkLogic.js') - -/** - * @param {Object} article - * @param {string[]} properties - * @param {string} useIDType - * @param {Object} meta - */ -function formatArticleForDatabase (article, properties, meta) { - const propertyValues = {} - for (const property of properties) { - const value = LinkLogic.getArticleProperty(article, property) - if (value && typeof value === 'string') { - propertyValues[property] = value - } - } - return { - id: article._id, - feedURL: meta.feedURL, - scheduleName: meta.scheduleName, - properties: propertyValues - } -} - -/** - * @param {Object} article - * @param {Object} document - * @param {string[]} properties - Feed properties - */ -function updatedDocumentForDatabase (article, document, properties) { - const docProperties = document.properties - let updated = false - for (const property of properties) { - const articleValue = LinkLogic.getArticleProperty(article, property) - if (!articleValue || typeof articleValue !== 'string') { - continue - } - const docValue = docProperties[property] - if (!docValue || docValue !== articleValue) { - docProperties[property] = articleValue - updated = true - } - } - const pruned = module.exports.prunedDocumentForDatabase(document, properties) - return updated || pruned -} - -/** - * @param {Object} document - * @param {string[]} properties - */ -function prunedDocumentForDatabase (document, properties) { - let updated = false - const docProperties = document.properties - for (const property in docProperties) { - if (!properties.includes(property)) { - delete docProperties[property] - updated = true - } - } - return updated -} - -/** - * @param {Object[]} articleList - * @param {Object[]} dbDocs - * @param {string[]} properties - */ -function getInsertsAndUpdates (articleList, dbDocs, properties, meta) { - if (!meta.feedURL) { - throw new Error('Missing feedURL for database insert/update') - } - if (!meta.scheduleName) { - throw new Error('Missing scheduleName for database insert/update') - } - const dbIDs = new Set() - for (var i = dbDocs.length - 1; i >= 0; --i) { - dbIDs.add(dbDocs[i].id) - } - const toInsert = [] - const toUpdate = [] - // Insert - for (const article of articleList) { - const articleID = article._id - if (!articleID) { - continue - } - if (!dbIDs.has(articleID)) { - toInsert.push(module.exports.formatArticleForDatabase(article, properties, meta)) - } - } - // Update - for (var j = dbDocs.length - 1; j >= 0; --j) { - const doc = dbDocs[j] - const article = articleList.find(a => a._id === doc.id) - let updated = false - if (article) { - updated = module.exports.updatedDocumentForDatabase(article, doc, properties) - } else { - updated = module.exports.prunedDocumentForDatabase(doc, properties) - } - if (updated) { - toUpdate.push(doc) - } - } - return { - toInsert, - toUpdate - } -} - -/** - * @param {Object[]} documents - * @param {Object[]} memoryCollection - */ -async function insertDocuments (documents, memoryCollection) { - if (documents.length === 0) { - return - } - if (memoryCollection) { - documents.forEach(doc => memoryCollection.push({ ...doc })) - } else { - const Model = Article.Model - const insert = [] - for (var i = documents.length - 1; i >= 0; --i) { - insert.push(new Model(documents[i])) - } - await Model.insertMany(insert) - } -} - -/** - * @param {Object[]} documents - * @param {Object[]} memoryCollection - */ -async function updateDocuments (documents, memoryCollection) { - if (documents.length === 0) { - return - } - if (memoryCollection) { - const updatedDocsByID = {} - for (const doc of documents) { - updatedDocsByID[doc.id] = doc - } - for (let i = 0; i < memoryCollection.length; ++i) { - const id = memoryCollection[i].id - const updatedDoc = updatedDocsByID[id] - if (updatedDoc) { - memoryCollection[i] = updatedDoc - } - } - } else { - const promises = [] - for (var i = documents.length - 1; i >= 0; --i) { - const doc = documents[i] - promises.push(Article.Model.updateOne({ - _id: doc._id - }, { - $set: doc - }).exec()) - } - await Promise.all(promises) - } -} - -/** - * @param {Object[]} documents - */ -async function mapArticleDocumentsToURL (documents) { - /** @type {Object[]>} */ - const map = {} - for (var i = documents.length - 1; i >= 0; --i) { - const article = documents[i] - const feedURL = article.feedURL - if (!map[feedURL]) { - map[feedURL] = [article] - } else { - map[feedURL].push(article) - } - } - return map -} - -/** - * @param {string} scheduleName - * @param {Object[]>} memoryCollection - * @param {string[]} urls - */ -async function getAllDocuments (scheduleName, memoryCollection, urls) { - if (memoryCollection) { - return memoryCollection - } else { - const documents = await Article.Model.find({ - scheduleName, - feedURL: { - $in: urls - } - }).lean().exec() - return module.exports.mapArticleDocumentsToURL(documents) - } -} - -module.exports = { - mapArticleDocumentsToURL, - updatedDocumentForDatabase, - prunedDocumentForDatabase, - formatArticleForDatabase, - getAllDocuments, - getInsertsAndUpdates, - insertDocuments, - updateDocuments -} diff --git a/services/bot/src/util/devLevels.js b/services/bot/src/util/devLevels.js deleted file mode 100644 index e63813df9..000000000 --- a/services/bot/src/util/devLevels.js +++ /dev/null @@ -1,30 +0,0 @@ -const getConfig = require('../config.js').get - -/** - * 1 = disable commands and message sending, heapdump - * 2 = disable message listener - * 3 = disable cycle database usage - * 4 = disable cycles - */ - -/** - * @returns {number} - */ -exports.getDevLevel = (config) => { - if (!config) { - config = getConfig() - } - return config.dev -} - -exports.devMinimum = (level) => (config) => { - const devLevel = exports.getDevLevel(config) - return devLevel >= level -} - -exports.dumpHeap = exports.devMinimum(1) -exports.disableCommands = exports.devMinimum(1) -exports.disableOutgoingMessages = exports.devMinimum(1) -exports.disableMessageListener = exports.devMinimum(2) -exports.disableCycleDatabase = exports.devMinimum(3) -exports.disableCycles = exports.devMinimum(4) diff --git a/services/bot/src/util/dumpHeap.js b/services/bot/src/util/dumpHeap.js deleted file mode 100644 index 75958ec79..000000000 --- a/services/bot/src/util/dumpHeap.js +++ /dev/null @@ -1,19 +0,0 @@ -const fs = require('fs') -const createLogger = require('./logger/create.js') - -function dumpHeap (prefix) { - const log = createLogger() - try { - const heapdump = require('heapdump') - if (!fs.existsSync('./settings/heapdump')) { - fs.mkdirSync('./settings/heapdump') - } - const filename = `./settings/heapdump/${prefix}-${Date.now()}.heapsnapshot` - heapdump.writeSnapshot(filename) - log.info(`Dumped heap at ${filename}`) - } catch (err) { - log.error(err, 'Failed to dump heap') - } -} - -module.exports = dumpHeap diff --git a/services/bot/src/util/ipc.js b/services/bot/src/util/ipc.js deleted file mode 100644 index 725eaae28..000000000 --- a/services/bot/src/util/ipc.js +++ /dev/null @@ -1,78 +0,0 @@ -class IPC { - /** - * This should only be used in child processes. - * @param {string} type - * @param {Object} data - * @param {boolean} _loopback - */ - static send (type, data, _loopback = false) { - process.send({ - _drss: true, - _loopback, - type, - data - }) - } - - /** - * If the message is valid for this bot - * @param {Object} message - * @returns {boolean} - */ - static isValid (message) { - return message._drss === true - } - - /** - * If the message loops back to the clients - * @param {Object} message - * @returns {boolean} - */ - static isLoopback (message) { - return message._loopback === true - } - - /** - * Send a message to a channel that could be on any client - * @param {string} channel - Channel ID - * @param {string} message - Message to send - */ - static sendChannelAlert (channel, message) { - // this.send(this.TYPES.SEND_CHANNEL_MESSAGE, { - // channel, - // message, - // alert: true - // }, true) - } - - /** - * Send a message to users of a channel that could be on any client - * @param {string} channel - Channel ID of the guild - * @param {string} message - Message to send - */ - static sendUserAlert (channel, message) { - // this.send(this.TYPES.SEND_USER_MESSAGE, { - // channel, - // message - // }, true) - } - - static get TYPES () { - return { - KILL: 'kill', - SHARD_READY: 'shardReady', - INIT_COMPLETE: 'initComplete', - SCHEDULE_COMPLETE: 'scheduleComplete', - START_INIT: 'startInit', - FINISHED_INIT: 'finishedInit', - RUN_SCHEDULE: 'runSchedule', - SHARD_STOPPED: 'shardStopped', - SEND_USER_ALERT: 'sendUserAlert', - NEW_ARTICLE: 'newArticle', - ADD_DEBUG_FEEDID: 'addDebugFeedID', - REMOVE_DEBUG_FEEDID: 'removeDebugFeedID' - } - } -} - -module.exports = IPC diff --git a/services/bot/src/util/listeners.js b/services/bot/src/util/listeners.js deleted file mode 100644 index da34da3e8..000000000 --- a/services/bot/src/util/listeners.js +++ /dev/null @@ -1,26 +0,0 @@ -const fs = require('fs') -const path = require('path') -const Command = require('../structs/Command.js') -const devLevels = require('./devLevels.js') -const eventHandlers = [] - -exports.createManagers = (bot) => { - const fileNames = fs.readdirSync(path.join(__dirname, '..', 'events')) - for (const fileName of fileNames) { - const eventName = fileName.replace('.js', '') - if (eventName === 'message' && devLevels.disableMessageListener()) { - continue - } - const eventHandler = require(`../events/${fileName}`) - eventHandlers.push({ name: eventName, func: eventHandler }) - bot.on(eventName, eventHandler) - } -} - -exports.disableAll = (bot) => { - for (const eventHandler of eventHandlers) { - bot.removeListener(eventHandler.name, eventHandler.func) - } - eventHandlers.length = 0 - Command.disable() -} diff --git a/services/bot/src/util/logger/create.js b/services/bot/src/util/logger/create.js deleted file mode 100644 index 418b46cd2..000000000 --- a/services/bot/src/util/logger/create.js +++ /dev/null @@ -1,44 +0,0 @@ -const pino = require('pino') -const serializers = require('./serializers.js') -const getConfig = require('../../config.js').get - -function createLogger (tag = '-', base = {}) { - const config = getConfig() - const prettyPrint = { - translateTime: 'SYS:standard', - messageFormat: '[{tag}] \x1b[0m{msg}', - ignore: 'hostname,tag' - } - - const pinoConfig = { - base: { - tag: String(tag), - ...base - }, - customLevels: { - owner: 35 - }, - prettyPrint, - serializers: { - guild: serializers.guild, - channel: serializers.channel, - role: serializers.channel, - user: serializers.user, - message: serializers.message, - feed: serializers.feed, - error: pino.stdSerializers.err - }, - enabled: process.env.NODE_ENV !== 'test' - } - - let destination - if (pinoConfig.enabled) { - destination = config.log.destination || undefined - pinoConfig.level = config.log.level - pinoConfig.prettyPrint = !destination ? prettyPrint : false - } - - return pino(pinoConfig, pino.destination(destination)) -} - -module.exports = createLogger diff --git a/services/bot/src/util/logger/serializers.js b/services/bot/src/util/logger/serializers.js deleted file mode 100644 index 8ce0b52f2..000000000 --- a/services/bot/src/util/logger/serializers.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @param {import('discord.js').Guild} guild - */ -function guild (guild) { - if (!guild) { - return guild - } - return `${guild.id}, ${guild.name}` -} - -/** -* @param {import('discord.js').TextChannel} channel -*/ -function channel (channel) { - if (!channel) { - return channel - } - return `${channel.id}, ${channel.name}` -} - -/** -* @param {import('discord.js').User} user -*/ -function user (user) { - if (!user) { - return user - } - return `${user.id}, ${user.username}` -} - -/** - * @param {import('discord.js').Message} message - */ -function message (message) { - if (!message) { - return message - } - return `${message.content}` -} - -/** - * @param {import('../../structs/db/Feed')} feed - */ -function feed (feed) { - if (!feed) { - return feed - } - return `${feed._id}` -} - -module.exports = { - guild, - channel, - user, - message, - feed -} diff --git a/services/bot/src/util/pascalToSnake.js b/services/bot/src/util/pascalToSnake.js deleted file mode 100644 index 768d652bf..000000000 --- a/services/bot/src/util/pascalToSnake.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @param {string} pascal - */ -function pascalToSnake (pascal) { - const replaced = pascal.replace(/[A-Z]/g, substr => { - return `_${substr.toLowerCase()}` - }) - // Remove the underscore at the beginning of the string - return replaced.slice(1, replaced.length) -} - -module.exports = pascalToSnake diff --git a/services/bot/src/util/processor.js b/services/bot/src/util/processor.js deleted file mode 100644 index 66d916fdf..000000000 --- a/services/bot/src/util/processor.js +++ /dev/null @@ -1,215 +0,0 @@ -const connectDb = require('../util/connectDatabase.js') -const createLogger = require('./logger/create.js') -const FeedFetcher = require('../util/FeedFetcher.js') -const RequestError = require('../structs/errors/RequestError.js') -const FeedParserError = require('../structs/errors/FeedParserError.js') -const LinkLogic = require('../structs/LinkLogic.js') -const initialize = require('../initialization/index.js') -const databaseFuncs = require('../util/database.js') -const devLevels = require('./devLevels.js') -const setConfig = require('../config.js').set - -async function fetchFeed (headers, url, runId, urlLog) { - const fetchOptions = {} - if (headers) { - if (!headers.lastModified || !headers.etag) { - throw new Error(`Headers exist for a link, but missing lastModified and etag (${url})`) - } - fetchOptions.headers = { - 'If-Modified-Since': headers.lastModified, - 'If-None-Match': headers.etag - } - } - const { stream, response } = await FeedFetcher.fetchURL(url, fetchOptions) - if (response.status === 304) { - return null - } else { - const lastModified = response.headers['last-modified'] - const etag = response.headers.etag - - if (lastModified && etag) { - process.send({ - status: 'headers', - link: url, - lastModified, - etag, - runId - }) - urlLog('Sending back headers') - } - return { - stream, - response - } - } -} - -async function parseStream (stream, charset, url, urlLog) { - const { articleList } = await FeedFetcher.parseStream(stream, url, charset) - return articleList -} - -async function syncDatabase (articleList, databaseDocs, feeds, meta, isDatabaseless) { - const allComparisons = new Set() - for (const feedID in feeds) { - const feed = feeds[feedID] - feed.ncomparisons.forEach(v => allComparisons.add(v)) - feed.pcomparisons.forEach(v => allComparisons.add(v)) - } - const { - toInsert, - toUpdate - } = await databaseFuncs.getInsertsAndUpdates( - articleList, - databaseDocs, - Array.from(allComparisons), - meta - ) - - const memoryCollection = isDatabaseless ? databaseDocs : undefined - await databaseFuncs.insertDocuments(toInsert, memoryCollection) - await databaseFuncs.updateDocuments(toUpdate, memoryCollection) -} - -/** - * @param {import('../structs/NewArticle.js')[]} newArticles - * @param {import('pino').Logger} log - */ -async function sendArticles (newArticles, runId) { - const len = newArticles.length - for (var i = 0; i < len; ++i) { - const newArticle = newArticles[i] - process.send({ - status: 'newArticle', - newArticle: newArticle.toJSON(), - runId - }) - } -} - -async function getFeed (data, log) { - const { link, rssList, headers, toDebug, docs, memoryCollections, scheduleName, runNum, config, testRun, runId } = data - const isDatabaseless = !!memoryCollections - const debugLogger = log.child({ - url: link - }) - const urlLog = toDebug ? debugLogger.info.bind(debugLogger) : () => {} - urlLog('Isolated processor received in batch') - - let articleList - try { - // Request URL - urlLog('Requesting url') - const fetchData = await fetchFeed(headers[link], link, runId, urlLog) - if (!fetchData) { - urlLog('304 response, sending success') - process.send({ status: 'connected', runId }) - process.send({ status: 'success', link, runId }) - return - } - // Parse feed - const { stream, response } = fetchData - const charset = FeedFetcher.getCharsetFromResponse(response) - urlLog(`Parsing stream with ${charset} charset`) - articleList = await parseStream(stream, charset, link, urlLog) - process.send({ status: 'connected', runId }) - if (articleList.length === 0) { - urlLog('No articles found, sending success') - process.send({ status: 'success', link, runId }) - return - } - } catch (err) { - if (!(err instanceof RequestError) && !(err instanceof FeedParserError)) { - log.error(err, 'URL connection') - } else if (config.log.linkErrs) { - log.warn({ error: err }, `Skipping ${link}`) - } - urlLog({ error: err }, 'Sending failed status during connection') - process.send({ status: 'connected', runId }) - process.send({ status: 'failed', link, rssList, reason: err.message, runId }) - return - } - - if (testRun || devLevels.disableCycleDatabase(config)) { - return process.send({ - status: 'success', - link, - runId - }) - } - - // Go through articles - try { - /** - * Run the logic to get any new articles before syncDatabase modifies - * databaseless memory collections in-place - * - * Any new n/p comparisons are also delayed by 1 cycle since docs - * are fetched before getFeed (before they're updated below this) - */ - const logic = new LinkLogic({ articleList, ...data }) - const result = await logic.run(docs) - /** - * @type {import('../structs/NewArticle.js')[]} - */ - const newArticles = result.newArticles - - /** - * Then sync the database - */ - const meta = { - feedURL: link, - scheduleName - } - await syncDatabase(articleList, docs, rssList, meta, isDatabaseless) - - /** - * Then finally send new articles to prevent spam if sync fails - */ - if (runNum !== 0 || config.feeds.sendFirstCycle === true) { - urlLog(`${newArticles.length} new articles found`) - await sendArticles(newArticles, runId) - } - - process.send({ - status: 'success', - link, - memoryCollection: isDatabaseless ? docs : undefined, - runId - }) - } catch (err) { - log.error(err, `Cycle logic for ${link}`) - process.send({ status: 'failed', link, rssList, reason: err.message, runId }) - } -} - -async function connectToDatabase (config) { - if (!config.database.uri.startsWith('mongo')) { - return - } - const connection = await connectDb(config.database.uri, config.database.connection) - await initialize.setupModels(connection) -} - -process.on('message', async m => { - const currentBatch = m.currentBatch - const { debugURLs, scheduleName, memoryCollections, config } = m - const logMarker = scheduleName - const log = createLogger(logMarker) - const urls = Object.keys(currentBatch) - setConfig(config) - try { - await connectToDatabase(config) - const articleDocuments = await databaseFuncs.getAllDocuments(scheduleName, memoryCollections, urls) - const promises = [] - for (var link in currentBatch) { - const docs = articleDocuments[link] || [] - const rssList = currentBatch[link] - const toDebug = debugURLs.includes(link) - promises.push(getFeed({ ...m, link, toDebug, rssList, docs }, log)) - } - await Promise.all(promises) - } catch (err) { - log.error(err, 'processor') - } -}) diff --git a/services/feed-requests/src/cache-storage/cache-storage.service.ts b/services/feed-requests/src/cache-storage/cache-storage.service.ts index 4c82aa20c..b4bdc146b 100644 --- a/services/feed-requests/src/cache-storage/cache-storage.service.ts +++ b/services/feed-requests/src/cache-storage/cache-storage.service.ts @@ -17,18 +17,6 @@ export class CacheStorageService { return `feed-requests:${key}`; } - async get(key: string): Promise { - try { - return await this.redisClient.get(this.generateKey(key)); - } catch (err) { - logger.error(`Failed to get content from cache storage`, { - err: (err as Error).stack, - }); - - return null; - } - } - async set({ key, body,