Compare commits

...
137 Commits
Author SHA1 Message Date
TrèsAbhi bb1c0c7625 Merge pull request #49 from tresabhi/1.15.2 2023-11-19 14:05:46 -06:00
TrèsAbhi 1170676141 feat: changelogs 2023-11-19 14:05:39 -06:00
TrèsAbhi 7baaaa9db9 fix: remove seeding player neighbor checking 2023-11-19 14:02:00 -06:00
TrèsAbhi c1ecef0179 fix: add max target position 2023-11-19 13:55:11 -06:00
TrèsAbhi 032d98e6bd fix: version number 2023-11-16 11:53:13 -06:00
TrèsAbhi f4a90cd531 Merge pull request #48 from tresabhi/1.15.1 2023-11-16 11:51:58 -06:00
TrèsAbhi 82f03583fe feat: updated docs with latest info 2023-11-16 11:51:10 -06:00
TrèsAbhi 81c1a50052 fix: guide link 2023-11-16 11:47:32 -06:00
TrèsAbhi c7207799e2 feat: changelogs 2023-11-16 11:45:44 -06:00
TrèsAbhi e0a719ee93 fix: patient JSON parsing 2023-11-16 11:41:52 -06:00
TrèsAbhi ee6d2e7d82 feat: remove neighbor relative positional caching 2023-11-16 11:14:38 -06:00
TrèsAbhi 14710b5218 fix: use non-caching apis for ratings command 2023-11-16 11:13:51 -06:00
TrèsAbhi 4b5e0d1c1c feat: american neighbor spelling 2023-11-16 10:58:19 -06:00
TrèsAbhi 9968e8346c Merge pull request #47 from tresabhi/1.15.0 2023-11-15 19:54:03 -06:00
TrèsAbhi bc4da17f10 feat: changelogs 2023-11-15 19:53:34 -06:00
TrèsAbhi da56c563fc feat: more advanced PSAs 2023-11-15 19:30:03 -06:00
TrèsAbhi 95f4dbf6a0 fix: missing season 2023-11-15 19:23:18 -06:00
TrèsAbhi 6c36f343ea fix: current season score jumping 2023-11-15 19:21:08 -06:00
TrèsAbhi 9257ebdd1e feat: skeletons 2023-11-15 16:42:56 -06:00
TrèsAbhi 8f1017195f fix: league jumping for current season 2023-11-15 16:42:52 -06:00
TrèsAbhi 20da294b36 feat: empty no-rewards 2023-11-15 12:18:32 -06:00
TrèsAbhi a63415461a feat: non-loaded page blocking 2023-11-15 12:16:49 -06:00
TrèsAbhi 2bb4c78551 fix: smart neighbor count for next page caching 2023-11-15 12:00:57 -06:00
TrèsAbhi 2909e61721 feat: middle-most caching 2023-11-14 21:13:09 -06:00
TrèsAbhi 171da2a194 fix: no search caching 2023-11-14 21:00:54 -06:00
TrèsAbhi 933c468e2d feat: split id and score 2023-11-14 20:56:27 -06:00
TrèsAbhi 0d1eb9b12e feat: better reward handling 2023-11-14 20:12:34 -06:00
TrèsAbhi 02a03b4d68 feat: advanced PSAs 2023-11-14 13:42:15 -06:00
TrèsAbhi ef9f3bd361 feat: rewards 2023-11-14 13:07:17 -06:00
TrèsAbhi f18b6ee754 feat: radix table 2023-11-14 12:40:49 -06:00
TrèsAbhi deb9e463e1 feat: highlighted player stats 2023-11-14 11:24:38 -06:00
TrèsAbhi c799534009 feat: position relative neighbor caching 2023-11-14 10:42:38 -06:00
TrèsAbhi aea01072fb fix: fetch neighbors after redundancy check 2023-11-14 10:37:09 -06:00
TrèsAbhi ca8dd5a702 fix: no existing cache overwrites for neighbors 2023-11-14 10:29:57 -06:00
TrèsAbhi e79c4a1e3f fix: no cache neighbors 2023-11-14 10:24:10 -06:00
TrèsAbhi e19d0d948a fix: removed ratings info caching 2023-11-14 07:34:47 -06:00
TrèsAbhi a160ccd095 feat: reintroduced overwriting 2023-11-13 23:06:43 -06:00
TrèsAbhi d2d0477186 fix: remove number fetcher 2023-11-13 23:03:28 -06:00
TrèsAbhi c742c0725d feat: removed swr caching 2023-11-13 23:00:57 -06:00
TrèsAbhi 9e6343a111 feat: remove overwrite avoidance 2023-11-13 22:47:17 -06:00
TrèsAbhi faf33b5f2d fix: punctuation 2023-11-13 22:27:26 -06:00
TrèsAbhi 8a78ac7d82 feat: removed /today command 2023-11-13 22:20:09 -06:00
TrèsAbhi d7c7814b3a feat: credits command 2023-11-13 22:19:49 -06:00
TrèsAbhi 15bf02f829 feat: CORS proxy for neighbors 2023-11-13 21:52:21 -06:00
TrèsAbhi 4f62a594b3 feat: button page turning exception 2023-11-13 21:41:37 -06:00
TrèsAbhi 64253d0120 feat: page turning in current season 2023-11-13 21:31:36 -06:00
TrèsAbhi 286936131d feat: simpler welcome message 2023-11-13 21:16:45 -06:00
TrèsAbhi 95588ef6b6 feat: patient fetching 2023-11-13 21:14:56 -06:00
TrèsAbhi daef04ccd8 feat: lowered retry time to 1 second 2023-11-13 13:15:55 -06:00
TrèsAbhi 86e385c548 feat: rewrote jump to score for current season 2023-11-13 12:52:39 -06:00
TrèsAbhi cc193b99d6 fix: bug fixes 2023-11-13 12:27:06 -06:00
TrèsAbhi c026007937 fix: first page caching 2023-11-11 23:07:13 -06:00
TrèsAbhi dbc117a9d4 fix: archived jump to score 2023-11-11 22:20:36 -06:00
TrèsAbhi 34665bd97a fix: fixed archived search results 2023-11-11 22:10:32 -06:00
TrèsAbhi 6d05e607b3 feat: fixed rows per page 2023-11-11 21:42:26 -06:00
TrèsAbhi 14bfc3018b feat: proxy APIs 2023-11-11 21:37:59 -06:00
TrèsAbhi 7e4f195f94 feat: optimized highlighted players 2023-11-11 20:59:48 -06:00
TrèsAbhi 4b6de934a5 feat: current season already cached checking 2023-11-11 20:51:48 -06:00
TrèsAbhi 14fbb595fd fix: better loading screen 2023-11-08 19:38:59 -06:00
TrèsAbhi 18a5fb80d0 feat: more code polish 2023-11-08 19:28:22 -06:00
TrèsAbhi 551ef79f03 feat: jump to position for current season 2023-11-08 17:50:00 -06:00
TrèsAbhi 81aec6bad2 feat: search players for current season 2023-11-08 12:17:08 -06:00
TrèsAbhi d08949c597 feat: jump to league for current season 2023-11-08 11:16:51 -06:00
TrèsAbhi 212aee0373 feat: current season traversal 2023-11-07 20:47:20 -06:00
TrèsAbhi 059b78c9fb feat: page turner disabling buttons 2023-11-07 19:22:28 -06:00
TrèsAbhi 11ba5fc28e feat: more robust deleted player handling 2023-11-07 19:15:41 -06:00
TrèsAbhi fbde9ec64f fix: no double fetch and caching 2023-11-07 18:58:19 -06:00
TrèsAbhi 6600bba58c feat: better layout 2023-11-07 17:33:41 -06:00
TrèsAbhi 9e340d2e16 fix: deleted clan handling 2023-11-07 17:24:51 -06:00
TrèsAbhi 6f784e9e94 feat: remove clan apis 2023-11-07 17:24:36 -06:00
TrèsAbhi 62f6aabf05 feat: better jump buttons 2023-11-07 11:55:27 -06:00
TrèsAbhi 46457a8bdc feat: jump to score 2023-11-07 11:50:51 -06:00
TrèsAbhi 1081e836f5 fix: region based player search 2023-11-07 11:40:46 -06:00
TrèsAbhi dfe1e37592 feat: public blitzkrieg-db 2023-11-07 11:36:08 -06:00
TrèsAbhi 0b9db21c10 feat: deleted user handling 2023-11-07 11:13:30 -06:00
TrèsAbhi 57527115d4 feat: jump to league 2023-11-07 11:02:11 -06:00
TrèsAbhi 8d2679c458 feat: better page turner 2023-11-07 10:50:41 -06:00
TrèsAbhi ca00f18a6a feat: jump to position 2023-11-07 10:47:28 -06:00
TrèsAbhi 466f68701f feat: archived player lookup 2023-11-07 10:32:01 -06:00
TrèsAbhi 1bb83da253 feat: per-page theming 2023-11-07 10:06:15 -06:00
TrèsAbhi bd1e17bac9 feat: tankopedia extractor 2023-11-06 23:02:26 -06:00
TrèsAbhi 322f95e42e feat: rows per page 2023-11-02 11:14:13 -05:00
TrèsAbhi 3ea77f5899 feat: autocomplete size limits 2023-11-01 11:45:19 -05:00
TrèsAbhi 882e61ff44 feat: isolated first archived season number 2023-11-01 11:27:41 -05:00
TrèsAbhi d991be4934 fix: user error now results in user friendly message 2023-11-01 11:21:30 -05:00
TrèsAbhi dfba8bd15f feat: better blitz fetch error management 2023-10-31 15:32:35 -05:00
TrèsAbhi 87e23673fb feat: clan cache 2023-10-31 15:31:03 -05:00
TrèsAbhi 8fbd1acf4a fix: set region 2023-10-31 15:24:00 -05:00
TrèsAbhi d7dfbaa799 feat: username display 2023-10-31 15:20:37 -05:00
TrèsAbhi 04ce20857e feat: no cap 2023-10-31 13:32:08 -05:00
TrèsAbhi 4550246d63 feat: players rendering 2023-10-31 13:30:47 -05:00
TrèsAbhi dd673f44ae feat: use swr 2023-10-31 13:15:28 -05:00
TrèsAbhi 3245284ecc feat: website apis 2023-10-31 12:58:24 -05:00
TrèsAbhi 3d9872a671 feat: patient ratings leaderboard building 2023-10-31 12:41:27 -05:00
TrèsAbhi f02db2a2d8 feat: data driven latest archived season number 2023-10-31 12:35:32 -05:00
TrèsAbhi d1ebb79432 feat: simpler non-user error messaging 2023-10-31 11:56:06 -05:00
TrèsAbhi 44f03a1fce fix: cors url 2023-10-31 11:43:28 -05:00
TrèsAbhi 6b6ee94d9b feat: use corsproxy.io 2023-10-31 11:41:04 -05:00
TrèsAbhi 81c2ca80bc feat: better ratings toolbar 2023-10-30 21:57:22 -05:00
TrèsAbhi c83cec8ed7 feat: simpler descs 2023-10-30 21:56:01 -05:00
TrèsAbhi 93ffe5c507 feat: options for ratings tool 2023-10-29 13:40:32 -05:00
TrèsAbhi 8cd1a70333 feat: isolated tankopediaInfo 2023-10-29 11:54:23 -05:00
TrèsAbhi 06ae024c2c Merge pull request #46 from tresabhi/1.14.4 2023-10-25 10:29:24 -05:00
TrèsAbhi ce8fd0f22f feat: package version 2023-10-25 10:28:07 -05:00
TrèsAbhi 7dc1aff1dc feat: changelogs 2023-10-25 10:27:56 -05:00
TrèsAbhi a60003127f feat: instructions for streamers 2023-10-25 10:26:35 -05:00
TrèsAbhi 7d1ba58806 feat: updated hero logo 2023-10-20 11:31:59 -05:00
TrèsAbhi 963d71e221 Merge pull request #38 from tresabhi/imgbot 2023-10-20 11:29:08 -05:00
TrèsAbhi 2a804fda1c Merge pull request #44 from tresabhi/dependabot/npm_and_yarn/babel/traverse-7.23.2 2023-10-20 11:28:46 -05:00
TrèsAbhi 597a1a04d2 Merge pull request #45 from tresabhi/1.14.3 2023-10-20 11:26:24 -05:00
TrèsAbhi aec6f49ea1 Update 1.14.2.md 2023-10-20 11:26:13 -05:00
TrèsAbhi e1ad710de5 fix: corrected version number 2023-10-20 11:26:03 -05:00
TrèsAbhi edc8447617 feat: changelogs 2023-10-20 11:24:19 -05:00
TrèsAbhi f726d4f8e8 feat: don't manage queue after task completion 2023-10-20 11:12:06 -05:00
TrèsAbhi 1f333f6b88 fix: manage queue after space available 2023-10-20 11:11:41 -05:00
TrèsAbhi 9b855fca47 fix: fetchBlitz return type 2023-10-20 11:08:22 -05:00
TrèsAbhi c5f433fdf4 feat: removed unnesesary type 2023-10-20 10:57:24 -05:00
TrèsAbhi fd61dae1c1 feat: added queues to fetchBlitz 2023-10-20 10:50:54 -05:00
TrèsAbhi 2c45f9759f fix: removed lie about username inference 2023-10-20 10:29:01 -05:00
TrèsAbhi 0d3261f748 feat: named asia region short name "ASIA" 2023-10-20 10:28:41 -05:00
TrèsAbhi d38e873800 feat: used short region names for username autocomplete 2023-10-20 10:28:20 -05:00
TrèsAbhi ca38914095 feat: chunked images in breakdown 2023-10-20 10:22:12 -05:00
TrèsAbhi ee5beeea45 fix: preserve image order 2023-10-20 10:12:01 -05:00
dependabot[bot] a9d40d9f49 build(deps): bump @babel/traverse from 7.22.20 to 7.23.2
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.20 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2023-10-18 13:18:13 +00:00
TrèsAbhi b8be916c18 Merge pull request #43 from tresabhi/1.14.2 2023-10-17 22:08:51 -05:00
TrèsAbhi 24d0313fd3 feat: changelogs 2023-10-17 22:08:39 -05:00
TrèsAbhi c3d2a81327 fix: generic stats name overflow 2023-10-17 22:06:50 -05:00
TrèsAbhi dfeb117663 fix: getClanInfo now uses clan_id 2023-10-17 22:04:27 -05:00
TrèsAbhi 495a830506 Merge pull request #42 from tresabhi/1.14.1 2023-10-17 21:14:13 -05:00
TrèsAbhi a730f67ca5 feat: changelogs 2023-10-17 21:14:30 -05:00
TrèsAbhi 9a3867770d feat: reduced render workers to 2 2023-10-17 21:01:26 -05:00
TrèsAbhi f8a66bb5e7 feat: simpler /link message 2023-10-17 20:59:33 -05:00
TrèsAbhi 4b00e1342e feat: user error discrimination 2023-10-17 20:58:36 -05:00
TrèsAbhi f9e0584f00 fix: memory leak in render threads 2023-10-17 20:45:45 -05:00
TrèsAbhi 900c290e75 Update cd-host.yml 2023-10-16 20:50:40 -05:00
TrèsAbhi 69e1577d18 fix: prisma copy over 2023-10-16 20:44:47 -05:00
ImgBotApp e3864b86a4 [ImgBot] Optimize images
*Total -- 21,554.66kb -> 14,287.64kb (33.71%)

/docs/public/assets/images/hero.png -- 8.27kb -> 4.13kb (50.07%)
/public/assets/banners/tankopedia.png -- 2,422.26kb -> 1,285.27kb (46.94%)
/public/assets/banners/profile.png -- 2,004.61kb -> 1,143.78kb (42.94%)
/public/assets/banners/inactive.png -- 2,675.88kb -> 1,700.96kb (36.43%)
/public/assets/banners/ratings.png -- 3,878.81kb -> 2,495.66kb (35.66%)
/public/assets/banners/more.png -- 8,407.57kb -> 5,532.24kb (34.2%)
/public/assets/banners/session.png -- 2,157.27kb -> 2,125.60kb (1.47%)

Signed-off-by: ImgBotApp <[email protected]>
2023-10-01 22:45:47 +00:00
98 changed files with 2936 additions and 641 deletions

No files matched your search

+1 -1
View File
@@ -29,4 +29,4 @@ jobs:
unzip bot.zip unzip bot.zip
- name: Run bot - name: Run bot
run: NODE_ENV=production GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} node --enable-source-maps index.cjs run: NODE_ENV=production GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} node --enable-source-maps index.cjs
+2 -1
View File
@@ -13,4 +13,5 @@ test.*
/docs/.vitepress/cache /docs/.vitepress/cache
servers.csv servers.csv
.next/ .next/
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
temp/
+5
View File
@@ -6,15 +6,19 @@
"blitzstars", "blitzstars",
"cleandist", "cleandist",
"cockroackdb", "cockroackdb",
"corsproxy",
"customperiod", "customperiod",
"deburr", "deburr",
"diacriticless", "diacriticless",
"Everytime", "Everytime",
"fullstats", "fullstats",
"fuzzysort", "fuzzysort",
"immer",
"kranvagn", "kranvagn",
"Leaderboard", "Leaderboard",
"leaderboards", "leaderboards",
"MURICA",
"Oopsy",
"outdir", "outdir",
"outfile", "outfile",
"ownedtanks", "ownedtanks",
@@ -35,6 +39,7 @@
"subnation", "subnation",
"tankaverages", "tankaverages",
"tankopedia", "tankopedia",
"tankopedias",
"tankstats", "tankstats",
"tresabhi", "tresabhi",
"Unicum", "Unicum",
+1 -1
View File
@@ -13,7 +13,7 @@ export default defineConfig({
themeConfig: { themeConfig: {
nav: [ nav: [
{ text: 'Home', link: '/' }, { text: 'Home', link: '/' },
{ text: 'Guide', link: '/guide/about' }, { text: 'Guide', link: '/guide/introduction' },
], ],
sidebar: [ sidebar: [
+17
View File
@@ -0,0 +1,17 @@
# Blitzkrieg 1.14.1
Oopsy daisy, a memory leak!
## Changes
- Simplified message for users to use `/link`
## Fixes
- Fixed a memory leak in the render thread manager
## Technical changes
- Added `UserError` extending `Error`
- This lets the bot avoid spamming the debug console for mistakes users make
- Reduced render threads to 2
+8
View File
@@ -0,0 +1,8 @@
# Blitzkrieg 1.14.2
A late night hot fix for my homies
## Fixes
- Clan info is now fetched using `clan_id` instead of `account_id`
- Generic stats row names are now bounded by a max width
+24
View File
@@ -0,0 +1,24 @@
# Blitzkrieg 1.14.3
Pull your brush out and start polishing.
## Changes
![HisRoyalFatness' career breakdown](https://i.imgur.com/GhgMTnS.png)
- Rows in `/breakdown` are now chunked into `8` per image
- This is true unless the number of pages exceeds `10` in which case the rows are even distributed across all `10` pages
- `10` is the maximum number of images that Discord allows
- Short region names are used in username autocomplete instead of their full names
- Example: "North America" is "NA" now
## Fixes
- Removed reference to username inference in the username option description
- Removed an unnecessary type `asd` that did absolutely nothing used nowhere
## Technical changes
- Image orders are now preserved even if processed in parallel and all completed out of order
- `fetchBlitz` is now throttled to `10` requests per second
- Request overflows are managed by queueing
+7
View File
@@ -0,0 +1,7 @@
# Blitzkrieg 1.14.4
Streamers get some knowledge today
## Changes
- Added a video in the embed button prompt
+72
View File
@@ -0,0 +1,72 @@
# Blitzkrieg 1.15.0
Ratings will never be the same.
## Features
![Blitzkrieg Ratings. Elevate your game. Keep it simple.](https://i.imgur.com/7PZVAGQ.png)
- Blitzkrieg ratings leaderboard
- Available at https://blitz-krieg.vercel.app/tools/ratings
- **Highly experimental; [please report bugs on Discord server](https://discord.gg/nDt7AjGJQH)**
- Current season leaderboards are live meaning that there may be scores and positions that are out of order
- Options
- Regions can be swapped between NA, EU, and Asia
- The current season and all archived season (from 49 and onwards) are available
- Jump: used to jump to certain players
- Score: finds the first player with at least your desired score
- Position: finds the player at that position
- League: finds the top-most player in that league
- Player: finds the player
- **Jumping to scores and positions for the current season is a compounding function (hence the loading screen) and is limited to the speed of Wargaming's APIs which is pretty darn slow**
- Page controls
- Flip between pages left and right
- 33 players are loaded at once
- Left control is disabled on the first page and vice versa
- Page turning controls are also disabled if the next/previous page hasn't loaded yet
- Page numbers can be manually typed in the text field too
- This is available both at the top and the bottom of the screen
- Players
- Shows the position of the player
- Shows the player nickname and clan name
- "Deleted player ID" us shown when loading a deleted player
- A skeleton is shown when the player and clan is loading
- Shows the player percentile
- Shows reward for the position
- This is blank when there is no reward
- This column isn't available for archived seasons
- Shows the score
- A skeleton is shown when loading
- Click on a row to highlight it
- Using a jump button also highlights a row
- `/credits`: thanks who helped made this project
## Changes
- Error messages that user face are now even more simplified
- Increased username and clan autocomplete size limits to `25`
- Added capability for per-page accent theming
- Example: the ratings page is red while the session tracker is blue
- Simplified welcome message for SKLLD clan server
## Technical Changes
- Isolated tankopedia info
- Implemented `corsproxy.io` for non-timeout tolerant endpoints
- Latest archived season number is now data driven
- Ratings archives are now public
- This eliminates the need of `octokit`
- Ratings leaderboard archiver is now error tolerant
- Fetch attempts are made every `1000ms`
- Blitz errors are now managed better freeing up the queue faster
- Isolated first archived season number
- Added more advanced PSAs
- These can now feature images or embeds
- These can now modify message contents
- These can also now be secret
## Experimental Changes
- Added tankopedia extractor
- Retrieves tankopedia data from the game files
- This is not utilized as of right now
+24
View File
@@ -0,0 +1,24 @@
# Blitzkrieg 1.15.1
'MURICA!
## Changes
- "Neighbor" is now spelled in the American way
- Updated documentation website with the latest information
## Fixes
- Fixed the guide link on the documentation website
## Technical features
- Added capability to fetch patiently while also parsing the JSON
## Technical changes
- Removed neighbor relative position caching
## Technical fixes
- `/ratings` now uses the non-caching APIs
+9
View File
@@ -0,0 +1,9 @@
# Blitzkrieg 1.15.2
Infinity reduced to a finite value.
## Fixes
- Added a maximum value to the jump to position button
- Removed neighbor checking for the seeding player
- This fixes the jump to position button not loading at all
-17
View File
@@ -1,17 +0,0 @@
# Installing Blitzkrieg
It's easy as 1, 2, 3!
## Step 1
Join a server that already has Blitzkrieg or [join our official Discord server](https://discord.gg/nDt7AjGJQH).
## Step 2
View the bot's profile and click "Add to Server".
<video src="https://i.imgur.com/Zq0P3m2.mp4" autoplay loop muted width=320 title="how to install"></video>
## Step 3
Review the permissions the bot is asking for and add it to your server!
+13 -1
View File
@@ -1,6 +1,10 @@
# Introduction # Introduction
Blitzkrieg is a multipurpose bot for anything and everything World of Tanks Blitz. 🎉 Services for everything and anything World of Tanks Blitz
## Bot
Blitzkrieg is available as a Discord bot and can be [installed very easily](/guide/invite).
- Track statistics - Track statistics
- Find inactive players in a clan - Find inactive players in a clan
@@ -8,3 +12,11 @@ Blitzkrieg is a multipurpose bot for anything and everything World of Tanks Blit
- Create embeds for streams - Create embeds for streams
- Track a player/tanks statistical evolution - Track a player/tanks statistical evolution
- ...and a variety of other similar things - ...and a variety of other similar things
## Website
Provides a bunch of very helpful tools; https://blitz-krieg.vercel.app/
- Session tracker
- Ratings leaderboard
- Way more to come
+2 -2
View File
@@ -12,10 +12,10 @@ hero:
actions: actions:
- theme: brand - theme: brand
text: Add to Discord Server text: Add to Discord Server
link: guide/install link: /guide/invite
- theme: alt - theme: alt
text: Guide text: Guide
link: /guide/about link: /guide/introduction
features: features:
- title: Clean and Efficient UI - title: Clean and Efficient UI
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 183 KiB

+1
View File
@@ -7,6 +7,7 @@ const nextConfig = {
...withVanillaExtract(), ...withVanillaExtract(),
distDir: 'dist/website', distDir: 'dist/website',
reactStrictMode: false,
}; };
export default nextConfig; export default nextConfig;
+7 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "blitzkrieg", "name": "blitzkrieg",
"version": "1.14.0", "version": "1.15.2",
"description": "🎉 Tools for everything World of Tanks Blitz", "description": "🎉 Tools for everything World of Tanks Blitz",
"main": "src/server.ts", "main": "src/server.ts",
"type": "module", "type": "module",
@@ -9,6 +9,7 @@
"build:website": "next build", "build:website": "next build",
"build:docs": "vitepress build docs", "build:docs": "vitepress build docs",
"build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard", "build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard",
"build:tankopedia": "vite-node --script scripts/buildTankopedia",
"dev:bot": "yarn build:bot && yarn start:bot", "dev:bot": "yarn build:bot && yarn start:bot",
"dev:docs": "vitepress dev docs", "dev:docs": "vitepress dev docs",
"dev:website": "next dev", "dev:website": "next dev",
@@ -33,12 +34,14 @@
"@resvg/resvg-js": "^2.4.1", "@resvg/resvg-js": "^2.4.1",
"@vanilla-extract/css": "^1.13.0", "@vanilla-extract/css": "^1.13.0",
"bepaint": "^0.1.0-beta.3", "bepaint": "^0.1.0-beta.3",
"decompress": "^4.2.1",
"discord.js": "^14.13.0", "discord.js": "^14.13.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"esbuild": "^0.19.3", "esbuild": "^0.19.3",
"express": "^4.18.2", "express": "^4.18.2",
"fuzzysort": "^2.0.4", "fuzzysort": "^2.0.4",
"immer": "^10.0.2", "immer": "^10.0.2",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-escape": "^2.0.0", "markdown-escape": "^2.0.0",
"next": "^13.5.2", "next": "^13.5.2",
@@ -49,12 +52,15 @@
"vite-node": "^0.34.5", "vite-node": "^0.34.5",
"vitepress": "^1.0.0-rc.20", "vitepress": "^1.0.0-rc.20",
"vitepress-plugin-auto-sidebar": "^1.1.0", "vitepress-plugin-auto-sidebar": "^1.1.0",
"xml-js": "^1.6.11",
"zustand": "^4.4.1" "zustand": "^4.4.1"
}, },
"devDependencies": { "devDependencies": {
"@total-typescript/ts-reset": "^0.5.1", "@total-typescript/ts-reset": "^0.5.1",
"@types/decompress": "^4.2.6",
"@types/escape-html": "^1.0.2", "@types/escape-html": "^1.0.2",
"@types/express": "^4.17.18", "@types/express": "^4.17.18",
"@types/js-yaml": "^4.0.8",
"@types/lodash": "^4.14.199", "@types/lodash": "^4.14.199",
"@types/markdown-escape": "^1.1.0", "@types/markdown-escape": "^1.1.0",
"@types/node": "^20.6.5", "@types/node": "^20.6.5",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 MiB

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

+36 -28
View File
@@ -1,36 +1,44 @@
import { build } from 'esbuild'; import { build } from 'esbuild';
import { readdir } from 'fs/promises'; import { cp, readdir } from 'fs/promises';
const FILES = [ readdir('src/workers').then((workers) => {
'bot', const files = [
'index', 'bot',
'index',
(await readdir('src/workers')).map( ...workers.map((file) => `workers/${file.replace('.ts', '')}`),
(file) => `workers/${file.replace('.ts', '')}`, ];
),
];
await build({ build({
entryPoints: FILES.map((file) => `src/${file}.ts`), entryPoints: files.map((file) => `src/${file}.ts`),
outdir: 'dist/bot', outdir: 'dist/bot',
platform: 'node', platform: 'node',
tsconfig: 'tsconfig.esbuild.json', tsconfig: 'tsconfig.esbuild.json',
loader: { loader: {
'.node': 'copy', '.node': 'copy',
'.ttf': 'file', '.ttf': 'file',
}, },
format: 'cjs', format: 'cjs',
jsx: 'transform', jsx: 'transform',
outExtension: { outExtension: {
'.js': '.cjs', '.js': '.cjs',
}, },
bundle: true, bundle: true,
sourcemap: true, sourcemap: true,
minifyIdentifiers: false, // cause goofy errors minifyIdentifiers: false, // causes goofy errors
minifySyntax: true, minifySyntax: true,
minifyWhitespace: true, minifyWhitespace: true,
logLevel: 'info', logLevel: 'info',
});
}); });
// copy schema.prisma
cp('prisma/schema.prisma', 'dist/bot/schema.prisma');
readdir('node_modules/prisma').then((files) =>
files
.filter((file) => file.endsWith('.node'))
.map((file) => cp(`node_modules/prisma/${file}`, `dist/bot/${file}`)),
);
+14 -17
View File
@@ -6,6 +6,7 @@ import {
RatingsNeighbors, RatingsNeighbors,
RatingsPlayer, RatingsPlayer,
} from '../src/commands/ratings'; } from '../src/commands/ratings';
import { patientFetchJSON } from '../src/core/blitzkrieg/patientFetchJSON';
import commitMultipleFiles from './commitMultipleFiles.js'; import commitMultipleFiles from './commitMultipleFiles.js';
/* /*
@@ -13,12 +14,13 @@ import commitMultipleFiles from './commitMultipleFiles.js';
* Central European Time (UTC+1): use 0 23 * * *. * Central European Time (UTC+1): use 0 23 * * *.
* Central Asia Standard Time (UTC+7): use 0 17 * * *. * Central Asia Standard Time (UTC+7): use 0 17 * * *.
* *
* na/ratings/49/info.json: season info like rewards, start & end time, etc. * [region]/ratings/[season]/info.json: season info like rewards, start & end time, etc.
* na/ratings/49/latest.json: the absolute latest leaderboard recorded at UTC+0 midnight * [region]/ratings/[season]/latest.json: the absolute latest leaderboard recorded at UTC+0 midnight
* na/ratings/49/midnight.json: last midnight's leaderboard relative to the region * [region]/ratings/[season]/midnight.json: last midnight's leaderboard relative to the region
*/ */
export type BlitzkriegRatingsLeaderboard = { id: number; score: number }[]; export type BlitzkriegRatingsLeaderboardEntry = { id: number; score: number };
export type BlitzkriegRatingsLeaderboard = BlitzkriegRatingsLeaderboardEntry[];
const publish = argv.includes('--publish'); const publish = argv.includes('--publish');
const latest = argv.includes('--latest'); const latest = argv.includes('--latest');
@@ -26,12 +28,10 @@ const server = argv.find((arg) => arg.startsWith('--region='))?.split('=')[1];
if (!server) throw new Error('Region parameter not specified'); if (!server) throw new Error('Region parameter not specified');
const NEIGHBORS = 1000; const NEIGHBORS = 1024;
const PLAYERS = await fetch( const PLAYERS = await patientFetchJSON<RatingsInfo>(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`,
) ).then((data) => (data.detail === undefined ? data.count : undefined));
.then((response) => response.json() as Promise<RatingsInfo>)
.then((data) => (data.detail === undefined ? data.count : undefined));
if (PLAYERS === undefined) { if (PLAYERS === undefined) {
throw new Error('No current ratings season running'); throw new Error('No current ratings season running');
@@ -42,9 +42,9 @@ const leaderboard: BlitzkriegRatingsLeaderboard = [];
let registered = 0; let registered = 0;
async function branchFromPlayer(id: number) { async function branchFromPlayer(id: number) {
const { neighbors } = await fetch( const { neighbors } = await patientFetchJSON<RatingsNeighbors>(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`, `https://${server}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`,
).then((response) => response.json() as Promise<RatingsNeighbors>); );
const firstPlayer = neighbors[0]; const firstPlayer = neighbors[0];
const lastPlayer = neighbors[neighbors.length - 1]; const lastPlayer = neighbors[neighbors.length - 1];
@@ -73,9 +73,9 @@ async function branchFromPlayer(id: number) {
} }
async function branchFromLeague(id: number) { async function branchFromLeague(id: number) {
const { result } = await fetch( const { result } = await patientFetchJSON<{ result: RatingsPlayer[] }>(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`, `https://${server}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`,
).then((response) => response.json() as Promise<{ result: RatingsPlayer[] }>); );
const firstPlayer = result[0]; const firstPlayer = result[0];
const lastPlayer = result[result.length - 1]; const lastPlayer = result[result.length - 1];
@@ -111,11 +111,8 @@ const leaderboardJSON = JSON.stringify(leaderboard);
if (publish) { if (publish) {
const octokit = new Octokit({ auth: env.GH_TOKEN }); const octokit = new Octokit({ auth: env.GH_TOKEN });
const info = await fetch( const info = await patientFetchJSON<RatingsInfo & { detail: undefined }>(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`,
).then(
(response) =>
response.json() as Promise<RatingsInfo & { detail: undefined }>,
); );
const normalizedServer = server === 'na' ? 'com' : server; const normalizedServer = server === 'na' ? 'com' : server;
+86
View File
@@ -0,0 +1,86 @@
import decompress from 'decompress';
import { readFile, readdir, rm, writeFile } from 'fs/promises';
import { load } from 'js-yaml';
import { ElementCompact, xml2js } from 'xml-js';
import { NATION_IDS } from '../src/constants/nations';
const REWRITE = false;
const VEHICLE_DEFS = 'assets/Data/XML/item_defs/vehicles';
const APK_FILE = 'temp/blitz.apk';
const STRINGS = 'assets/Data/Strings';
const LANGUAGE = 'en';
const DIRECTORIES_OF_INTEREST = [VEHICLE_DEFS, STRINGS];
if (REWRITE) {
await rm('temp/blitz', { recursive: true, force: true });
decompress(APK_FILE, 'temp/blitz', {
filter: (file) =>
DIRECTORIES_OF_INTEREST.some((dir) => file.path.startsWith(dir)),
});
}
interface BlitzkriegTankopedia {
id: number;
nation: string;
name: string;
name_short?: string;
is_premium: boolean;
is_collector: boolean;
tier: number;
type: string;
}
const strings = await readFile(
`temp/blitz/${STRINGS}/${LANGUAGE}.yaml`,
'utf-8',
).then((data) => load(data) as Record<string, string>);
const tankopedia: Record<number, BlitzkriegTankopedia> = {};
const nations = await readdir(`temp/blitz/${VEHICLE_DEFS}`);
await Promise.all(
nations
.filter((nation) => nation !== 'common')
.map(async (nation) => {
const listXML = await readFile(
`temp/blitz/${VEHICLE_DEFS}/${nation}/list.xml`,
'utf-8',
);
const list = xml2js(listXML, { compact: true }) as ElementCompact;
await Promise.all(
Object.entries<ElementCompact>(list.root).map(
async ([vehicle, vehicleData]) => {
// const vehicleXML = await readFile(
// `temp/blitz/${VEHICLE_DEFS}/${nation}/${vehicle}.xml`,
// 'utf-8',
// ).then((xml) => xml2js(xml, { compact: true }) as ElementCompact);
const nationVehicleId = parseInt(list.root[vehicle].id._text);
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
// TODO: use userString property instead
const name = strings[`#${nation}_vehicles:${vehicle}`];
const name_short = strings[`#${nation}_vehicles:${vehicle}_short`];
const is_premium = 'gold' in vehicleData.price;
const is_collector = vehicleData.sellPrice
? 'gold' in vehicleData.sellPrice
: false;
const tier = parseInt(vehicleData.level._text);
const tags = vehicleData.tags._text.split(' ');
const type = tags[0];
tankopedia[id] = {
id,
name,
name_short,
nation,
is_premium,
is_collector,
tier,
type,
};
},
),
);
}),
);
writeFile('dist/tankopedia.json', JSON.stringify(tankopedia, null, 2));
@@ -0,0 +1,10 @@
import { NextResponse } from 'next/server';
import { Region } from '../../../../../../constants/regions';
import getRatingsInfo from '../../../../../../core/blitz/getRatingsInfo';
export async function GET(
request: Request,
{ params }: { params: { region: Region } },
) {
return NextResponse.json(await getRatingsInfo(params.region));
}
@@ -0,0 +1,12 @@
import { NextResponse } from 'next/server';
import { Region } from '../../../../../../../constants/regions';
import { getRatingsLeague } from '../../../../../../../core/blitz/getRatingsLeague';
export async function GET(
request: Request,
{ params }: { params: { region: Region; league: number } },
) {
return NextResponse.json(
await getRatingsLeague(params.region, params.league, true),
);
}
@@ -0,0 +1,12 @@
import { NextResponse } from 'next/server';
import { Region } from '../../../../../../../constants/regions';
import { searchCurrentRatingsPlayers } from '../../../../../../../core/blitz/searchCurrentRatingsPlayers';
export async function GET(
request: Request,
{ params }: { params: { region: Region; username: string } },
) {
return NextResponse.json(
await searchCurrentRatingsPlayers(params.region, params.username),
);
}
+1 -1
View File
@@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
import * as Breakdown from '../../../components/Breakdown'; import * as Breakdown from '../../../components/Breakdown';
import { TreeTypeEnum } from '../../../components/Tanks'; import { TreeTypeEnum } from '../../../components/Tanks';
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
import fetchBlitz from '../../../core/blitz/fetchWargaming'; import fetchBlitz from '../../../core/blitz/fetchBlitz';
import { tankAverages } from '../../../core/blitzstars/tankAverages'; import { tankAverages } from '../../../core/blitzstars/tankAverages';
import { import {
TankopediaEntry, TankopediaEntry,
+1 -6
View File
@@ -41,12 +41,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
: 'url(https://i.imgur.com/PhS06NJ.png)', : 'url(https://i.imgur.com/PhS06NJ.png)',
}} }}
> >
<Theme <Theme appearance="dark" panelBackground="translucent" radius="full">
appearance="dark"
panelBackground="translucent"
radius="full"
accentColor="blue"
>
{!isEmbed && <Navbar />} {!isEmbed && <Navbar />}
<Flex direction="column">{children}</Flex> <Flex direction="column">{children}</Flex>
@@ -0,0 +1,13 @@
import { keyframes, style } from '@vanilla-extract/css';
const spinKeyframes = keyframes({
from: { transform: 'rotate(0deg)' },
to: { transform: 'rotate(360deg)' },
});
export const infiniteSpin = style({
animationName: spinKeyframes,
animationIterationCount: 'infinite',
animationTimingFunction: 'linear',
animationDuration: '1s',
});
@@ -0,0 +1,95 @@
import {
CaretLeftIcon,
CaretRightIcon,
SymbolIcon,
} from '@radix-ui/react-icons';
import { Button, Flex, TextField } from '@radix-ui/themes';
import { range } from 'lodash';
import { useEffect, useRef } from 'react';
import { infiniteSpin } from './index.css';
interface PageTurnerProps {
leaderboard: Record<number, number>;
page: number;
rowsPerPage: number;
pages: number;
onPageChange: (page: number, isButtonClick: boolean) => void;
totalPlayers: number;
}
export function PageTurner({
page,
rowsPerPage,
onPageChange,
pages,
leaderboard,
totalPlayers,
}: PageTurnerProps) {
const pageInput = useRef<HTMLInputElement>(null);
const rightLoading = range(
page * rowsPerPage,
Math.min(totalPlayers - 1, page * rowsPerPage + rowsPerPage + 1),
).some((index) => !(index in leaderboard));
const leftLoading = range(
Math.max(0, page * rowsPerPage - 1),
Math.max(0, page * rowsPerPage - rowsPerPage - 1) + 1,
).some((index) => !(index in leaderboard));
useEffect(() => {
if (pageInput.current) pageInput.current.value = `${page + 1}`;
}, [page]);
return (
<Flex justify="center" wrap="wrap" gap="2">
<Flex gap="2">
<Button
variant="soft"
onClick={() => onPageChange(Math.max(page - 1, 0), true)}
disabled={page === 0 || leftLoading}
>
{leftLoading ? (
<SymbolIcon className={infiniteSpin} />
) : (
<CaretLeftIcon />
)}
</Button>
<TextField.Root>
<TextField.Slot>Page</TextField.Slot>
<TextField.Input
type="number"
ref={pageInput}
style={{ width: 64, textAlign: 'center' }}
onBlur={(event) => {
onPageChange(
Math.max(
0,
Math.min(pages - 1, event.target.valueAsNumber - 1),
),
false,
);
}}
onKeyDown={(event) => {
if (event.key === 'Enter') {
(event.target as HTMLInputElement).blur();
}
}}
/>
<TextField.Slot>out of {pages}</TextField.Slot>
</TextField.Root>
<Button
variant="soft"
onClick={() => {
onPageChange(Math.min(page + 1, pages - 1), true);
}}
disabled={page === pages - 1 || rightLoading}
>
{rightLoading ? (
<SymbolIcon className={infiniteSpin} />
) : (
<CaretRightIcon />
)}
</Button>
</Flex>
</Flex>
);
}
File diff suppressed because it is too large. Load diff
+16 -13
View File
@@ -51,7 +51,7 @@ export default function Page() {
}, [session]); }, [session]);
return ( return (
<PageWrapper> <PageWrapper color="blue">
<div className={styles.toolBar}> <div className={styles.toolBar}>
<div style={{ flex: 1, boxSizing: 'border-box', position: 'relative' }}> <div style={{ flex: 1, boxSizing: 'border-box', position: 'relative' }}>
<TextField.Root> <TextField.Root>
@@ -124,7 +124,7 @@ export default function Page() {
<Button <Button
variant="soft" variant="soft"
onClick={(event) => { onClick={(event) => {
if (!session.promptBeforeReset) { if (!session.resetPrompt) {
event.preventDefault(); event.preventDefault();
resetSession(); resetSession();
} }
@@ -153,7 +153,7 @@ export default function Page() {
mr="1" mr="1"
onCheckedChange={(checked) => onCheckedChange={(checked) =>
useSession.setState({ useSession.setState({
promptBeforeReset: !checked, resetPrompt: !checked,
}) })
} }
/> />
@@ -183,7 +183,7 @@ export default function Page() {
variant="soft" variant="soft"
className={styles.toolbarButton} className={styles.toolbarButton}
onClick={(event) => { onClick={(event) => {
if (!session.showEmbedPrompt) { if (!session.embedPrompt) {
event.preventDefault(); event.preventDefault();
resetSession(); resetSession();
} }
@@ -202,14 +202,17 @@ export default function Page() {
Copied link to your clipboard Copied link to your clipboard
</AlertDialog.Title> </AlertDialog.Title>
<AlertDialog.Description> <AlertDialog.Description>
Keep in mind: Instructions for streamers:
<ul> <iframe
<li>Paste this link into your browser to view the embed</li> style={{
<li>Embeds abide your custom options</li> width: '100%',
<li> aspectRatio: '16 / 9',
Options do not transfer across websites/streaming softwares border: 'none',
</li> }}
</ul> src="https://www.youtube-nocookie.com/embed/e--GwjyrJAE?si=ftEiCmpbMI2FUY5N"
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
></iframe>
</AlertDialog.Description> </AlertDialog.Description>
<Text size="2"> <Text size="2">
@@ -217,7 +220,7 @@ export default function Page() {
mr="1" mr="1"
onCheckedChange={(checked) => onCheckedChange={(checked) =>
useSession.setState({ useSession.setState({
showEmbedPrompt: !checked, embedPrompt: !checked,
}) })
} }
/> />
+130 -100
View File
@@ -1,4 +1,5 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { chunk } from 'lodash';
import * as Breakdown from '../components/Breakdown'; import * as Breakdown from '../components/Breakdown';
import NoData, { NoDataType } from '../components/NoData'; import NoData, { NoDataType } from '../components/NoData';
import TitleBar from '../components/TitleBar'; import TitleBar from '../components/TitleBar';
@@ -35,6 +36,9 @@ import { StatFilters, filterStats } from '../core/statistics/filterStats';
import getWN8Percentile from '../core/statistics/getWN8Percentile'; import getWN8Percentile from '../core/statistics/getWN8Percentile';
import { CommandRegistryRaw } from '../events/interactionCreate'; import { CommandRegistryRaw } from '../events/interactionCreate';
const ROWS_PER_PAGE = 8;
const MAX_PAGES = 10;
async function render( async function render(
{ region, id }: ResolvedPlayer, { region, id }: ResolvedPlayer,
{ start, end, name }: ResolvedPeriod, { start, end, name }: ResolvedPeriod,
@@ -132,38 +136,80 @@ async function render(
0, 0,
) / careerBattles; ) / careerBattles;
return ( const children: JSX.Element[] = [];
<Wrapper>
<TitleBar
name={accountInfo.nickname}
image={
clanData?.clan
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData?.clan?.emblem_set_id}.png`
: undefined
}
description={`${name} • ${filterDescriptions}`}
/>
{filteredOrder.length === 0 && ( if (filteredOrder.length > 0) {
<NoData type={NoDataType.BattlesInPeriod} /> children.push(
)} <Breakdown.Row
{filteredOrder.length > 0 && ( key={id}
<Breakdown.Root> type="summary"
title="Total"
stats={[
{
title: 'Battles',
current: currentBattles,
career: careerBattles,
},
{
title: 'Winrate',
current: `${(100 * currentWinrate).toFixed(2)}%`,
career: `${(100 * careerWinrate).toFixed(2)}%`,
delta: currentWinrate - careerWinrate,
},
{
title: 'WN8',
current:
currentWN8 === undefined
? undefined
: Math.round(currentWN8).toLocaleString(),
career:
careerWN8 === undefined
? undefined
: Math.round(careerWN8).toLocaleString(),
percentile:
currentWN8 === undefined
? undefined
: getWN8Percentile(currentWN8),
},
{
title: 'Damage',
current: Math.round(currentDamage).toLocaleString(),
career: Math.round(careerDamage).toLocaleString(),
delta: currentDamage - careerDamage,
},
]}
/>,
);
await Promise.all(
filteredOrder.map(async (id, index) => {
const tankopediaEntry = awaitedTankopedia[id];
const current = orderedCurrentStats[index];
const career = orderedCareerStats[index];
const currentWN8 = orderedCurrentWN8[index];
const careerWN8 = orderedCareerWN8[index];
children.push(
<Breakdown.Row <Breakdown.Row
key={id} key={id}
type="summary" type="tank"
title="Total" tankType={tankopediaEntry?.type}
treeType={tankopediaEntry ? await getTreeType(id) : undefined}
title={await resolveTankName(id)}
stats={[ stats={[
{ {
title: 'Battles', title: 'Battles',
current: currentBattles, current: current.battles.toLocaleString(),
career: careerBattles, career: career.battles.toLocaleString(),
}, },
{ {
title: 'Winrate', title: 'Winrate',
current: `${(100 * currentWinrate).toFixed(2)}%`, current: `${(100 * (current.wins / current.battles)).toFixed(
career: `${(100 * careerWinrate).toFixed(2)}%`, 2,
delta: currentWinrate - careerWinrate, )}%`,
career: `${(100 * (career.wins / career.battles)).toFixed(2)}%`,
delta:
current.wins / current.battles - career.wins / career.battles,
}, },
{ {
title: 'WN8', title: 'WN8',
@@ -182,83 +228,67 @@ async function render(
}, },
{ {
title: 'Damage', title: 'Damage',
current: Math.round(currentDamage).toLocaleString(), current: Math.round(
career: Math.round(careerDamage).toLocaleString(), current.damage_dealt / current.battles,
delta: currentDamage - careerDamage, ).toLocaleString(),
career: Math.round(
career.damage_dealt / career.battles,
).toLocaleString(),
delta:
current.damage_dealt / current.battles -
career.damage_dealt / career.battles,
}, },
]} ]}
/> />,
);
}),
);
}
{await Promise.all( const pages = chunk(
filteredOrder.map(async (id, index) => { children,
const tankopediaEntry = awaitedTankopedia[id]; Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE),
const current = orderedCurrentStats[index];
const career = orderedCareerStats[index];
const currentWN8 = orderedCurrentWN8[index];
const careerWN8 = orderedCareerWN8[index];
return (
<Breakdown.Row
key={id}
type="tank"
tankType={tankopediaEntry?.type}
treeType={tankopediaEntry ? await getTreeType(id) : undefined}
title={await resolveTankName(id)}
stats={[
{
title: 'Battles',
current: current.battles.toLocaleString(),
career: career.battles.toLocaleString(),
},
{
title: 'Winrate',
current: `${(
100 *
(current.wins / current.battles)
).toFixed(2)}%`,
career: `${(100 * (career.wins / career.battles)).toFixed(
2,
)}%`,
delta:
current.wins / current.battles -
career.wins / career.battles,
},
{
title: 'WN8',
current:
currentWN8 === undefined
? undefined
: Math.round(currentWN8).toLocaleString(),
career:
careerWN8 === undefined
? undefined
: Math.round(careerWN8).toLocaleString(),
percentile:
currentWN8 === undefined
? undefined
: getWN8Percentile(currentWN8),
},
{
title: 'Damage',
current: Math.round(
current.damage_dealt / current.battles,
).toLocaleString(),
career: Math.round(
career.damage_dealt / career.battles,
).toLocaleString(),
delta:
current.damage_dealt / current.battles -
career.damage_dealt / career.battles,
},
]}
/>
);
}),
)}
</Breakdown.Root>
)}
</Wrapper>
); );
if (filteredOrder.length > 0) {
return pages
.map((page, index) => (
<Wrapper>
{index === 0 && (
<TitleBar
name={accountInfo.nickname}
image={
clanData?.clan
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData?.clan?.emblem_set_id}.png`
: undefined
}
description={`${name} • ${filterDescriptions}`}
/>
)}
<Breakdown.Root>{page}</Breakdown.Root>
</Wrapper>
))
.reverse();
} else {
return [
<Wrapper>
<TitleBar
name={accountInfo.nickname}
image={
clanData?.clan
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData?.clan?.emblem_set_id}.png`
: undefined
}
description={`${name} • ${filterDescriptions}`}
/>
{filteredOrder.length === 0 && (
<NoData type={NoDataType.BattlesInPeriod} />
)}
</Wrapper>,
];
}
} }
export const breakdownCommand = new Promise<CommandRegistryRaw>( export const breakdownCommand = new Promise<CommandRegistryRaw>(
@@ -286,11 +316,11 @@ export const breakdownCommand = new Promise<CommandRegistryRaw>(
...filters, ...filters,
}); });
return Promise.all([ return [
render(player, period, filters), ...(await render(player, period, filters)),
buttonPrimary(path, 'Refresh'), buttonPrimary(path, 'Refresh'),
getBlitzStarsLinkButton(player.region, player.id), await getBlitzStarsLinkButton(player.region, player.id),
]); ];
}, },
autocomplete: (interaction) => { autocomplete: (interaction) => {
+15
View File
@@ -0,0 +1,15 @@
import { AttachmentBuilder, SlashCommandBuilder } from 'discord.js';
import { CommandRegistry } from '../events/interactionCreate';
export const creditsCommand: CommandRegistry = {
inProduction: true,
inPublic: true,
command: new SlashCommandBuilder()
.setName('credits')
.setDescription('Me and the people who made this bot'),
async handler() {
return new AttachmentBuilder('https://i.imgur.com/ZF5ch33.png');
},
};
+1 -1
View File
@@ -8,13 +8,13 @@ import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import getTankStats from '../core/blitz/getTankStats'; import getTankStats from '../core/blitz/getTankStats';
import getTreeType from '../core/blitz/getTreeType'; import getTreeType from '../core/blitz/getTreeType';
import resolveTankName from '../core/blitz/resolveTankName'; import resolveTankName from '../core/blitz/resolveTankName';
import { tankopediaInfo } from '../core/blitz/tankopediaInfo';
import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL'; import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
import { import {
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
TankopediaEntry, TankopediaEntry,
Tier, Tier,
tankopedia, tankopedia,
tankopediaInfo,
} from '../core/blitzstars/tankopedia'; } from '../core/blitzstars/tankopedia';
import addTierChoices from '../core/discord/addTierChoices'; import addTierChoices from '../core/discord/addTierChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
+66 -116
View File
@@ -4,21 +4,25 @@ import {
SlashCommandSubcommandBuilder, SlashCommandSubcommandBuilder,
SlashCommandSubcommandGroupBuilder, SlashCommandSubcommandGroupBuilder,
} from 'discord.js'; } from 'discord.js';
import { range } from 'lodash';
import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard'; import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard';
import * as Leaderboard from '../components/Leaderboard'; import * as Leaderboard from '../components/Leaderboard';
import TitleBar from '../components/TitleBar'; import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper'; import Wrapper from '../components/Wrapper';
import { LEAGUES } from '../constants/leagues';
import { FIRST_ARCHIVED_RATINGS_SEASON } from '../constants/ratings';
import { REGION_NAMES_SHORT, Region } from '../constants/regions'; import { REGION_NAMES_SHORT, Region } from '../constants/regions';
import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import getRatingsInfo from '../core/blitz/getRatingsInfo'; import getRatingsInfo from '../core/blitz/getRatingsInfo';
import { getRatingsLeague } from '../core/blitz/getRatingsLeague';
import { getRatingsNeighbors } from '../core/blitz/getRatingsNeighbors';
import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain'; import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain';
import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber';
import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo'; import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo';
import getMidnightLeaderboard, { import { getArchivedRatingsLeaderboard } from '../core/blitzkrieg/getArchivedRatingsLeaderboard';
DATABASE_REPO, import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingsMidnightLeaderboard';
} from '../core/blitzkrieg/getMidnightLeaderboard'; import { UserError } from '../core/blitzkrieg/userError';
import { octokit } from '../core/blitzkrieg/octokit';
import throwError from '../core/blitzkrieg/throwError';
import addRegionChoices from '../core/discord/addRegionChoices'; import addRegionChoices from '../core/discord/addRegionChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
@@ -113,14 +117,6 @@ export interface RatingsNeighbors {
neighbors: RatingsPlayer[]; neighbors: RatingsPlayer[];
} }
const LEAGUES = [
{ name: 'Diamond', minScore: 5000 },
{ name: 'Platinum', minScore: 4000 },
{ name: 'Gold', minScore: 3000 },
{ name: 'Silver', minScore: 2000 },
{ name: 'Bronze', minScore: 0 },
];
function addSubcommands( function addSubcommands(
option: SlashCommandSubcommandGroupBuilder, option: SlashCommandSubcommandGroupBuilder,
addOptions = (option: SlashCommandSubcommandBuilder) => option, addOptions = (option: SlashCommandSubcommandBuilder) => option,
@@ -129,14 +125,14 @@ function addSubcommands(
.addSubcommand((option) => .addSubcommand((option) =>
addOptions( addOptions(
option option
.setName('neighbours') .setName('neighbors')
.setDescription('Your position and neighbours'), .setDescription('Your position and neighbors'),
) )
.addStringOption(addUsernameChoices) .addStringOption(addUsernameChoices)
.addIntegerOption((option) => .addIntegerOption((option) =>
option option
.setName('limit') .setName('limit')
.setDescription('How many neighbours to display (default: 10)') .setDescription('How many neighbors to display (default: 10)')
.setRequired(false) .setRequired(false)
.setMinValue(5) .setMinValue(5)
.setMaxValue(30), .setMaxValue(30),
@@ -178,23 +174,17 @@ const noOngoingSeason = embedNegative(
export const ratingsCommand = new Promise<CommandRegistryRaw>( export const ratingsCommand = new Promise<CommandRegistryRaw>(
async (resolve) => { async (resolve) => {
const seasonChoices = await octokit.repos const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
.getContent({ const seasonChoices = range(
...DATABASE_REPO, FIRST_ARCHIVED_RATINGS_SEASON,
path: 'com/ratings', latestArchivedSeasonNumber + 1,
}) ).map(
.then(({ data }) => { (number) =>
if (!Array.isArray(data)) { ({
throw throwError('Archived ratings data is malformed'); name: `Season ${number}`,
} value: `${number}`,
return data.map( }) satisfies APIApplicationCommandOptionChoice<string>,
(folder) => );
({
name: `Season ${folder.name}`,
value: folder.name,
}) satisfies APIApplicationCommandOptionChoice<string>,
);
});
const command = new SlashCommandBuilder() const command = new SlashCommandBuilder()
.setName('ratings') .setName('ratings')
@@ -240,7 +230,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
| 'current'; | 'current';
const subcommand = interaction.options.getSubcommand(true) as const subcommand = interaction.options.getSubcommand(true) as
| 'league' | 'league'
| 'neighbours'; | 'neighbors';
const limit = interaction.options.getInteger('limit') ?? 10; const limit = interaction.options.getInteger('limit') ?? 10;
const seasonOption = interaction.options.getString('season'); const seasonOption = interaction.options.getString('season');
const season = seasonOption ? parseInt(seasonOption) : undefined; const season = seasonOption ? parseInt(seasonOption) : undefined;
@@ -262,18 +252,15 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
const region = interaction.options.getString('region') as Region; const region = interaction.options.getString('region') as Region;
regionRatingsInfo = await (subcommandGroup === 'current' regionRatingsInfo = await (subcommandGroup === 'current'
? getRatingsInfo(region) ? getRatingsInfo(region)
: getArchivedRatingsInfo(season!, region)); : getArchivedRatingsInfo(region, season!));
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
const regionSubdomain = regionToRegionSubdomain(region); const regionSubdomain = regionToRegionSubdomain(region);
const result = const result =
subcommandGroup === 'current' subcommandGroup === 'current'
? await fetch( ? await getRatingsLeague(region, leagueIndex).then(
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${leagueIndex}/top/`, async (result) =>
)
.then((response) => response.json() as Promise<LeagueTop>)
.then(async ({ result }) =>
result.slice(0, limit).map( result.slice(0, limit).map(
(player) => (player) =>
({ ({
@@ -284,20 +271,9 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
nickname: player.nickname, nickname: player.nickname,
}) satisfies SimplifiedPlayer, }) satisfies SimplifiedPlayer,
), ),
) )
: await octokit.repos : await getArchivedRatingsLeaderboard(region, season!).then(
.getContent({ async (players) => {
...DATABASE_REPO,
path: `${region}/ratings/${season}/latest.json`,
})
.then(async ({ data }) => {
if (Array.isArray(data) || data.type !== 'file') {
throw throwError('Archived ratings data is malformed');
}
const players = (await fetch(data.download_url!).then(
(response) => response.json(),
)) as BlitzkriegRatingsLeaderboard;
const firstPlayerIndex = const firstPlayerIndex =
leagueIndex === 0 leagueIndex === 0
? 0 ? 0
@@ -328,15 +304,16 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
`Unknown Player ${player.id}`, `Unknown Player ${player.id}`,
}) satisfies SimplifiedPlayer, }) satisfies SimplifiedPlayer,
); );
}); },
);
const leagueInfo = regionRatingsInfo.leagues[leagueIndex]; const leagueInfo = regionRatingsInfo.leagues[leagueIndex];
midnightLeaderboard = midnightLeaderboard =
subcommandGroup === 'current' subcommandGroup === 'current'
? await getMidnightLeaderboard( ? await getArchivedRatingsMidnightLeaderboard(
region, region,
regionRatingsInfo.current_season, regionRatingsInfo.current_season,
) )
: await getMidnightLeaderboard(region, season!); : await getArchivedRatingsMidnightLeaderboard(region, season!);
players = result; players = result;
playersBefore = result ? result[0].position - 1 : 0; playersBefore = result ? result[0].position - 1 : 0;
@@ -354,73 +331,43 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
regionRatingsInfo = regionRatingsInfo =
subcommandGroup === 'current' subcommandGroup === 'current'
? await getRatingsInfo(region) ? await getRatingsInfo(region)
: await octokit.repos : await getArchivedRatingsInfo(region, season!);
.getContent({
...DATABASE_REPO,
path: `${region}/ratings/${season}/info.json`,
})
.then(async ({ data }) => {
if (Array.isArray(data) || data.type !== 'file') {
throw throwError('Archived ratings info is malformed');
}
const response = fetch(data.download_url!);
return (await response.then((response) =>
response.json(),
)) as RatingsInfo;
});
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
const accountInfo = await getAccountInfo(region, id); const accountInfo = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan; const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const regionSubdomain = regionToRegionSubdomain(region);
const neighbors = const neighbors =
subcommandGroup === 'current' subcommandGroup === 'current'
? await fetch( ? await getRatingsNeighbors(
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${Math.round( region,
limit / 2, id,
)}`, Math.round(limit / 2),
).then((players) =>
players.neighbors.map(
(player) =>
({
id: player.spa_id,
position: player.number,
score: player.score,
clan: player.clan_tag,
nickname: player.nickname,
}) satisfies SimplifiedPlayer,
),
) )
.then( : await getArchivedRatingsLeaderboard(region, season!).then(
(response) => response.json() as Promise<RatingsNeighbors>, async (players) => {
)
.then((players) =>
players.neighbors.map(
(player) =>
({
id: player.spa_id,
position: player.number,
score: player.score,
clan: player.clan_tag,
nickname: player.nickname,
}) satisfies SimplifiedPlayer,
),
)
: await octokit.repos
.getContent({
...DATABASE_REPO,
path: `${region}/ratings/${season}/latest.json`,
})
.then(async ({ data }) => {
if (Array.isArray(data) || data.type !== 'file') {
throw throwError(
'Archived ratings latest data is malformed',
);
}
const response = fetch(data.download_url!);
const players = (await response.then((response) =>
response.json(),
)) as BlitzkriegRatingsLeaderboard;
const playerIndex = players.findIndex( const playerIndex = players.findIndex(
(player) => player.id === id, (player) => player.id === id,
); );
if (playerIndex === -1) { if (playerIndex === -1) {
throw throwError( throw new UserError(
`${accountInfo.nickname} didn't player ratings in season ${season}`, `${accountInfo.nickname} didn't player ratings in season ${season}`,
'This player did not participate in this season or did not get past calibration.', {
cause:
'This player did not participate in this season or did not get past calibration.',
},
); );
} }
@@ -442,14 +389,15 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
clan: clanData[player.id]!.clan?.tag, clan: clanData[player.id]!.clan?.tag,
nickname: clanData[player.id]!.account_name, nickname: clanData[player.id]!.account_name,
})); }));
}); },
);
midnightLeaderboard = midnightLeaderboard =
subcommandGroup === 'current' subcommandGroup === 'current'
? await getMidnightLeaderboard( ? await getArchivedRatingsMidnightLeaderboard(
region, region,
regionRatingsInfo.current_season, regionRatingsInfo.current_season,
) )
: await getMidnightLeaderboard(region, season!); : await getArchivedRatingsMidnightLeaderboard(region, season!);
if (clan && neighbors) titleNameDiscriminator = `[${clan.tag}]`; if (clan && neighbors) titleNameDiscriminator = `[${clan.tag}]`;
@@ -460,7 +408,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
titleName = neighbors titleName = neighbors
? accountInfo.nickname ? accountInfo.nickname
: `No Ongoing Season - ${REGION_NAMES_SHORT[region]}`; : `No Ongoing Season - ${REGION_NAMES_SHORT[region]}`;
titleDescription = 'Ratings neighbours'; titleDescription = 'Ratings neighbors';
playersBefore = neighbors ? neighbors[0].position - 1 : 0; playersBefore = neighbors ? neighbors[0].position - 1 : 0;
playersAfter = playersAfter =
regionRatingsInfo.count - neighbors[neighbors.length - 1].position; regionRatingsInfo.count - neighbors[neighbors.length - 1].position;
@@ -513,10 +461,12 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
{items && ( {items && (
<Leaderboard.Root> <Leaderboard.Root>
{playersBefore > 0 && ( {playersBefore > 0 && (
<Leaderboard.Gap number={playersBefore} /> <Leaderboard.Gap message={`+ ${playersBefore} more`} />
)} )}
{items} {items}
{playersAfter > 0 && <Leaderboard.Gap number={playersAfter} />} {playersAfter > 0 && (
<Leaderboard.Gap message={`+ ${playersAfter} more`} />
)}
</Leaderboard.Root> </Leaderboard.Root>
)} )}
</Wrapper> </Wrapper>
+1 -1
View File
@@ -2,7 +2,7 @@ import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape'; import markdownEscape from 'markdown-escape';
import { REGION_NAMES, Region } from '../constants/regions'; import { REGION_NAMES, Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import fetchBlitz from '../core/blitz/fetchWargaming'; import fetchBlitz from '../core/blitz/fetchBlitz';
import addClanChoices from '../core/discord/addClanChoices'; import addClanChoices from '../core/discord/addClanChoices';
import addRegionChoices from '../core/discord/addRegionChoices'; import addRegionChoices from '../core/discord/addRegionChoices';
import embedInfo from '../core/discord/embedInfo'; import embedInfo from '../core/discord/embedInfo';
+1 -1
View File
@@ -2,7 +2,7 @@ import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape'; import markdownEscape from 'markdown-escape';
import { REGION_NAMES, Region } from '../constants/regions'; import { REGION_NAMES, Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import fetchBlitz from '../core/blitz/fetchWargaming'; import fetchBlitz from '../core/blitz/fetchBlitz';
import { import {
AccountList, AccountList,
usernamePattern, usernamePattern,
-18
View File
@@ -1,18 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
import { CommandRegistry } from '../events/interactionCreate';
/**
* TODO: @deprecated
*/
export const todayCommand: CommandRegistry = {
inProduction: true,
inPublic: true,
command: new SlashCommandBuilder()
.setName('today')
.setDescription('This command has been renamed to `/breakdown today`'),
handler() {
return '# This command has been renamed to `/breakdown today`\n`/today` now comes with filters, periods and more! Use `/breakdown today` for same old functionality but also do checkout the other options.';
},
};
@@ -15,9 +15,18 @@ export default function GenericStatsRow({ stat }: GenericStatsRowProps) {
alignItems: 'center', alignItems: 'center',
}} }}
> >
<span style={{ color: theme.colors.textLowContrast, fontSize: 16 }}> <span
style={{
color: theme.colors.textLowContrast,
fontSize: 16,
whiteSpace: 'nowrap',
maxWidth: 128,
overflow: 'hidden',
}}
>
{stat[0]} {stat[0]}
</span> </span>
<span <span
style={{ style={{
color: theme.colors.textHighContrast, color: theme.colors.textHighContrast,
@@ -1,10 +1,10 @@
import { theme } from '../../../stitches.config'; import { theme } from '../../../stitches.config';
export interface GapProps { export interface GapProps {
number: number; message: string;
} }
export function Gap({ number }: GapProps) { export function Gap({ message: number }: GapProps) {
return ( return (
<div <div
style={{ style={{
@@ -22,7 +22,7 @@ export function Gap({ number }: GapProps) {
fontSize: 16, fontSize: 16,
}} }}
> >
+ {number.toLocaleString()} more {number.toLocaleString()}
</span> </span>
</div> </div>
); );
+18 -12
View File
@@ -1,10 +1,11 @@
import { ComponentProps } from 'react';
import { RatingsReward } from '../../../commands/ratings'; import { RatingsReward } from '../../../commands/ratings';
import { theme } from '../../../stitches.config'; import { theme } from '../../../stitches.config';
export interface ItemProps { export interface ItemProps extends ComponentProps<'div'> {
position: number; position: number;
deltaPosition?: number; deltaPosition?: number;
score: number; score?: number;
deltaScore?: number; deltaScore?: number;
reward?: RatingsReward; reward?: RatingsReward;
nickname: string; nickname: string;
@@ -25,10 +26,13 @@ export function Item({
nickname, nickname,
clan, clan,
highlight, highlight,
...props
}: ItemProps) { }: ItemProps) {
return ( return (
<div <div
{...props}
style={{ style={{
cursor: 'pointer',
display: 'flex', display: 'flex',
padding: 8, padding: 8,
backgroundColor: highlight backgroundColor: highlight
@@ -185,16 +189,18 @@ export function Item({
} }
/> />
)} )}
<span {score !== undefined && (
style={{ <span
color: highlight style={{
? theme.colors.textHighContrast_blue color: highlight
: theme.colors.textHighContrast, ? theme.colors.textHighContrast_blue
fontSize: 16, : theme.colors.textHighContrast,
}} fontSize: 16,
> }}
{score.toLocaleString()} >
</span> {score.toLocaleString()}
</span>
)}
</div> </div>
</div> </div>
); );
+22 -15
View File
@@ -1,24 +1,31 @@
import { Flex, Theme } from '@radix-ui/themes';
import { ComponentProps } from 'react'; import { ComponentProps } from 'react';
interface PageWrapperProps extends ComponentProps<typeof Flex> {
color?: ComponentProps<typeof Theme>['accentColor'];
}
export default function PageWrapper({ export default function PageWrapper({
style, style,
color,
...props ...props
}: ComponentProps<'div'>) { }: PageWrapperProps) {
return ( return (
<div <Theme accentColor={color}>
style={{ <Flex
width: '100%', {...props}
maxWidth: 800, direction="column"
margin: 'auto', gap="4"
display: 'flex', style={{
padding: 16, width: '100%',
flexDirection: 'column', maxWidth: 800,
gap: 16, margin: 'auto',
boxSizing: 'border-box', padding: 16,
boxSizing: 'border-box',
...style, ...style,
}} }}
{...props} />
/> </Theme>
); );
} }
+13
View File
@@ -0,0 +1,13 @@
import { slateDarkA } from '@radix-ui/colors';
import { keyframes, style } from '@vanilla-extract/css';
const animation = keyframes({
'0%': { backgroundColor: slateDarkA.slateA2 },
'50%': { backgroundColor: slateDarkA.slateA3 },
'100%': { backgroundColor: slateDarkA.slateA2 },
});
export const skeleton = style({
borderRadius: 8,
animation: `${animation} 2s ease-in-out infinite`,
});
+6
View File
@@ -0,0 +1,6 @@
import { ComponentProps } from 'react';
import { skeleton } from './index.css';
export function Skeleton({ className, ...props }: ComponentProps<'div'>) {
return <div {...props} className={`${className} ${skeleton}`} />;
}
+7
View File
@@ -0,0 +1,7 @@
export const LEAGUES = [
{ name: 'Diamond', minScore: 5000 },
{ name: 'Platinum', minScore: 4000 },
{ name: 'Gold', minScore: 3000 },
{ name: 'Silver', minScore: 2000 },
{ name: 'Bronze', minScore: 0 },
];
+11
View File
@@ -0,0 +1,11 @@
export const NATION_IDS: Record<string, number> = {
ussr: 0,
germany: 1,
usa: 2,
china: 3,
france: 4,
uk: 5,
japan: 6,
other: 7,
european: 8,
};
+1
View File
@@ -0,0 +1 @@
export const FIRST_ARCHIVED_RATINGS_SEASON = 49;
+4 -2
View File
@@ -1,4 +1,6 @@
export type Region = 'com' | 'eu' | 'asia'; export const REGIONS = ['com', 'eu', 'asia'] as const;
export type Region = (typeof REGIONS)[number];
export type RegionSubdomain = 'na' | 'eu' | 'asia'; export type RegionSubdomain = 'na' | 'eu' | 'asia';
export const REGION_NAMES: Record<Region, string> = { export const REGION_NAMES: Record<Region, string> = {
@@ -11,7 +13,7 @@ export const REGION_NAMES: Record<Region, string> = {
export const REGION_NAMES_SHORT: Record<Region, string> = { export const REGION_NAMES_SHORT: Record<Region, string> = {
com: 'NA', com: 'NA',
eu: 'EU', eu: 'EU',
asia: 'AS', asia: 'ASIA',
}; };
export const REGION_SUBDOMAIN_NAMES: Record<RegionSubdomain, string> = { export const REGION_SUBDOMAIN_NAMES: Record<RegionSubdomain, string> = {
+2 -3
View File
@@ -9,13 +9,12 @@ export const TOOLS: Tool[] = [
{ {
id: 'session', id: 'session',
title: 'Session tracker', title: 'Session tracker',
description: 'Track your performance in a session', description: 'Live session performance tracker',
}, },
{ {
id: 'ratings', id: 'ratings',
title: 'Ratings', title: 'Ratings',
description: 'Current and old season leader boards', description: 'Current and archived ratings leaderboards',
disabled: true,
}, },
{ {
id: 'tankopedia', id: 'tankopedia',
+56
View File
@@ -0,0 +1,56 @@
export type BlitzResponse<Data extends object> =
| {
status: 'error';
error: { field: string; message: string; code: 402; value: null };
}
| {
status: 'ok';
data: Data;
};
const MAX_CALLS_PER_SECOND = 10;
const queue: { url: string; resolve: (data: object) => void }[] = [];
let inProgress = 0;
async function manageQueue() {
if (queue.length > 0 && inProgress < MAX_CALLS_PER_SECOND) {
inProgress++;
const timeout = setTimeout(() => {
inProgress--;
manageQueue();
}, 1000);
const { url, resolve } = queue.shift()!;
const data = (await fetch(url)
.then((response) => response.json())
.catch((error) => ({ status: 'error', error }))) as BlitzResponse<
typeof resolve
>;
if (data.status === 'ok') {
resolve(data.data);
} else {
clearTimeout(timeout);
inProgress--;
manageQueue();
throw new Error(`Wargaming response error status: "${data.status}"`, {
cause: `Message: "${data.error.message}"\nURL: "${url}"`,
});
}
}
}
export default function fetchBlitz<Data extends object>(url: string) {
return new Promise<Data>((resolve) => {
queue.push({
url,
resolve(data) {
resolve(data as Data);
},
});
manageQueue();
});
}
-26
View File
@@ -1,26 +0,0 @@
import throwError from '../blitzkrieg/throwError';
export type BlitzResponse<Data extends object> =
| {
status: 'error';
error: { field: string; message: string; code: 402; value: null };
}
| {
status: 'ok';
data: Data;
};
export default async function fetchBlitz<Data extends object>(url: string) {
const data = (await fetch(url).then((response) =>
response.json(),
)) as BlitzResponse<Data>;
if (data.status === 'ok') {
return data.data;
} else {
throw throwError(
`Wargaming response error status:"${data.status}"`,
`Message: "${data.error.message}"\nURL: "${url}"`,
);
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import fetchBlitz from './fetchWargaming'; import fetchBlitz from './fetchBlitz';
import { normalizeIds } from './normalizeIds'; import { normalizeIds } from './normalizeIds';
interface IndividualAccountAchievements { interface IndividualAccountAchievements {
+1 -1
View File
@@ -1,6 +1,6 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import fetchBlitz from './fetchWargaming'; import fetchBlitz from './fetchBlitz';
import { normalizeIds } from './normalizeIds'; import { normalizeIds } from './normalizeIds';
export interface AllStats { export interface AllStats {
+1 -1
View File
@@ -1,6 +1,6 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import fetchBlitz from './fetchWargaming'; import fetchBlitz from './fetchBlitz';
import { normalizeExtra } from './normalizeExtra'; import { normalizeExtra } from './normalizeExtra';
import { normalizeIds } from './normalizeIds'; import { normalizeIds } from './normalizeIds';
+2 -2
View File
@@ -1,6 +1,6 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import fetchBlitz from './fetchWargaming'; import fetchBlitz from './fetchBlitz';
import { normalizeIds } from './normalizeIds'; import { normalizeIds } from './normalizeIds';
export interface IndividualClanInfo { export interface IndividualClanInfo {
@@ -41,7 +41,7 @@ export async function getClanInfo<
ReturnType = Ids extends number ? IndividualClanInfo : IndividualClanInfo[], ReturnType = Ids extends number ? IndividualClanInfo : IndividualClanInfo[],
>(region: Region, ids: Ids) { >(region: Region, ids: Ids) {
const object = await fetchBlitz<ClanInfo>( const object = await fetchBlitz<ClanInfo>(
`https://api.wotblitz.${region}/wotb/clans/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${normalizeIds( `https://api.wotblitz.${region}/wotb/clans/info/?application_id=${WARGAMING_APPLICATION_ID}&clan_id=${normalizeIds(
ids, ids,
)}`, )}`,
); );
+10 -4
View File
@@ -1,12 +1,18 @@
import { RatingsInfo } from '../../commands/ratings'; import { RatingsInfo } from '../../commands/ratings';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { context } from '../blitzkrieg/context';
import { patientFetch } from '../blitzkrieg/patientFetch';
import regionToRegionSubdomain from './regionToRegionSubdomain'; import regionToRegionSubdomain from './regionToRegionSubdomain';
export default async function getRatingsInfo(region: Region) { export default async function getRatingsInfo(region: Region) {
const response = await fetch( const response = await patientFetch(
`https://${regionToRegionSubdomain( context === 'website'
region, ? `/api/${region}/ratings/current/info`
)}.wotblitz.com/en/api/rating-leaderboards/season/`, : `https://${regionToRegionSubdomain(
region,
)}.wotblitz.com/en/api/rating-leaderboards/season/`,
undefined,
{ cache: 'no-store' },
); );
return (await response.json()) as Promise<RatingsInfo>; return (await response.json()) as Promise<RatingsInfo>;
+28
View File
@@ -0,0 +1,28 @@
import { LeagueTop, RatingsPlayer } from '../../commands/ratings';
import { Region } from '../../constants/regions';
import { context } from '../blitzkrieg/context';
import { patientFetch } from '../blitzkrieg/patientFetch';
import regionToRegionSubdomain from './regionToRegionSubdomain';
export async function getRatingsLeague(
region: Region,
league: number,
usePatientFetcher = false,
) {
const fetcher = (url: string) =>
usePatientFetcher
? patientFetch(url, undefined, { cache: 'no-store' })
: fetch(url, { cache: 'no-store' });
const response = await fetcher(
context === 'website'
? `/api/${region}/ratings/current/league/${league}`
: `https://${regionToRegionSubdomain(
region,
)}.wotblitz.com/en/api/rating-leaderboards/league/${league}/top/`,
);
const json = await response.json();
return context === 'website'
? (json as RatingsPlayer[])
: (json as LeagueTop).result;
}
+27
View File
@@ -0,0 +1,27 @@
import { RatingsNeighbors } from '../../commands/ratings';
import { Region } from '../../constants/regions';
import { patientFetch } from '../blitzkrieg/patientFetch';
import { withCORSProxy } from '../blitzkrieg/withCORSProxy';
import regionToRegionSubdomain from './regionToRegionSubdomain';
export async function getRatingsNeighbors(
region: Region,
id: number,
count: number,
usePatientFetcher = false,
) {
const fetcher = (url: string) =>
usePatientFetcher
? patientFetch(url, undefined, { cache: 'no-store' })
: fetch(url, { cache: 'no-store' });
const response = await fetcher(
withCORSProxy(
`https://${regionToRegionSubdomain(
region,
)}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}`,
),
);
const json = (await response.json()) as RatingsNeighbors;
return json;
}
+6 -6
View File
@@ -1,8 +1,8 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { TanksStats } from '../../types/tanksStats'; import { TanksStats } from '../../types/tanksStats';
import throwError from '../blitzkrieg/throwError'; import { UserError } from '../blitzkrieg/userError';
import fetchBlitz from './fetchWargaming'; import fetchBlitz from './fetchBlitz';
export default async function getTankStats(region: Region, id: number) { export default async function getTankStats(region: Region, id: number) {
const tankStats = await fetchBlitz<TanksStats>( const tankStats = await fetchBlitz<TanksStats>(
@@ -10,10 +10,10 @@ export default async function getTankStats(region: Region, id: number) {
); );
if (tankStats[id] === null) { if (tankStats[id] === null) {
throw throwError( throw new UserError('No tank stats available', {
'No tank stats available', cause:
"This player doesn't have any stats for tanks available. This may not be the player you are looking for.", "This player doesn't have any stats for tanks available. This may not be the player you are looking for.",
); });
} }
return tankStats[id]; return tankStats[id];
+7 -9
View File
@@ -1,5 +1,5 @@
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
import throwError from '../blitzkrieg/throwError'; import { UserError } from '../blitzkrieg/userError';
import { TANKS, tankopedia } from '../blitzstars/tankopedia'; import { TANKS, tankopedia } from '../blitzstars/tankopedia';
export default async function resolveTankId(tank: string | number) { export default async function resolveTankId(tank: string | number) {
@@ -12,10 +12,9 @@ export default async function resolveTankId(tank: string | number) {
}); });
if (searchResult.length === 0) { if (searchResult.length === 0) {
throw throwError( throw new UserError('Tank not found', {
'Tank not found', cause: `Could not find tank by the name "${tank}".`,
`Could not find tank by the name "${tank}".`, });
);
} else { } else {
return searchResult[0].obj.tank_id; return searchResult[0].obj.tank_id;
} }
@@ -23,10 +22,9 @@ export default async function resolveTankId(tank: string | number) {
if ((await tankopedia)[number]) { if ((await tankopedia)[number]) {
return number; return number;
} else { } else {
throw throwError( throw new UserError('Tank not found', {
'Tank not found', cause: `Could not find tank by the ID "${number}".`,
`Could not find tank by the ID "${number}".`, });
);
} }
} }
} }
+20 -18
View File
@@ -1,7 +1,7 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { Clan, ClanList } from '../../types/clanList'; import { Clan, ClanList } from '../../types/clanList';
import fetchBlitz from './fetchWargaming'; import fetchBlitz from './fetchBlitz';
export type ClanListWithRegion = (Clan & { export type ClanListWithRegion = (Clan & {
region: 'com' | 'eu' | 'asia'; region: 'com' | 'eu' | 'asia';
@@ -9,43 +9,45 @@ export type ClanListWithRegion = (Clan & {
export default async function searchClansAcrossRegions( export default async function searchClansAcrossRegions(
search: string, search: string,
limit = 9, limit = 25,
) { ) {
const trimmed = search.trim(); const trimmed = search.trim();
if (trimmed.length < 3 && trimmed.length > 100) return []; if (trimmed.length < 3 && trimmed.length > 100) return [];
const normalizedLimit = Math.round(limit / 3); const normalizedLimit = Math.floor(limit / 3);
// TODO: remove splice once wargaming fixes the functionality of the limit felid
return ( return (
await Promise.all([ await Promise.all([
fetchBlitz<ClanList>( fetchBlitz<ClanList>(
`https://api.wotblitz.com/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`, `https://api.wotblitz.com/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
).then( ).then((value) =>
(value) => value
value && .map((account) => ({
value.map((account) => ({
...account, ...account,
region: 'com' as Region, region: 'com' as Region,
})), }))
.splice(0, normalizedLimit),
), ),
fetchBlitz<ClanList>( fetchBlitz<ClanList>(
`https://api.wotblitz.eu/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`, `https://api.wotblitz.eu/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
).then( ).then((value) =>
(value) => value
value && .map((account) => ({
value.map((account) => ({
...account, ...account,
region: 'eu' as Region, region: 'eu' as Region,
})), }))
.splice(0, normalizedLimit),
), ),
fetchBlitz<ClanList>( fetchBlitz<ClanList>(
`https://api.wotblitz.asia/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`, `https://api.wotblitz.asia/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
).then( ).then((value) =>
(value) => value
value && .map((account) => ({
value.map((account) => ({
...account, ...account,
region: 'asia' as Region, region: 'asia' as Region,
})), }))
.splice(0, normalizedLimit),
), ),
]) ])
) )
@@ -0,0 +1,43 @@
import { Region } from '../../constants/regions';
import { context } from '../blitzkrieg/context';
import regionToRegionSubdomain from './regionToRegionSubdomain';
type CurrentRatingsPlayerSearch = Record<
number,
{
spa_id: number;
nickname: string;
clan_tag: string;
} & (
| {
skip: true;
}
| {
skip: false;
mmr: number;
season_number: number;
calibrationBattlesLeft: number;
number: number;
updated_at: string;
neighbors: [];
}
)
>;
export async function searchCurrentRatingsPlayers(
region: Region,
name: string,
) {
const encodedSearch = encodeURIComponent(name);
const response = await fetch(
context === 'website'
? `/api/${region}/ratings/current/search/${encodedSearch}`
: `https://${regionToRegionSubdomain(
region,
)}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`,
{ cache: 'no-store' },
);
const accountList = (await response.json()) as CurrentRatingsPlayerSearch;
return accountList;
}
+6 -6
View File
@@ -1,13 +1,13 @@
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import fetchBlitz from './fetchWargaming'; import fetchBlitz from './fetchBlitz';
interface Account { export interface AccountListItem {
nickname: string; nickname: string;
account_id: number; account_id: number;
} }
export type AccountList = Account[]; export type AccountList = AccountListItem[];
export type AccountListWithServer = (Account & { export type AccountListWithServer = (AccountListItem & {
region: Region; region: Region;
})[]; })[];
@@ -16,10 +16,10 @@ export const usernamePatternWithoutPosition = /[a-zA-Z0-9_]{3,24}/;
export default async function searchPlayersAcrossRegions( export default async function searchPlayersAcrossRegions(
search: string, search: string,
limit = 9, limit = 25,
) { ) {
const trimmedSearch = search.trim(); const trimmedSearch = search.trim();
const normalizedLimit = Math.round(limit / 3); const normalizedLimit = Math.floor(limit / 3);
const encodedSearch = encodeURIComponent(trimmedSearch); const encodedSearch = encodeURIComponent(trimmedSearch);
if (usernamePattern.test(trimmedSearch)) { if (usernamePattern.test(trimmedSearch)) {
+15
View File
@@ -0,0 +1,15 @@
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import fetchBlitz from './fetchBlitz';
export interface TankopediaInfo {
achievement_sections: Record<string, { name: string; order: number }>;
tanks_updated_at: number;
languages: Record<string, string>;
vehicle_types: Record<string, string>;
vehicle_nations: Record<string, string>;
game_version: string;
}
export const tankopediaInfo = fetchBlitz<TankopediaInfo>(
`https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${WARGAMING_APPLICATION_ID}`,
);
+2 -2
View File
@@ -1,4 +1,4 @@
type Context = 'website' | 'bot'; type Context = 'website' | 'server';
export const context: Context = export const context: Context =
typeof window === 'undefined' ? 'bot' : 'website'; typeof window === 'undefined' ? 'server' : 'website';
+2 -5
View File
@@ -1,10 +1,7 @@
import { TREE_TYPE_NAMES } from '../../components/Tanks'; import { TREE_TYPE_NAMES } from '../../components/Tanks';
import resolveTankName from '../blitz/resolveTankName'; import resolveTankName from '../blitz/resolveTankName';
import { import { tankopediaInfo } from '../blitz/tankopediaInfo';
TIER_ROMAN_NUMERALS, import { TIER_ROMAN_NUMERALS, Tier } from '../blitzstars/tankopedia';
Tier,
tankopediaInfo,
} from '../blitzstars/tankopedia';
import { StatFilters } from '../statistics/filterStats'; import { StatFilters } from '../statistics/filterStats';
export async function filtersToDescription({ export async function filtersToDescription({
@@ -0,0 +1,33 @@
import getRatingsInfo from '../blitz/getRatingsInfo';
let cachedLatestArchivedSeasonNumber: number | null = null;
export async function getArchivedLatestSeasonNumber() {
const ratingsInfo = await getRatingsInfo('com');
if (ratingsInfo.detail) {
const comURL = (
(await fetch(
'https://api.github.com/repos/tresabhi/blitzkrieg-db/git/trees/main',
).then((response) => response.json())) as {
tree: { path: string; url: string }[];
}
).tree.find(({ path }) => path === 'com')!.url;
const ratingsURL = (
(await fetch(comURL).then((response) => response.json())) as {
tree: [{ url: string }];
}
).tree[0].url;
const latestSeason = Math.max(
...(
(await fetch(ratingsURL).then((response) => {
response.json();
})) as { tree: { path: string }[] }
).tree.map(({ path }) => parseInt(path)),
);
return latestSeason;
} else {
return ratingsInfo.current_season - 1;
}
}
+9 -18
View File
@@ -1,10 +1,7 @@
import { RatingsInfo } from '../../commands/ratings'; import { RatingsInfo } from '../../commands/ratings';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { DATABASE_REPO } from './getMidnightLeaderboard';
import { octokit } from './octokit';
import throwError from './throwError';
const ARCHIVED_RATINGS_CACHE: Record< const INFO_CACHE: Record<
Region, Region,
Record< Record<
number, number,
@@ -19,27 +16,21 @@ const ARCHIVED_RATINGS_CACHE: Record<
}; };
export default async function getArchivedRatingsInfo( export default async function getArchivedRatingsInfo(
season: number,
region: Region, region: Region,
season: number,
) { ) {
if (ARCHIVED_RATINGS_CACHE[region][season]) { if (INFO_CACHE[region][season]) {
return ARCHIVED_RATINGS_CACHE[region][season]; return INFO_CACHE[region][season];
} }
const { data } = await octokit.repos.getContent({ const response = await fetch(
...DATABASE_REPO, `https://raw.githubusercontent.com/tresabhi/blitzkrieg-db/main/${region}/ratings/${season}/info.json`,
path: `${region}/ratings/${season}/info.json`, );
}); const jsonContent = (await response.json()) as RatingsInfo & {
if (Array.isArray(data) || data.type !== 'file')
throw throwError('Archived ratings info is malformed');
const content = Buffer.from(data.content, 'base64').toString();
const jsonContent = JSON.parse(content) as RatingsInfo & {
detail: undefined; detail: undefined;
}; };
ARCHIVED_RATINGS_CACHE[region][season] = jsonContent; INFO_CACHE[region][season] = jsonContent;
return jsonContent; return jsonContent;
} }
@@ -0,0 +1,28 @@
import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard';
import { Region } from '../../constants/regions';
const LEADERBOARD_CACHE: Record<
Region,
Record<number, BlitzkriegRatingsLeaderboard>
> = {
asia: {},
eu: {},
com: {},
};
export async function getArchivedRatingsLeaderboard(
region: Region,
season: number,
) {
if (LEADERBOARD_CACHE[region][season]) {
return LEADERBOARD_CACHE[region][season];
}
const response = await fetch(
`https://raw.githubusercontent.com/tresabhi/blitzkrieg-db/main/${region}/ratings/${season}/latest.json`,
);
const json = (await response.json()) as BlitzkriegRatingsLeaderboard;
LEADERBOARD_CACHE[region][season] = json;
return json;
}
@@ -1,6 +1,5 @@
import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard'; import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import { octokit } from './octokit';
export const DATABASE_REPO = { owner: 'tresabhi', repo: 'blitzkrieg-db' }; export const DATABASE_REPO = { owner: 'tresabhi', repo: 'blitzkrieg-db' };
@@ -13,7 +12,7 @@ const MIDNIGHT_LEADERBOARD_CACHE: Record<
asia: {}, asia: {},
}; };
export default async function getMidnightLeaderboard( export default async function getArchivedRatingsMidnightLeaderboard(
region: Region, region: Region,
season: number, season: number,
) { ) {
@@ -22,19 +21,13 @@ export default async function getMidnightLeaderboard(
} }
try { try {
const { data } = await octokit.repos.getContent({ const response = await fetch(
...DATABASE_REPO, `https://raw.githubusercontent.com/tresabhi/blitzkrieg-db/main/${region}/ratings/${season}/midnight.json`,
path: `${region}/ratings/${season}/midnight.json`, );
}); const json = (await response.json()) as BlitzkriegRatingsLeaderboard;
MIDNIGHT_LEADERBOARD_CACHE[region][season] = json;
if (!Array.isArray(data) && data.type === 'file') { return json;
const response = await fetch(data.download_url!);
const json = (await response.json()) as BlitzkriegRatingsLeaderboard;
MIDNIGHT_LEADERBOARD_CACHE[region][season] = json;
return json;
}
} catch (error) { } catch (error) {
console.warn( console.warn(
`No midnight leaderboard found for season ${season}, region ${region}`, `No midnight leaderboard found for season ${season}, region ${region}`,
+21
View File
@@ -0,0 +1,21 @@
export async function patientFetch(
url: string,
timeout = 1000,
options?: Partial<{ cache: 'no-store' }>,
) {
let patientResponse: Response;
while (patientResponse! === undefined) {
try {
patientResponse = await fetch(url, options);
} catch (error) {
console.warn(
`Failed to fetch ${url}; retrying in ${timeout}ms...`,
error,
);
await new Promise((resolve) => setTimeout(resolve, timeout));
}
}
return patientResponse;
}
+23
View File
@@ -0,0 +1,23 @@
export async function patientFetchJSON<Type extends object>(
url: string,
timeout = 1000,
options?: Partial<{ cache: 'no-store' }>,
) {
let patientJSON: Type;
while (patientJSON! === undefined) {
try {
patientJSON = (await fetch(url, options).then(({ json }) =>
json(),
)) as Type;
} catch (error) {
console.warn(
`Failed to fetch ${url} as JSON; retrying in ${timeout}ms...`,
error,
);
await new Promise((resolve) => setTimeout(resolve, timeout));
}
}
return patientJSON;
}
+13 -9
View File
@@ -1,6 +1,6 @@
import { Worker } from 'worker_threads'; import { Worker } from 'worker_threads';
const MAX_WORKERS = 3; const MAX_WORKERS = 2;
const workers = [...Array(MAX_WORKERS)].map(() => { const workers = [...Array(MAX_WORKERS)].map(() => {
const worker = new Worker(`${__dirname}/workers/render.cjs`); const worker = new Worker(`${__dirname}/workers/render.cjs`);
@@ -19,18 +19,22 @@ function manageQueue() {
function cleanup() { function cleanup() {
workers.push(worker); workers.push(worker);
worker.off('message', handleMessage);
worker.off('error', handleError);
manageQueue(); manageQueue();
} }
function handleMessage(png: Uint8Array) {
cleanup();
resolve(Buffer.from(png));
}
function handleError(error: Error) {
cleanup();
throw error;
}
worker.postMessage(svg); worker.postMessage(svg);
worker.on('message', (png: Uint8Array) => { worker.on('message', handleMessage);
cleanup(); worker.on('error', handleError);
resolve(Buffer.from(png));
});
worker.on('error', (error) => {
cleanup();
throw error;
});
} }
} }
-3
View File
@@ -1,3 +0,0 @@
export default function throwError(message: string, cause?: string) {
return new Error(message, { cause });
}
+4
View File
@@ -0,0 +1,4 @@
/**
* Discriminator for user caused errors to not report in logs
*/
export class UserError extends Error {}
+7
View File
@@ -0,0 +1,7 @@
import { context } from './context';
export function withCORSProxy(url: string) {
return context === 'website'
? `https://corsproxy.io/?${encodeURIComponent(url)}`
: url;
}
+4 -4
View File
@@ -85,13 +85,13 @@ tankopedia;
export type TankAverages = Record<number, IndividualTankAverage>; export type TankAverages = Record<number, IndividualTankAverage>;
export const tankAverages = fetch( export const tankAverages = fetch(
context === 'bot' context === 'website'
? 'https://www.blitzstars.com/api/tankaverages.json' ? '/api/tank-averages'
: '/api/tank-averages', : 'https://www.blitzstars.com/api/tankaverages.json',
) )
.then((response) => response.json()) .then((response) => response.json())
.then((json) => { .then((json) => {
if (context === 'bot') { if (context === 'server') {
const partialTankAverages: TankAverages = {}; const partialTankAverages: TankAverages = {};
(json as IndividualTankAverage[]).forEach((individualTankAverage) => { (json as IndividualTankAverage[]).forEach((individualTankAverage) => {
+4 -22
View File
@@ -1,7 +1,4 @@
import { deburr } from 'lodash'; import { deburr } from 'lodash';
import { TreeTypeString } from '../../components/Tanks';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import fetchBlitz from '../blitz/fetchWargaming';
import { context } from '../blitzkrieg/context'; import { context } from '../blitzkrieg/context';
export interface TankopediaEntry { export interface TankopediaEntry {
@@ -16,19 +13,18 @@ export interface TankopediaEntry {
type: string; type: string;
description: string; description: string;
} }
type asd = TreeTypeString;
export interface Tankopedia { export interface Tankopedia {
[id: number]: TankopediaEntry | undefined; [id: number]: TankopediaEntry | undefined;
} }
export const tankopedia = fetch( export const tankopedia = fetch(
context === 'bot' context === 'website'
? 'https://www.blitzstars.com/bs-tankopedia.json' ? '/api/tankopedia'
: '/api/tankopedia', : 'https://www.blitzstars.com/bs-tankopedia.json',
) )
.then(async (response) => response.json()) .then(async (response) => response.json())
.then((wrapperTankopedia) => .then((wrapperTankopedia) =>
context === 'bot' context === 'server'
? (wrapperTankopedia as { data: Tankopedia }).data ? (wrapperTankopedia as { data: Tankopedia }).data
: (wrapperTankopedia as Tankopedia), : (wrapperTankopedia as Tankopedia),
); );
@@ -58,20 +54,6 @@ export const TANK_NAMES_DIACRITICS = TANK_NAMES.then((tankNames) =>
), ),
); );
export interface TankopediaInfo {
achievement_sections: Record<string, { name: string; order: number }>;
tanks_updated_at: number;
languages: Record<string, string>;
vehicle_types: Record<string, string>;
vehicle_nations: Record<string, string>;
game_version: string;
}
// this is blocking because info is needed for command creation
export const tankopediaInfo = fetchBlitz<TankopediaInfo>(
`https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${WARGAMING_APPLICATION_ID}`,
);
export const TANK_ICONS: Record<string, string> = { export const TANK_ICONS: Record<string, string> = {
'AT-SPG': 'https://i.imgur.com/BIHSEH0.png', 'AT-SPG': 'https://i.imgur.com/BIHSEH0.png',
lightTank: 'https://i.imgur.com/CSNha5V.png', lightTank: 'https://i.imgur.com/CSNha5V.png',
+1 -1
View File
@@ -4,7 +4,7 @@ import {
SlashCommandSubcommandGroupBuilder, SlashCommandSubcommandGroupBuilder,
} from 'discord.js'; } from 'discord.js';
import { TREE_TYPE_NAMES, TreeTypeString } from '../../components/Tanks'; import { TREE_TYPE_NAMES, TreeTypeString } from '../../components/Tanks';
import { tankopediaInfo } from '../blitzstars/tankopedia'; import { tankopediaInfo } from '../blitz/tankopediaInfo';
import addPeriodSubCommands from './addPeriodSubCommands'; import addPeriodSubCommands from './addPeriodSubCommands';
import addTankChoices from './addTankChoices'; import addTankChoices from './addTankChoices';
import addTierChoices from './addTierChoices'; import addTierChoices from './addTierChoices';
+1 -3
View File
@@ -3,8 +3,6 @@ import { SlashCommandStringOption } from 'discord.js';
export default function addUsernameChoices(option: SlashCommandStringOption) { export default function addUsernameChoices(option: SlashCommandStringOption) {
return option return option
.setName('username') .setName('username')
.setDescription( .setDescription('The username you use in Blitz')
'The username you use in Blitz (inferred from Discord nickname by default)',
)
.setAutocomplete(true); .setAutocomplete(true);
} }
+23 -10
View File
@@ -3,26 +3,39 @@ import {
AutocompleteInteraction, AutocompleteInteraction,
CacheType, CacheType,
} from 'discord.js'; } from 'discord.js';
import { REGION_NAMES } from '../../constants/regions'; import { REGION_NAMES_SHORT } from '../../constants/regions';
import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions'; import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions';
export const DISCORD_CHOICES_MAX_NAME_SIZE = 25;
export const OVERFLOW_SUFFIX = '…';
export default async function autocompleteClan( export default async function autocompleteClan(
interaction: AutocompleteInteraction<CacheType>, interaction: AutocompleteInteraction<CacheType>,
) { ) {
const focusedOption = interaction.options.getFocused(true); const focusedOption = interaction.options.getFocused(true);
if (focusedOption.name !== 'clan') return; if (focusedOption.name !== 'clan') return;
if (focusedOption.value.length < 2) return interaction.respond([]); if (focusedOption.value.length < 2) return interaction.respond([]);
const players = await searchClansAcrossRegions(focusedOption.value); const clans = await searchClansAcrossRegions(focusedOption.value);
await interaction.respond( await interaction.respond(
players clans
? players.map( ? clans.map((clan) => {
(player) => let name = `${clan.name} (${REGION_NAMES_SHORT[clan.region]})`;
({
name: `${player.name} (${REGION_NAMES[player.region]})`, if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) {
value: `${player.region}/${player.clan_id}`, const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE;
}) satisfies ApplicationCommandOptionChoiceData<string>,
) name = `${clan.name.slice(
0,
clan.name.length - overSize - OVERFLOW_SUFFIX.length,
)}${OVERFLOW_SUFFIX} (${REGION_NAMES_SHORT[clan.region]})`;
}
return {
name,
value: `${clan.region}/${clan.clan_id}`,
} satisfies ApplicationCommandOptionChoiceData<string>;
})
: [], : [],
); );
} }
+21 -7
View File
@@ -5,6 +5,10 @@ import {
} from 'discord.js'; } from 'discord.js';
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
import { TANK_NAMES_DIACRITICS } from '../blitzstars/tankopedia'; import { TANK_NAMES_DIACRITICS } from '../blitzstars/tankopedia';
import {
DISCORD_CHOICES_MAX_NAME_SIZE,
OVERFLOW_SUFFIX,
} from './autocompleteClan';
export default async function autocompleteTanks( export default async function autocompleteTanks(
interaction: AutocompleteInteraction<CacheType>, interaction: AutocompleteInteraction<CacheType>,
@@ -18,13 +22,23 @@ export default async function autocompleteTanks(
go(focusedOption.value, await TANK_NAMES_DIACRITICS, { go(focusedOption.value, await TANK_NAMES_DIACRITICS, {
keys: ['combined'], keys: ['combined'],
limit: 10, limit: 10,
}).map( }).map(async (item) => {
async (item) => let name = item.obj.original;
({
name: item.obj.original, if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) {
value: `${item.obj.id}`, const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE;
}) satisfies ApplicationCommandOptionChoiceData<string>,
), name = `${item.obj.original.slice(
0,
item.obj.original.length - overSize - OVERFLOW_SUFFIX.length,
)}${OVERFLOW_SUFFIX}`;
}
return {
name,
value: `${item.obj.id}`,
} satisfies ApplicationCommandOptionChoiceData<string>;
}),
) )
: [], : [],
); );
+24 -8
View File
@@ -3,8 +3,12 @@ import {
AutocompleteInteraction, AutocompleteInteraction,
CacheType, CacheType,
} from 'discord.js'; } from 'discord.js';
import { REGION_NAMES } from '../../constants/regions'; import { REGION_NAMES_SHORT } from '../../constants/regions';
import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions'; import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions';
import {
DISCORD_CHOICES_MAX_NAME_SIZE,
OVERFLOW_SUFFIX,
} from './autocompleteClan';
export default async function autocompleteUsername( export default async function autocompleteUsername(
interaction: AutocompleteInteraction<CacheType>, interaction: AutocompleteInteraction<CacheType>,
@@ -16,13 +20,25 @@ export default async function autocompleteUsername(
try { try {
await interaction.respond( await interaction.respond(
players players
? players.map( ? players.map((player) => {
(player) => let name = `${player.nickname} (${
({ REGION_NAMES_SHORT[player.region]
name: `${player.nickname} (${REGION_NAMES[player.region]})`, })`;
value: `${player.region}/${player.account_id}`,
}) satisfies ApplicationCommandOptionChoiceData<string>, if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) {
) const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE;
name = `${player.nickname.slice(
0,
player.nickname.length - overSize - OVERFLOW_SUFFIX.length,
)}${OVERFLOW_SUFFIX} (${REGION_NAMES_SHORT[player.region]})`;
}
return {
name,
value: `${player.region}/${player.account_id}`,
} satisfies ApplicationCommandOptionChoiceData<string>;
})
: [], : [],
); );
} catch (error) {} } catch (error) {}
@@ -11,6 +11,7 @@ import jsxToPngThreaded from '../blitzkrieg/jsxToPngThreaded';
export default async function normalizeInteractionReturnable( export default async function normalizeInteractionReturnable(
returnable: InteractionReturnable, returnable: InteractionReturnable,
) { ) {
const images: [number, Buffer][] = [];
const reply: InteractionEditReplyOptions = {}; const reply: InteractionEditReplyOptions = {};
const awaitedReturnable = await returnable; const awaitedReturnable = await returnable;
@@ -19,7 +20,7 @@ export default async function normalizeInteractionReturnable(
: [awaitedReturnable]; : [awaitedReturnable];
await Promise.all( await Promise.all(
normalizedReturnable.map(async (item) => { normalizedReturnable.map(async (item, index) => {
if (typeof item === 'string') { if (typeof item === 'string') {
reply.content = item; reply.content = item;
} else if (item instanceof EmbedBuilder) { } else if (item instanceof EmbedBuilder) {
@@ -38,11 +39,16 @@ export default async function normalizeInteractionReturnable(
return; return;
} else { } else {
const image = await jsxToPngThreaded(item); const image = await jsxToPngThreaded(item);
if (!reply.files) reply.files = []; images.push([index, image]);
reply.files.push(image);
} }
}), }),
); );
if (images.length > 0) {
images.sort(([a], [b]) => b - a);
if (!reply.files) reply.files = [];
images.forEach(([, image]) => reply.files?.push(image));
}
return reply; return reply;
} }
+22 -5
View File
@@ -1,13 +1,30 @@
export interface PSAData { export type PSAData = {
title: string; secret?: boolean;
description: string; links?: {
} title: string;
url: string;
}[];
commands?: string[];
} & (
| {
type: 'image';
image: string;
title?: string;
}
| {
type: 'embed';
title: string;
description: string;
}
);
export interface PSA { export interface PSA {
data: PSAData | null; data: PSAData | null;
} }
export const psa: PSA = { data: null }; export const psa: PSA = {
data: null,
};
fetch( fetch(
'https://gist.githubusercontent.com/tresabhi/ed4b136f08856e615212e573aabe1968/raw', 'https://gist.githubusercontent.com/tresabhi/ed4b136f08856e615212e573aabe1968/raw',
+4 -5
View File
@@ -1,7 +1,7 @@
import { ChatInputCommandInteraction } from 'discord.js'; import { ChatInputCommandInteraction } from 'discord.js';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions'; import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions';
import throwError from '../blitzkrieg/throwError'; import { UserError } from '../blitzkrieg/userError';
import { serverAndIdPattern } from './resolvePlayerFromCommand'; import { serverAndIdPattern } from './resolvePlayerFromCommand';
export default async function resolveClanFromCommand( export default async function resolveClanFromCommand(
@@ -18,10 +18,9 @@ export default async function resolveClanFromCommand(
if (accounts[0]) { if (accounts[0]) {
return { region: accounts[0].region, id: accounts[0].clan_id }; return { region: accounts[0].region, id: accounts[0].clan_id };
} else { } else {
throw throwError( throw new UserError('Could not find clan', {
'Could not find clan', cause: `I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`,
`I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`, });
);
} }
} }
} }
+7 -9
View File
@@ -3,7 +3,7 @@ import markdownEscape from 'markdown-escape';
import { Region } from '../../constants/regions'; import { Region } from '../../constants/regions';
import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions'; import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions';
import { getBlitzFromDiscord } from '../blitzkrieg/discordBlitz'; import { getBlitzFromDiscord } from '../blitzkrieg/discordBlitz';
import throwError from '../blitzkrieg/throwError'; import { UserError } from '../blitzkrieg/userError';
export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/; export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/;
@@ -34,12 +34,11 @@ export default async function resolvePlayerFromCommand(
id: accounts[0].account_id, id: accounts[0].account_id,
} satisfies ResolvedPlayer; } satisfies ResolvedPlayer;
} else { } else {
throw throwError( throw new UserError('Could not find user', {
'Could not find user', cause: `I couldn't find user "${markdownEscape(
`I couldn't find user "${markdownEscape(
commandUsername, commandUsername,
)}". Try picking an user from the search result, typing in a valid username, or using the \`/link\` command.`, )}". Try picking an user from the search result, typing in a valid username, or using the \`/link\` command.`,
); });
} }
} }
} else { } else {
@@ -48,10 +47,9 @@ export default async function resolvePlayerFromCommand(
if (account) { if (account) {
return { region: account.region, id: account.blitz }; return { region: account.region, id: account.blitz };
} else { } else {
throw throwError( throw new UserError('Use the `/link` command', {
"You're not linked", cause: 'Link your Blitz and Discord accounts to get started.',
'Use the `/link` command to link your Discord and Blitz accounts.', });
);
} }
} }
} }
+2 -17
View File
@@ -1,12 +1,5 @@
import { import { AttachmentBuilder, GuildMember, TextChannel } from 'discord.js';
AttachmentBuilder,
EmbedBuilder,
GuildMember,
TextChannel,
} from 'discord.js';
import markdownEscape from 'markdown-escape';
import discord from '../../discord.json' assert { type: 'json' }; import discord from '../../discord.json' assert { type: 'json' };
import { ACCENT_COLOR } from '../constants/colors';
export default async function (member: GuildMember) { export default async function (member: GuildMember) {
if (member.guild.id === discord.sklld_guild_id) { if (member.guild.id === discord.sklld_guild_id) {
@@ -15,15 +8,7 @@ export default async function (member: GuildMember) {
discord.sklld_verify_channel, discord.sklld_verify_channel,
) as TextChannel ) as TextChannel
).send({ ).send({
content: member.toString(), content: `# Welcome, ${member.toString()}!\nPlease use the \`/link\` command like the video below to continue.`,
embeds: [
new EmbedBuilder()
.setColor(ACCENT_COLOR)
.setTitle(`Welcome, ${markdownEscape(member.user.displayName)}`)
.setDescription(
`Welcome to the Skilled server! To continue, please use the \`/link\` command.`,
),
],
files: [new AttachmentBuilder('https://i.imgur.com/2p6GFgC.gif')], files: [new AttachmentBuilder('https://i.imgur.com/2p6GFgC.gif')],
}); });
} }
@@ -1,6 +1,5 @@
import { ButtonInteraction, CacheType } from 'discord.js'; import { ButtonInteraction, CacheType } from 'discord.js';
import { commands } from '..'; import { commands } from '..';
import throwError from '../../../core/blitzkrieg/throwError';
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable'; import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
export default async function handleButton( export default async function handleButton(
@@ -13,8 +12,7 @@ export default async function handleButton(
const button = (await commands)[commandName]?.button; const button = (await commands)[commandName]?.button;
if (!button) { if (!button) {
throwError(`Button handler not found for "${commandName}"`); throw new Error(`Button handler not found for "${commandName}"`);
return;
} }
if (interaction.message.interaction?.user.id !== interaction.user.id) { if (interaction.message.interaction?.user.id !== interaction.user.id) {
@@ -1,16 +1,20 @@
import { import {
ActionRowBuilder, ActionRowBuilder,
AttachmentBuilder,
ButtonBuilder, ButtonBuilder,
ButtonStyle, ButtonStyle,
CacheType, CacheType,
ChatInputCommandInteraction, ChatInputCommandInteraction,
InteractionReplyOptions,
} from 'discord.js'; } from 'discord.js';
import { commands } from '..'; import { commands } from '..';
import discord from '../../../../discord.json' assert { type: 'json' }; import discord from '../../../../discord.json' assert { type: 'json' };
import { psa } from '../../../core/discord/psa'; import { UserError } from '../../../core/blitzkrieg/userError';
import buttonLink from '../../../core/discord/buttonLink';
import embedNegative from '../../../core/discord/embedNegative'; import embedNegative from '../../../core/discord/embedNegative';
import embedWarning from '../../../core/discord/embedWarning'; import embedWarning from '../../../core/discord/embedWarning';
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable'; import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
import { psa } from '../../../core/discord/psa';
export default async function handleChatInputCommand( export default async function handleChatInputCommand(
interaction: ChatInputCommandInteraction<CacheType>, interaction: ChatInputCommandInteraction<CacheType>,
@@ -19,50 +23,122 @@ export default async function handleChatInputCommand(
await interaction.deferReply(); await interaction.deferReply();
if (registry.inPreview && interaction.guildId !== discord.tres_guild_id) {
interaction.editReply({
embeds: [
embedWarning(
`\`/${registry.command.name}\` is in Public Preview`,
'[Join the official Discord server](https://discord.gg/nDt7AjGJQH) to gain early access to this command before it is released to the public.',
),
],
});
return;
}
try { try {
if (registry.inPreview && interaction.guildId !== discord.tres_guild_id) {
interaction.editReply({
embeds: [
embedWarning(
`\`/${registry.command.name}\` is in Public Preview`,
'[Join the official Discord server](https://discord.gg/nDt7AjGJQH) to gain early access to this command before it is released to the public.',
),
],
});
return;
}
const returnable = await registry.handler(interaction); const returnable = await registry.handler(interaction);
if (registry.handlesInteraction) return; if (registry.handlesInteraction) return;
const reply = await normalizeInteractionReturnable(returnable!); const reply = await normalizeInteractionReturnable(returnable!);
if (psa.data) { if (
if (!reply.embeds) reply.embeds = []; psa.data &&
reply.embeds.push(embedWarning(psa.data.title, psa.data.description)); !psa.data.secret &&
(psa.data.commands === undefined ||
psa.data.commands.includes(interaction.commandName))
) {
if (psa.data.type === 'embed') {
if (!reply.embeds) reply.embeds = [];
reply.embeds.push(embedWarning(psa.data.title, psa.data.description));
} else {
if (!reply.files) reply.files = [];
reply.files.push(new AttachmentBuilder(psa.data.image));
reply.content = reply.content ?? psa.data.title;
}
if (psa.data.links) {
if (!reply.components?.[0]) {
reply.components = [new ActionRowBuilder<ButtonBuilder>()];
}
(reply.components[0] as ActionRowBuilder<ButtonBuilder>).addComponents(
psa.data.links.map(({ title, url }) => buttonLink(url, title)),
);
}
} }
await interaction.editReply(reply); await interaction.editReply(reply);
if (
psa.data &&
psa.data.secret &&
(psa.data.commands === undefined ||
psa.data.commands.includes(interaction.commandName))
) {
const followUp: InteractionReplyOptions = {
ephemeral: true,
};
if (psa.data.type === 'embed') {
if (!followUp.embeds) followUp.embeds = [];
followUp.embeds.push(
embedWarning(psa.data.title, psa.data.description),
);
} else {
if (!followUp.files) followUp.files = [];
followUp.files.push(new AttachmentBuilder(psa.data.image));
followUp.content = followUp.content ?? psa.data.title;
}
if (psa.data.links) {
if (!followUp.components?.[0]) {
followUp.components = [new ActionRowBuilder<ButtonBuilder>()];
}
(
followUp.components[0] as ActionRowBuilder<ButtonBuilder>
).addComponents(
psa.data.links.map(({ title, url }) => buttonLink(url, title)),
);
}
interaction.followUp(followUp);
}
} catch (error) { } catch (error) {
const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents( const components = [
new ButtonBuilder() new ActionRowBuilder<ButtonBuilder>().addComponents(
.setLabel('Get Help on Discord Server') new ButtonBuilder()
.setURL('https://discord.gg/nDt7AjGJQH') .setLabel('Get Help on Discord Server')
.setStyle(ButtonStyle.Link), .setURL('https://discord.gg/nDt7AjGJQH')
); .setStyle(ButtonStyle.Link),
),
];
console.error(interaction.commandName, error); if (error instanceof UserError) {
interaction.editReply({
embeds: [
embedNegative(
error.message,
(error.cause as string | undefined) ??
'No further information is available.',
),
],
components,
});
} else {
console.error(interaction.commandName, error);
await interaction.editReply({ interaction.editReply({
embeds: [ embeds: [
embedNegative( embedNegative(
(error as Error).message, 'Blitzkrieg ran into an error!',
`${(error as Error).cause ?? 'No further information is available.'}`, "We're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.",
), ),
], ],
components: [actionRow], components,
}); });
}
} }
} }
+2 -2
View File
@@ -16,6 +16,7 @@ import {
import discord from '../../../discord.json' assert { type: 'json' }; import discord from '../../../discord.json' assert { type: 'json' };
import { aboutCommand } from '../../commands/about'; import { aboutCommand } from '../../commands/about';
import { breakdownCommand } from '../../commands/breakdown'; import { breakdownCommand } from '../../commands/breakdown';
import { creditsCommand } from '../../commands/credits';
import { debugCommand } from '../../commands/debug'; import { debugCommand } from '../../commands/debug';
import { eligibleCommand } from '../../commands/eligible'; import { eligibleCommand } from '../../commands/eligible';
import { evolutionCommand } from '../../commands/evolution'; import { evolutionCommand } from '../../commands/evolution';
@@ -31,7 +32,6 @@ import { searchClansCommand } from '../../commands/searchClans';
import { searchPlayersCommand } from '../../commands/searchPlayers'; import { searchPlayersCommand } from '../../commands/searchPlayers';
import { searchTanksCommand } from '../../commands/searchTanks'; import { searchTanksCommand } from '../../commands/searchTanks';
import { statsCommand } from '../../commands/stats'; import { statsCommand } from '../../commands/stats';
import { todayCommand } from '../../commands/today';
import getClientId from '../../core/blitzkrieg/getClientId'; import getClientId from '../../core/blitzkrieg/getClientId';
import isDev from '../../core/blitzkrieg/isDev'; import isDev from '../../core/blitzkrieg/isDev';
import { secrets } from '../../core/blitzkrieg/secrets'; import { secrets } from '../../core/blitzkrieg/secrets';
@@ -102,7 +102,7 @@ export const COMMANDS_RAW: CommandRegistry[] = [
evolutionCommand, evolutionCommand,
statsCommand, statsCommand,
ratingsCommand, ratingsCommand,
todayCommand, creditsCommand,
]; ];
export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) => export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) =>
+2
View File
@@ -1,6 +1,7 @@
import { import {
amberDark, amberDark,
blueDark, blueDark,
orangeDark,
slate, slate,
slateDark, slateDark,
tealDark, tealDark,
@@ -16,6 +17,7 @@ export const theme = {
...createColors(slate, 'light'), ...createColors(slate, 'light'),
...createColors(amberDark, 'amber'), ...createColors(amberDark, 'amber'),
...createColors(orangeDark, 'orange'),
...createColors(blueDark, 'blue'), ...createColors(blueDark, 'blue'),
...createColors(tealDark, 'teal'), ...createColors(tealDark, 'teal'),
}, },
+5 -5
View File
@@ -4,7 +4,7 @@ import { devtools, persist } from 'zustand/middleware';
import { CustomColumnDisplay } from '../app/tools/session/components/CustomColumn'; import { CustomColumnDisplay } from '../app/tools/session/components/CustomColumn';
import { Region } from '../constants/regions'; import { Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import fetchBlitz from '../core/blitz/fetchWargaming'; import fetchBlitz from '../core/blitz/fetchBlitz';
import { NormalizedTankStats, TanksStats } from '../types/tanksStats'; import { NormalizedTankStats, TanksStats } from '../types/tanksStats';
type Session = ( type Session = (
@@ -21,8 +21,8 @@ type Session = (
time: number; time: number;
} }
) & { ) & {
promptBeforeReset: boolean; resetPrompt: boolean;
showEmbedPrompt: boolean; embedPrompt: boolean;
customColumns: { customColumns: {
display: CustomColumnDisplay; display: CustomColumnDisplay;
showDelta: boolean; showDelta: boolean;
@@ -67,10 +67,10 @@ export const useSession = create<Session>()(
persist( persist(
(set) => ({ (set) => ({
isTracking: false, isTracking: false,
promptBeforeReset: true, resetPrompt: true,
showTotal: true, showTotal: true,
showCareer: true, showCareer: true,
showEmbedPrompt: true, embedPrompt: true,
customColumns: [ customColumns: [
{ display: 'battles', showDelta: false }, { display: 'battles', showDelta: false },
+366 -17
View File
@@ -185,6 +185,16 @@
"@jridgewell/trace-mapping" "^0.3.17" "@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1" jsesc "^2.5.1"
"@babel/generator@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
dependencies:
"@babel/types" "^7.23.0"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-compilation-targets@^7.22.15": "@babel/helper-compilation-targets@^7.22.15":
version "7.22.15" version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52"
@@ -201,13 +211,13 @@
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
"@babel/helper-function-name@^7.22.5": "@babel/helper-function-name@^7.23.0":
version "7.22.5" version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
dependencies: dependencies:
"@babel/template" "^7.22.5" "@babel/template" "^7.22.15"
"@babel/types" "^7.22.5" "@babel/types" "^7.23.0"
"@babel/helper-hoist-variables@^7.22.5": "@babel/helper-hoist-variables@^7.22.5":
version "7.22.5" version "7.22.5"
@@ -291,6 +301,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95"
integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==
"@babel/parser@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
"@babel/plugin-syntax-typescript@^7.20.0": "@babel/plugin-syntax-typescript@^7.20.0":
version "7.22.5" version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272"
@@ -312,7 +327,7 @@
dependencies: dependencies:
regenerator-runtime "^0.14.0" regenerator-runtime "^0.14.0"
"@babel/template@^7.22.15", "@babel/template@^7.22.5": "@babel/template@^7.22.15":
version "7.22.15" version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
@@ -322,18 +337,18 @@
"@babel/types" "^7.22.15" "@babel/types" "^7.22.15"
"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20": "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20":
version "7.22.20" version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.20.tgz#db572d9cb5c79e02d83e5618b82f6991c07584c9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
integrity sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw== integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
dependencies: dependencies:
"@babel/code-frame" "^7.22.13" "@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.22.15" "@babel/generator" "^7.23.0"
"@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.22.5" "@babel/helper-function-name" "^7.23.0"
"@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.22.16" "@babel/parser" "^7.23.0"
"@babel/types" "^7.22.19" "@babel/types" "^7.23.0"
debug "^4.1.0" debug "^4.1.0"
globals "^11.1.0" globals "^11.1.0"
@@ -346,6 +361,15 @@
"@babel/helper-validator-identifier" "^7.22.19" "@babel/helper-validator-identifier" "^7.22.19"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@babel/types@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@discordjs/builders@^1.6.5": "@discordjs/builders@^1.6.5":
version "1.6.5" version "1.6.5"
resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.6.5.tgz#3e23912eaab1d542b61ca0fa7202e5aaef2b7200" resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.6.5.tgz#3e23912eaab1d542b61ca0fa7202e5aaef2b7200"
@@ -1858,6 +1882,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/decompress@^4.2.6":
version "4.2.6"
resolved "https://registry.yarnpkg.com/@types/decompress/-/decompress-4.2.6.tgz#2ae3fb360c4d27593f51293e54990b9e709477ae"
integrity sha512-ULmsMkKxU7aIGbnxP4Rz28hLdRZ4q0cdy6kcb8dx+UgDGOn+id5fibvoeTnjuolhrRM7f7TOtGADeKEk60SSsQ==
dependencies:
"@types/node" "*"
"@types/escape-html@^1.0.2": "@types/escape-html@^1.0.2":
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.2.tgz#072b7b13784fb3cee9c2450c22f36405983f5e3c" resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.2.tgz#072b7b13784fb3cee9c2450c22f36405983f5e3c"
@@ -1888,6 +1919,11 @@
resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.2.tgz#a86e00bbde8950364f8e7846687259ffcd96e8c2" resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.2.tgz#a86e00bbde8950364f8e7846687259ffcd96e8c2"
integrity sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg== integrity sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==
"@types/js-yaml@^4.0.8":
version "4.0.8"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.8.tgz#7574e422d70d4a1b41f517d1d9abc61be2299a97"
integrity sha512-m6jnPk1VhlYRiLFm3f8X9Uep761f+CK8mHyS65LutH2OhmBF0BeMEjHgg05usH8PLZMWWc/BUR9RPmkvpWnyRA==
"@types/linkify-it@*": "@types/linkify-it@*":
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.3.tgz#15a0712296c5041733c79efe233ba17ae5a7587b" resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.3.tgz#15a0712296c5041733c79efe233ba17ae5a7587b"
@@ -2309,6 +2345,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw== integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
before-after-hook@^2.2.0: before-after-hook@^2.2.0:
version "2.2.3" version "2.2.3"
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c"
@@ -2324,6 +2365,14 @@ big.js@^5.2.2:
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
bl@^1.0.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7"
integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==
dependencies:
readable-stream "^2.3.5"
safe-buffer "^5.1.1"
[email protected]: [email protected]:
version "1.20.1" version "1.20.1"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
@@ -2366,11 +2415,42 @@ browserslist@^4.21.9:
node-releases "^2.0.13" node-releases "^2.0.13"
update-browserslist-db "^1.0.13" update-browserslist-db "^1.0.13"
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
buffer-alloc@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
dependencies:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==
buffer-from@^1.0.0: buffer-from@^1.0.0:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
buffer@^5.2.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
[email protected], busboy@^1.6.0: [email protected], busboy@^1.6.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
@@ -2472,6 +2552,11 @@ color-name@^1.1.4, color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
commander@^2.8.1:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^4.1.1: commander@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
@@ -2516,6 +2601,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cross-spawn@^7.0.0: cross-spawn@^7.0.0:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -2578,6 +2668,59 @@ debug@^4.1.0, debug@^4.3.1, debug@^4.3.4:
dependencies: dependencies:
ms "2.1.2" ms "2.1.2"
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==
dependencies:
file-type "^5.2.0"
is-stream "^1.1.0"
tar-stream "^1.5.2"
decompress-tarbz2@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
dependencies:
decompress-tar "^4.1.0"
file-type "^6.1.0"
is-stream "^1.1.0"
seek-bzip "^1.0.5"
unbzip2-stream "^1.0.9"
decompress-targz@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==
dependencies:
decompress-tar "^4.1.1"
file-type "^5.2.0"
is-stream "^1.1.0"
decompress-unzip@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
integrity sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==
dependencies:
file-type "^3.8.0"
get-stream "^2.2.0"
pify "^2.3.0"
yauzl "^2.4.2"
decompress@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.1.tgz#007f55cc6a62c055afa37c07eb6a4ee1b773f118"
integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==
dependencies:
decompress-tar "^4.0.0"
decompress-tarbz2 "^4.0.0"
decompress-targz "^4.0.0"
decompress-unzip "^4.0.1"
graceful-fs "^4.1.10"
make-dir "^1.0.0"
pify "^2.3.0"
strip-dirs "^2.0.0"
[email protected]: [email protected]:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84"
@@ -2706,6 +2849,13 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
end-of-stream@^1.0.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
dependencies:
once "^1.4.0"
[email protected]: [email protected]:
version "0.17.6" version "0.17.6"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.6.tgz#bbccd4433629deb6e0a83860b3b61da120ba4e01" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.6.tgz#bbccd4433629deb6e0a83860b3b61da120ba4e01"
@@ -2883,11 +3033,33 @@ fastq@^1.6.0:
dependencies: dependencies:
reusify "^1.0.4" reusify "^1.0.4"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
dependencies:
pend "~1.2.0"
fflate@^0.7.3: fflate@^0.7.3:
version "0.7.4" version "0.7.4"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50"
integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw== integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
file-type@^3.8.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
integrity sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==
file-type@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
integrity sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==
file-type@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==
fill-range@^7.0.1: fill-range@^7.0.1:
version "7.0.1" version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@@ -2951,6 +3123,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fsevents@~2.3.2: fsevents@~2.3.2:
version "2.3.3" version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
@@ -2986,6 +3163,14 @@ get-nonce@^1.0.0:
resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
get-stream@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
integrity sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==
dependencies:
object-assign "^4.0.1"
pinkie-promise "^2.0.0"
glob-parent@^5.1.2: glob-parent@^5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -3025,7 +3210,7 @@ globby@^13.1.3:
merge2 "^1.4.1" merge2 "^1.4.1"
slash "^4.0.0" slash "^4.0.0"
graceful-fs@^4.1.2: graceful-fs@^4.1.10, graceful-fs@^4.1.2:
version "4.2.11" version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -3090,6 +3275,11 @@ identity-function@^1.0.0:
resolved "https://registry.yarnpkg.com/identity-function/-/identity-function-1.0.0.tgz#bea1159f0985239be3ca348edf40ce2f0dd2c21d" resolved "https://registry.yarnpkg.com/identity-function/-/identity-function-1.0.0.tgz#bea1159f0985239be3ca348edf40ce2f0dd2c21d"
integrity sha512-kNrgUK0qI+9qLTBidsH85HjDLpZfrrS0ElquKKe/fJFdB3D7VeKdXXEvOPDUHSHOzdZKCAAaQIWWyp0l2yq6pw== integrity sha512-kNrgUK0qI+9qLTBidsH85HjDLpZfrrS0ElquKKe/fJFdB3D7VeKdXXEvOPDUHSHOzdZKCAAaQIWWyp0l2yq6pw==
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.1.8, ignore@^5.2.4: ignore@^5.1.8, ignore@^5.2.4:
version "5.2.4" version "5.2.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
@@ -3105,7 +3295,7 @@ indent-string@^4.0.0:
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
[email protected]: [email protected], inherits@~2.0.3:
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -3144,6 +3334,11 @@ is-iterable@^1.1.0:
resolved "https://registry.yarnpkg.com/is-iterable/-/is-iterable-1.1.1.tgz#71f9aa6f113e1d968ebe1d41cff4c8fb23a817bc" resolved "https://registry.yarnpkg.com/is-iterable/-/is-iterable-1.1.1.tgz#71f9aa6f113e1d968ebe1d41cff4c8fb23a817bc"
integrity sha512-EdOZCr0NsGE00Pot+x1ZFx9MJK3C6wy91geZpXwvwexDLJvA4nzYyZf7r+EIwSeVsOLDdBz7ATg9NqKTzuNYuQ== integrity sha512-EdOZCr0NsGE00Pot+x1ZFx9MJK3C6wy91geZpXwvwexDLJvA4nzYyZf7r+EIwSeVsOLDdBz7ATg9NqKTzuNYuQ==
is-natural-number@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
integrity sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==
is-number@^4.0.0: is-number@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
@@ -3159,6 +3354,16 @@ is-plain-object@^5.0.0:
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
isexe@^2.0.0: isexe@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -3325,6 +3530,13 @@ magic-string@^0.30.0:
dependencies: dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15" "@jridgewell/sourcemap-codec" "^1.4.15"
make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
dependencies:
pify "^3.0.0"
map-obj@^2.0.0: map-obj@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9"
@@ -3483,6 +3695,11 @@ npm-normalize-package-bin@^3.0.0:
resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz#25447e32a9a7de1f51362c61a559233b89947832" resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz#25447e32a9a7de1f51362c61a559233b89947832"
integrity sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ== integrity sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==
object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
object-inspect@^1.9.0: object-inspect@^1.9.0:
version "1.12.3" version "1.12.3"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
@@ -3594,6 +3811,11 @@ pathe@^1.1.0, pathe@^1.1.1:
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a" resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a"
integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q== integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
picocolors@^1.0.0: picocolors@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
@@ -3604,6 +3826,28 @@ picomatch@^2.3.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==
dependencies:
pinkie "^2.0.0"
pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==
pkg-types@^1.0.3: pkg-types@^1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868" resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868"
@@ -3660,6 +3904,11 @@ prisma@^5.4.1:
dependencies: dependencies:
"@prisma/engines" "5.4.1" "@prisma/engines" "5.4.1"
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
proxy-addr@~2.0.7: proxy-addr@~2.0.7:
version "2.0.7" version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
@@ -3754,6 +4003,19 @@ read-package-json-fast@^3.0.0:
json-parse-even-better-errors "^3.0.0" json-parse-even-better-errors "^3.0.0"
npm-normalize-package-bin "^3.0.0" npm-normalize-package-bin "^3.0.0"
readable-stream@^2.3.0, readable-stream@^2.3.5:
version "2.3.8"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
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"
regenerator-runtime@^0.14.0: regenerator-runtime@^0.14.0:
version "0.14.0" version "0.14.0"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
@@ -3788,11 +4050,16 @@ run-parallel@^1.1.9:
dependencies: dependencies:
queue-microtask "^1.2.2" queue-microtask "^1.2.2"
[email protected]: [email protected], safe-buffer@^5.1.1:
version "5.2.1" version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
"safer-buffer@>= 2.1.2 < 3": "safer-buffer@>= 2.1.2 < 3":
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@@ -3814,6 +4081,11 @@ satori@^0.10.8:
postcss-value-parser "^4.2.0" postcss-value-parser "^4.2.0"
yoga-wasm-web "^0.3.3" yoga-wasm-web "^0.3.3"
sax@^1.2.4:
version "1.3.0"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
scheduler@^0.23.0: scheduler@^0.23.0:
version "0.23.0" version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
@@ -3821,6 +4093,13 @@ scheduler@^0.23.0:
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
seek-bzip@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4"
integrity sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==
dependencies:
commander "^2.8.1"
semver@^6.3.1: semver@^6.3.1:
version "6.3.1" version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
@@ -3963,6 +4242,13 @@ string.prototype.codepointat@^0.2.1:
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc" resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc"
integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg== integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
name strip-ansi-cjs name strip-ansi-cjs
version "6.0.1" version "6.0.1"
@@ -3978,6 +4264,13 @@ strip-ansi@^7.0.1:
dependencies: dependencies:
ansi-regex "^6.0.1" ansi-regex "^6.0.1"
strip-dirs@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==
dependencies:
is-natural-number "^4.0.1"
strip-json-comments@^5.0.0: strip-json-comments@^5.0.0:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-5.0.1.tgz#0d8b7d01b23848ed7dbdf4baaaa31a8250d8cfa0" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-5.0.1.tgz#0d8b7d01b23848ed7dbdf4baaaa31a8250d8cfa0"
@@ -4014,11 +4307,34 @@ tabbable@^6.2.0:
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
tar-stream@^1.5.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
dependencies:
bl "^1.0.0"
buffer-alloc "^1.2.0"
end-of-stream "^1.0.0"
fs-constants "^1.0.0"
readable-stream "^2.3.0"
to-buffer "^1.1.1"
xtend "^4.0.0"
through@^2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
tiny-inflate@^1.0.0: tiny-inflate@^1.0.0:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==
to-buffer@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
to-fast-properties@^2.0.0: to-fast-properties@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@@ -4083,6 +4399,14 @@ ufo@^1.3.0:
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.0.tgz#c92f8ac209daff607c57bbd75029e190930a0019" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.0.tgz#c92f8ac209daff607c57bbd75029e190930a0019"
integrity sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw== integrity sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==
unbzip2-stream@^1.0.9:
version "1.4.3"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==
dependencies:
buffer "^5.2.1"
through "^2.3.8"
[email protected]: [email protected]:
version "5.22.1" version "5.22.1"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b" resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b"
@@ -4143,6 +4467,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
[email protected]: [email protected]:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
@@ -4294,11 +4623,31 @@ ws@^8.13.0:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==
xml-js@^1.6.11:
version "1.6.11"
resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
dependencies:
sax "^1.2.4"
xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yallist@^3.0.2: yallist@^3.0.2:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yauzl@^2.4.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"
yocto-queue@^0.1.0: yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"