mirror of
				https://github.com/maxswa/osrs-json-hiscores.git
				synced 2025-10-15 10:19:04 +00:00 
			
		
		
		
	Compare commits
	
		
			131 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 4872d04301 | ||
|   | 55312ba0fd | ||
|   | 331a448503 | ||
|   | 84c704a846 | ||
|   | 561495632c | ||
|   | bdfdf1eb14 | ||
|   | e349776060 | ||
|   | 546bc5acf7 | ||
|   | 66528cd9d9 | ||
|   | 99ea3fb722 | ||
|   | 7f16d26e3c | ||
|   | ed9fcf47b3 | ||
|   | bb935bb6c9 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | d046fffa9e | ||
|   | 5483e54d78 | ||
|   | 97532b0c03 | ||
|   | f9a56e2b3a | ||
|   | 9f43f0c39f | ||
|   | d67ffa3e20 | ||
|   | 7bdde9f5d1 | ||
|   | b4a50e69c8 | ||
|   | 7935c5a523 | ||
|   | f1e2155428 | ||
|   | dd90fe1260 | ||
|   | 12c4e530a2 | ||
|   | 5a2b0d9ad8 | ||
|   | c43858eac1 | ||
|   | 0d0081f4f6 | ||
|   | 3570f0f07d | ||
|   | 3ddda54e02 | ||
|   | 388f01522a | ||
|   | 24b3de2a29 | ||
|   | fc7897d4c7 | ||
|   | 72ea31490f | ||
|   | 66825955c1 | ||
|   | 4b4bf8c702 | ||
|   | 53981e7468 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | a90e5b98dd | ||
|   | 16a975f10b | ||
|   | c800501188 | ||
|   | 8a320df015 | ||
|   | 169462c446 | ||
|   | 317a972314 | ||
|   | c558f8402c | ||
|   | 41c6da4d5c | ||
|   | 99ad5eb662 | ||
|   | a8cf1713af | ||
|   | 11391c90f2 | ||
|   | e4d2b10488 | ||
|   | 0cfd0e2471 | ||
|   | 9c284ad4b0 | ||
|   | ec60a72c08 | ||
|   | d45e2efcfd | ||
|   | f80b417121 | ||
|   | 415fe2f02b | ||
|   | 968799176f | ||
|   | f45ae67813 | ||
|   | 7724636b40 | ||
|   | 3954b04acc | ||
|   | ee7389455c | ||
|   | 54c11635e0 | ||
|   | dc799cf363 | ||
|   | d4c7aaff2a | ||
|   | 03c089ebff | ||
|   | 96641d875a | ||
|   | b2bdd3d2ce | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | a4ddfaf199 | ||
|   | efbcdfc709 | ||
|   | fe913aba1f | ||
|   | 1497653bab | ||
|   | f76de38eb1 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 2f1d10b309 | ||
|   | f08424cb81 | ||
|   | 69088db3b6 | ||
|   | 9cc89735cd | ||
|   | a126ae4fc7 | ||
|   | 52f3df1838 | ||
|   | 2891bbddc4 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | e2ea2fe33d | ||
|   | b7ae812f45 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 240628d9cc | ||
|   | b754663f6d | ||
|   | 0678f7c979 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 2a60283a93 | ||
|   | b912731cf9 | ||
|   | cf44c57ec6 | ||
|   | 3f34c9ab61 | ||
|   | 1ef73b6b0c | ||
|   | 08ecf68689 | ||
|   | ab3ee70237 | ||
|   | cad20d41cb | ||
|   | 2877b9c8a1 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | afbb22749a | ||
|   | eebf76a34c | ||
|   | e60442a7ce | ||
|   | 1132a085e1 | ||
|   | a2a34a46e3 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 71a76a12fd | ||
|   | 3a72315498 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | c1c9833ad0 | ||
|   | 9fe03746e1 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 66a4fb47a6 | ||
|   | 83c44148a7 | ||
|   | 44dea6b35c | ||
|   | 0bf491638c | ||
|   | febca534b0 | ||
|   | 8daee5c39b | ||
|   | f98cf8aaaa | ||
|   | 5d9f6a6bac | ||
|   | 8737b50a82 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 1255e7e3d2 | ||
|   | f4135d0b0f | ||
|   | 6135ff459c | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 9fb7e4cebc | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 11350b2444 | ||
|   | e0d6c5b20f | ||
|   | c8a4440ac3 | ||
|   | f3781cf142 | ||
|   | 5fbc04217b | ||
|   | eb5d0ba49f | ||
|   | c68e3bebea | ||
|   | aa6fd6f39a | ||
|   | cc9aae8ced | ||
|   | 12882b4e34 | ||
|   | b17a8b9299 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 50f2c19257 | ||
|   | d892f8e67b | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | af5664ae6e | ||
|   | 20c5c2f730 | ||
|   | 04e59cff04 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | ad2249ba76 | 
							
								
								
									
										12
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,19 +10,19 @@ jobs: | |||||||
|   build: |   build: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v3.3.0 | ||||||
|       - uses: borales/actions-yarn@v2.0.0 |       - uses: borales/actions-yarn@v4.2.0 | ||||||
|         with: |         with: | ||||||
|           cmd: install |           cmd: install | ||||||
|       - uses: borales/actions-yarn@v2.0.0 |       - uses: borales/actions-yarn@v4.2.0 | ||||||
|         with: |         with: | ||||||
|           cmd: lint |           cmd: lint | ||||||
|       - uses: borales/actions-yarn@v2.0.0 |       - uses: borales/actions-yarn@v4.2.0 | ||||||
|         with: |         with: | ||||||
|           cmd: format |           cmd: format | ||||||
|       - uses: borales/actions-yarn@v2.0.0 |       - uses: borales/actions-yarn@v4.2.0 | ||||||
|         with: |         with: | ||||||
|           cmd: build |           cmd: build | ||||||
|       - uses: borales/actions-yarn@v2.0.0 |       - uses: borales/actions-yarn@v4.2.0 | ||||||
|         with: |         with: | ||||||
|           cmd: test |           cmd: test | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,4 @@ | |||||||
| .idea/ | .idea/ | ||||||
| node_modules | node_modules | ||||||
| /lib | /lib | ||||||
|  | .vscode | ||||||
							
								
								
									
										30
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								README.md
									
									
									
									
									
								
							| @@ -3,7 +3,7 @@ | |||||||
| [](https://www.npmjs.com/package/osrs-json-hiscores) | [](https://www.npmjs.com/package/osrs-json-hiscores) | ||||||
| [](https://npm-stat.com/charts.html?package=osrs-json-hiscores) | [](https://npm-stat.com/charts.html?package=osrs-json-hiscores) | ||||||
| [](https://github.com/maxswa/osrs-json-hiscores/blob/master/src/types.ts) | [](https://github.com/maxswa/osrs-json-hiscores/blob/master/src/types.ts) | ||||||
| [](https://github.com/maxswa/osrs-json-hiscores/actions/workflows/main.yml?query=branch%3Amain) | [](https://github.com/maxswa/osrs-json-hiscores/actions/workflows/main.yml?query=branch%3Amain) | ||||||
|  |  | ||||||
| **The Old School Runescape API wrapper that does more!** | **The Old School Runescape API wrapper that does more!** | ||||||
|  |  | ||||||
| @@ -105,11 +105,15 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | |||||||
| ### Minigames | ### Minigames | ||||||
|  |  | ||||||
| | Minigame                        |       Param       | | | Minigame                        |       Param       | | ||||||
| | ---------------------- | :---------------: | | | ------------------------------- | :---------------: | | ||||||
| | Bounty Hunter (Rogue)  |     `rogueBH`     | | | Bounty Hunter (Legacy - Rogue)  |     `rogueBH`     | | ||||||
| | Bounty Hunter (Hunter) |    `hunterBH`     | | | Bounty Hunter (Legacy - Hunter) |    `hunterBH`     | | ||||||
| | Last Man Standing      | `lastManStanding` | | | Bounty Hunter (Rogue)           |    `rogueBHV2`    | | ||||||
|  | | Bounty Hunter (Hunter)          |   `hunterBHV2`    | | ||||||
|  | | LMS - Rank                      | `lastManStanding` | | ||||||
|  | | PvP Arena - Rank                |    `pvpArena`     | | ||||||
| | Soul Wars Zeal                  |  `soulWarsZeal`   | | | Soul Wars Zeal                  |  `soulWarsZeal`   | | ||||||
|  | | Rifts closed                    |   `riftsClosed`   | | ||||||
|  |  | ||||||
| ### Leagues | ### Leagues | ||||||
|  |  | ||||||
| @@ -123,9 +127,11 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | |||||||
| | --------------------------------- | :----------------------------: | | | --------------------------------- | :----------------------------: | | ||||||
| | Abyssal Sire                      |         `abyssalSire`          | | | Abyssal Sire                      |         `abyssalSire`          | | ||||||
| | Alchemical Hydra                  |       `alchemicalHydra`        | | | Alchemical Hydra                  |       `alchemicalHydra`        | | ||||||
|  | | Artio                             |            `artio`             | | ||||||
| | Barrows Chests                    |           `barrows`            | | | Barrows Chests                    |           `barrows`            | | ||||||
| | Bryophyta                         |          `bryophyta`           | | | Bryophyta                         |          `bryophyta`           | | ||||||
| | Callisto                          |           `callisto`           | | | Callisto                          |           `callisto`           | | ||||||
|  | | Calvar'ion                        |          `calvarion`           | | ||||||
| | Cerberus                          |           `cerberus`           | | | Cerberus                          |           `cerberus`           | | ||||||
| | Chambers Of Xeric                 |       `chambersOfXeric`        | | | Chambers Of Xeric                 |       `chambersOfXeric`        | | ||||||
| | Chambers Of Xeric: Challenge Mode | `chambersOfXericChallengeMode` | | | Chambers Of Xeric: Challenge Mode | `chambersOfXericChallengeMode` | | ||||||
| @@ -138,6 +144,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | |||||||
| | Dagannoth Rex                     |         `dagannothRex`         | | | Dagannoth Rex                     |         `dagannothRex`         | | ||||||
| | Dagannoth Supreme                 |       `dagannothSupreme`       | | | Dagannoth Supreme                 |       `dagannothSupreme`       | | ||||||
| | Deranged Archaeologist            |    `derangedArchaeologist`     | | | Deranged Archaeologist            |    `derangedArchaeologist`     | | ||||||
|  | | Duke Sucellus                     |         `dukeSucellus`         | | ||||||
| | General Graardor                  |       `generalGraardor`        | | | General Graardor                  |       `generalGraardor`        | | ||||||
| | Giant Mole                        |          `giantMole`           | | | Giant Mole                        |          `giantMole`           | | ||||||
| | Grotesque Guardians               |      `grotesqueGuardians`      | | | Grotesque Guardians               |      `grotesqueGuardians`      | | ||||||
| @@ -148,19 +155,28 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | |||||||
| | Kreearra                          |           `kreeArra`           | | | Kreearra                          |           `kreeArra`           | | ||||||
| | K'ril Tsutsaroth                  |        `krilTsutsaroth`        | | | K'ril Tsutsaroth                  |        `krilTsutsaroth`        | | ||||||
| | Mimic                             |            `mimic`             | | | Mimic                             |            `mimic`             | | ||||||
| | The Nightmare of Ashihama         |          `nightmare`           | | | Nex                               |             `nex`              | | ||||||
|  | | Nightmare                         |          `nightmare`           | | ||||||
|  | | Phosani's Nightmare               |      `phosanisNightmare`       | | ||||||
| | Obor                              |             `obor`             | | | Obor                              |             `obor`             | | ||||||
|  | | Phantom Muspah                    |        `phantomMuspah`         | | ||||||
| | Sarachnis                         |          `sarachnis`           | | | Sarachnis                         |          `sarachnis`           | | ||||||
| | Scorpia                           |           `scorpia`            | | | Scorpia                           |           `scorpia`            | | ||||||
| | Skotizo                           |           `skotizo`            | | | Skotizo                           |           `skotizo`            | | ||||||
|  | | Spindel                           |           `spindel`            | | ||||||
| | Tempoross                         |          `tempoross`           | | | Tempoross                         |          `tempoross`           | | ||||||
| | The Gauntlet                      |           `gauntlet`           | | | The Gauntlet                      |           `gauntlet`           | | ||||||
| | The Corrupted Gauntlet            |      `corruptedGauntlet`       | | | The Corrupted Gauntlet            |      `corruptedGauntlet`       | | ||||||
|  | | The Leviathan                     |          `leviathan`           | | ||||||
|  | | The Whisperer                     |          `whisperer`           | | ||||||
| | Theatre Of Blood                  |        `theatreOfBlood`        | | | Theatre Of Blood                  |        `theatreOfBlood`        | | ||||||
| | Theatre Of Blood: Hard Mode       |    `theatreOfBloodHardMode`    | | | Theatre Of Blood: Hard Mode       |    `theatreOfBloodHardMode`    | | ||||||
| | Thermonuclear Smoke Devil         |   `thermonuclearSmokeDevil`    | | | Thermonuclear Smoke Devil         |   `thermonuclearSmokeDevil`    | | ||||||
|  | | Tombs of Amascut                  |        `tombsOfAmascut`        | | ||||||
|  | | Tombs of Amascut: Expert Mode     |   `tombsOfAmascutExpertMode`   | | ||||||
| | TzKal-Zuk                         |           `tzKalZuk`           | | | TzKal-Zuk                         |           `tzKalZuk`           | | ||||||
| | TzTok-Jad                         |           `tzTokJad`           | | | TzTok-Jad                         |           `tzTokJad`           | | ||||||
|  | | Vardorvis                         |          `vardorvis`           | | ||||||
| | Venenatis                         |          `venenatis`           | | | Venenatis                         |          `venenatis`           | | ||||||
| | Vetion                            |            `vetion`            | | | Vetion                            |            `vetion`            | | ||||||
| | Vorkath                           |           `vorkath`            | | | Vorkath                           |           `vorkath`            | | ||||||
| @@ -190,7 +206,9 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | |||||||
|     leaguePoints: {}, |     leaguePoints: {}, | ||||||
|     bountyHunter: {}, |     bountyHunter: {}, | ||||||
|     lastManStanding: {}, |     lastManStanding: {}, | ||||||
|  |     pvpArena: {}, | ||||||
|     soulWarsZeal: {}, |     soulWarsZeal: {}, | ||||||
|  |     riftsClosed: {}, | ||||||
|     bosses: {} |     bosses: {} | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1836
									
								
								__tests__/attackTopPage.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1836
									
								
								__tests__/attackTopPage.html
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1917
									
								
								__tests__/b0atyNamePage.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1917
									
								
								__tests__/b0atyNamePage.html
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										98
									
								
								__tests__/b0atyStats.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								__tests__/b0atyStats.csv
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | 3923,2277,792976020 | ||||||
|  | 23024,99,29963318 | ||||||
|  | 12244,99,28197154 | ||||||
|  | 2261,99,134139762 | ||||||
|  | 4074,99,112054504 | ||||||
|  | 7166,99,101083246 | ||||||
|  | 3747,99,15110682 | ||||||
|  | 4516,99,50824811 | ||||||
|  | 126288,99,13353110 | ||||||
|  | 24800,99,16366466 | ||||||
|  | 49293,99,13453226 | ||||||
|  | 20784,99,16654289 | ||||||
|  | 16376,99,19114676 | ||||||
|  | 42069,99,14055874 | ||||||
|  | 9305,99,16527135 | ||||||
|  | 25135,99,14657869 | ||||||
|  | 35831,99,13233130 | ||||||
|  | 6197,99,15739327 | ||||||
|  | 48215,99,13963265 | ||||||
|  | 4786,99,33830972 | ||||||
|  | 3402,99,62531484 | ||||||
|  | 4314,99,22733194 | ||||||
|  | 14826,99,17399368 | ||||||
|  | 1009,99,17989158 | ||||||
|  | -1,-1 | ||||||
|  | 22270,10 | ||||||
|  | 10481,3 | ||||||
|  | 149217,8 | ||||||
|  | 103672,7 | ||||||
|  | 253,9508 | ||||||
|  | 713,825 | ||||||
|  | 71,6000 | ||||||
|  | 1943,1600 | ||||||
|  | 42364,287 | ||||||
|  | 467,436 | ||||||
|  | 1634,360 | ||||||
|  | 12937,2521 | ||||||
|  | -1,-1 | ||||||
|  | 5222,14780 | ||||||
|  | 3007,1060 | ||||||
|  | 2578,2192 | ||||||
|  | 84703,594 | ||||||
|  | 19500,143 | ||||||
|  | 3335,1830 | ||||||
|  | 191697,5 | ||||||
|  | 27457,293 | ||||||
|  | 43607,58 | ||||||
|  | 17384,1905 | ||||||
|  | 171607,33 | ||||||
|  | 2074,501 | ||||||
|  | 78908,50 | ||||||
|  | 135830,26 | ||||||
|  | 48781,287 | ||||||
|  | 2112,2000 | ||||||
|  | 138748,41 | ||||||
|  | 31297,707 | ||||||
|  | 40908,698 | ||||||
|  | 31273,714 | ||||||
|  | 76034,25 | ||||||
|  | 2391,688 | ||||||
|  | 17409,1228 | ||||||
|  | 60841,474 | ||||||
|  | 1895,2140 | ||||||
|  | 19948,159 | ||||||
|  | 1855,2046 | ||||||
|  | 46423,579 | ||||||
|  | 107879,1908 | ||||||
|  | 9791,920 | ||||||
|  | 2050,1444 | ||||||
|  | 848,33 | ||||||
|  | 24797,620 | ||||||
|  | 4157,744 | ||||||
|  | 4154,500 | ||||||
|  | 880,217 | ||||||
|  | 15860,251 | ||||||
|  | 60424,119 | ||||||
|  | 100429,26 | ||||||
|  | 358191,9 | ||||||
|  | 1131,1569 | ||||||
|  | 33908,273 | ||||||
|  | 165651,7 | ||||||
|  | 64278,201 | ||||||
|  | 1533,583 | ||||||
|  | 748,700 | ||||||
|  | 21700,250 | ||||||
|  | 13200,7 | ||||||
|  | 6622,3066 | ||||||
|  | 87093,35 | ||||||
|  | 2449,502 | ||||||
|  | 14541,2 | ||||||
|  | -1,-1 | ||||||
|  | 1048,1432 | ||||||
|  | 138,7096 | ||||||
|  | 58203,51 | ||||||
|  | 13949,2780 | ||||||
|  | 827440,62 | ||||||
|  | 153191,25 | ||||||
|  | 216690,274 | ||||||
| Can't render this file because it has a wrong number of fields in line 25. | 
							
								
								
									
										194
									
								
								__tests__/b0atyStats.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								__tests__/b0atyStats.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,194 @@ | |||||||
|  | { | ||||||
|  |   "skills": [ | ||||||
|  |     { | ||||||
|  |       "id": 0, | ||||||
|  |       "name": "Overall", | ||||||
|  |       "rank": 3923, | ||||||
|  |       "level": 2277, | ||||||
|  |       "xp": 792976020 | ||||||
|  |     }, | ||||||
|  |     { "id": 1, "name": "Attack", "rank": 23024, "level": 99, "xp": 29963318 }, | ||||||
|  |     { "id": 2, "name": "Defence", "rank": 12244, "level": 99, "xp": 28197154 }, | ||||||
|  |     { "id": 3, "name": "Strength", "rank": 2261, "level": 99, "xp": 134139762 }, | ||||||
|  |     { | ||||||
|  |       "id": 4, | ||||||
|  |       "name": "Hitpoints", | ||||||
|  |       "rank": 4074, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 112054504 | ||||||
|  |     }, | ||||||
|  |     { "id": 5, "name": "Ranged", "rank": 7166, "level": 99, "xp": 101083246 }, | ||||||
|  |     { "id": 6, "name": "Prayer", "rank": 3747, "level": 99, "xp": 15110682 }, | ||||||
|  |     { "id": 7, "name": "Magic", "rank": 4516, "level": 99, "xp": 50824811 }, | ||||||
|  |     { "id": 8, "name": "Cooking", "rank": 126288, "level": 99, "xp": 13353110 }, | ||||||
|  |     { | ||||||
|  |       "id": 9, | ||||||
|  |       "name": "Woodcutting", | ||||||
|  |       "rank": 24800, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 16366466 | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "id": 10, | ||||||
|  |       "name": "Fletching", | ||||||
|  |       "rank": 49293, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 13453226 | ||||||
|  |     }, | ||||||
|  |     { "id": 11, "name": "Fishing", "rank": 20784, "level": 99, "xp": 16654289 }, | ||||||
|  |     { | ||||||
|  |       "id": 12, | ||||||
|  |       "name": "Firemaking", | ||||||
|  |       "rank": 16376, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 19114676 | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "id": 13, | ||||||
|  |       "name": "Crafting", | ||||||
|  |       "rank": 42069, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 14055874 | ||||||
|  |     }, | ||||||
|  |     { "id": 14, "name": "Smithing", "rank": 9305, "level": 99, "xp": 16527135 }, | ||||||
|  |     { "id": 15, "name": "Mining", "rank": 25135, "level": 99, "xp": 14657869 }, | ||||||
|  |     { | ||||||
|  |       "id": 16, | ||||||
|  |       "name": "Herblore", | ||||||
|  |       "rank": 35831, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 13233130 | ||||||
|  |     }, | ||||||
|  |     { "id": 17, "name": "Agility", "rank": 6197, "level": 99, "xp": 15739327 }, | ||||||
|  |     { | ||||||
|  |       "id": 18, | ||||||
|  |       "name": "Thieving", | ||||||
|  |       "rank": 48215, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 13963265 | ||||||
|  |     }, | ||||||
|  |     { "id": 19, "name": "Slayer", "rank": 4786, "level": 99, "xp": 33830972 }, | ||||||
|  |     { "id": 20, "name": "Farming", "rank": 3402, "level": 99, "xp": 62531484 }, | ||||||
|  |     { | ||||||
|  |       "id": 21, | ||||||
|  |       "name": "Runecraft", | ||||||
|  |       "rank": 4314, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 22733194 | ||||||
|  |     }, | ||||||
|  |     { "id": 22, "name": "Hunter", "rank": 14826, "level": 99, "xp": 17399368 }, | ||||||
|  |     { | ||||||
|  |       "id": 23, | ||||||
|  |       "name": "Construction", | ||||||
|  |       "rank": 1009, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 17989158 | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "activities": [ | ||||||
|  |     { "id": 0, "name": "League Points", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 1, "name": "Bounty Hunter - Hunter", "rank": 22270, "score": 10 }, | ||||||
|  |     { "id": 2, "name": "Bounty Hunter - Rogue", "rank": 10481, "score": 3 }, | ||||||
|  |     { | ||||||
|  |       "id": 3, | ||||||
|  |       "name": "Bounty Hunter (Legacy) - Hunter", | ||||||
|  |       "rank": 149217, | ||||||
|  |       "score": 8 | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "id": 4, | ||||||
|  |       "name": "Bounty Hunter (Legacy) - Rogue", | ||||||
|  |       "rank": 103672, | ||||||
|  |       "score": 7 | ||||||
|  |     }, | ||||||
|  |     { "id": 5, "name": "Clue Scrolls (all)", "rank": 253, "score": 9508 }, | ||||||
|  |     { "id": 6, "name": "Clue Scrolls (beginner)", "rank": 713, "score": 825 }, | ||||||
|  |     { "id": 7, "name": "Clue Scrolls (easy)", "rank": 71, "score": 6000 }, | ||||||
|  |     { "id": 8, "name": "Clue Scrolls (medium)", "rank": 1943, "score": 1600 }, | ||||||
|  |     { "id": 9, "name": "Clue Scrolls (hard)", "rank": 42364, "score": 287 }, | ||||||
|  |     { "id": 10, "name": "Clue Scrolls (elite)", "rank": 467, "score": 436 }, | ||||||
|  |     { "id": 11, "name": "Clue Scrolls (master)", "rank": 1634, "score": 360 }, | ||||||
|  |     { "id": 12, "name": "LMS - Rank", "rank": 12937, "score": 2521 }, | ||||||
|  |     { "id": 13, "name": "PvP Arena - Rank", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 14, "name": "Soul Wars Zeal", "rank": 5222, "score": 14780 }, | ||||||
|  |     { "id": 15, "name": "Rifts closed", "rank": 3007, "score": 1060 }, | ||||||
|  |     { "id": 16, "name": "Abyssal Sire", "rank": 2578, "score": 2192 }, | ||||||
|  |     { "id": 17, "name": "Alchemical Hydra", "rank": 84703, "score": 594 }, | ||||||
|  |     { "id": 18, "name": "Artio", "rank": 19500, "score": 143 }, | ||||||
|  |     { "id": 19, "name": "Barrows Chests", "rank": 3335, "score": 1830 }, | ||||||
|  |     { "id": 20, "name": "Bryophyta", "rank": 191697, "score": 5 }, | ||||||
|  |     { "id": 21, "name": "Callisto", "rank": 27457, "score": 293 }, | ||||||
|  |     { "id": 22, "name": "Calvar'ion", "rank": 43607, "score": 58 }, | ||||||
|  |     { "id": 23, "name": "Cerberus", "rank": 17384, "score": 1905 }, | ||||||
|  |     { "id": 24, "name": "Chambers of Xeric", "rank": 171607, "score": 33 }, | ||||||
|  |     { | ||||||
|  |       "id": 25, | ||||||
|  |       "name": "Chambers of Xeric: Challenge Mode", | ||||||
|  |       "rank": 2074, | ||||||
|  |       "score": 501 | ||||||
|  |     }, | ||||||
|  |     { "id": 26, "name": "Chaos Elemental", "rank": 78908, "score": 50 }, | ||||||
|  |     { "id": 27, "name": "Chaos Fanatic", "rank": 135830, "score": 26 }, | ||||||
|  |     { "id": 28, "name": "Commander Zilyana", "rank": 48781, "score": 287 }, | ||||||
|  |     { "id": 29, "name": "Corporeal Beast", "rank": 2112, "score": 2000 }, | ||||||
|  |     { "id": 30, "name": "Crazy Archaeologist", "rank": 138748, "score": 41 }, | ||||||
|  |     { "id": 31, "name": "Dagannoth Prime", "rank": 31297, "score": 707 }, | ||||||
|  |     { "id": 32, "name": "Dagannoth Rex", "rank": 40908, "score": 698 }, | ||||||
|  |     { "id": 33, "name": "Dagannoth Supreme", "rank": 31273, "score": 714 }, | ||||||
|  |     { "id": 34, "name": "Deranged Archaeologist", "rank": 76034, "score": 25 }, | ||||||
|  |     { "id": 35, "name": "Duke Sucellus", "rank": 2391, "score": 688 }, | ||||||
|  |     { "id": 36, "name": "General Graardor", "rank": 17409, "score": 1228 }, | ||||||
|  |     { "id": 37, "name": "Giant Mole", "rank": 60841, "score": 474 }, | ||||||
|  |     { "id": 38, "name": "Grotesque Guardians", "rank": 1895, "score": 2140 }, | ||||||
|  |     { "id": 39, "name": "Hespori", "rank": 19948, "score": 159 }, | ||||||
|  |     { "id": 40, "name": "Kalphite Queen", "rank": 1855, "score": 2046 }, | ||||||
|  |     { "id": 41, "name": "King Black Dragon", "rank": 46423, "score": 579 }, | ||||||
|  |     { "id": 42, "name": "Kraken", "rank": 107879, "score": 1908 }, | ||||||
|  |     { "id": 43, "name": "Kree'Arra", "rank": 9791, "score": 920 }, | ||||||
|  |     { "id": 44, "name": "K'ril Tsutsaroth", "rank": 2050, "score": 1444 }, | ||||||
|  |     { "id": 45, "name": "Mimic", "rank": 848, "score": 33 }, | ||||||
|  |     { "id": 46, "name": "Nex", "rank": 24797, "score": 620 }, | ||||||
|  |     { "id": 47, "name": "Nightmare", "rank": 4157, "score": 744 }, | ||||||
|  |     { "id": 48, "name": "Phosani's Nightmare", "rank": 4154, "score": 500 }, | ||||||
|  |     { "id": 49, "name": "Obor", "rank": 880, "score": 217 }, | ||||||
|  |     { "id": 50, "name": "Phantom Muspah", "rank": 15860, "score": 251 }, | ||||||
|  |     { "id": 51, "name": "Sarachnis", "rank": 60424, "score": 119 }, | ||||||
|  |     { "id": 52, "name": "Scorpia", "rank": 100429, "score": 26 }, | ||||||
|  |     { "id": 53, "name": "Skotizo", "rank": 358191, "score": 9 }, | ||||||
|  |     { "id": 54, "name": "Spindel", "rank": 1131, "score": 1569 }, | ||||||
|  |     { "id": 55, "name": "Tempoross", "rank": 33908, "score": 273 }, | ||||||
|  |     { "id": 56, "name": "The Gauntlet", "rank": 165651, "score": 7 }, | ||||||
|  |     { "id": 57, "name": "The Corrupted Gauntlet", "rank": 64278, "score": 201 }, | ||||||
|  |     { "id": 58, "name": "The Leviathan", "rank": 1533, "score": 583 }, | ||||||
|  |     { "id": 59, "name": "The Whisperer", "rank": 748, "score": 700 }, | ||||||
|  |     { "id": 60, "name": "Theatre of Blood", "rank": 21700, "score": 250 }, | ||||||
|  |     { | ||||||
|  |       "id": 61, | ||||||
|  |       "name": "Theatre of Blood: Hard Mode", | ||||||
|  |       "rank": 13200, | ||||||
|  |       "score": 7 | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "id": 62, | ||||||
|  |       "name": "Thermonuclear Smoke Devil", | ||||||
|  |       "rank": 6622, | ||||||
|  |       "score": 3066 | ||||||
|  |     }, | ||||||
|  |     { "id": 63, "name": "Tombs of Amascut", "rank": 87093, "score": 35 }, | ||||||
|  |     { | ||||||
|  |       "id": 64, | ||||||
|  |       "name": "Tombs of Amascut: Expert Mode", | ||||||
|  |       "rank": 2449, | ||||||
|  |       "score": 502 | ||||||
|  |     }, | ||||||
|  |     { "id": 65, "name": "TzKal-Zuk", "rank": 14541, "score": 2 }, | ||||||
|  |     { "id": 66, "name": "TzTok-Jad", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 67, "name": "Vardorvis", "rank": 1048, "score": 1432 }, | ||||||
|  |     { "id": 68, "name": "Venenatis", "rank": 138, "score": 7096 }, | ||||||
|  |     { "id": 69, "name": "Vet'ion", "rank": 58203, "score": 51 }, | ||||||
|  |     { "id": 70, "name": "Vorkath", "rank": 13949, "score": 2780 }, | ||||||
|  |     { "id": 71, "name": "Wintertodt", "rank": 827440, "score": 62 }, | ||||||
|  |     { "id": 72, "name": "Zalcano", "rank": 153191, "score": 25 }, | ||||||
|  |     { "id": 73, "name": "Zulrah", "rank": 216690, "score": 274 } | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -1,82 +0,0 @@ | |||||||
| 1,2277,4600000000 |  | ||||||
| 15,99,200000000 |  | ||||||
| 28,99,200000000 |  | ||||||
| 18,99,200000000 |  | ||||||
| 7,99,200000000 |  | ||||||
| 8,99,200000000 |  | ||||||
| 11,99,200000000 |  | ||||||
| 32,99,200000000 |  | ||||||
| 159,99,200000000 |  | ||||||
| 15,99,200000000 |  | ||||||
| 12,99,200000000 |  | ||||||
| 9,99,200000000 |  | ||||||
| 48,99,200000000 |  | ||||||
| 4,99,200000000 |  | ||||||
| 3,99,200000000 |  | ||||||
| 25,99,200000000 |  | ||||||
| 5,99,200000000 |  | ||||||
| 23,99,200000000 |  | ||||||
| 12,99,200000000 |  | ||||||
| 2,99,200000000 |  | ||||||
| 19,99,200000000 |  | ||||||
| 7,99,200000000 |  | ||||||
| 4,99,200000000 |  | ||||||
| 4,99,200000000 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| 347584,22 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| Can't render this file because it has a wrong number of fields in line 25. | 
| @@ -12,7 +12,10 @@ import { | |||||||
|   getSkillPageURL, |   getSkillPageURL, | ||||||
|   getStatsURL, |   getStatsURL, | ||||||
|   BOSSES, |   BOSSES, | ||||||
|   Boss |   InvalidFormatError, | ||||||
|  |   BH_MODES, | ||||||
|  |   parseJsonStats, | ||||||
|  |   HiscoresResponse | ||||||
| } from '../src/index'; | } from '../src/index'; | ||||||
|  |  | ||||||
| const B0ATY_NAME = 'B0ATY'; | const B0ATY_NAME = 'B0ATY'; | ||||||
| @@ -21,16 +24,20 @@ const LYNX_TITAN_SPACE_NAME = 'lYnX tiTaN'; | |||||||
| const LYNX_TITAN_UNDERSCORE_NAME = 'lYnX_tiTaN'; | const LYNX_TITAN_UNDERSCORE_NAME = 'lYnX_tiTaN'; | ||||||
| const LYNX_TITAN_HYPHEN_NAME = 'lYnX-tiTaN'; | const LYNX_TITAN_HYPHEN_NAME = 'lYnX-tiTaN'; | ||||||
| const LYNX_TITAN_FORMATTED_NAME = 'Lynx Titan'; | const LYNX_TITAN_FORMATTED_NAME = 'Lynx Titan'; | ||||||
| const FYSAD_FORMATTED_NAME = 'Fysad'; |  | ||||||
|  |  | ||||||
| const attackTopPage = readFileSync(`${__dirname}/attackTopPage.html`, 'utf8'); | const attackTopPage = readFileSync(`${__dirname}/attackTopPage.html`, 'utf8'); | ||||||
| const b0atyNamePage = readFileSync(`${__dirname}/b0atyNamePage.html`, 'utf8'); | const b0atyNamePage = readFileSync(`${__dirname}/b0atyNamePage.html`, 'utf8'); | ||||||
| const lynxTitanStats = readFileSync(`${__dirname}/lynxTitanStats.csv`, 'utf8'); | const b0atyStatsCsv = readFileSync(`${__dirname}/b0atyStats.csv`, 'utf8'); | ||||||
|  | const b0atyStatsJson: HiscoresResponse = JSON.parse( | ||||||
|  |   readFileSync(`${__dirname}/b0atyStats.json`, 'utf8') | ||||||
|  | ); | ||||||
|  | const lynxTitanStats = JSON.parse( | ||||||
|  |   readFileSync(`${__dirname}/lynxTitanStats.json`, 'utf8') | ||||||
|  | ); | ||||||
| const lynxTitanNamePage = readFileSync( | const lynxTitanNamePage = readFileSync( | ||||||
|   `${__dirname}/lynxTitanNamePage.html`, |   `${__dirname}/lynxTitanNamePage.html`, | ||||||
|   'utf8' |   'utf8' | ||||||
| ); | ); | ||||||
| const fysadStatsSeasonal = readFileSync(`${__dirname}/fysadStatsSeasonal.csv`, 'utf8'); |  | ||||||
|  |  | ||||||
| jest.spyOn(axios, 'get').mockImplementation((url) => { | jest.spyOn(axios, 'get').mockImplementation((url) => { | ||||||
|   const lynxUrls = [ |   const lynxUrls = [ | ||||||
| @@ -47,12 +54,9 @@ jest.spyOn(axios, 'get').mockImplementation((url) => { | |||||||
|   if (getSkillPageURL('main', 'attack', 1) === url) { |   if (getSkillPageURL('main', 'attack', 1) === url) { | ||||||
|     return Promise.resolve({ data: attackTopPage }); |     return Promise.resolve({ data: attackTopPage }); | ||||||
|   } |   } | ||||||
|   if (getStatsURL('main', LYNX_TITAN_FORMATTED_NAME) === url) { |   if (getStatsURL('main', LYNX_TITAN_FORMATTED_NAME, true) === url) { | ||||||
|     return Promise.resolve({ status: 200, data: lynxTitanStats }); |     return Promise.resolve({ status: 200, data: lynxTitanStats }); | ||||||
|   } |   } | ||||||
|   if (getStatsURL('seasonal', FYSAD_FORMATTED_NAME) === url) { |  | ||||||
|     return Promise.resolve({ status: 200, data: fysadStatsSeasonal }); |  | ||||||
|   } |  | ||||||
|   throw new Error(`No mock response for URL: ${url}`); |   throw new Error(`No mock response for URL: ${url}`); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -82,6 +86,8 @@ test('Parse CSV to json', () => { | |||||||
|   810,99,37688883 |   810,99,37688883 | ||||||
|   92,99,32005622 |   92,99,32005622 | ||||||
|   23423,478 |   23423,478 | ||||||
|  |   89914,35 | ||||||
|  |   99834,25 | ||||||
|   99831,23 |   99831,23 | ||||||
|   89912,37 |   89912,37 | ||||||
|   32,12148 |   32,12148 | ||||||
| @@ -92,12 +98,16 @@ test('Parse CSV to json', () => { | |||||||
|   392,250 |   392,250 | ||||||
|   1,6143 |   1,6143 | ||||||
|   4814,898 |   4814,898 | ||||||
|  |   13,4057 | ||||||
|   37,225 |   37,225 | ||||||
|  |   25,1110 | ||||||
|   382,2780 |   382,2780 | ||||||
|   944,3000 |   944,3000 | ||||||
|  |   704,892 | ||||||
|   1981,1452 |   1981,1452 | ||||||
|   4981,23 |   4981,23 | ||||||
|   888,1046 |   888,1046 | ||||||
|  |   444,792 | ||||||
|   613,4856 |   613,4856 | ||||||
|   102,4038 |   102,4038 | ||||||
|   156,334 |   156,334 | ||||||
| @@ -110,6 +120,7 @@ test('Parse CSV to json', () => { | |||||||
|   4342,1655 |   4342,1655 | ||||||
|   966,2951 |   966,2951 | ||||||
|   10151,1 |   10151,1 | ||||||
|  |   1289,2477 | ||||||
|   1288,2407 |   1288,2407 | ||||||
|   377,4669 |   377,4669 | ||||||
|   545,1567 |   545,1567 | ||||||
| @@ -120,19 +131,28 @@ test('Parse CSV to json', () => { | |||||||
|   625,2391 |   625,2391 | ||||||
|   120,2981 |   120,2981 | ||||||
|   1,109 |   1,109 | ||||||
|  |   99,39002 | ||||||
|   3,22666 |   3,22666 | ||||||
|  |   2,84 | ||||||
|   26,323 |   26,323 | ||||||
|  |   44,6233 | ||||||
|   201,1101 |   201,1101 | ||||||
|   82,3404 |   82,3404 | ||||||
|   5085,61 |   5085,61 | ||||||
|  |   678,903 | ||||||
|   18823,23 |   18823,23 | ||||||
|   63,375 |   63,375 | ||||||
|   2870,6 |   2870,6 | ||||||
|  |   2871,7 | ||||||
|  |   2872,8 | ||||||
|   6984,138 |   6984,138 | ||||||
|   23,923141 |   23,923141 | ||||||
|   4043,2000 |   4043,2000 | ||||||
|  |   4073,1020 | ||||||
|  |   289,13070 | ||||||
|   489,8 |   489,8 | ||||||
|   967,47 |   967,47 | ||||||
|  |   968,48 | ||||||
|   11155,223 |   11155,223 | ||||||
|   1940,272 |   1940,272 | ||||||
|   8623,1340 |   8623,1340 | ||||||
| @@ -169,11 +189,15 @@ test('Parse CSV to json', () => { | |||||||
|     }, |     }, | ||||||
|     leaguePoints: { rank: 23423, score: 478 }, |     leaguePoints: { rank: 23423, score: 478 }, | ||||||
|     bountyHunter: { |     bountyHunter: { | ||||||
|  |       hunterV2: { rank: 89914, score: 35 }, | ||||||
|  |       rogueV2: { rank: 99834, score: 25 }, | ||||||
|       hunter: { rank: 99831, score: 23 }, |       hunter: { rank: 99831, score: 23 }, | ||||||
|       rogue: { rank: 89912, score: 37 } |       rogue: { rank: 89912, score: 37 } | ||||||
|     }, |     }, | ||||||
|     lastManStanding: { rank: 4814, score: 898 }, |     lastManStanding: { rank: 4814, score: 898 }, | ||||||
|  |     pvpArena: { rank: 13, score: 4057 }, | ||||||
|     soulWarsZeal: { rank: 37, score: 225 }, |     soulWarsZeal: { rank: 37, score: 225 }, | ||||||
|  |     riftsClosed: { rank: 25, score: 1110 }, | ||||||
|     clues: { |     clues: { | ||||||
|       all: { rank: 32, score: 12148 }, |       all: { rank: 32, score: 12148 }, | ||||||
|       beginner: { rank: 3105, score: 76 }, |       beginner: { rank: 3105, score: 76 }, | ||||||
| @@ -186,9 +210,11 @@ test('Parse CSV to json', () => { | |||||||
|     bosses: { |     bosses: { | ||||||
|       abyssalSire: { rank: 382, score: 2780 }, |       abyssalSire: { rank: 382, score: 2780 }, | ||||||
|       alchemicalHydra: { rank: 944, score: 3000 }, |       alchemicalHydra: { rank: 944, score: 3000 }, | ||||||
|  |       artio: { rank: 704, score: 892 }, | ||||||
|       barrows: { rank: 1981, score: 1452 }, |       barrows: { rank: 1981, score: 1452 }, | ||||||
|       bryophyta: { rank: 4981, score: 23 }, |       bryophyta: { rank: 4981, score: 23 }, | ||||||
|       callisto: { rank: 888, score: 1046 }, |       callisto: { rank: 888, score: 1046 }, | ||||||
|  |       calvarion: { rank: 444, score: 792 }, | ||||||
|       cerberus: { rank: 613, score: 4856 }, |       cerberus: { rank: 613, score: 4856 }, | ||||||
|       chambersOfXeric: { rank: 102, score: 4038 }, |       chambersOfXeric: { rank: 102, score: 4038 }, | ||||||
|       chambersOfXericChallengeMode: { rank: 156, score: 334 }, |       chambersOfXericChallengeMode: { rank: 156, score: 334 }, | ||||||
| @@ -201,6 +227,7 @@ test('Parse CSV to json', () => { | |||||||
|       dagannothRex: { rank: 4342, score: 1655 }, |       dagannothRex: { rank: 4342, score: 1655 }, | ||||||
|       dagannothSupreme: { rank: 966, score: 2951 }, |       dagannothSupreme: { rank: 966, score: 2951 }, | ||||||
|       derangedArchaeologist: { rank: 10151, score: 1 }, |       derangedArchaeologist: { rank: 10151, score: 1 }, | ||||||
|  |       dukeSucellus: { rank: 1289, score: 2477 }, | ||||||
|       generalGraardor: { rank: 1288, score: 2407 }, |       generalGraardor: { rank: 1288, score: 2407 }, | ||||||
|       giantMole: { rank: 377, score: 4669 }, |       giantMole: { rank: 377, score: 4669 }, | ||||||
|       grotesqueGuardians: { rank: 545, score: 1567 }, |       grotesqueGuardians: { rank: 545, score: 1567 }, | ||||||
| @@ -211,19 +238,28 @@ test('Parse CSV to json', () => { | |||||||
|       kreeArra: { rank: 625, score: 2391 }, |       kreeArra: { rank: 625, score: 2391 }, | ||||||
|       krilTsutsaroth: { rank: 120, score: 2981 }, |       krilTsutsaroth: { rank: 120, score: 2981 }, | ||||||
|       mimic: { rank: 1, score: 109 }, |       mimic: { rank: 1, score: 109 }, | ||||||
|  |       nex: { rank: 99, score: 39002 }, | ||||||
|       nightmare: { rank: 3, score: 22666 }, |       nightmare: { rank: 3, score: 22666 }, | ||||||
|  |       phosanisNightmare: { rank: 2, score: 84 }, | ||||||
|       obor: { rank: 26, score: 323 }, |       obor: { rank: 26, score: 323 }, | ||||||
|  |       phantomMuspah: { rank: 44, score: 6233 }, | ||||||
|       sarachnis: { rank: 201, score: 1101 }, |       sarachnis: { rank: 201, score: 1101 }, | ||||||
|       scorpia: { rank: 82, score: 3404 }, |       scorpia: { rank: 82, score: 3404 }, | ||||||
|       skotizo: { rank: 5085, score: 61 }, |       skotizo: { rank: 5085, score: 61 }, | ||||||
|  |       spindel: { rank: 678, score: 903 }, | ||||||
|       tempoross: { rank: 18823, score: 23 }, |       tempoross: { rank: 18823, score: 23 }, | ||||||
|       gauntlet: { rank: 63, score: 375 }, |       gauntlet: { rank: 63, score: 375 }, | ||||||
|       corruptedGauntlet: { rank: 2870, score: 6 }, |       corruptedGauntlet: { rank: 2870, score: 6 }, | ||||||
|  |       leviathan: { rank: 2871, score: 7 }, | ||||||
|  |       whisperer: { rank: 2872, score: 8 }, | ||||||
|       theatreOfBlood: { rank: 6984, score: 138 }, |       theatreOfBlood: { rank: 6984, score: 138 }, | ||||||
|       theatreOfBloodHardMode: { rank: 23, score: 923141 }, |       theatreOfBloodHardMode: { rank: 23, score: 923141 }, | ||||||
|       thermonuclearSmokeDevil: { rank: 4043, score: 2000 }, |       thermonuclearSmokeDevil: { rank: 4043, score: 2000 }, | ||||||
|  |       tombsOfAmascut: { rank: 4073, score: 1020 }, | ||||||
|  |       tombsOfAmascutExpertMode: { rank: 289, score: 13070 }, | ||||||
|       tzKalZuk: { rank: 489, score: 8 }, |       tzKalZuk: { rank: 489, score: 8 }, | ||||||
|       tzTokJad: { rank: 967, score: 47 }, |       tzTokJad: { rank: 967, score: 47 }, | ||||||
|  |       vardorvis: { rank: 968, score: 48 }, | ||||||
|       venenatis: { rank: 11155, score: 223 }, |       venenatis: { rank: 11155, score: 223 }, | ||||||
|       vetion: { rank: 1940, score: 272 }, |       vetion: { rank: 1940, score: 272 }, | ||||||
|       vorkath: { rank: 8623, score: 1340 }, |       vorkath: { rank: 8623, score: 1340 }, | ||||||
| @@ -236,6 +272,18 @@ test('Parse CSV to json', () => { | |||||||
|   expect(parseStats(csv)).toStrictEqual(expectedOutput); |   expect(parseStats(csv)).toStrictEqual(expectedOutput); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | test('Parse CSV with unknown activity', () => { | ||||||
|  |   const statsWithUnknownActivity = `${lynxTitanStats} | ||||||
|  |     -1,-1`; | ||||||
|  |   expect(() => parseStats(statsWithUnknownActivity)).toThrow( | ||||||
|  |     InvalidFormatError | ||||||
|  |   ); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('Parse invalid CSV', () => { | ||||||
|  |   expect(() => parseStats('invalid')).toThrow(InvalidFormatError); | ||||||
|  | }); | ||||||
|  |  | ||||||
| describe('Get name format', () => { | describe('Get name format', () => { | ||||||
|   it('gets a name with a space', async () => { |   it('gets a name with a space', async () => { | ||||||
|     const data = await getRSNFormat(LYNX_TITAN_SPACE_NAME); |     const data = await getRSNFormat(LYNX_TITAN_SPACE_NAME); | ||||||
| @@ -448,7 +496,9 @@ test('Get non-existent player', async () => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| test('Get stats by gamemode', async () => { | test('Get stats by gamemode', async () => { | ||||||
|   const { skills, bosses } = await getStatsByGamemode(LYNX_TITAN_FORMATTED_NAME); |   const { skills, bosses, bountyHunter } = await getStatsByGamemode( | ||||||
|  |     LYNX_TITAN_FORMATTED_NAME | ||||||
|  |   ); | ||||||
|  |  | ||||||
|   expect(skills).toMatchObject({ |   expect(skills).toMatchObject({ | ||||||
|     overall: { rank: expect.any(Number), level: 2277, xp: 4600000000 }, |     overall: { rank: expect.any(Number), level: 2277, xp: 4600000000 }, | ||||||
| @@ -479,18 +529,67 @@ test('Get stats by gamemode', async () => { | |||||||
|  |  | ||||||
|   const bossKeys = Object.keys(bosses); |   const bossKeys = Object.keys(bosses); | ||||||
|   expect(bossKeys).toStrictEqual(BOSSES); |   expect(bossKeys).toStrictEqual(BOSSES); | ||||||
|  |   const bountyHunterKeys = Object.keys(bountyHunter); | ||||||
|  |   expect(bountyHunterKeys).toStrictEqual(BH_MODES); | ||||||
|  |  | ||||||
|   expect.assertions(2); |   expect.assertions(3); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| test('Get stats by game mode seasonal (omit TOB: Hard Mode from bosses)', async () => { | describe('Get stats options', () => { | ||||||
|   const {bosses} = await getStatsByGamemode(FYSAD_FORMATTED_NAME, 'seasonal'); |   const rsn = 'player'; | ||||||
|   const bossKeys = Object.keys(bosses); |   let axiosMock: jest.Mock; | ||||||
|  |   beforeEach(() => { | ||||||
|   const filteredBosses = BOSSES.filter(boss => boss !== 'theatreOfBloodHardMode'); |     axios.get = jest.fn( | ||||||
|  |       (url) => | ||||||
|   expect(bossKeys).toStrictEqual(filteredBosses); |         new Promise<any>((resolve) => { | ||||||
|   expect(bossKeys).not.toContain<Boss>('theatreOfBloodHardMode'); |           resolve( | ||||||
|  |             url === getPlayerTableURL('main', rsn) | ||||||
|   expect.assertions(2); |               ? { data: lynxTitanNamePage } | ||||||
|  |               : { status: 200, data: lynxTitanStats } | ||||||
|  |           ); | ||||||
|  |         }) | ||||||
|  |     ); | ||||||
|  |     axiosMock = axios.get as any; | ||||||
|  |     axiosMock.mockClear(); | ||||||
|  |   }); | ||||||
|  |   it('fetches all gamemodes and formatted RSN when no options provided', async () => { | ||||||
|  |     await getStats(rsn); | ||||||
|  |     expect(axiosMock.mock.calls.map((val) => val[0])).toEqual([ | ||||||
|  |       getStatsURL('main', rsn, true), | ||||||
|  |       getPlayerTableURL('main', rsn), | ||||||
|  |       getStatsURL('ironman', rsn, true), | ||||||
|  |       getStatsURL('hardcore', rsn, true), | ||||||
|  |       getStatsURL('ultimate', rsn, true) | ||||||
|  |     ]); | ||||||
|  |   }); | ||||||
|  |   it('skips fetching formatted RSN when option is provided', async () => { | ||||||
|  |     await getStats(rsn, { shouldGetFormattedRsn: false }); | ||||||
|  |     expect( | ||||||
|  |       axiosMock.mock.calls.some( | ||||||
|  |         (val) => val[0] === getPlayerTableURL('main', rsn) | ||||||
|  |       ) | ||||||
|  |     ).toBeFalsy(); | ||||||
|  |   }); | ||||||
|  |   it('skips fetching game mode when option is provided', async () => { | ||||||
|  |     await getStats(rsn, { | ||||||
|  |       otherGamemodes: ['ironman', 'ultimate'] | ||||||
|  |     }); | ||||||
|  |     expect( | ||||||
|  |       axiosMock.mock.calls.some( | ||||||
|  |         (val) => val[0] === getStatsURL('hardcore', rsn) | ||||||
|  |       ) | ||||||
|  |     ).toBeFalsy(); | ||||||
|  |   }); | ||||||
|  |   it('omits excluded gamemodes', async () => { | ||||||
|  |     const response = await getStats(rsn, { | ||||||
|  |       otherGamemodes: ['ironman', 'ultimate'] | ||||||
|  |     }); | ||||||
|  |     expect(response.hardcore).toBeUndefined(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('CSV and JSON parsing outputs identical object', async () => { | ||||||
|  |   const csvOutput = parseStats(b0atyStatsCsv); | ||||||
|  |   const jsonOutput = parseJsonStats(b0atyStatsJson); | ||||||
|  |   expect(csvOutput).toEqual(jsonOutput); | ||||||
| }); | }); | ||||||
|   | |||||||
							
								
								
									
										1904
									
								
								__tests__/lynxTitanNamePage.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1904
									
								
								__tests__/lynxTitanNamePage.html
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,82 +0,0 @@ | |||||||
| 1,2277,4600000000 |  | ||||||
| 15,99,200000000 |  | ||||||
| 28,99,200000000 |  | ||||||
| 18,99,200000000 |  | ||||||
| 7,99,200000000 |  | ||||||
| 8,99,200000000 |  | ||||||
| 11,99,200000000 |  | ||||||
| 32,99,200000000 |  | ||||||
| 159,99,200000000 |  | ||||||
| 15,99,200000000 |  | ||||||
| 12,99,200000000 |  | ||||||
| 9,99,200000000 |  | ||||||
| 48,99,200000000 |  | ||||||
| 4,99,200000000 |  | ||||||
| 3,99,200000000 |  | ||||||
| 25,99,200000000 |  | ||||||
| 5,99,200000000 |  | ||||||
| 23,99,200000000 |  | ||||||
| 12,99,200000000 |  | ||||||
| 2,99,200000000 |  | ||||||
| 19,99,200000000 |  | ||||||
| 7,99,200000000 |  | ||||||
| 4,99,200000000 |  | ||||||
| 4,99,200000000 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| 347584,22 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| -1,-1 |  | ||||||
| Can't render this file because it has a wrong number of fields in line 25. | 
							
								
								
									
										147
									
								
								__tests__/lynxTitanStats.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								__tests__/lynxTitanStats.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | |||||||
|  | { | ||||||
|  |   "skills": [ | ||||||
|  |     { "id": 0, "name": "Overall", "rank": 1, "level": 2277, "xp": 4600000000 }, | ||||||
|  |     { "id": 1, "name": "Attack", "rank": 15, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 2, "name": "Defence", "rank": 28, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 3, "name": "Strength", "rank": 18, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 4, "name": "Hitpoints", "rank": 7, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 5, "name": "Ranged", "rank": 8, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 6, "name": "Prayer", "rank": 11, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 7, "name": "Magic", "rank": 32, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 8, "name": "Cooking", "rank": 157, "level": 99, "xp": 200000000 }, | ||||||
|  |     { | ||||||
|  |       "id": 9, | ||||||
|  |       "name": "Woodcutting", | ||||||
|  |       "rank": 15, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 200000000 | ||||||
|  |     }, | ||||||
|  |     { "id": 10, "name": "Fletching", "rank": 12, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 11, "name": "Fishing", "rank": 9, "level": 99, "xp": 200000000 }, | ||||||
|  |     { | ||||||
|  |       "id": 12, | ||||||
|  |       "name": "Firemaking", | ||||||
|  |       "rank": 48, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 200000000 | ||||||
|  |     }, | ||||||
|  |     { "id": 13, "name": "Crafting", "rank": 4, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 14, "name": "Smithing", "rank": 3, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 15, "name": "Mining", "rank": 25, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 16, "name": "Herblore", "rank": 5, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 17, "name": "Agility", "rank": 24, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 18, "name": "Thieving", "rank": 12, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 19, "name": "Slayer", "rank": 2, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 20, "name": "Farming", "rank": 19, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 21, "name": "Runecraft", "rank": 7, "level": 99, "xp": 200000000 }, | ||||||
|  |     { "id": 22, "name": "Hunter", "rank": 4, "level": 99, "xp": 200000000 }, | ||||||
|  |     { | ||||||
|  |       "id": 23, | ||||||
|  |       "name": "Construction", | ||||||
|  |       "rank": 4, | ||||||
|  |       "level": 99, | ||||||
|  |       "xp": 200000000 | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "activities": [ | ||||||
|  |     { "id": 0, "name": "League Points", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 1, "name": "Bounty Hunter - Hunter", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 2, "name": "Bounty Hunter - Rogue", "rank": -1, "score": -1 }, | ||||||
|  |     { | ||||||
|  |       "id": 3, | ||||||
|  |       "name": "Bounty Hunter (Legacy) - Hunter", | ||||||
|  |       "rank": -1, | ||||||
|  |       "score": -1 | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "id": 4, | ||||||
|  |       "name": "Bounty Hunter (Legacy) - Rogue", | ||||||
|  |       "rank": -1, | ||||||
|  |       "score": -1 | ||||||
|  |     }, | ||||||
|  |     { "id": 5, "name": "Clue Scrolls (all)", "rank": 769527, "score": 22 }, | ||||||
|  |     { "id": 6, "name": "Clue Scrolls (beginner)", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 7, "name": "Clue Scrolls (easy)", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 8, "name": "Clue Scrolls (medium)", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 9, "name": "Clue Scrolls (hard)", "rank": 483333, "score": 22 }, | ||||||
|  |     { "id": 10, "name": "Clue Scrolls (elite)", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 11, "name": "Clue Scrolls (master)", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 12, "name": "LMS - Rank", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 13, "name": "PvP Arena - Rank", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 14, "name": "Soul Wars Zeal", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 15, "name": "Rifts closed", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 16, "name": "Abyssal Sire", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 17, "name": "Alchemical Hydra", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 18, "name": "Artio", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 19, "name": "Barrows Chests", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 20, "name": "Bryophyta", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 21, "name": "Callisto", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 22, "name": "Calvar'ion", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 23, "name": "Cerberus", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 24, "name": "Chambers of Xeric", "rank": -1, "score": -1 }, | ||||||
|  |     { | ||||||
|  |       "id": 25, | ||||||
|  |       "name": "Chambers of Xeric: Challenge Mode", | ||||||
|  |       "rank": -1, | ||||||
|  |       "score": -1 | ||||||
|  |     }, | ||||||
|  |     { "id": 26, "name": "Chaos Elemental", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 27, "name": "Chaos Fanatic", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 28, "name": "Commander Zilyana", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 29, "name": "Corporeal Beast", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 30, "name": "Crazy Archaeologist", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 31, "name": "Dagannoth Prime", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 32, "name": "Dagannoth Rex", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 33, "name": "Dagannoth Supreme", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 34, "name": "Deranged Archaeologist", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 35, "name": "Duke Sucellus", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 36, "name": "General Graardor", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 37, "name": "Giant Mole", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 38, "name": "Grotesque Guardians", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 39, "name": "Hespori", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 40, "name": "Kalphite Queen", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 41, "name": "King Black Dragon", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 42, "name": "Kraken", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 43, "name": "Kree'Arra", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 44, "name": "K'ril Tsutsaroth", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 45, "name": "Mimic", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 46, "name": "Nex", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 47, "name": "Nightmare", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 48, "name": "Phosani's Nightmare", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 49, "name": "Obor", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 50, "name": "Phantom Muspah", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 51, "name": "Sarachnis", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 52, "name": "Scorpia", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 53, "name": "Skotizo", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 54, "name": "Spindel", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 55, "name": "Tempoross", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 56, "name": "The Gauntlet", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 57, "name": "The Corrupted Gauntlet", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 58, "name": "The Leviathan", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 59, "name": "The Whisperer", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 60, "name": "Theatre of Blood", "rank": -1, "score": -1 }, | ||||||
|  |     { | ||||||
|  |       "id": 61, | ||||||
|  |       "name": "Theatre of Blood: Hard Mode", | ||||||
|  |       "rank": -1, | ||||||
|  |       "score": -1 | ||||||
|  |     }, | ||||||
|  |     { "id": 62, "name": "Thermonuclear Smoke Devil", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 63, "name": "Tombs of Amascut", "rank": -1, "score": -1 }, | ||||||
|  |     { | ||||||
|  |       "id": 64, | ||||||
|  |       "name": "Tombs of Amascut: Expert Mode", | ||||||
|  |       "rank": -1, | ||||||
|  |       "score": -1 | ||||||
|  |     }, | ||||||
|  |     { "id": 65, "name": "TzKal-Zuk", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 66, "name": "TzTok-Jad", "rank": 198, "score": 186 }, | ||||||
|  |     { "id": 67, "name": "Vardorvis", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 68, "name": "Venenatis", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 69, "name": "Vet'ion", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 70, "name": "Vorkath", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 71, "name": "Wintertodt", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 72, "name": "Zalcano", "rank": -1, "score": -1 }, | ||||||
|  |     { "id": 73, "name": "Zulrah", "rank": -1, "score": -1 } | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										51
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "osrs-json-hiscores", |   "name": "osrs-json-hiscores", | ||||||
|   "version": "2.5.1", |   "version": "2.16.2", | ||||||
|   "description": "The Old School Runescape API wrapper that does more!", |   "description": "The Old School Runescape API wrapper that does more!", | ||||||
|   "main": "lib/index.js", |   "main": "lib/index.js", | ||||||
|   "types": "lib/index.d.ts", |   "types": "lib/index.d.ts", | ||||||
| @@ -30,6 +30,9 @@ | |||||||
|     "stats", |     "stats", | ||||||
|     "skills" |     "skills" | ||||||
|   ], |   ], | ||||||
|  |   "publishConfig": { | ||||||
|  |     "registry": "https://registry.npmjs.org" | ||||||
|  |   }, | ||||||
|   "author": "maxswa", |   "author": "maxswa", | ||||||
|   "license": "ISC", |   "license": "ISC", | ||||||
|   "bugs": { |   "bugs": { | ||||||
| @@ -58,18 +61,23 @@ | |||||||
|       "@typescript-eslint" |       "@typescript-eslint" | ||||||
|     ], |     ], | ||||||
|     "extends": [ |     "extends": [ | ||||||
|       "eslint-config-airbnb-typescript", |       "airbnb-base", | ||||||
|  |       "airbnb-typescript/base", | ||||||
|       "prettier" |       "prettier" | ||||||
|     ], |     ], | ||||||
|     "ignorePatterns": [ |     "ignorePatterns": [ | ||||||
|       "**/@types/*" |       "**/@types/*" | ||||||
|     ] |     ], | ||||||
|  |     "rules": { | ||||||
|  |       "max-classes-per-file": "off" | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
|   "prettier": { |   "prettier": { | ||||||
|     "trailingComma": "none", |     "trailingComma": "none", | ||||||
|     "tabWidth": 2, |     "tabWidth": 2, | ||||||
|     "semi": true, |     "semi": true, | ||||||
|     "singleQuote": true |     "singleQuote": true, | ||||||
|  |     "endOfLine": "auto" | ||||||
|   }, |   }, | ||||||
|   "jest": { |   "jest": { | ||||||
|     "transform": { |     "transform": { | ||||||
| @@ -87,28 +95,25 @@ | |||||||
|     "testEnvironment": "node" |     "testEnvironment": "node" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "axios": "^0.21.1", |     "axios": "^1.6.2", | ||||||
|     "jsdom": "^16.3.0", |     "jsdom": "^22.1.0", | ||||||
|     "useragent-generator": "^1.1.0" |     "useragent-generator": "^1.1.0" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/jest": "^26.0.21", |     "@types/jest": "^29.5.3", | ||||||
|     "@types/jsdom": "^16.2.3", |     "@types/jsdom": "^21.1.1", | ||||||
|     "@typescript-eslint/eslint-plugin": "^4.19.0", |     "@typescript-eslint/eslint-plugin": "^6.0.0", | ||||||
|     "@typescript-eslint/parser": "^4.19.0", |     "@typescript-eslint/parser": "^6.0.0", | ||||||
|     "eslint": "^7.22.0", |     "eslint": "^8.44.0", | ||||||
|     "eslint-config-airbnb-typescript": "^12.3.1", |     "eslint-config-airbnb-typescript": "^17.1.0", | ||||||
|     "eslint-config-prettier": "^8.1.0", |     "eslint-config-prettier": "^8.8.0", | ||||||
|     "eslint-plugin-import": "^2.22.1", |     "eslint-plugin-import": "^2.27.5", | ||||||
|     "eslint-plugin-jsx-a11y": "^6.4.1", |  | ||||||
|     "eslint-plugin-react": "^7.23.1", |  | ||||||
|     "eslint-plugin-react-hooks": "^4.2.0", |  | ||||||
|     "husky": "^5.2.0", |     "husky": "^5.2.0", | ||||||
|     "jest": "^26.6.3", |     "jest": "^29.6.1", | ||||||
|     "lint-staged": "^10.5.4", |     "lint-staged": "^13.2.3", | ||||||
|     "np": "6.5.0", |     "np": "^7.7.0", | ||||||
|     "prettier": "^2.2.1", |     "prettier": "^3.0.0", | ||||||
|     "ts-jest": "^26.5.4", |     "ts-jest": "^29.1.1", | ||||||
|     "typescript": "^4.2.3" |     "typescript": "^5.1.6" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										275
									
								
								src/hiscores.ts
									
									
									
									
									
								
							
							
						
						
									
										275
									
								
								src/hiscores.ts
									
									
									
									
									
								
							| @@ -1,4 +1,5 @@ | |||||||
| import { JSDOM } from 'jsdom'; | import axios, { AxiosRequestConfig } from 'axios'; | ||||||
|  | import { BinaryData, JSDOM } from 'jsdom'; | ||||||
| import { | import { | ||||||
|   Player, |   Player, | ||||||
|   Activity, |   Activity, | ||||||
| @@ -12,7 +13,9 @@ import { | |||||||
|   PlayerSkillRow, |   PlayerSkillRow, | ||||||
|   ActivityName, |   ActivityName, | ||||||
|   PlayerActivityRow, |   PlayerActivityRow, | ||||||
|   Bosses |   Bosses, | ||||||
|  |   GetStatsOptions, | ||||||
|  |   HiscoresResponse | ||||||
| } from './types'; | } from './types'; | ||||||
| import { | import { | ||||||
|   getStatsURL, |   getStatsURL, | ||||||
| @@ -27,53 +30,171 @@ import { | |||||||
|   rsnFromElement, |   rsnFromElement, | ||||||
|   getActivityPageURL, |   getActivityPageURL, | ||||||
|   httpGet, |   httpGet, | ||||||
|   BOSSES |   BOSSES, | ||||||
|  |   InvalidFormatError, | ||||||
|  |   PlayerNotFoundError, | ||||||
|  |   HiScoresError, | ||||||
|  |   validateRSN, | ||||||
|  |   FORMATTED_SKILL_NAMES, | ||||||
|  |   FORMATTED_BH_NAMES, | ||||||
|  |   FORMATTED_CLUE_NAMES, | ||||||
|  |   FORMATTED_BOSS_NAMES, | ||||||
|  |   FORMATTED_LEAGUE_POINTS, | ||||||
|  |   FORMATTED_LMS, | ||||||
|  |   FORMATTED_PVP_ARENA, | ||||||
|  |   FORMATTED_SOUL_WARS, | ||||||
|  |   FORMATTED_RIFTS_CLOSED | ||||||
| } from './utils'; | } from './utils'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Gets a player's stats from the official OSRS JSON endpoint. | ||||||
|  |  * | ||||||
|  |  * @param rsn Username of the player. | ||||||
|  |  * @param mode Gamemode to fetch ranks for. | ||||||
|  |  * @param config Optional axios request config object. | ||||||
|  |  * @returns Official JSON stats object. | ||||||
|  |  */ | ||||||
|  | export async function getOfficialStats( | ||||||
|  |   rsn: string, | ||||||
|  |   mode: Gamemode = 'main', | ||||||
|  |   config?: AxiosRequestConfig | ||||||
|  | ): Promise<HiscoresResponse> { | ||||||
|  |   validateRSN(rsn); | ||||||
|  |  | ||||||
|  |   const url = getStatsURL(mode, rsn, true); | ||||||
|  |   try { | ||||||
|  |     const response = await httpGet<HiscoresResponse>(url, config); | ||||||
|  |     return response.data; | ||||||
|  |   } catch (err) { | ||||||
|  |     if (!axios.isAxiosError(err)) throw err; | ||||||
|  |  | ||||||
|  |     if (err.response?.status === 404) throw new PlayerNotFoundError(); | ||||||
|  |  | ||||||
|  |     throw new HiScoresError(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Screen scrapes the hiscores to get the formatted rsn of a player. |  * Screen scrapes the hiscores to get the formatted rsn of a player. | ||||||
|  * |  * | ||||||
|  * @param rsn Username of the player. |  * @param rsn Username of the player. | ||||||
|  |  * @param config Optional axios request config object. | ||||||
|  * @returns Formatted version of the rsn. |  * @returns Formatted version of the rsn. | ||||||
|  */ |  */ | ||||||
| export async function getRSNFormat(rsn: string): Promise<string> { | export async function getRSNFormat( | ||||||
|   if (typeof rsn !== 'string') { |   rsn: string, | ||||||
|     throw Error('RSN must be a string'); |   config?: AxiosRequestConfig | ||||||
|   } else if (!/^[a-zA-Z0-9 _-]+$/.test(rsn)) { | ): Promise<string> { | ||||||
|     throw Error('RSN contains invalid character'); |   validateRSN(rsn); | ||||||
|   } else if (rsn.length > 12 || rsn.length < 1) { |  | ||||||
|     throw Error('RSN must be between 1 and 12 characters'); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   const url = getPlayerTableURL('main', rsn); |   const url = getPlayerTableURL('main', rsn); | ||||||
|   try { |   try { | ||||||
|     const response = await httpGet(url); |     const response = await httpGet<string | Buffer | BinaryData | undefined>( | ||||||
|     const dom = new JSDOM(response.data); |       url, | ||||||
|     const spans = dom.window.document.querySelectorAll( |       config | ||||||
|       'span[style="color:#AA0022;"]' |  | ||||||
|     ); |     ); | ||||||
|     if (spans.length >= 2) { |     const dom = new JSDOM(response.data); | ||||||
|       const nameSpan = spans[1]; |     const anchor = dom.window.document.querySelector( | ||||||
|       return rsnFromElement(nameSpan); |       '.personal-hiscores__row.personal-hiscores__row--type-highlight a' | ||||||
|  |     ); | ||||||
|  |     if (anchor) { | ||||||
|  |       return rsnFromElement(anchor); | ||||||
|     } |     } | ||||||
|     throw Error('Player not found'); |     throw new PlayerNotFoundError(); | ||||||
|   } catch { |   } catch { | ||||||
|     throw Error('Player not found'); |     throw new HiScoresError(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Parses official JSON object of raw stats and returns a stats object. | ||||||
|  |  * | ||||||
|  |  * @param csv Raw JSON from the official OSRS API. | ||||||
|  |  * @returns Parsed stats object. | ||||||
|  |  */ | ||||||
|  | export function parseJsonStats(json: HiscoresResponse): Stats { | ||||||
|  |   const getActivity = (formattedName: string): Activity => { | ||||||
|  |     const hiscoresActivity = json.activities.find( | ||||||
|  |       // We must match on name here since id is not guaranteed to be the same between updates | ||||||
|  |       ({ name }) => name.toLowerCase() === formattedName.toLowerCase() | ||||||
|  |     ); | ||||||
|  |     return { | ||||||
|  |       rank: hiscoresActivity?.rank ?? -1, | ||||||
|  |       score: hiscoresActivity?.score ?? -1 | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  |   const reduceActivity = <Key extends string, Reduced = Record<Key, Activity>>( | ||||||
|  |     keys: Key[], | ||||||
|  |     formattedNames: Record<Key, string> | ||||||
|  |   ): Reduced => | ||||||
|  |     keys.reduce<Reduced>( | ||||||
|  |       (reducer, key) => ({ | ||||||
|  |         ...reducer, | ||||||
|  |         [key]: getActivity(formattedNames[key]) | ||||||
|  |       }), | ||||||
|  |       {} as Reduced | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |   const skills = SKILLS.reduce<Skills>((skillsObject, skillName) => { | ||||||
|  |     const hiscoresSkill = json.skills.find( | ||||||
|  |       // We must match on name here since id is not guaranteed to be the same between updates | ||||||
|  |       ({ name }) => | ||||||
|  |         name.toLowerCase() === FORMATTED_SKILL_NAMES[skillName].toLowerCase() | ||||||
|  |     ); | ||||||
|  |     return { | ||||||
|  |       ...skillsObject, | ||||||
|  |       [skillName]: { | ||||||
|  |         rank: hiscoresSkill?.rank ?? -1, | ||||||
|  |         level: hiscoresSkill?.level ?? -1, | ||||||
|  |         xp: hiscoresSkill?.xp ?? -1 | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   }, {} as Skills); | ||||||
|  |  | ||||||
|  |   const bountyHunter = reduceActivity(BH_MODES, FORMATTED_BH_NAMES); | ||||||
|  |   const clues = reduceActivity(CLUES, FORMATTED_CLUE_NAMES); | ||||||
|  |   const bosses = reduceActivity(BOSSES, FORMATTED_BOSS_NAMES); | ||||||
|  |  | ||||||
|  |   const leaguePoints = getActivity(FORMATTED_LEAGUE_POINTS); | ||||||
|  |   const lastManStanding = getActivity(FORMATTED_LMS); | ||||||
|  |   const pvpArena = getActivity(FORMATTED_PVP_ARENA); | ||||||
|  |   const soulWarsZeal = getActivity(FORMATTED_SOUL_WARS); | ||||||
|  |   const riftsClosed = getActivity(FORMATTED_RIFTS_CLOSED); | ||||||
|  |  | ||||||
|  |   const stats: Stats = { | ||||||
|  |     skills, | ||||||
|  |     leaguePoints, | ||||||
|  |     bountyHunter, | ||||||
|  |     lastManStanding, | ||||||
|  |     pvpArena, | ||||||
|  |     soulWarsZeal, | ||||||
|  |     riftsClosed, | ||||||
|  |     clues, | ||||||
|  |     bosses | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return stats; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Parses CSV string of raw stats and returns a stats object. |  * Parses CSV string of raw stats and returns a stats object. | ||||||
|  * |  * | ||||||
|  * @param csv Raw CSV from the official OSRS API. |  * @param csv Raw CSV from the official OSRS API. | ||||||
|  * @returns Parsed stats object. |  * @returns Parsed stats object. | ||||||
|  */ |  */ | ||||||
| export function parseStats(csv: string, mode: Gamemode = 'main'): Stats { | export function parseStats(csv: string): Stats { | ||||||
|   const splitCSV = csv |   const splitCSV = csv | ||||||
|     .split('\n') |     .split('\n') | ||||||
|     .filter((entry) => !!entry) |     .filter((entry) => !!entry) | ||||||
|     .map((stat) => stat.split(',')); |     .map((stat) => stat.split(',')); | ||||||
|  |  | ||||||
|  |   if ( | ||||||
|  |     splitCSV.length !== | ||||||
|  |     SKILLS.length + BH_MODES.length + CLUES.length + BOSSES.length + 5 | ||||||
|  |   ) { | ||||||
|  |     throw new InvalidFormatError(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   const skillObjects: Skill[] = splitCSV |   const skillObjects: Skill[] = splitCSV | ||||||
|     .filter((stat) => stat.length === 3) |     .filter((stat) => stat.length === 3) | ||||||
|     .map((stat) => { |     .map((stat) => { | ||||||
| @@ -97,17 +218,12 @@ export function parseStats(csv: string, mode: Gamemode = 'main'): Stats { | |||||||
|       return activity; |       return activity; | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|   /** `seasonal` API results don't currently include TOB: Hard Mode, so it needs to be filtered out in that case. */ |  | ||||||
|   const filteredBosses = |  | ||||||
|     mode === 'seasonal' |  | ||||||
|       ? BOSSES.filter((boss) => boss !== 'theatreOfBloodHardMode') |  | ||||||
|       : BOSSES; |  | ||||||
|  |  | ||||||
|   const [leaguePoints] = activityObjects.splice(0, 1); |   const [leaguePoints] = activityObjects.splice(0, 1); | ||||||
|   const bhObjects = activityObjects.splice(0, BH_MODES.length); |   const bhObjects = activityObjects.splice(0, BH_MODES.length); | ||||||
|   const clueObjects = activityObjects.splice(0, CLUES.length); |   const clueObjects = activityObjects.splice(0, CLUES.length); | ||||||
|   const [lastManStanding, soulWarsZeal] = activityObjects.splice(0, 2); |   const [lastManStanding, pvpArena, soulWarsZeal, riftsClosed] = | ||||||
|   const bossObjects = activityObjects.splice(0, filteredBosses.length); |     activityObjects.splice(0, 4); | ||||||
|  |   const bossObjects = activityObjects.splice(0, BOSSES.length); | ||||||
|  |  | ||||||
|   const skills: Skills = skillObjects.reduce<Skills>((prev, curr, index) => { |   const skills: Skills = skillObjects.reduce<Skills>((prev, curr, index) => { | ||||||
|     const newSkills = { ...prev }; |     const newSkills = { ...prev }; | ||||||
| @@ -129,7 +245,7 @@ export function parseStats(csv: string, mode: Gamemode = 'main'): Stats { | |||||||
|  |  | ||||||
|   const bosses: Bosses = bossObjects.reduce<Bosses>((prev, curr, index) => { |   const bosses: Bosses = bossObjects.reduce<Bosses>((prev, curr, index) => { | ||||||
|     const newBosses = { ...prev }; |     const newBosses = { ...prev }; | ||||||
|     newBosses[filteredBosses[index]] = curr; |     newBosses[BOSSES[index]] = curr; | ||||||
|     return newBosses; |     return newBosses; | ||||||
|   }, {} as Bosses); |   }, {} as Bosses); | ||||||
|  |  | ||||||
| @@ -138,7 +254,9 @@ export function parseStats(csv: string, mode: Gamemode = 'main'): Stats { | |||||||
|     leaguePoints, |     leaguePoints, | ||||||
|     bountyHunter, |     bountyHunter, | ||||||
|     lastManStanding, |     lastManStanding, | ||||||
|  |     pvpArena, | ||||||
|     soulWarsZeal, |     soulWarsZeal, | ||||||
|  |     riftsClosed, | ||||||
|     clues, |     clues, | ||||||
|     bosses |     bosses | ||||||
|   }; |   }; | ||||||
| @@ -155,25 +273,32 @@ export function parseStats(csv: string, mode: Gamemode = 'main'): Stats { | |||||||
|  * @param rsn Username of the player. |  * @param rsn Username of the player. | ||||||
|  * @returns Player object. |  * @returns Player object. | ||||||
|  */ |  */ | ||||||
| export async function getStats(rsn: string): Promise<Player> { | export async function getStats( | ||||||
|   if (typeof rsn !== 'string') { |   rsn: string, | ||||||
|     throw Error('RSN must be a string'); |   options?: GetStatsOptions | ||||||
|   } else if (!/^[a-zA-Z0-9 _]+$/.test(rsn)) { | ): Promise<Player> { | ||||||
|     throw Error('RSN contains invalid character'); |   validateRSN(rsn); | ||||||
|   } else if (rsn.length > 12 || rsn.length < 1) { |   const otherGamemodes = options?.otherGamemodes ?? [ | ||||||
|     throw Error('RSN must be between 1 and 12 characters'); |     'ironman', | ||||||
|   } |     'hardcore', | ||||||
|  |     'ultimate' | ||||||
|  |   ]; | ||||||
|  |   const shouldGetFormattedRsn = options?.shouldGetFormattedRsn ?? true; | ||||||
|  |  | ||||||
|   const mainRes = await httpGet(getStatsURL('main', rsn)); |   const main = await getOfficialStats(rsn, 'main', options?.axiosConfigs?.main); | ||||||
|   if (mainRes.status === 200) { |  | ||||||
|     const otherResponses = await Promise.all([ |  | ||||||
|       httpGet(getStatsURL('ironman', rsn)).catch((err) => err), |  | ||||||
|       httpGet(getStatsURL('hardcore', rsn)).catch((err) => err), |  | ||||||
|       httpGet(getStatsURL('ultimate', rsn)).catch((err) => err), |  | ||||||
|       getRSNFormat(rsn).catch(() => undefined) |  | ||||||
|     ]); |  | ||||||
|  |  | ||||||
|     const [ironRes, hcRes, ultRes, formattedName] = otherResponses; |   const getModeStats = async ( | ||||||
|  |     mode: Extract<Gamemode, 'ironman' | 'hardcore' | 'ultimate'> | ||||||
|  |   ): Promise<HiscoresResponse | undefined> => | ||||||
|  |     otherGamemodes.includes(mode) | ||||||
|  |       ? getOfficialStats(rsn, mode, options?.axiosConfigs?.[mode]) | ||||||
|  |       .catch(() => undefined) | ||||||
|  |       : undefined; | ||||||
|  |   const formattedName = shouldGetFormattedRsn | ||||||
|  |     ? await getRSNFormat(rsn, options?.axiosConfigs?.rsn).catch( | ||||||
|  |         () => undefined | ||||||
|  |       ) | ||||||
|  |     : undefined; | ||||||
|  |  | ||||||
|   const player: Player = { |   const player: Player = { | ||||||
|     name: formattedName ?? rsn, |     name: formattedName ?? rsn, | ||||||
| @@ -182,13 +307,16 @@ export async function getStats(rsn: string): Promise<Player> { | |||||||
|     deulted: false, |     deulted: false, | ||||||
|     deironed: false |     deironed: false | ||||||
|   }; |   }; | ||||||
|     player.main = parseStats(mainRes.data); |   player.main = parseJsonStats(main); | ||||||
|  |  | ||||||
|     if (ironRes.status === 200) { |   const iron = await getModeStats('ironman'); | ||||||
|       player.ironman = parseStats(ironRes.data); |   if (iron) { | ||||||
|       if (hcRes.status === 200) { |     player.ironman = parseJsonStats(iron); | ||||||
|  |     const hc = await getModeStats('hardcore'); | ||||||
|  |     const ult = await getModeStats('ultimate'); | ||||||
|  |     if (hc) { | ||||||
|       player.mode = 'hardcore'; |       player.mode = 'hardcore'; | ||||||
|         player.hardcore = parseStats(hcRes.data); |       player.hardcore = parseJsonStats(hc); | ||||||
|       if ( |       if ( | ||||||
|         player.ironman.skills.overall.xp !== player.hardcore.skills.overall.xp |         player.ironman.skills.overall.xp !== player.hardcore.skills.overall.xp | ||||||
|       ) { |       ) { | ||||||
| @@ -201,9 +329,9 @@ export async function getStats(rsn: string): Promise<Player> { | |||||||
|         player.deironed = true; |         player.deironed = true; | ||||||
|         player.mode = 'main'; |         player.mode = 'main'; | ||||||
|       } |       } | ||||||
|       } else if (ultRes.status === 200) { |     } else if (ult) { | ||||||
|       player.mode = 'ultimate'; |       player.mode = 'ultimate'; | ||||||
|         player.ultimate = parseStats(ultRes.data); |       player.ultimate = parseJsonStats(ult); | ||||||
|       if ( |       if ( | ||||||
|         player.ironman.skills.overall.xp !== player.ultimate.skills.overall.xp |         player.ironman.skills.overall.xp !== player.ultimate.skills.overall.xp | ||||||
|       ) { |       ) { | ||||||
| @@ -228,8 +356,6 @@ export async function getStats(rsn: string): Promise<Player> { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   return player; |   return player; | ||||||
|   } |  | ||||||
|   throw Error('Player not found'); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -237,26 +363,20 @@ export async function getStats(rsn: string): Promise<Player> { | |||||||
|  * |  * | ||||||
|  * @param rsn Username of the player. |  * @param rsn Username of the player. | ||||||
|  * @param mode Gamemode to fetch ranks for. |  * @param mode Gamemode to fetch ranks for. | ||||||
|  |  * @param config Optional axios request config object. | ||||||
|  * @returns Stats object. |  * @returns Stats object. | ||||||
|  */ |  */ | ||||||
| export async function getStatsByGamemode( | export async function getStatsByGamemode( | ||||||
|   rsn: string, |   rsn: string, | ||||||
|   mode: Gamemode = 'main' |   mode: Gamemode = 'main', | ||||||
|  |   config?: AxiosRequestConfig | ||||||
| ): Promise<Stats> { | ): Promise<Stats> { | ||||||
|   if (typeof rsn !== 'string') { |   validateRSN(rsn); | ||||||
|     throw Error('RSN must be a string'); |   if (!GAMEMODES.includes(mode)) { | ||||||
|   } else if (!/^[a-zA-Z0-9 _]+$/.test(rsn)) { |  | ||||||
|     throw Error('RSN contains invalid character'); |  | ||||||
|   } else if (rsn.length > 12 || rsn.length < 1) { |  | ||||||
|     throw Error('RSN must be between 1 and 12 characters'); |  | ||||||
|   } else if (!GAMEMODES.includes(mode)) { |  | ||||||
|     throw Error('Invalid game mode'); |     throw Error('Invalid game mode'); | ||||||
|   } |   } | ||||||
|   const response = await httpGet(getStatsURL(mode, rsn)); |   const response = await getOfficialStats(rsn, mode, config); | ||||||
|   if (response.status !== 200) { |   const stats = parseJsonStats(response); | ||||||
|     throw Error('Player not found'); |  | ||||||
|   } |  | ||||||
|   const stats = parseStats(response.data, mode); |  | ||||||
|  |  | ||||||
|   return stats; |   return stats; | ||||||
| } | } | ||||||
| @@ -264,7 +384,8 @@ export async function getStatsByGamemode( | |||||||
| export async function getSkillPage( | export async function getSkillPage( | ||||||
|   skill: SkillName, |   skill: SkillName, | ||||||
|   mode: Gamemode = 'main', |   mode: Gamemode = 'main', | ||||||
|   page: number = 1 |   page: number = 1, | ||||||
|  |   config?: AxiosRequestConfig | ||||||
| ): Promise<PlayerSkillRow[]> { | ): Promise<PlayerSkillRow[]> { | ||||||
|   if (!GAMEMODES.includes(mode)) { |   if (!GAMEMODES.includes(mode)) { | ||||||
|     throw Error('Invalid game mode'); |     throw Error('Invalid game mode'); | ||||||
| @@ -275,7 +396,10 @@ export async function getSkillPage( | |||||||
|   } |   } | ||||||
|   const url = getSkillPageURL(mode, skill, page); |   const url = getSkillPageURL(mode, skill, page); | ||||||
|  |  | ||||||
|   const response = await httpGet(url); |   const response = await httpGet<string | Buffer | BinaryData | undefined>( | ||||||
|  |     url, | ||||||
|  |     config | ||||||
|  |   ); | ||||||
|   const dom = new JSDOM(response.data); |   const dom = new JSDOM(response.data); | ||||||
|   const playersHTML = dom.window.document.querySelectorAll( |   const playersHTML = dom.window.document.querySelectorAll( | ||||||
|     '.personal-hiscores__row' |     '.personal-hiscores__row' | ||||||
| @@ -307,12 +431,14 @@ export async function getSkillPage( | |||||||
|  * @param activity Name of the activity or boss to fetch hiscores for. |  * @param activity Name of the activity or boss to fetch hiscores for. | ||||||
|  * @param mode Gamemode to fetch ranks for. |  * @param mode Gamemode to fetch ranks for. | ||||||
|  * @param page Page number. |  * @param page Page number. | ||||||
|  |  * @param config Optional axios request config object. | ||||||
|  * @returns Array of `PlayerActivityRow` objects. |  * @returns Array of `PlayerActivityRow` objects. | ||||||
|  */ |  */ | ||||||
| export async function getActivityPage( | export async function getActivityPage( | ||||||
|   activity: ActivityName, |   activity: ActivityName, | ||||||
|   mode: Gamemode = 'main', |   mode: Gamemode = 'main', | ||||||
|   page: number = 1 |   page: number = 1, | ||||||
|  |   config?: AxiosRequestConfig | ||||||
| ): Promise<PlayerActivityRow[]> { | ): Promise<PlayerActivityRow[]> { | ||||||
|   if (!GAMEMODES.includes(mode)) { |   if (!GAMEMODES.includes(mode)) { | ||||||
|     throw Error('Invalid game mode'); |     throw Error('Invalid game mode'); | ||||||
| @@ -323,7 +449,10 @@ export async function getActivityPage( | |||||||
|   } |   } | ||||||
|   const url = getActivityPageURL(mode, activity, page); |   const url = getActivityPageURL(mode, activity, page); | ||||||
|  |  | ||||||
|   const response = await httpGet(url); |   const response = await httpGet<string | Buffer | BinaryData | undefined>( | ||||||
|  |     url, | ||||||
|  |     config | ||||||
|  |   ); | ||||||
|   const dom = new JSDOM(response.data); |   const dom = new JSDOM(response.data); | ||||||
|   const playersHTML = dom.window.document.querySelectorAll( |   const playersHTML = dom.window.document.querySelectorAll( | ||||||
|     '.personal-hiscores__row' |     '.personal-hiscores__row' | ||||||
|   | |||||||
							
								
								
									
										87
									
								
								src/types.ts
									
									
									
									
									
								
							
							
						
						
									
										87
									
								
								src/types.ts
									
									
									
									
									
								
							| @@ -1,3 +1,5 @@ | |||||||
|  | import { AxiosRequestConfig } from 'axios'; | ||||||
|  |  | ||||||
| export type Gamemode = | export type Gamemode = | ||||||
|   | 'main' |   | 'main' | ||||||
|   | 'ironman' |   | 'ironman' | ||||||
| @@ -5,7 +7,10 @@ export type Gamemode = | |||||||
|   | 'hardcore' |   | 'hardcore' | ||||||
|   | 'deadman' |   | 'deadman' | ||||||
|   | 'seasonal' |   | 'seasonal' | ||||||
|   | 'tournament'; |   | 'tournament' | ||||||
|  |   | 'skiller' | ||||||
|  |   | 'oneDefence' | ||||||
|  |   | 'freshStart'; | ||||||
|  |  | ||||||
| export interface Skill { | export interface Skill { | ||||||
|   rank: number; |   rank: number; | ||||||
| @@ -57,16 +62,18 @@ export type ClueType = | |||||||
|  |  | ||||||
| export type Clues = { [Type in ClueType]: Activity }; | export type Clues = { [Type in ClueType]: Activity }; | ||||||
|  |  | ||||||
| export type BHType = 'rogue' | 'hunter'; | export type BHType = 'rogue' | 'hunter' | 'rogueV2' | 'hunterV2'; | ||||||
|  |  | ||||||
| export type BH = { [Type in BHType]: Activity }; | export type BH = { [Type in BHType]: Activity }; | ||||||
|  |  | ||||||
| export type Boss = | export type Boss = | ||||||
|   | 'abyssalSire' |   | 'abyssalSire' | ||||||
|   | 'alchemicalHydra' |   | 'alchemicalHydra' | ||||||
|  |   | 'artio' | ||||||
|   | 'barrows' |   | 'barrows' | ||||||
|   | 'bryophyta' |   | 'bryophyta' | ||||||
|   | 'callisto' |   | 'callisto' | ||||||
|  |   | 'calvarion' | ||||||
|   | 'cerberus' |   | 'cerberus' | ||||||
|   | 'chambersOfXeric' |   | 'chambersOfXeric' | ||||||
|   | 'chambersOfXericChallengeMode' |   | 'chambersOfXericChallengeMode' | ||||||
| @@ -79,6 +86,7 @@ export type Boss = | |||||||
|   | 'dagannothRex' |   | 'dagannothRex' | ||||||
|   | 'dagannothSupreme' |   | 'dagannothSupreme' | ||||||
|   | 'derangedArchaeologist' |   | 'derangedArchaeologist' | ||||||
|  |   | 'dukeSucellus' | ||||||
|   | 'generalGraardor' |   | 'generalGraardor' | ||||||
|   | 'giantMole' |   | 'giantMole' | ||||||
|   | 'grotesqueGuardians' |   | 'grotesqueGuardians' | ||||||
| @@ -89,19 +97,28 @@ export type Boss = | |||||||
|   | 'kreeArra' |   | 'kreeArra' | ||||||
|   | 'krilTsutsaroth' |   | 'krilTsutsaroth' | ||||||
|   | 'mimic' |   | 'mimic' | ||||||
|  |   | 'nex' | ||||||
|   | 'nightmare' |   | 'nightmare' | ||||||
|  |   | 'phosanisNightmare' | ||||||
|   | 'obor' |   | 'obor' | ||||||
|  |   | 'phantomMuspah' | ||||||
|   | 'sarachnis' |   | 'sarachnis' | ||||||
|   | 'scorpia' |   | 'scorpia' | ||||||
|   | 'skotizo' |   | 'skotizo' | ||||||
|  |   | 'spindel' | ||||||
|   | 'tempoross' |   | 'tempoross' | ||||||
|   | 'gauntlet' |   | 'gauntlet' | ||||||
|   | 'corruptedGauntlet' |   | 'corruptedGauntlet' | ||||||
|  |   | 'leviathan' | ||||||
|  |   | 'whisperer' | ||||||
|   | 'theatreOfBlood' |   | 'theatreOfBlood' | ||||||
|   | 'theatreOfBloodHardMode' |   | 'theatreOfBloodHardMode' | ||||||
|   | 'thermonuclearSmokeDevil' |   | 'thermonuclearSmokeDevil' | ||||||
|  |   | 'tombsOfAmascut' | ||||||
|  |   | 'tombsOfAmascutExpertMode' | ||||||
|   | 'tzKalZuk' |   | 'tzKalZuk' | ||||||
|   | 'tzTokJad' |   | 'tzTokJad' | ||||||
|  |   | 'vardorvis' | ||||||
|   | 'venenatis' |   | 'venenatis' | ||||||
|   | 'vetion' |   | 'vetion' | ||||||
|   | 'vorkath' |   | 'vorkath' | ||||||
| @@ -113,10 +130,14 @@ export type Bosses = { [Type in Boss]: Activity }; | |||||||
|  |  | ||||||
| export type ActivityName = | export type ActivityName = | ||||||
|   | 'leaguePoints' |   | 'leaguePoints' | ||||||
|  |   | 'hunterBHV2' | ||||||
|  |   | 'rogueBHV2' | ||||||
|   | 'hunterBH' |   | 'hunterBH' | ||||||
|   | 'rogueBH' |   | 'rogueBH' | ||||||
|   | 'lastManStanding' |   | 'lastManStanding' | ||||||
|  |   | 'pvpArena' | ||||||
|   | 'soulWarsZeal' |   | 'soulWarsZeal' | ||||||
|  |   | 'riftsClosed' | ||||||
|   | 'allClues' |   | 'allClues' | ||||||
|   | 'beginnerClues' |   | 'beginnerClues' | ||||||
|   | 'easyClues' |   | 'easyClues' | ||||||
| @@ -132,7 +153,9 @@ export interface Stats { | |||||||
|   leaguePoints: Activity; |   leaguePoints: Activity; | ||||||
|   bountyHunter: BH; |   bountyHunter: BH; | ||||||
|   lastManStanding: Activity; |   lastManStanding: Activity; | ||||||
|  |   pvpArena: Activity; | ||||||
|   soulWarsZeal: Activity; |   soulWarsZeal: Activity; | ||||||
|  |   riftsClosed: Activity; | ||||||
|   bosses: Bosses; |   bosses: Bosses; | ||||||
| } | } | ||||||
| export type Modes = { [M in Gamemode]?: Stats }; | export type Modes = { [M in Gamemode]?: Stats }; | ||||||
| @@ -154,3 +177,63 @@ export interface PlayerActivityRow extends Activity { | |||||||
|   name: string; |   name: string; | ||||||
|   dead: boolean; |   dead: boolean; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface GetStatsOptions { | ||||||
|  |   /** | ||||||
|  |    * Other game modes to fetch ranks for. | ||||||
|  |    * @defaultvalue `['ironman', 'hardcore', 'ultimate']` | ||||||
|  |    */ | ||||||
|  |   otherGamemodes?: Extract<Gamemode, 'ironman' | 'hardcore' | 'ultimate'>[]; | ||||||
|  |   /** | ||||||
|  |    * If true, the formatted RSN will be fetched. Otherwise it will return the provided, unformatted RSN. | ||||||
|  |    * @defaultvalue `true` | ||||||
|  |    */ | ||||||
|  |   shouldGetFormattedRsn?: boolean; | ||||||
|  |   /** | ||||||
|  |    * Map of configs for each requests that can take place in the `getStats` function. | ||||||
|  |    */ | ||||||
|  |   axiosConfigs?: Partial<Record<Gamemode, AxiosRequestConfig>> & { | ||||||
|  |     /** | ||||||
|  |      * The axios request config object to use for the RSN format request. | ||||||
|  |      */ | ||||||
|  |     rsn?: AxiosRequestConfig; | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface HiscoresCommon { | ||||||
|  |   /** | ||||||
|  |    * This field behaves more like an index than a true unique ID. | ||||||
|  |    */ | ||||||
|  |   id: number; | ||||||
|  |   /** | ||||||
|  |    * The display name of this skill / activity. | ||||||
|  |    */ | ||||||
|  |   name: string; | ||||||
|  |   /** | ||||||
|  |    * The player's official hiscores rank in this skill / activity. | ||||||
|  |    */ | ||||||
|  |   rank: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface HiscoresSkill extends HiscoresCommon { | ||||||
|  |   /** | ||||||
|  |    * The player's current level in this skill. | ||||||
|  |    */ | ||||||
|  |   level: number; | ||||||
|  |   /** | ||||||
|  |    * The player's current experience in this skill. | ||||||
|  |    */ | ||||||
|  |   xp: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface HiscoresActivity extends HiscoresCommon { | ||||||
|  |   /** | ||||||
|  |    * The player's current score in this activity. | ||||||
|  |    */ | ||||||
|  |   score: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface HiscoresResponse { | ||||||
|  |   skills: HiscoresSkill[]; | ||||||
|  |   activities: HiscoresActivity[]; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import { | |||||||
|  |  | ||||||
| export const BASE_URL = 'https://secure.runescape.com/m=hiscore_oldschool'; | export const BASE_URL = 'https://secure.runescape.com/m=hiscore_oldschool'; | ||||||
| export const STATS_URL = 'index_lite.ws?player='; | export const STATS_URL = 'index_lite.ws?player='; | ||||||
|  | export const JSON_STATS_URL = 'index_lite.json?player='; | ||||||
| export const SCORES_URL = 'overall.ws?'; | export const SCORES_URL = 'overall.ws?'; | ||||||
|  |  | ||||||
| export type GamemodeUrl = { | export type GamemodeUrl = { | ||||||
| @@ -22,7 +23,10 @@ export const GAMEMODE_URL: GamemodeUrl = { | |||||||
|   ultimate: `${BASE_URL}_ultimate/`, |   ultimate: `${BASE_URL}_ultimate/`, | ||||||
|   deadman: `${BASE_URL}_deadman/`, |   deadman: `${BASE_URL}_deadman/`, | ||||||
|   seasonal: `${BASE_URL}_seasonal/`, |   seasonal: `${BASE_URL}_seasonal/`, | ||||||
|   tournament: `${BASE_URL}_tournament/` |   tournament: `${BASE_URL}_tournament/`, | ||||||
|  |   skiller: `${BASE_URL}_skiller/`, | ||||||
|  |   oneDefence: `${BASE_URL}_skiller_defence/`, | ||||||
|  |   freshStart: `${BASE_URL}_fresh_start/` | ||||||
| }; | }; | ||||||
| export const SKILLS: SkillName[] = [ | export const SKILLS: SkillName[] = [ | ||||||
|   'overall', |   'overall', | ||||||
| @@ -59,7 +63,7 @@ export const CLUES: ClueType[] = [ | |||||||
|   'elite', |   'elite', | ||||||
|   'master' |   'master' | ||||||
| ]; | ]; | ||||||
| export const BH_MODES: BHType[] = ['hunter', 'rogue']; | export const BH_MODES: BHType[] = ['hunterV2', 'rogueV2', 'hunter', 'rogue']; | ||||||
| export const GAMEMODES: Gamemode[] = [ | export const GAMEMODES: Gamemode[] = [ | ||||||
|   'main', |   'main', | ||||||
|   'ironman', |   'ironman', | ||||||
| @@ -72,9 +76,11 @@ export const GAMEMODES: Gamemode[] = [ | |||||||
| export const BOSSES: Boss[] = [ | export const BOSSES: Boss[] = [ | ||||||
|   'abyssalSire', |   'abyssalSire', | ||||||
|   'alchemicalHydra', |   'alchemicalHydra', | ||||||
|  |   'artio', | ||||||
|   'barrows', |   'barrows', | ||||||
|   'bryophyta', |   'bryophyta', | ||||||
|   'callisto', |   'callisto', | ||||||
|  |   'calvarion', | ||||||
|   'cerberus', |   'cerberus', | ||||||
|   'chambersOfXeric', |   'chambersOfXeric', | ||||||
|   'chambersOfXericChallengeMode', |   'chambersOfXericChallengeMode', | ||||||
| @@ -87,6 +93,7 @@ export const BOSSES: Boss[] = [ | |||||||
|   'dagannothRex', |   'dagannothRex', | ||||||
|   'dagannothSupreme', |   'dagannothSupreme', | ||||||
|   'derangedArchaeologist', |   'derangedArchaeologist', | ||||||
|  |   'dukeSucellus', | ||||||
|   'generalGraardor', |   'generalGraardor', | ||||||
|   'giantMole', |   'giantMole', | ||||||
|   'grotesqueGuardians', |   'grotesqueGuardians', | ||||||
| @@ -97,19 +104,28 @@ export const BOSSES: Boss[] = [ | |||||||
|   'kreeArra', |   'kreeArra', | ||||||
|   'krilTsutsaroth', |   'krilTsutsaroth', | ||||||
|   'mimic', |   'mimic', | ||||||
|  |   'nex', | ||||||
|   'nightmare', |   'nightmare', | ||||||
|  |   'phosanisNightmare', | ||||||
|   'obor', |   'obor', | ||||||
|  |   'phantomMuspah', | ||||||
|   'sarachnis', |   'sarachnis', | ||||||
|   'scorpia', |   'scorpia', | ||||||
|   'skotizo', |   'skotizo', | ||||||
|  |   'spindel', | ||||||
|   'tempoross', |   'tempoross', | ||||||
|   'gauntlet', |   'gauntlet', | ||||||
|   'corruptedGauntlet', |   'corruptedGauntlet', | ||||||
|  |   'leviathan', | ||||||
|  |   'whisperer', | ||||||
|   'theatreOfBlood', |   'theatreOfBlood', | ||||||
|   'theatreOfBloodHardMode', |   'theatreOfBloodHardMode', | ||||||
|   'thermonuclearSmokeDevil', |   'thermonuclearSmokeDevil', | ||||||
|  |   'tombsOfAmascut', | ||||||
|  |   'tombsOfAmascutExpertMode', | ||||||
|   'tzKalZuk', |   'tzKalZuk', | ||||||
|   'tzTokJad', |   'tzTokJad', | ||||||
|  |   'vardorvis', | ||||||
|   'venenatis', |   'venenatis', | ||||||
|   'vetion', |   'vetion', | ||||||
|   'vorkath', |   'vorkath', | ||||||
| @@ -119,6 +135,8 @@ export const BOSSES: Boss[] = [ | |||||||
| ]; | ]; | ||||||
| export const ACTIVITIES: ActivityName[] = [ | export const ACTIVITIES: ActivityName[] = [ | ||||||
|   'leaguePoints', |   'leaguePoints', | ||||||
|  |   'hunterBHV2', | ||||||
|  |   'rogueBHV2', | ||||||
|   'hunterBH', |   'hunterBH', | ||||||
|   'rogueBH', |   'rogueBH', | ||||||
|   'allClues', |   'allClues', | ||||||
| @@ -129,7 +147,9 @@ export const ACTIVITIES: ActivityName[] = [ | |||||||
|   'eliteClues', |   'eliteClues', | ||||||
|   'masterClues', |   'masterClues', | ||||||
|   'lastManStanding', |   'lastManStanding', | ||||||
|  |   'pvpArena', | ||||||
|   'soulWarsZeal', |   'soulWarsZeal', | ||||||
|  |   'riftsClosed', | ||||||
|   ...BOSSES |   ...BOSSES | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @@ -140,9 +160,11 @@ export type FormattedBossNames = { | |||||||
| export const FORMATTED_BOSS_NAMES: FormattedBossNames = { | export const FORMATTED_BOSS_NAMES: FormattedBossNames = { | ||||||
|   abyssalSire: 'Abyssal Sire', |   abyssalSire: 'Abyssal Sire', | ||||||
|   alchemicalHydra: 'Alchemical Hydra', |   alchemicalHydra: 'Alchemical Hydra', | ||||||
|  |   artio: 'Artio', | ||||||
|   barrows: 'Barrows Chests', |   barrows: 'Barrows Chests', | ||||||
|   bryophyta: 'Bryophyta', |   bryophyta: 'Bryophyta', | ||||||
|   callisto: 'Callisto', |   callisto: 'Callisto', | ||||||
|  |   calvarion: "Calvar'ion", | ||||||
|   cerberus: 'Cerberus', |   cerberus: 'Cerberus', | ||||||
|   chambersOfXeric: 'Chambers of Xeric', |   chambersOfXeric: 'Chambers of Xeric', | ||||||
|   chambersOfXericChallengeMode: 'Chambers of Xeric: Challenge Mode', |   chambersOfXericChallengeMode: 'Chambers of Xeric: Challenge Mode', | ||||||
| @@ -155,6 +177,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = { | |||||||
|   dagannothRex: 'Dagannoth Rex', |   dagannothRex: 'Dagannoth Rex', | ||||||
|   dagannothSupreme: 'Dagannoth Supreme', |   dagannothSupreme: 'Dagannoth Supreme', | ||||||
|   derangedArchaeologist: 'Deranged Archaeologist', |   derangedArchaeologist: 'Deranged Archaeologist', | ||||||
|  |   dukeSucellus: 'Duke Sucellus', | ||||||
|   generalGraardor: 'General Graardor', |   generalGraardor: 'General Graardor', | ||||||
|   giantMole: 'Giant Mole', |   giantMole: 'Giant Mole', | ||||||
|   grotesqueGuardians: 'Grotesque Guardians', |   grotesqueGuardians: 'Grotesque Guardians', | ||||||
| @@ -165,19 +188,28 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = { | |||||||
|   kreeArra: "Kree'Arra", |   kreeArra: "Kree'Arra", | ||||||
|   krilTsutsaroth: "K'ril Tsutsaroth", |   krilTsutsaroth: "K'ril Tsutsaroth", | ||||||
|   mimic: 'Mimic', |   mimic: 'Mimic', | ||||||
|   nightmare: 'The Nightmare of Ashihama', |   nex: 'Nex', | ||||||
|  |   nightmare: 'Nightmare', | ||||||
|  |   phosanisNightmare: "Phosani's Nightmare", | ||||||
|   obor: 'Obor', |   obor: 'Obor', | ||||||
|  |   phantomMuspah: 'Phantom Muspah', | ||||||
|   sarachnis: 'Sarachnis', |   sarachnis: 'Sarachnis', | ||||||
|   scorpia: 'Scorpia', |   scorpia: 'Scorpia', | ||||||
|   skotizo: 'Skotizo', |   skotizo: 'Skotizo', | ||||||
|  |   spindel: 'Spindel', | ||||||
|   tempoross: 'Tempoross', |   tempoross: 'Tempoross', | ||||||
|   gauntlet: 'The Gauntlet', |   gauntlet: 'The Gauntlet', | ||||||
|   corruptedGauntlet: 'The Corrupted Gauntlet', |   corruptedGauntlet: 'The Corrupted Gauntlet', | ||||||
|  |   leviathan: 'The Leviathan', | ||||||
|  |   whisperer: 'The Whisperer', | ||||||
|   theatreOfBlood: 'Theatre of Blood', |   theatreOfBlood: 'Theatre of Blood', | ||||||
|   theatreOfBloodHardMode: 'Theatre of Blood: Hard Mode', |   theatreOfBloodHardMode: 'Theatre of Blood: Hard Mode', | ||||||
|   thermonuclearSmokeDevil: 'Thermonuclear Smoke Devil', |   thermonuclearSmokeDevil: 'Thermonuclear Smoke Devil', | ||||||
|  |   tombsOfAmascut: 'Tombs of Amascut', | ||||||
|  |   tombsOfAmascutExpertMode: 'Tombs of Amascut: Expert Mode', | ||||||
|   tzKalZuk: 'TzKal-Zuk', |   tzKalZuk: 'TzKal-Zuk', | ||||||
|   tzTokJad: 'TzTok-Jad', |   tzTokJad: 'TzTok-Jad', | ||||||
|  |   vardorvis: 'Vardorvis', | ||||||
|   venenatis: 'Venenatis', |   venenatis: 'Venenatis', | ||||||
|   vetion: "Vet'ion", |   vetion: "Vet'ion", | ||||||
|   vorkath: 'Vorkath', |   vorkath: 'Vorkath', | ||||||
| @@ -236,10 +268,54 @@ export type FormattedBHNames = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| export const FORMATTED_BH_NAMES: FormattedBHNames = { | export const FORMATTED_BH_NAMES: FormattedBHNames = { | ||||||
|   rogue: 'Bounty Hunter - Rogue', |   rogue: 'Bounty Hunter (Legacy) - Rogue', | ||||||
|   hunter: 'Bounty Hunter - Hunter' |   hunter: 'Bounty Hunter (Legacy) - Hunter', | ||||||
|  |   rogueV2: 'Bounty Hunter - Rogue', | ||||||
|  |   hunterV2: 'Bounty Hunter - Hunter' | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const FORMATTED_LMS = 'Last Man Standing'; | export const FORMATTED_LMS = 'LMS - Rank'; | ||||||
|  | export const FORMATTED_PVP_ARENA = 'PvP Arena - Rank'; | ||||||
| export const FORMATTED_SOUL_WARS = 'Soul Wars Zeal'; | export const FORMATTED_SOUL_WARS = 'Soul Wars Zeal'; | ||||||
| export const FORMATTED_LEAGUE_POINTS = 'League Points'; | export const FORMATTED_LEAGUE_POINTS = 'League Points'; | ||||||
|  | export const FORMATTED_RIFTS_CLOSED = 'Rifts closed'; | ||||||
|  |  | ||||||
|  | export const INVALID_FORMAT_ERROR = 'Invalid hiscores format'; | ||||||
|  | export const PLAYER_NOT_FOUND_ERROR = 'Player not found'; | ||||||
|  | export const HISCORES_ERROR = 'HiScores not responding'; | ||||||
|  |  | ||||||
|  | export class InvalidFormatError extends Error { | ||||||
|  |   __proto__ = Error; | ||||||
|  |  | ||||||
|  |   constructor() { | ||||||
|  |     super(INVALID_FORMAT_ERROR); | ||||||
|  |     Object.setPrototypeOf(this, InvalidFormatError.prototype); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class InvalidRSNError extends Error { | ||||||
|  |   __proto__ = Error; | ||||||
|  |  | ||||||
|  |   constructor(message: string) { | ||||||
|  |     super(message); | ||||||
|  |     Object.setPrototypeOf(this, InvalidRSNError.prototype); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class PlayerNotFoundError extends Error { | ||||||
|  |   __proto__ = Error; | ||||||
|  |  | ||||||
|  |   constructor() { | ||||||
|  |     super(PLAYER_NOT_FOUND_ERROR); | ||||||
|  |     Object.setPrototypeOf(this, PlayerNotFoundError.prototype); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class HiScoresError extends Error { | ||||||
|  |   __proto__ = Error; | ||||||
|  |  | ||||||
|  |   constructor() { | ||||||
|  |     super(HISCORES_ERROR); | ||||||
|  |     Object.setPrototypeOf(this, HiScoresError.prototype); | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import axios from 'axios'; | import axios, { AxiosRequestConfig } from 'axios'; | ||||||
| import * as ua from 'useragent-generator'; | import * as ua from 'useragent-generator'; | ||||||
| import { Gamemode, SkillName, ActivityName } from '../types'; | import { Gamemode, SkillName, ActivityName } from '../types'; | ||||||
| import { | import { | ||||||
| @@ -6,7 +6,9 @@ import { | |||||||
|   STATS_URL, |   STATS_URL, | ||||||
|   SCORES_URL, |   SCORES_URL, | ||||||
|   SKILLS, |   SKILLS, | ||||||
|   ACTIVITIES |   ACTIVITIES, | ||||||
|  |   JSON_STATS_URL, | ||||||
|  |   InvalidRSNError | ||||||
| } from './constants'; | } from './constants'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -14,10 +16,13 @@ import { | |||||||
|  * |  * | ||||||
|  * @param gamemode Gamemode to fetch ranks for. |  * @param gamemode Gamemode to fetch ranks for. | ||||||
|  * @param rsn Username of the player. |  * @param rsn Username of the player. | ||||||
|  |  * @param json If the JSON endpoint is desired instead of CSV. | ||||||
|  * @returns Encoded stats URL. |  * @returns Encoded stats URL. | ||||||
|  */ |  */ | ||||||
| export const getStatsURL = (gamemode: Gamemode, rsn: string) => | export const getStatsURL = (gamemode: Gamemode, rsn: string, json = false) => | ||||||
|   `${GAMEMODE_URL[gamemode]}${STATS_URL}${encodeURIComponent(rsn)}`; |   `${GAMEMODE_URL[gamemode]}${ | ||||||
|  |     json ? JSON_STATS_URL : STATS_URL | ||||||
|  |   }${encodeURIComponent(rsn)}`; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Will generate a player table URL for the official OSRS hiscores website. |  * Will generate a player table URL for the official OSRS hiscores website. | ||||||
| @@ -96,10 +101,29 @@ export const rsnFromElement = (el: Element | null) => { | |||||||
|  * @param url URL to run a `GET` request against. |  * @param url URL to run a `GET` request against. | ||||||
|  * @returns Axios response. |  * @returns Axios response. | ||||||
|  */ |  */ | ||||||
| export const httpGet = (url: string) => | export const httpGet = <Response>( | ||||||
|   axios.get(url, { |   url: string, | ||||||
|  |   config: AxiosRequestConfig = {} | ||||||
|  | ) => | ||||||
|  |   axios.get<Response>(url, { | ||||||
|     headers: { |     headers: { | ||||||
|       // without User-Agent header requests may be rejected by DDoS protection mechanism |       // without User-Agent header requests may be rejected by DDoS protection mechanism | ||||||
|       'User-Agent': ua.firefox(80) |       'User-Agent': ua.firefox(80) | ||||||
|     } |     }, | ||||||
|  |     ...config | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Validates that a provided RSN has the same username restrictions as Jagex. | ||||||
|  |  * @param rsn Username to validate. | ||||||
|  |  * @throws Error if the RSN fails validation. | ||||||
|  |  */ | ||||||
|  | export const validateRSN = (rsn: string) => { | ||||||
|  |   if (typeof rsn !== 'string') { | ||||||
|  |     throw new InvalidRSNError('RSN must be a string'); | ||||||
|  |   } else if (!/^[a-zA-Z0-9 _-]+$/.test(rsn)) { | ||||||
|  |     throw new InvalidRSNError('RSN contains invalid character'); | ||||||
|  |   } else if (rsn.length > 12 || rsn.length < 1) { | ||||||
|  |     throw new InvalidRSNError('RSN must be between 1 and 12 characters'); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user