mirror of
				https://github.com/maxswa/osrs-json-hiscores.git
				synced 2025-10-15 10:19:04 +00:00 
			
		
		
		
	Compare commits
	
		
			54 Commits
		
	
	
		
			v2.13.0
			...
			deadman-po
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 91066b5366 | ||
|   | 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 | 
							
								
								
									
										12
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,19 +10,19 @@ jobs: | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: borales/actions-yarn@v2.0.0 | ||||
|       - uses: actions/checkout@v3.3.0 | ||||
|       - uses: borales/actions-yarn@v4.2.0 | ||||
|         with: | ||||
|           cmd: install | ||||
|       - uses: borales/actions-yarn@v2.0.0 | ||||
|       - uses: borales/actions-yarn@v4.2.0 | ||||
|         with: | ||||
|           cmd: lint | ||||
|       - uses: borales/actions-yarn@v2.0.0 | ||||
|       - uses: borales/actions-yarn@v4.2.0 | ||||
|         with: | ||||
|           cmd: format | ||||
|       - uses: borales/actions-yarn@v2.0.0 | ||||
|       - uses: borales/actions-yarn@v4.2.0 | ||||
|         with: | ||||
|           cmd: build | ||||
|       - uses: borales/actions-yarn@v2.0.0 | ||||
|       - uses: borales/actions-yarn@v4.2.0 | ||||
|         with: | ||||
|           cmd: test | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,4 @@ | ||||
| .idea/ | ||||
| node_modules | ||||
| /lib | ||||
| .vscode | ||||
							
								
								
									
										27
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								README.md
									
									
									
									
									
								
							| @@ -105,19 +105,22 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | ||||
| ### Minigames | ||||
|  | ||||
| | Minigame                        |       Param       | | ||||
| | ---------------------- | :---------------: | | ||||
| | Bounty Hunter (Rogue)  |     `rogueBH`     | | ||||
| | Bounty Hunter (Hunter) |    `hunterBH`     | | ||||
| | Last Man Standing      | `lastManStanding` | | ||||
| | PvP Arena              |    `pvpArena`     | | ||||
| | ------------------------------- | :---------------: | | ||||
| | Bounty Hunter (Legacy - Rogue)  |     `rogueBH`     | | ||||
| | Bounty Hunter (Legacy - Hunter) |    `hunterBH`     | | ||||
| | Bounty Hunter (Rogue)           |    `rogueBHV2`    | | ||||
| | Bounty Hunter (Hunter)          |   `hunterBHV2`    | | ||||
| | LMS - Rank                      | `lastManStanding` | | ||||
| | PvP Arena - Rank                |    `pvpArena`     | | ||||
| | Soul Wars Zeal                  |  `soulWarsZeal`   | | ||||
| | Rifts Closed           |   `riftsClosed`   | | ||||
| | Rifts closed                    |   `riftsClosed`   | | ||||
|  | ||||
| ### Leagues | ||||
| ### Points | ||||
|  | ||||
| | Activity       |      Param      | | ||||
| | ------------- | :------------: | | ||||
| | -------------- | :-------------: | | ||||
| | League Points  | `leaguePoints`  | | ||||
| | Deadman Points | `deadmanPoints` | | ||||
|  | ||||
| ### Bosses | ||||
|  | ||||
| @@ -129,7 +132,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | ||||
| | Barrows Chests                    |           `barrows`            | | ||||
| | Bryophyta                         |          `bryophyta`           | | ||||
| | Callisto                          |           `callisto`           | | ||||
| | Cal'varion                        |          `calvarion`           | | ||||
| | Calvar'ion                        |          `calvarion`           | | ||||
| | Cerberus                          |           `cerberus`           | | ||||
| | Chambers Of Xeric                 |       `chambersOfXeric`        | | ||||
| | Chambers Of Xeric: Challenge Mode | `chambersOfXericChallengeMode` | | ||||
| @@ -142,6 +145,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | ||||
| | Dagannoth Rex                     |         `dagannothRex`         | | ||||
| | Dagannoth Supreme                 |       `dagannothSupreme`       | | ||||
| | Deranged Archaeologist            |    `derangedArchaeologist`     | | ||||
| | Duke Sucellus                     |         `dukeSucellus`         | | ||||
| | General Graardor                  |       `generalGraardor`        | | ||||
| | Giant Mole                        |          `giantMole`           | | ||||
| | Grotesque Guardians               |      `grotesqueGuardians`      | | ||||
| @@ -153,7 +157,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | ||||
| | K'ril Tsutsaroth                  |        `krilTsutsaroth`        | | ||||
| | Mimic                             |            `mimic`             | | ||||
| | Nex                               |             `nex`              | | ||||
| | The Nightmare of Ashihama         |          `nightmare`           | | ||||
| | Nightmare                         |          `nightmare`           | | ||||
| | Phosani's Nightmare               |      `phosanisNightmare`       | | ||||
| | Obor                              |             `obor`             | | ||||
| | Phantom Muspah                    |        `phantomMuspah`         | | ||||
| @@ -164,6 +168,8 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | ||||
| | Tempoross                         |          `tempoross`           | | ||||
| | The Gauntlet                      |           `gauntlet`           | | ||||
| | The Corrupted Gauntlet            |      `corruptedGauntlet`       | | ||||
| | The Leviathan                     |          `leviathan`           | | ||||
| | The Whisperer                     |          `whisperer`           | | ||||
| | Theatre Of Blood                  |        `theatreOfBlood`        | | ||||
| | Theatre Of Blood: Hard Mode       |    `theatreOfBloodHardMode`    | | ||||
| | Thermonuclear Smoke Devil         |   `thermonuclearSmokeDevil`    | | ||||
| @@ -171,6 +177,7 @@ Activities consist of all levels of clue scrolls as well as minigames and bosses | ||||
| | Tombs of Amascut: Expert Mode     |   `tombsOfAmascutExpertMode`   | | ||||
| | TzKal-Zuk                         |           `tzKalZuk`           | | ||||
| | TzTok-Jad                         |           `tzTokJad`           | | ||||
| | Vardorvis                         |          `vardorvis`           | | ||||
| | Venenatis                         |          `venenatis`           | | ||||
| | Vetion                            |            `vetion`            | | ||||
| | Vorkath                           |           `vorkath`            | | ||||
|   | ||||
							
								
								
									
										1391
									
								
								__tests__/attackTopPage.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1391
									
								
								__tests__/attackTopPage.html
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1563
									
								
								__tests__/b0atyNamePage.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1563
									
								
								__tests__/b0atyNamePage.html
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										99
									
								
								__tests__/b0atyStats.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								__tests__/b0atyStats.csv
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| 3745,2277,840426611 | ||||
| 24080,99,30276246 | ||||
| 9559,99,31228199 | ||||
| 2212,99,143599407 | ||||
| 3550,99,123248914 | ||||
| 6564,99,109953707 | ||||
| 4244,99,15144846 | ||||
| 4107,99,57072224 | ||||
| 135556,99,13353511 | ||||
| 25855,99,16903268 | ||||
| 53472,99,13456850 | ||||
| 22453,99,16654917 | ||||
| 17690,99,19118307 | ||||
| 45816,99,14059776 | ||||
| 10409,99,16530015 | ||||
| 27702,99,14765145 | ||||
| 39430,99,13235063 | ||||
| 6722,99,15754877 | ||||
| 51813,99,14023027 | ||||
| 5324,99,33835543 | ||||
| 3154,99,69527777 | ||||
| 4719,99,22733384 | ||||
| 15135,99,17952893 | ||||
| 1089,99,17998715 | ||||
| -1,-1 | ||||
| -1,-1 | ||||
| 26164,10 | ||||
| 12886,3 | ||||
| 148658,8 | ||||
| 103161,7 | ||||
| 377,9556 | ||||
| 862,825 | ||||
| 148,6000 | ||||
| 2153,1606 | ||||
| 42107,298 | ||||
| 504,459 | ||||
| 1816,368 | ||||
| 14701,2521 | ||||
| -1,-1 | ||||
| 6776,14780 | ||||
| 4773,1060 | ||||
| 2741,2192 | ||||
| 90203,594 | ||||
| 29573,143 | ||||
| 3668,1830 | ||||
| 206778,5 | ||||
| 32897,293 | ||||
| 64112,58 | ||||
| 18492,1905 | ||||
| 178941,34 | ||||
| 2379,501 | ||||
| 82902,50 | ||||
| 147358,26 | ||||
| 51973,287 | ||||
| 2377,2000 | ||||
| 147406,41 | ||||
| 32225,707 | ||||
| 42229,698 | ||||
| 32212,714 | ||||
| 79605,25 | ||||
| 9517,688 | ||||
| 19125,1228 | ||||
| 65849,474 | ||||
| 2080,2140 | ||||
| 16433,182 | ||||
| 2006,2046 | ||||
| 48575,580 | ||||
| 115056,1908 | ||||
| 10406,920 | ||||
| 2196,1444 | ||||
| 1005,33 | ||||
| 28413,620 | ||||
| 4157,744 | ||||
| 4261,500 | ||||
| 993,217 | ||||
| 21848,251 | ||||
| 66260,119 | ||||
| 108898,26 | ||||
| 373975,9 | ||||
| 2005,1569 | ||||
| 40001,273 | ||||
| 192608,7 | ||||
| 78367,201 | ||||
| 5679,583 | ||||
| 478,1760 | ||||
| 22932,250 | ||||
| 15059,7 | ||||
| 7161,3066 | ||||
| 101555,35 | ||||
| 610,933 | ||||
| 15733,2 | ||||
| -1,-1 | ||||
| 2051,2078 | ||||
| 194,7096 | ||||
| 69260,51 | ||||
| 14039,2780 | ||||
| 876367,62 | ||||
| 165341,25 | ||||
| 226318,275 | ||||
| Can't render this file because it has a wrong number of fields in line 25. | 
							
								
								
									
										201
									
								
								__tests__/b0atyStats.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								__tests__/b0atyStats.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,201 @@ | ||||
| { | ||||
|   "skills": [ | ||||
|     { | ||||
|       "id": 0, | ||||
|       "name": "Overall", | ||||
|       "rank": 3745, | ||||
|       "level": 2277, | ||||
|       "xp": 840426611 | ||||
|     }, | ||||
|     { "id": 1, "name": "Attack", "rank": 24080, "level": 99, "xp": 30276246 }, | ||||
|     { "id": 2, "name": "Defence", "rank": 9559, "level": 99, "xp": 31228199 }, | ||||
|     { "id": 3, "name": "Strength", "rank": 2212, "level": 99, "xp": 143599407 }, | ||||
|     { | ||||
|       "id": 4, | ||||
|       "name": "Hitpoints", | ||||
|       "rank": 3550, | ||||
|       "level": 99, | ||||
|       "xp": 123248914 | ||||
|     }, | ||||
|     { "id": 5, "name": "Ranged", "rank": 6564, "level": 99, "xp": 109953707 }, | ||||
|     { "id": 6, "name": "Prayer", "rank": 4244, "level": 99, "xp": 15144846 }, | ||||
|     { "id": 7, "name": "Magic", "rank": 4107, "level": 99, "xp": 57072224 }, | ||||
|     { "id": 8, "name": "Cooking", "rank": 135556, "level": 99, "xp": 13353511 }, | ||||
|     { | ||||
|       "id": 9, | ||||
|       "name": "Woodcutting", | ||||
|       "rank": 25855, | ||||
|       "level": 99, | ||||
|       "xp": 16903268 | ||||
|     }, | ||||
|     { | ||||
|       "id": 10, | ||||
|       "name": "Fletching", | ||||
|       "rank": 53472, | ||||
|       "level": 99, | ||||
|       "xp": 13456850 | ||||
|     }, | ||||
|     { "id": 11, "name": "Fishing", "rank": 22453, "level": 99, "xp": 16654917 }, | ||||
|     { | ||||
|       "id": 12, | ||||
|       "name": "Firemaking", | ||||
|       "rank": 17690, | ||||
|       "level": 99, | ||||
|       "xp": 19118307 | ||||
|     }, | ||||
|     { | ||||
|       "id": 13, | ||||
|       "name": "Crafting", | ||||
|       "rank": 45816, | ||||
|       "level": 99, | ||||
|       "xp": 14059776 | ||||
|     }, | ||||
|     { | ||||
|       "id": 14, | ||||
|       "name": "Smithing", | ||||
|       "rank": 10409, | ||||
|       "level": 99, | ||||
|       "xp": 16530015 | ||||
|     }, | ||||
|     { "id": 15, "name": "Mining", "rank": 27702, "level": 99, "xp": 14765145 }, | ||||
|     { | ||||
|       "id": 16, | ||||
|       "name": "Herblore", | ||||
|       "rank": 39430, | ||||
|       "level": 99, | ||||
|       "xp": 13235063 | ||||
|     }, | ||||
|     { "id": 17, "name": "Agility", "rank": 6722, "level": 99, "xp": 15754877 }, | ||||
|     { | ||||
|       "id": 18, | ||||
|       "name": "Thieving", | ||||
|       "rank": 51813, | ||||
|       "level": 99, | ||||
|       "xp": 14023027 | ||||
|     }, | ||||
|     { "id": 19, "name": "Slayer", "rank": 5324, "level": 99, "xp": 33835543 }, | ||||
|     { "id": 20, "name": "Farming", "rank": 3154, "level": 99, "xp": 69527777 }, | ||||
|     { | ||||
|       "id": 21, | ||||
|       "name": "Runecraft", | ||||
|       "rank": 4719, | ||||
|       "level": 99, | ||||
|       "xp": 22733384 | ||||
|     }, | ||||
|     { "id": 22, "name": "Hunter", "rank": 15135, "level": 99, "xp": 17952893 }, | ||||
|     { | ||||
|       "id": 23, | ||||
|       "name": "Construction", | ||||
|       "rank": 1089, | ||||
|       "level": 99, | ||||
|       "xp": 17998715 | ||||
|     } | ||||
|   ], | ||||
|   "activities": [ | ||||
|     { "id": 0, "name": "League Points", "rank": -1, "score": -1 }, | ||||
|     { "id": 1, "name": "Deadman Points", "rank": -1, "score": -1 }, | ||||
|     { "id": 2, "name": "Bounty Hunter - Hunter", "rank": 26164, "score": 10 }, | ||||
|     { "id": 3, "name": "Bounty Hunter - Rogue", "rank": 12886, "score": 3 }, | ||||
|     { | ||||
|       "id": 4, | ||||
|       "name": "Bounty Hunter (Legacy) - Hunter", | ||||
|       "rank": 148658, | ||||
|       "score": 8 | ||||
|     }, | ||||
|     { | ||||
|       "id": 5, | ||||
|       "name": "Bounty Hunter (Legacy) - Rogue", | ||||
|       "rank": 103161, | ||||
|       "score": 7 | ||||
|     }, | ||||
|     { "id": 6, "name": "Clue Scrolls (all)", "rank": 377, "score": 9556 }, | ||||
|     { "id": 7, "name": "Clue Scrolls (beginner)", "rank": 862, "score": 825 }, | ||||
|     { "id": 8, "name": "Clue Scrolls (easy)", "rank": 148, "score": 6000 }, | ||||
|     { "id": 9, "name": "Clue Scrolls (medium)", "rank": 2153, "score": 1606 }, | ||||
|     { "id": 10, "name": "Clue Scrolls (hard)", "rank": 42107, "score": 298 }, | ||||
|     { "id": 11, "name": "Clue Scrolls (elite)", "rank": 504, "score": 459 }, | ||||
|     { "id": 12, "name": "Clue Scrolls (master)", "rank": 1816, "score": 368 }, | ||||
|     { "id": 13, "name": "LMS - Rank", "rank": 14701, "score": 2521 }, | ||||
|     { "id": 14, "name": "PvP Arena - Rank", "rank": -1, "score": -1 }, | ||||
|     { "id": 15, "name": "Soul Wars Zeal", "rank": 6776, "score": 14780 }, | ||||
|     { "id": 16, "name": "Rifts closed", "rank": 4773, "score": 1060 }, | ||||
|     { "id": 17, "name": "Abyssal Sire", "rank": 2741, "score": 2192 }, | ||||
|     { "id": 18, "name": "Alchemical Hydra", "rank": 90203, "score": 594 }, | ||||
|     { "id": 19, "name": "Artio", "rank": 29573, "score": 143 }, | ||||
|     { "id": 20, "name": "Barrows Chests", "rank": 3668, "score": 1830 }, | ||||
|     { "id": 21, "name": "Bryophyta", "rank": 206778, "score": 5 }, | ||||
|     { "id": 22, "name": "Callisto", "rank": 32897, "score": 293 }, | ||||
|     { "id": 23, "name": "Calvar'ion", "rank": 64112, "score": 58 }, | ||||
|     { "id": 24, "name": "Cerberus", "rank": 18492, "score": 1905 }, | ||||
|     { "id": 25, "name": "Chambers of Xeric", "rank": 178941, "score": 34 }, | ||||
|     { | ||||
|       "id": 26, | ||||
|       "name": "Chambers of Xeric: Challenge Mode", | ||||
|       "rank": 2379, | ||||
|       "score": 501 | ||||
|     }, | ||||
|     { "id": 27, "name": "Chaos Elemental", "rank": 82902, "score": 50 }, | ||||
|     { "id": 28, "name": "Chaos Fanatic", "rank": 147358, "score": 26 }, | ||||
|     { "id": 29, "name": "Commander Zilyana", "rank": 51973, "score": 287 }, | ||||
|     { "id": 30, "name": "Corporeal Beast", "rank": 2377, "score": 2000 }, | ||||
|     { "id": 31, "name": "Crazy Archaeologist", "rank": 147406, "score": 41 }, | ||||
|     { "id": 32, "name": "Dagannoth Prime", "rank": 32225, "score": 707 }, | ||||
|     { "id": 33, "name": "Dagannoth Rex", "rank": 42229, "score": 698 }, | ||||
|     { "id": 34, "name": "Dagannoth Supreme", "rank": 32212, "score": 714 }, | ||||
|     { "id": 35, "name": "Deranged Archaeologist", "rank": 79605, "score": 25 }, | ||||
|     { "id": 36, "name": "Duke Sucellus", "rank": 9517, "score": 688 }, | ||||
|     { "id": 37, "name": "General Graardor", "rank": 19125, "score": 1228 }, | ||||
|     { "id": 38, "name": "Giant Mole", "rank": 65849, "score": 474 }, | ||||
|     { "id": 39, "name": "Grotesque Guardians", "rank": 2080, "score": 2140 }, | ||||
|     { "id": 40, "name": "Hespori", "rank": 16433, "score": 182 }, | ||||
|     { "id": 41, "name": "Kalphite Queen", "rank": 2006, "score": 2046 }, | ||||
|     { "id": 42, "name": "King Black Dragon", "rank": 48575, "score": 580 }, | ||||
|     { "id": 43, "name": "Kraken", "rank": 115056, "score": 1908 }, | ||||
|     { "id": 44, "name": "Kree'Arra", "rank": 10406, "score": 920 }, | ||||
|     { "id": 45, "name": "K'ril Tsutsaroth", "rank": 2196, "score": 1444 }, | ||||
|     { "id": 46, "name": "Mimic", "rank": 1005, "score": 33 }, | ||||
|     { "id": 47, "name": "Nex", "rank": 28413, "score": 620 }, | ||||
|     { "id": 48, "name": "Nightmare", "rank": 4157, "score": 744 }, | ||||
|     { "id": 49, "name": "Phosani's Nightmare", "rank": 4261, "score": 500 }, | ||||
|     { "id": 50, "name": "Obor", "rank": 993, "score": 217 }, | ||||
|     { "id": 51, "name": "Phantom Muspah", "rank": 21848, "score": 251 }, | ||||
|     { "id": 52, "name": "Sarachnis", "rank": 66260, "score": 119 }, | ||||
|     { "id": 53, "name": "Scorpia", "rank": 108898, "score": 26 }, | ||||
|     { "id": 54, "name": "Skotizo", "rank": 373975, "score": 9 }, | ||||
|     { "id": 55, "name": "Spindel", "rank": 2005, "score": 1569 }, | ||||
|     { "id": 56, "name": "Tempoross", "rank": 40001, "score": 273 }, | ||||
|     { "id": 57, "name": "The Gauntlet", "rank": 192608, "score": 7 }, | ||||
|     { "id": 58, "name": "The Corrupted Gauntlet", "rank": 78367, "score": 201 }, | ||||
|     { "id": 59, "name": "The Leviathan", "rank": 5679, "score": 583 }, | ||||
|     { "id": 60, "name": "The Whisperer", "rank": 478, "score": 1760 }, | ||||
|     { "id": 61, "name": "Theatre of Blood", "rank": 22932, "score": 250 }, | ||||
|     { | ||||
|       "id": 62, | ||||
|       "name": "Theatre of Blood: Hard Mode", | ||||
|       "rank": 15059, | ||||
|       "score": 7 | ||||
|     }, | ||||
|     { | ||||
|       "id": 63, | ||||
|       "name": "Thermonuclear Smoke Devil", | ||||
|       "rank": 7161, | ||||
|       "score": 3066 | ||||
|     }, | ||||
|     { "id": 64, "name": "Tombs of Amascut", "rank": 101555, "score": 35 }, | ||||
|     { | ||||
|       "id": 65, | ||||
|       "name": "Tombs of Amascut: Expert Mode", | ||||
|       "rank": 610, | ||||
|       "score": 933 | ||||
|     }, | ||||
|     { "id": 66, "name": "TzKal-Zuk", "rank": 15733, "score": 2 }, | ||||
|     { "id": 67, "name": "TzTok-Jad", "rank": -1, "score": -1 }, | ||||
|     { "id": 68, "name": "Vardorvis", "rank": 2051, "score": 2078 }, | ||||
|     { "id": 69, "name": "Venenatis", "rank": 194, "score": 7096 }, | ||||
|     { "id": 70, "name": "Vet'ion", "rank": 69260, "score": 51 }, | ||||
|     { "id": 71, "name": "Vorkath", "rank": 14039, "score": 2780 }, | ||||
|     { "id": 72, "name": "Wintertodt", "rank": 876367, "score": 62 }, | ||||
|     { "id": 73, "name": "Zalcano", "rank": 165341, "score": 25 }, | ||||
|     { "id": 74, "name": "Zulrah", "rank": 226318, "score": 275 } | ||||
|   ] | ||||
| } | ||||
| @@ -12,7 +12,10 @@ import { | ||||
|   getSkillPageURL, | ||||
|   getStatsURL, | ||||
|   BOSSES, | ||||
|   INVALID_FORMAT_ERROR | ||||
|   InvalidFormatError, | ||||
|   BH_MODES, | ||||
|   parseJsonStats, | ||||
|   HiscoresResponse | ||||
| } from '../src/index'; | ||||
|  | ||||
| const B0ATY_NAME = 'B0ATY'; | ||||
| @@ -24,7 +27,13 @@ const LYNX_TITAN_FORMATTED_NAME = 'Lynx Titan'; | ||||
|  | ||||
| const attackTopPage = readFileSync(`${__dirname}/attackTopPage.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( | ||||
|   `${__dirname}/lynxTitanNamePage.html`, | ||||
|   'utf8' | ||||
| @@ -45,7 +54,7 @@ jest.spyOn(axios, 'get').mockImplementation((url) => { | ||||
|   if (getSkillPageURL('main', 'attack', 1) === url) { | ||||
|     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 }); | ||||
|   } | ||||
|   throw new Error(`No mock response for URL: ${url}`); | ||||
| @@ -77,6 +86,9 @@ test('Parse CSV to json', () => { | ||||
|   810,99,37688883 | ||||
|   92,99,32005622 | ||||
|   23423,478 | ||||
|   89554,301 | ||||
|   89914,35 | ||||
|   99834,25 | ||||
|   99831,23 | ||||
|   89912,37 | ||||
|   32,12148 | ||||
| @@ -109,6 +121,7 @@ test('Parse CSV to json', () => { | ||||
|   4342,1655 | ||||
|   966,2951 | ||||
|   10151,1 | ||||
|   1289,2477 | ||||
|   1288,2407 | ||||
|   377,4669 | ||||
|   545,1567 | ||||
| @@ -131,6 +144,8 @@ test('Parse CSV to json', () => { | ||||
|   18823,23 | ||||
|   63,375 | ||||
|   2870,6 | ||||
|   2871,7 | ||||
|   2872,8 | ||||
|   6984,138 | ||||
|   23,923141 | ||||
|   4043,2000 | ||||
| @@ -138,6 +153,7 @@ test('Parse CSV to json', () => { | ||||
|   289,13070 | ||||
|   489,8 | ||||
|   967,47 | ||||
|   968,48 | ||||
|   11155,223 | ||||
|   1940,272 | ||||
|   8623,1340 | ||||
| @@ -173,7 +189,10 @@ test('Parse CSV to json', () => { | ||||
|       construction: { rank: 92, level: 99, xp: 32005622 } | ||||
|     }, | ||||
|     leaguePoints: { rank: 23423, score: 478 }, | ||||
|     deadmanPoints: { rank: 89554, score: 301 }, | ||||
|     bountyHunter: { | ||||
|       hunterV2: { rank: 89914, score: 35 }, | ||||
|       rogueV2: { rank: 99834, score: 25 }, | ||||
|       hunter: { rank: 99831, score: 23 }, | ||||
|       rogue: { rank: 89912, score: 37 } | ||||
|     }, | ||||
| @@ -210,6 +229,7 @@ test('Parse CSV to json', () => { | ||||
|       dagannothRex: { rank: 4342, score: 1655 }, | ||||
|       dagannothSupreme: { rank: 966, score: 2951 }, | ||||
|       derangedArchaeologist: { rank: 10151, score: 1 }, | ||||
|       dukeSucellus: { rank: 1289, score: 2477 }, | ||||
|       generalGraardor: { rank: 1288, score: 2407 }, | ||||
|       giantMole: { rank: 377, score: 4669 }, | ||||
|       grotesqueGuardians: { rank: 545, score: 1567 }, | ||||
| @@ -232,6 +252,8 @@ test('Parse CSV to json', () => { | ||||
|       tempoross: { rank: 18823, score: 23 }, | ||||
|       gauntlet: { rank: 63, score: 375 }, | ||||
|       corruptedGauntlet: { rank: 2870, score: 6 }, | ||||
|       leviathan: { rank: 2871, score: 7 }, | ||||
|       whisperer: { rank: 2872, score: 8 }, | ||||
|       theatreOfBlood: { rank: 6984, score: 138 }, | ||||
|       theatreOfBloodHardMode: { rank: 23, score: 923141 }, | ||||
|       thermonuclearSmokeDevil: { rank: 4043, score: 2000 }, | ||||
| @@ -239,6 +261,7 @@ test('Parse CSV to json', () => { | ||||
|       tombsOfAmascutExpertMode: { rank: 289, score: 13070 }, | ||||
|       tzKalZuk: { rank: 489, score: 8 }, | ||||
|       tzTokJad: { rank: 967, score: 47 }, | ||||
|       vardorvis: { rank: 968, score: 48 }, | ||||
|       venenatis: { rank: 11155, score: 223 }, | ||||
|       vetion: { rank: 1940, score: 272 }, | ||||
|       vorkath: { rank: 8623, score: 1340 }, | ||||
| @@ -255,12 +278,12 @@ test('Parse CSV with unknown activity', () => { | ||||
|   const statsWithUnknownActivity = `${lynxTitanStats} | ||||
|     -1,-1`; | ||||
|   expect(() => parseStats(statsWithUnknownActivity)).toThrow( | ||||
|     INVALID_FORMAT_ERROR | ||||
|     InvalidFormatError | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| test('Parse invalid CSV', () => { | ||||
|   expect(() => parseStats('invalid')).toThrow(INVALID_FORMAT_ERROR); | ||||
|   expect(() => parseStats('invalid')).toThrow(InvalidFormatError); | ||||
| }); | ||||
|  | ||||
| describe('Get name format', () => { | ||||
| @@ -475,7 +498,7 @@ test('Get non-existent player', async () => { | ||||
| }); | ||||
|  | ||||
| test('Get stats by gamemode', async () => { | ||||
|   const { skills, bosses } = await getStatsByGamemode( | ||||
|   const { skills, bosses, bountyHunter } = await getStatsByGamemode( | ||||
|     LYNX_TITAN_FORMATTED_NAME | ||||
|   ); | ||||
|  | ||||
| @@ -508,8 +531,10 @@ test('Get stats by gamemode', async () => { | ||||
|  | ||||
|   const bossKeys = Object.keys(bosses); | ||||
|   expect(bossKeys).toStrictEqual(BOSSES); | ||||
|   const bountyHunterKeys = Object.keys(bountyHunter); | ||||
|   expect(bountyHunterKeys).toStrictEqual(BH_MODES); | ||||
|  | ||||
|   expect.assertions(2); | ||||
|   expect.assertions(3); | ||||
| }); | ||||
|  | ||||
| describe('Get stats options', () => { | ||||
| @@ -518,13 +543,13 @@ describe('Get stats options', () => { | ||||
|   beforeEach(() => { | ||||
|     axios.get = jest.fn( | ||||
|       (url) => | ||||
|         new Promise<any>((resolve) => | ||||
|         new Promise<any>((resolve) => { | ||||
|           resolve( | ||||
|             url === getPlayerTableURL('main', rsn) | ||||
|               ? { data: lynxTitanNamePage } | ||||
|               : { status: 200, data: lynxTitanStats } | ||||
|           ) | ||||
|         ) | ||||
|           ); | ||||
|         }) | ||||
|     ); | ||||
|     axiosMock = axios.get as any; | ||||
|     axiosMock.mockClear(); | ||||
| @@ -532,11 +557,11 @@ describe('Get stats options', () => { | ||||
|   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), | ||||
|       getStatsURL('main', rsn, true), | ||||
|       getPlayerTableURL('main', rsn), | ||||
|       getStatsURL('ironman', rsn), | ||||
|       getStatsURL('hardcore', rsn), | ||||
|       getStatsURL('ultimate', rsn) | ||||
|       getStatsURL('ironman', rsn, true), | ||||
|       getStatsURL('hardcore', rsn, true), | ||||
|       getStatsURL('ultimate', rsn, true) | ||||
|     ]); | ||||
|   }); | ||||
|   it('skips fetching formatted RSN when option is provided', async () => { | ||||
| @@ -557,4 +582,16 @@ describe('Get stats options', () => { | ||||
|       ) | ||||
|     ).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); | ||||
| }); | ||||
|   | ||||
							
								
								
									
										1420
									
								
								__tests__/lynxTitanNamePage.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1420
									
								
								__tests__/lynxTitanNamePage.html
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,92 +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 | ||||
| -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. | 
							
								
								
									
										148
									
								
								__tests__/lynxTitanStats.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								__tests__/lynxTitanStats.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| { | ||||
|   "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": 31, "level": 99, "xp": 200000000 }, | ||||
|     { "id": 8, "name": "Cooking", "rank": 155, "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": 24, "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": 6, "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": "Deadman Points", "rank": -1, "score": -1 }, | ||||
|     { "id": 2, "name": "Bounty Hunter - Hunter", "rank": -1, "score": -1 }, | ||||
|     { "id": 3, "name": "Bounty Hunter - Rogue", "rank": -1, "score": -1 }, | ||||
|     { | ||||
|       "id": 4, | ||||
|       "name": "Bounty Hunter (Legacy) - Hunter", | ||||
|       "rank": -1, | ||||
|       "score": -1 | ||||
|     }, | ||||
|     { | ||||
|       "id": 5, | ||||
|       "name": "Bounty Hunter (Legacy) - Rogue", | ||||
|       "rank": -1, | ||||
|       "score": -1 | ||||
|     }, | ||||
|     { "id": 6, "name": "Clue Scrolls (all)", "rank": 805212, "score": 22 }, | ||||
|     { "id": 7, "name": "Clue Scrolls (beginner)", "rank": -1, "score": -1 }, | ||||
|     { "id": 8, "name": "Clue Scrolls (easy)", "rank": -1, "score": -1 }, | ||||
|     { "id": 9, "name": "Clue Scrolls (medium)", "rank": -1, "score": -1 }, | ||||
|     { "id": 10, "name": "Clue Scrolls (hard)", "rank": 502504, "score": 22 }, | ||||
|     { "id": 11, "name": "Clue Scrolls (elite)", "rank": -1, "score": -1 }, | ||||
|     { "id": 12, "name": "Clue Scrolls (master)", "rank": -1, "score": -1 }, | ||||
|     { "id": 13, "name": "LMS - Rank", "rank": -1, "score": -1 }, | ||||
|     { "id": 14, "name": "PvP Arena - Rank", "rank": -1, "score": -1 }, | ||||
|     { "id": 15, "name": "Soul Wars Zeal", "rank": -1, "score": -1 }, | ||||
|     { "id": 16, "name": "Rifts closed", "rank": -1, "score": -1 }, | ||||
|     { "id": 17, "name": "Abyssal Sire", "rank": -1, "score": -1 }, | ||||
|     { "id": 18, "name": "Alchemical Hydra", "rank": -1, "score": -1 }, | ||||
|     { "id": 19, "name": "Artio", "rank": -1, "score": -1 }, | ||||
|     { "id": 20, "name": "Barrows Chests", "rank": -1, "score": -1 }, | ||||
|     { "id": 21, "name": "Bryophyta", "rank": -1, "score": -1 }, | ||||
|     { "id": 22, "name": "Callisto", "rank": -1, "score": -1 }, | ||||
|     { "id": 23, "name": "Calvar'ion", "rank": -1, "score": -1 }, | ||||
|     { "id": 24, "name": "Cerberus", "rank": -1, "score": -1 }, | ||||
|     { "id": 25, "name": "Chambers of Xeric", "rank": -1, "score": -1 }, | ||||
|     { | ||||
|       "id": 26, | ||||
|       "name": "Chambers of Xeric: Challenge Mode", | ||||
|       "rank": -1, | ||||
|       "score": -1 | ||||
|     }, | ||||
|     { "id": 27, "name": "Chaos Elemental", "rank": -1, "score": -1 }, | ||||
|     { "id": 28, "name": "Chaos Fanatic", "rank": -1, "score": -1 }, | ||||
|     { "id": 29, "name": "Commander Zilyana", "rank": -1, "score": -1 }, | ||||
|     { "id": 30, "name": "Corporeal Beast", "rank": -1, "score": -1 }, | ||||
|     { "id": 31, "name": "Crazy Archaeologist", "rank": -1, "score": -1 }, | ||||
|     { "id": 32, "name": "Dagannoth Prime", "rank": -1, "score": -1 }, | ||||
|     { "id": 33, "name": "Dagannoth Rex", "rank": -1, "score": -1 }, | ||||
|     { "id": 34, "name": "Dagannoth Supreme", "rank": -1, "score": -1 }, | ||||
|     { "id": 35, "name": "Deranged Archaeologist", "rank": -1, "score": -1 }, | ||||
|     { "id": 36, "name": "Duke Sucellus", "rank": -1, "score": -1 }, | ||||
|     { "id": 37, "name": "General Graardor", "rank": -1, "score": -1 }, | ||||
|     { "id": 38, "name": "Giant Mole", "rank": -1, "score": -1 }, | ||||
|     { "id": 39, "name": "Grotesque Guardians", "rank": -1, "score": -1 }, | ||||
|     { "id": 40, "name": "Hespori", "rank": -1, "score": -1 }, | ||||
|     { "id": 41, "name": "Kalphite Queen", "rank": -1, "score": -1 }, | ||||
|     { "id": 42, "name": "King Black Dragon", "rank": -1, "score": -1 }, | ||||
|     { "id": 43, "name": "Kraken", "rank": -1, "score": -1 }, | ||||
|     { "id": 44, "name": "Kree'Arra", "rank": -1, "score": -1 }, | ||||
|     { "id": 45, "name": "K'ril Tsutsaroth", "rank": -1, "score": -1 }, | ||||
|     { "id": 46, "name": "Mimic", "rank": -1, "score": -1 }, | ||||
|     { "id": 47, "name": "Nex", "rank": -1, "score": -1 }, | ||||
|     { "id": 48, "name": "Nightmare", "rank": -1, "score": -1 }, | ||||
|     { "id": 49, "name": "Phosani's Nightmare", "rank": -1, "score": -1 }, | ||||
|     { "id": 50, "name": "Obor", "rank": -1, "score": -1 }, | ||||
|     { "id": 51, "name": "Phantom Muspah", "rank": -1, "score": -1 }, | ||||
|     { "id": 52, "name": "Sarachnis", "rank": -1, "score": -1 }, | ||||
|     { "id": 53, "name": "Scorpia", "rank": -1, "score": -1 }, | ||||
|     { "id": 54, "name": "Skotizo", "rank": -1, "score": -1 }, | ||||
|     { "id": 55, "name": "Spindel", "rank": -1, "score": -1 }, | ||||
|     { "id": 56, "name": "Tempoross", "rank": -1, "score": -1 }, | ||||
|     { "id": 57, "name": "The Gauntlet", "rank": -1, "score": -1 }, | ||||
|     { "id": 58, "name": "The Corrupted Gauntlet", "rank": -1, "score": -1 }, | ||||
|     { "id": 59, "name": "The Leviathan", "rank": -1, "score": -1 }, | ||||
|     { "id": 60, "name": "The Whisperer", "rank": -1, "score": -1 }, | ||||
|     { "id": 61, "name": "Theatre of Blood", "rank": -1, "score": -1 }, | ||||
|     { | ||||
|       "id": 62, | ||||
|       "name": "Theatre of Blood: Hard Mode", | ||||
|       "rank": -1, | ||||
|       "score": -1 | ||||
|     }, | ||||
|     { "id": 63, "name": "Thermonuclear Smoke Devil", "rank": -1, "score": -1 }, | ||||
|     { "id": 64, "name": "Tombs of Amascut", "rank": -1, "score": -1 }, | ||||
|     { | ||||
|       "id": 65, | ||||
|       "name": "Tombs of Amascut: Expert Mode", | ||||
|       "rank": -1, | ||||
|       "score": -1 | ||||
|     }, | ||||
|     { "id": 66, "name": "TzKal-Zuk", "rank": -1, "score": -1 }, | ||||
|     { "id": 67, "name": "TzTok-Jad", "rank": 225, "score": 186 }, | ||||
|     { "id": 68, "name": "Vardorvis", "rank": -1, "score": -1 }, | ||||
|     { "id": 69, "name": "Venenatis", "rank": -1, "score": -1 }, | ||||
|     { "id": 70, "name": "Vet'ion", "rank": -1, "score": -1 }, | ||||
|     { "id": 71, "name": "Vorkath", "rank": -1, "score": -1 }, | ||||
|     { "id": 72, "name": "Wintertodt", "rank": -1, "score": -1 }, | ||||
|     { "id": 73, "name": "Zalcano", "rank": -1, "score": -1 }, | ||||
|     { "id": 74, "name": "Zulrah", "rank": -1, "score": -1 } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										46
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "osrs-json-hiscores", | ||||
|   "version": "2.13.0", | ||||
|   "version": "2.16.2", | ||||
|   "description": "The Old School Runescape API wrapper that does more!", | ||||
|   "main": "lib/index.js", | ||||
|   "types": "lib/index.d.ts", | ||||
| @@ -61,18 +61,23 @@ | ||||
|       "@typescript-eslint" | ||||
|     ], | ||||
|     "extends": [ | ||||
|       "eslint-config-airbnb-typescript", | ||||
|       "airbnb-base", | ||||
|       "airbnb-typescript/base", | ||||
|       "prettier" | ||||
|     ], | ||||
|     "ignorePatterns": [ | ||||
|       "**/@types/*" | ||||
|     ] | ||||
|     ], | ||||
|     "rules": { | ||||
|       "max-classes-per-file": "off" | ||||
|     } | ||||
|   }, | ||||
|   "prettier": { | ||||
|     "trailingComma": "none", | ||||
|     "tabWidth": 2, | ||||
|     "semi": true, | ||||
|     "singleQuote": true | ||||
|     "singleQuote": true, | ||||
|     "endOfLine": "auto" | ||||
|   }, | ||||
|   "jest": { | ||||
|     "transform": { | ||||
| @@ -90,28 +95,25 @@ | ||||
|     "testEnvironment": "node" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "axios": "^0.21.1", | ||||
|     "jsdom": "^16.3.0", | ||||
|     "axios": "^1.6.2", | ||||
|     "jsdom": "^22.1.0", | ||||
|     "useragent-generator": "^1.1.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/jest": "^26.0.21", | ||||
|     "@types/jsdom": "^16.2.3", | ||||
|     "@typescript-eslint/eslint-plugin": "^4.19.0", | ||||
|     "@typescript-eslint/parser": "^4.19.0", | ||||
|     "eslint": "^7.22.0", | ||||
|     "eslint-config-airbnb-typescript": "^12.3.1", | ||||
|     "eslint-config-prettier": "^8.1.0", | ||||
|     "eslint-plugin-import": "^2.22.1", | ||||
|     "eslint-plugin-jsx-a11y": "^6.4.1", | ||||
|     "eslint-plugin-react": "^7.23.1", | ||||
|     "eslint-plugin-react-hooks": "^4.2.0", | ||||
|     "@types/jest": "^29.5.3", | ||||
|     "@types/jsdom": "^21.1.1", | ||||
|     "@typescript-eslint/eslint-plugin": "^6.0.0", | ||||
|     "@typescript-eslint/parser": "^6.0.0", | ||||
|     "eslint": "^8.44.0", | ||||
|     "eslint-config-airbnb-typescript": "^17.1.0", | ||||
|     "eslint-config-prettier": "^8.8.0", | ||||
|     "eslint-plugin-import": "^2.27.5", | ||||
|     "husky": "^5.2.0", | ||||
|     "jest": "^26.6.3", | ||||
|     "lint-staged": "^10.5.4", | ||||
|     "jest": "^29.6.1", | ||||
|     "lint-staged": "^13.2.3", | ||||
|     "np": "^7.7.0", | ||||
|     "prettier": "^2.2.1", | ||||
|     "ts-jest": "^26.5.4", | ||||
|     "typescript": "^4.2.3" | ||||
|     "prettier": "^3.0.0", | ||||
|     "ts-jest": "^29.1.1", | ||||
|     "typescript": "^5.1.6" | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										235
									
								
								src/hiscores.ts
									
									
									
									
									
								
							
							
						
						
									
										235
									
								
								src/hiscores.ts
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| import { AxiosResponse } from 'axios'; | ||||
| import axios, { AxiosRequestConfig } from 'axios'; | ||||
| import { BinaryData, JSDOM } from 'jsdom'; | ||||
| import { | ||||
|   Player, | ||||
| @@ -14,7 +14,8 @@ import { | ||||
|   ActivityName, | ||||
|   PlayerActivityRow, | ||||
|   Bosses, | ||||
|   GetStatsOptions | ||||
|   GetStatsOptions, | ||||
|   HiscoresResponse | ||||
| } from './types'; | ||||
| import { | ||||
|   getStatsURL, | ||||
| @@ -30,23 +31,68 @@ import { | ||||
|   getActivityPageURL, | ||||
|   httpGet, | ||||
|   BOSSES, | ||||
|   INVALID_FORMAT_ERROR, | ||||
|   validateRSN | ||||
|   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, | ||||
|   FORMATTED_DEADMAN_POINTS | ||||
| } 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. | ||||
|  * | ||||
|  * @param rsn Username of the player. | ||||
|  * @param config Optional axios request config object. | ||||
|  * @returns Formatted version of the rsn. | ||||
|  */ | ||||
| export async function getRSNFormat(rsn: string): Promise<string> { | ||||
| export async function getRSNFormat( | ||||
|   rsn: string, | ||||
|   config?: AxiosRequestConfig | ||||
| ): Promise<string> { | ||||
|   validateRSN(rsn); | ||||
|  | ||||
|   const url = getPlayerTableURL('main', rsn); | ||||
|   try { | ||||
|     const response = await httpGet<string | Buffer | BinaryData | undefined>( | ||||
|       url | ||||
|       url, | ||||
|       config | ||||
|     ); | ||||
|     const dom = new JSDOM(response.data); | ||||
|     const anchor = dom.window.document.querySelector( | ||||
| @@ -55,12 +101,84 @@ export async function getRSNFormat(rsn: string): Promise<string> { | ||||
|     if (anchor) { | ||||
|       return rsnFromElement(anchor); | ||||
|     } | ||||
|     throw Error('Player not found'); | ||||
|     throw new PlayerNotFoundError(); | ||||
|   } 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 deadmanPoints = getActivity(FORMATTED_DEADMAN_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, | ||||
|     deadmanPoints, | ||||
|     bountyHunter, | ||||
|     lastManStanding, | ||||
|     pvpArena, | ||||
|     soulWarsZeal, | ||||
|     riftsClosed, | ||||
|     clues, | ||||
|     bosses | ||||
|   }; | ||||
|  | ||||
|   return stats; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Parses CSV string of raw stats and returns a stats object. | ||||
|  * | ||||
| @@ -73,11 +191,8 @@ export function parseStats(csv: string): Stats { | ||||
|     .filter((entry) => !!entry) | ||||
|     .map((stat) => stat.split(',')); | ||||
|  | ||||
|   if ( | ||||
|     splitCSV.length !== | ||||
|     SKILLS.length + BH_MODES.length + CLUES.length + BOSSES.length + 5 | ||||
|   ) { | ||||
|     throw Error(INVALID_FORMAT_ERROR); | ||||
|   if (splitCSV.length !== SKILLS.length + ACTIVITIES.length) { | ||||
|     throw new InvalidFormatError(); | ||||
|   } | ||||
|  | ||||
|   const skillObjects: Skill[] = splitCSV | ||||
| @@ -103,15 +218,11 @@ export function parseStats(csv: string): Stats { | ||||
|       return activity; | ||||
|     }); | ||||
|  | ||||
|   const [leaguePoints] = activityObjects.splice(0, 1); | ||||
|   const [leaguePoints, deadmanPoints] = activityObjects.splice(0, 2); | ||||
|   const bhObjects = activityObjects.splice(0, BH_MODES.length); | ||||
|   const clueObjects = activityObjects.splice(0, CLUES.length); | ||||
|   const [ | ||||
|     lastManStanding, | ||||
|     pvpArena, | ||||
|     soulWarsZeal, | ||||
|     riftsClosed | ||||
|   ] = activityObjects.splice(0, 4); | ||||
|   const [lastManStanding, pvpArena, soulWarsZeal, riftsClosed] = | ||||
|     activityObjects.splice(0, 4); | ||||
|   const bossObjects = activityObjects.splice(0, BOSSES.length); | ||||
|  | ||||
|   const skills: Skills = skillObjects.reduce<Skills>((prev, curr, index) => { | ||||
| @@ -141,6 +252,7 @@ export function parseStats(csv: string): Stats { | ||||
|   const stats: Stats = { | ||||
|     skills, | ||||
|     leaguePoints, | ||||
|     deadmanPoints, | ||||
|     bountyHunter, | ||||
|     lastManStanding, | ||||
|     pvpArena, | ||||
| @@ -174,23 +286,18 @@ export async function getStats( | ||||
|   ]; | ||||
|   const shouldGetFormattedRsn = options?.shouldGetFormattedRsn ?? true; | ||||
|  | ||||
|   const mainRes = await httpGet<string>(getStatsURL('main', rsn)); | ||||
|   if (mainRes.status === 200) { | ||||
|     const emptyResponse: AxiosResponse<string> = { | ||||
|       status: 404, | ||||
|       data: '', | ||||
|       statusText: '', | ||||
|       headers: {}, | ||||
|       config: {} | ||||
|     }; | ||||
|   const main = await getOfficialStats(rsn, 'main', options?.axiosConfigs?.main); | ||||
|  | ||||
|   const getModeStats = async ( | ||||
|     mode: Extract<Gamemode, 'ironman' | 'hardcore' | 'ultimate'> | ||||
|     ): Promise<AxiosResponse<string>> => | ||||
|   ): Promise<HiscoresResponse | undefined> => | ||||
|     otherGamemodes.includes(mode) | ||||
|         ? httpGet<string>(getStatsURL(mode, rsn)).catch((err) => err) | ||||
|         : emptyResponse; | ||||
|       ? getOfficialStats(rsn, mode, options?.axiosConfigs?.[mode]).catch( | ||||
|           () => undefined | ||||
|         ) | ||||
|       : undefined; | ||||
|   const formattedName = shouldGetFormattedRsn | ||||
|       ? await getRSNFormat(rsn).catch(() => undefined) | ||||
|     ? await getRSNFormat(rsn, options?.axiosConfigs?.rsn).catch(() => undefined) | ||||
|     : undefined; | ||||
|  | ||||
|   const player: Player = { | ||||
| @@ -200,48 +307,42 @@ export async function getStats( | ||||
|     deulted: false, | ||||
|     deironed: false | ||||
|   }; | ||||
|     player.main = parseStats(mainRes.data); | ||||
|   player.main = parseJsonStats(main); | ||||
|  | ||||
|     const ironRes = await getModeStats('ironman'); | ||||
|     if (ironRes.status === 200) { | ||||
|       player.ironman = parseStats(ironRes.data); | ||||
|       const hcRes = await getModeStats('hardcore'); | ||||
|       const ultRes = await getModeStats('ultimate'); | ||||
|       if (hcRes.status === 200) { | ||||
|   const iron = await getModeStats('ironman'); | ||||
|   if (iron) { | ||||
|     player.ironman = parseJsonStats(iron); | ||||
|     const hc = await getModeStats('hardcore'); | ||||
|     const ult = await getModeStats('ultimate'); | ||||
|     if (hc) { | ||||
|       player.mode = 'hardcore'; | ||||
|         player.hardcore = parseStats(hcRes.data); | ||||
|       player.hardcore = parseJsonStats(hc); | ||||
|       if ( | ||||
|         player.ironman.skills.overall.xp !== player.hardcore.skills.overall.xp | ||||
|       ) { | ||||
|         player.dead = true; | ||||
|         player.mode = 'ironman'; | ||||
|       } | ||||
|         if ( | ||||
|           player.main.skills.overall.xp !== player.ironman.skills.overall.xp | ||||
|         ) { | ||||
|       if (player.main.skills.overall.xp !== player.ironman.skills.overall.xp) { | ||||
|         player.deironed = true; | ||||
|         player.mode = 'main'; | ||||
|       } | ||||
|       } else if (ultRes.status === 200) { | ||||
|     } else if (ult) { | ||||
|       player.mode = 'ultimate'; | ||||
|         player.ultimate = parseStats(ultRes.data); | ||||
|       player.ultimate = parseJsonStats(ult); | ||||
|       if ( | ||||
|         player.ironman.skills.overall.xp !== player.ultimate.skills.overall.xp | ||||
|       ) { | ||||
|         player.deulted = true; | ||||
|         player.mode = 'ironman'; | ||||
|       } | ||||
|         if ( | ||||
|           player.main.skills.overall.xp !== player.ironman.skills.overall.xp | ||||
|         ) { | ||||
|       if (player.main.skills.overall.xp !== player.ironman.skills.overall.xp) { | ||||
|         player.deironed = true; | ||||
|         player.mode = 'main'; | ||||
|       } | ||||
|     } else { | ||||
|       player.mode = 'ironman'; | ||||
|         if ( | ||||
|           player.main.skills.overall.xp !== player.ironman.skills.overall.xp | ||||
|         ) { | ||||
|       if (player.main.skills.overall.xp !== player.ironman.skills.overall.xp) { | ||||
|         player.deironed = true; | ||||
|         player.mode = 'main'; | ||||
|       } | ||||
| @@ -250,29 +351,26 @@ export async function getStats( | ||||
|  | ||||
|   return player; | ||||
| } | ||||
|   throw Error('Player not found'); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Fetches stats from the OSRS API and returns them as an object. | ||||
|  * | ||||
|  * @param rsn Username of the player. | ||||
|  * @param mode Gamemode to fetch ranks for. | ||||
|  * @param config Optional axios request config object. | ||||
|  * @returns Stats object. | ||||
|  */ | ||||
| export async function getStatsByGamemode( | ||||
|   rsn: string, | ||||
|   mode: Gamemode = 'main' | ||||
|   mode: Gamemode = 'main', | ||||
|   config?: AxiosRequestConfig | ||||
| ): Promise<Stats> { | ||||
|   validateRSN(rsn); | ||||
|   if (!GAMEMODES.includes(mode)) { | ||||
|     throw Error('Invalid game mode'); | ||||
|   } | ||||
|   const response = await httpGet<string>(getStatsURL(mode, rsn)); | ||||
|   if (response.status !== 200) { | ||||
|     throw Error('Player not found'); | ||||
|   } | ||||
|   const stats = parseStats(response.data); | ||||
|   const response = await getOfficialStats(rsn, mode, config); | ||||
|   const stats = parseJsonStats(response); | ||||
|  | ||||
|   return stats; | ||||
| } | ||||
| @@ -280,7 +378,8 @@ export async function getStatsByGamemode( | ||||
| export async function getSkillPage( | ||||
|   skill: SkillName, | ||||
|   mode: Gamemode = 'main', | ||||
|   page: number = 1 | ||||
|   page: number = 1, | ||||
|   config?: AxiosRequestConfig | ||||
| ): Promise<PlayerSkillRow[]> { | ||||
|   if (!GAMEMODES.includes(mode)) { | ||||
|     throw Error('Invalid game mode'); | ||||
| @@ -291,7 +390,10 @@ export async function getSkillPage( | ||||
|   } | ||||
|   const url = getSkillPageURL(mode, skill, page); | ||||
|  | ||||
|   const response = await httpGet<string | Buffer | BinaryData | undefined>(url); | ||||
|   const response = await httpGet<string | Buffer | BinaryData | undefined>( | ||||
|     url, | ||||
|     config | ||||
|   ); | ||||
|   const dom = new JSDOM(response.data); | ||||
|   const playersHTML = dom.window.document.querySelectorAll( | ||||
|     '.personal-hiscores__row' | ||||
| @@ -323,12 +425,14 @@ export async function getSkillPage( | ||||
|  * @param activity Name of the activity or boss to fetch hiscores for. | ||||
|  * @param mode Gamemode to fetch ranks for. | ||||
|  * @param page Page number. | ||||
|  * @param config Optional axios request config object. | ||||
|  * @returns Array of `PlayerActivityRow` objects. | ||||
|  */ | ||||
| export async function getActivityPage( | ||||
|   activity: ActivityName, | ||||
|   mode: Gamemode = 'main', | ||||
|   page: number = 1 | ||||
|   page: number = 1, | ||||
|   config?: AxiosRequestConfig | ||||
| ): Promise<PlayerActivityRow[]> { | ||||
|   if (!GAMEMODES.includes(mode)) { | ||||
|     throw Error('Invalid game mode'); | ||||
| @@ -339,7 +443,10 @@ export async function getActivityPage( | ||||
|   } | ||||
|   const url = getActivityPageURL(mode, activity, page); | ||||
|  | ||||
|   const response = await httpGet<string | Buffer | BinaryData | undefined>(url); | ||||
|   const response = await httpGet<string | Buffer | BinaryData | undefined>( | ||||
|     url, | ||||
|     config | ||||
|   ); | ||||
|   const dom = new JSDOM(response.data); | ||||
|   const playersHTML = dom.window.document.querySelectorAll( | ||||
|     '.personal-hiscores__row' | ||||
|   | ||||
							
								
								
									
										65
									
								
								src/types.ts
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								src/types.ts
									
									
									
									
									
								
							| @@ -1,3 +1,5 @@ | ||||
| import { AxiosRequestConfig } from 'axios'; | ||||
|  | ||||
| export type Gamemode = | ||||
|   | 'main' | ||||
|   | 'ironman' | ||||
| @@ -60,7 +62,7 @@ export type ClueType = | ||||
|  | ||||
| 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 }; | ||||
|  | ||||
| @@ -84,6 +86,7 @@ export type Boss = | ||||
|   | 'dagannothRex' | ||||
|   | 'dagannothSupreme' | ||||
|   | 'derangedArchaeologist' | ||||
|   | 'dukeSucellus' | ||||
|   | 'generalGraardor' | ||||
|   | 'giantMole' | ||||
|   | 'grotesqueGuardians' | ||||
| @@ -106,6 +109,8 @@ export type Boss = | ||||
|   | 'tempoross' | ||||
|   | 'gauntlet' | ||||
|   | 'corruptedGauntlet' | ||||
|   | 'leviathan' | ||||
|   | 'whisperer' | ||||
|   | 'theatreOfBlood' | ||||
|   | 'theatreOfBloodHardMode' | ||||
|   | 'thermonuclearSmokeDevil' | ||||
| @@ -113,6 +118,7 @@ export type Boss = | ||||
|   | 'tombsOfAmascutExpertMode' | ||||
|   | 'tzKalZuk' | ||||
|   | 'tzTokJad' | ||||
|   | 'vardorvis' | ||||
|   | 'venenatis' | ||||
|   | 'vetion' | ||||
|   | 'vorkath' | ||||
| @@ -124,6 +130,9 @@ export type Bosses = { [Type in Boss]: Activity }; | ||||
|  | ||||
| export type ActivityName = | ||||
|   | 'leaguePoints' | ||||
|   | 'deadmanPoints' | ||||
|   | 'hunterBHV2' | ||||
|   | 'rogueBHV2' | ||||
|   | 'hunterBH' | ||||
|   | 'rogueBH' | ||||
|   | 'lastManStanding' | ||||
| @@ -142,7 +151,14 @@ export type ActivityName = | ||||
| export interface Stats { | ||||
|   skills: Skills; | ||||
|   clues: Clues; | ||||
|   /** | ||||
|    * Will only contain rank and score data for the `seasonal` gamemode. | ||||
|    */ | ||||
|   leaguePoints: Activity; | ||||
|   /** | ||||
|    * Will only contain rank and score data for the `tournament` gamemode. | ||||
|    */ | ||||
|   deadmanPoints: Activity; | ||||
|   bountyHunter: BH; | ||||
|   lastManStanding: Activity; | ||||
|   pvpArena: Activity; | ||||
| @@ -181,4 +197,51 @@ export interface GetStatsOptions { | ||||
|    * @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 STATS_URL = 'index_lite.ws?player='; | ||||
| export const JSON_STATS_URL = 'index_lite.json?player='; | ||||
| export const SCORES_URL = 'overall.ws?'; | ||||
|  | ||||
| export type GamemodeUrl = { | ||||
| @@ -62,7 +63,7 @@ export const CLUES: ClueType[] = [ | ||||
|   'elite', | ||||
|   'master' | ||||
| ]; | ||||
| export const BH_MODES: BHType[] = ['hunter', 'rogue']; | ||||
| export const BH_MODES: BHType[] = ['hunterV2', 'rogueV2', 'hunter', 'rogue']; | ||||
| export const GAMEMODES: Gamemode[] = [ | ||||
|   'main', | ||||
|   'ironman', | ||||
| @@ -92,6 +93,7 @@ export const BOSSES: Boss[] = [ | ||||
|   'dagannothRex', | ||||
|   'dagannothSupreme', | ||||
|   'derangedArchaeologist', | ||||
|   'dukeSucellus', | ||||
|   'generalGraardor', | ||||
|   'giantMole', | ||||
|   'grotesqueGuardians', | ||||
| @@ -114,6 +116,8 @@ export const BOSSES: Boss[] = [ | ||||
|   'tempoross', | ||||
|   'gauntlet', | ||||
|   'corruptedGauntlet', | ||||
|   'leviathan', | ||||
|   'whisperer', | ||||
|   'theatreOfBlood', | ||||
|   'theatreOfBloodHardMode', | ||||
|   'thermonuclearSmokeDevil', | ||||
| @@ -121,6 +125,7 @@ export const BOSSES: Boss[] = [ | ||||
|   'tombsOfAmascutExpertMode', | ||||
|   'tzKalZuk', | ||||
|   'tzTokJad', | ||||
|   'vardorvis', | ||||
|   'venenatis', | ||||
|   'vetion', | ||||
|   'vorkath', | ||||
| @@ -130,6 +135,9 @@ export const BOSSES: Boss[] = [ | ||||
| ]; | ||||
| export const ACTIVITIES: ActivityName[] = [ | ||||
|   'leaguePoints', | ||||
|   'deadmanPoints', | ||||
|   'hunterBHV2', | ||||
|   'rogueBHV2', | ||||
|   'hunterBH', | ||||
|   'rogueBH', | ||||
|   'allClues', | ||||
| @@ -157,7 +165,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = { | ||||
|   barrows: 'Barrows Chests', | ||||
|   bryophyta: 'Bryophyta', | ||||
|   callisto: 'Callisto', | ||||
|   calvarion: "Cal'varion", | ||||
|   calvarion: "Calvar'ion", | ||||
|   cerberus: 'Cerberus', | ||||
|   chambersOfXeric: 'Chambers of Xeric', | ||||
|   chambersOfXericChallengeMode: 'Chambers of Xeric: Challenge Mode', | ||||
| @@ -170,6 +178,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = { | ||||
|   dagannothRex: 'Dagannoth Rex', | ||||
|   dagannothSupreme: 'Dagannoth Supreme', | ||||
|   derangedArchaeologist: 'Deranged Archaeologist', | ||||
|   dukeSucellus: 'Duke Sucellus', | ||||
|   generalGraardor: 'General Graardor', | ||||
|   giantMole: 'Giant Mole', | ||||
|   grotesqueGuardians: 'Grotesque Guardians', | ||||
| @@ -181,7 +190,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = { | ||||
|   krilTsutsaroth: "K'ril Tsutsaroth", | ||||
|   mimic: 'Mimic', | ||||
|   nex: 'Nex', | ||||
|   nightmare: 'The Nightmare of Ashihama', | ||||
|   nightmare: 'Nightmare', | ||||
|   phosanisNightmare: "Phosani's Nightmare", | ||||
|   obor: 'Obor', | ||||
|   phantomMuspah: 'Phantom Muspah', | ||||
| @@ -192,6 +201,8 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = { | ||||
|   tempoross: 'Tempoross', | ||||
|   gauntlet: 'The Gauntlet', | ||||
|   corruptedGauntlet: 'The Corrupted Gauntlet', | ||||
|   leviathan: 'The Leviathan', | ||||
|   whisperer: 'The Whisperer', | ||||
|   theatreOfBlood: 'Theatre of Blood', | ||||
|   theatreOfBloodHardMode: 'Theatre of Blood: Hard Mode', | ||||
|   thermonuclearSmokeDevil: 'Thermonuclear Smoke Devil', | ||||
| @@ -199,6 +210,7 @@ export const FORMATTED_BOSS_NAMES: FormattedBossNames = { | ||||
|   tombsOfAmascutExpertMode: 'Tombs of Amascut: Expert Mode', | ||||
|   tzKalZuk: 'TzKal-Zuk', | ||||
|   tzTokJad: 'TzTok-Jad', | ||||
|   vardorvis: 'Vardorvis', | ||||
|   venenatis: 'Venenatis', | ||||
|   vetion: "Vet'ion", | ||||
|   vorkath: 'Vorkath', | ||||
| @@ -257,14 +269,55 @@ export type FormattedBHNames = { | ||||
| }; | ||||
|  | ||||
| export const FORMATTED_BH_NAMES: FormattedBHNames = { | ||||
|   rogue: 'Bounty Hunter - Rogue', | ||||
|   hunter: 'Bounty Hunter - Hunter' | ||||
|   rogue: 'Bounty Hunter (Legacy) - Rogue', | ||||
|   hunter: 'Bounty Hunter (Legacy) - Hunter', | ||||
|   rogueV2: 'Bounty Hunter - Rogue', | ||||
|   hunterV2: 'Bounty Hunter - Hunter' | ||||
| }; | ||||
|  | ||||
| export const FORMATTED_LMS = 'Last Man Standing'; | ||||
| export const FORMATTED_PVP_ARENA = 'PvP Arena'; | ||||
| 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_LEAGUE_POINTS = 'League Points'; | ||||
| export const FORMATTED_RIFTS_CLOSED = 'Rifts Closed'; | ||||
| export const FORMATTED_DEADMAN_POINTS = 'Deadman 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 { Gamemode, SkillName, ActivityName } from '../types'; | ||||
| import { | ||||
| @@ -6,7 +6,9 @@ import { | ||||
|   STATS_URL, | ||||
|   SCORES_URL, | ||||
|   SKILLS, | ||||
|   ACTIVITIES | ||||
|   ACTIVITIES, | ||||
|   JSON_STATS_URL, | ||||
|   InvalidRSNError | ||||
| } from './constants'; | ||||
|  | ||||
| /** | ||||
| @@ -14,10 +16,13 @@ import { | ||||
|  * | ||||
|  * @param gamemode Gamemode to fetch ranks for. | ||||
|  * @param rsn Username of the player. | ||||
|  * @param json If the JSON endpoint is desired instead of CSV. | ||||
|  * @returns Encoded stats URL. | ||||
|  */ | ||||
| export const getStatsURL = (gamemode: Gamemode, rsn: string) => | ||||
|   `${GAMEMODE_URL[gamemode]}${STATS_URL}${encodeURIComponent(rsn)}`; | ||||
| export const getStatsURL = (gamemode: Gamemode, rsn: string, json = false) => | ||||
|   `${GAMEMODE_URL[gamemode]}${ | ||||
|     json ? JSON_STATS_URL : STATS_URL | ||||
|   }${encodeURIComponent(rsn)}`; | ||||
|  | ||||
| /** | ||||
|  * Will generate a player table URL for the official OSRS hiscores website. | ||||
| @@ -96,12 +101,16 @@ export const rsnFromElement = (el: Element | null) => { | ||||
|  * @param url URL to run a `GET` request against. | ||||
|  * @returns Axios response. | ||||
|  */ | ||||
| export const httpGet = <Response>(url: string) => | ||||
| export const httpGet = <Response>( | ||||
|   url: string, | ||||
|   config: AxiosRequestConfig = {} | ||||
| ) => | ||||
|   axios.get<Response>(url, { | ||||
|     headers: { | ||||
|       // without User-Agent header requests may be rejected by DDoS protection mechanism | ||||
|       'User-Agent': ua.firefox(80) | ||||
|     } | ||||
|     }, | ||||
|     ...config | ||||
|   }); | ||||
|  | ||||
| /** | ||||
| @@ -111,10 +120,10 @@ export const httpGet = <Response>(url: string) => | ||||
|  */ | ||||
| export const validateRSN = (rsn: string) => { | ||||
|   if (typeof rsn !== 'string') { | ||||
|     throw Error('RSN must be a string'); | ||||
|     throw new InvalidRSNError('RSN must be a string'); | ||||
|   } else if (!/^[a-zA-Z0-9 _-]+$/.test(rsn)) { | ||||
|     throw Error('RSN contains invalid character'); | ||||
|     throw new InvalidRSNError('RSN contains invalid character'); | ||||
|   } else if (rsn.length > 12 || rsn.length < 1) { | ||||
|     throw Error('RSN must be between 1 and 12 characters'); | ||||
|     throw new InvalidRSNError('RSN must be between 1 and 12 characters'); | ||||
|   } | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user