doshidak / showdex

Pokémon Showdown extension that harnesses the power of parabolic calculus to strategically extract your opponents' Elo.
GNU Affero General Public License v3.0
104 stars 18 forks source link

perform a rating lookup if unreported in the battle #77

Closed doshidak closed 1 year ago

doshidak commented 1 year ago

Requested by: Iodyne

Unrated battles (e.g., tourneys, private matches via challenges, actually unrated battles like gen8unratedrandombattle) won't have the rating property populated in each player's Showdown.Side, e.g., in battle.p1.rating, battle.p2.rating, etc.


Apparently you can obtain any user's rating using the /rank <name> chat command in Showdown.

For instance, running the following command in chat:

/rank showdex_tester

returns:

showdown-rank-command

In Showdown's ConsoleRoom (defined in /js/client-chat.js of pokemon-showdown-client), the parseCommand() handles the user's command input.

It appears that this fetches an API (via jQuery's $.get()) with the URL obtained from the following:

app.user.getActionPHP();

'/~~showdown/action.php'

Note
This path doesn't seem to exist on other sites like smogtours.psim.us, so we should probably default to using https://play.pokemonshowdown.com as the base URL, defined by an environment variable.

and URL parameters:

{
  act: 'ladderget',
  user: targets[0],
}

Note
targets[0] is set before the $.get().
From our previous example command, targets[0] would be 'showdex_tester'.

We can see what this API returns via curl:

$ curl -X GET -G 'https://play.pokemonshowdown.com/~~showdown/action.php' \
  -d 'act=ladderget' \
  -d 'user=showdex_tester' \
  | cut -c2- | json_pp

Note
cut is used here since the API seems to return almost valid JSON, except for a ] character at the start:

][{"col1":"1","elo":"1000", ...

This is why we're cutting (basically String.prototypes.slice()) the response starting from the second character (i.e., [, via the 2 in -c2-) to the end (i.e., ], via the undefined end index in -c2-).

Afterwards, the resulting output is piped (|) into json_pp, which pretty prints the minified JSON response.

As a side note, -c2 will only return the second character (e.g., [) and -c2-3 will return the second and third characters (e.g., [{).

resulting in:

[
  {
    "col1": "1",
    "elo": "1000",
    "entryid": "99647518",
    "formatid": "gen1randombattle",
    "gxe": "45.6",
    "l": "1",
    "r": "1500",
    "rd": "130",
    "rpr": "1464.8737488932",
    "rprd": "122.67859691228",
    "rpsigma": "0",
    "rptime": "1666947600",
    "sigma": "0",
    "t": "0",
    "userid": "showdextester",
    "username": "showdex_tester",
    "w": "0"
  },
  {
    "col1": "3",
    "elo": "1112.9768898525",
    "entryid": "99646456",
    "formatid": "gen4randombattle",
    "gxe": "62.7",
    "l": "0",
    "r": "1577.0133926361",
    "rd": "116.5196410861",
    "rpr": "1602.6183626703",
    "rprd": "111.00092156858",
    "rpsigma": "0",
    "rptime": "1667034000",
    "sigma": "0",
    "t": "0",
    "userid": "showdextester",
    "username": "showdex_tester",
    "w": "3"
  },
  {
    "col1": "4",
    "elo": "1080.4614897423",
    "entryid": "99695589",
    "formatid": "gen5randombattle",
    "gxe": "46.5",
    "l": "2",
    "r": "1435.5678294812",
    "rd": "113.88560086033",
    "rpr": "1472.6303395851",
    "rprd": "109.1241782473",
    "rpsigma": "0",
    "rptime": "1668157200",
    "sigma": "0",
    "t": "0",
    "userid": "showdextester",
    "username": "showdex_tester",
    "w": "2"
  },
  {
    "col1": "1",
    "elo": "1040",
    "entryid": "99697773",
    "formatid": "gen7monotypebattlefactory",
    "gxe": "55",
    "l": "0",
    "r": "1500",
    "rd": "130",
    "rpr": "1540.1606143388",
    "rprd": "122.8583080769",
    "rpsigma": "0",
    "rptime": "1667034000",
    "sigma": "0",
    "t": "0",
    "userid": "showdextester",
    "username": "showdex_tester",
    "w": "1"
  },
  {
    "col1": "1",
    "elo": "1049.2244187701",
    "entryid": "98309521",
    "formatid": "gen7ou",
    "gxe": "55.2",
    "l": "0",
    "r": "1500",
    "rd": "130",
    "rpr": "1541.7772832554",
    "rprd": "121.83410780454",
    "rpsigma": "0",
    "rptime": "1664701200",
    "sigma": "0",
    "t": "0",
    "userid": "showdextester",
    "username": "showdex_tester",
    "w": "1"
  },
  {
    "col1": "1",
    "elo": "1047.6520729483",
    "entryid": "99646262",
    "formatid": "gen7randombattle",
    "gxe": "54.7",
    "l": "0",
    "r": "1500",
    "rd": "130",
    "rpr": "1537.3330981879",
    "rprd": "121.94666900838",
    "rpsigma": "0",
    "rptime": "1666947600",
    "sigma": "0",
    "t": "0",
    "userid": "showdextester",
    "username": "showdex_tester",
    "w": "1"
  },
  {
    "col1": "1",
    "elo": "1056.3717357733",
    "entryid": "99646045",
    "formatid": "gen8battlefactory",
    "gxe": "54.8",
    "l": "0",
    "r": "1500",
    "rd": "130",
    "rpr": "1538.050418597",
    "rprd": "122.07370125514",
    "rpsigma": "0",
    "rptime": "1666947600",
    "sigma": "0",
    "t": "0",
    "userid": "showdextester",
    "username": "showdex_tester",
    "w": "1"
  },
  {
    "col1": "7",
    "elo": "1175.502688618",
    "entryid": "97830291",
    "formatid": "gen8nationaldex",
    "gxe": "62.6",
    "l": "1",
    "r": "1576.3074174963",
    "rd": "108.62227750761",
    "rpr": "1601.6151118985",
    "rprd": "104.36591307833",
    "rpsigma": "0",
    "rptime": "1668157200",
    "sigma": "0",
    "t": "0",
    "userid": "showdextester",
    "username": "showdex_tester",
    "w": "6"
  },
  {
    "col1": "8",
    "elo": "1102.0679268558",
    "entryid": "98091616",
    "formatid": "gen8ou",
    "gxe": "52.1",
    "l": "3",
    "r": "1544.282355",
    "rd": "100.54207630281",
    "rpr": "1516.2131212849",
    "rprd": "97.171495833419",
    "rpsigma": "0",
    "rptime": "1666947600",
    "sigma": "0",
    "t": "0",
    "userid": "showdextester",
    "username": "showdex_tester",
    "w": "5"
  },
  {
    "col1": "46",
    "elo": "1473.0002208892",
    "entryid": "98078492",
    "formatid": "gen8randombattle",
    "gxe": "71.7",
    "l": "13",
    "r": "1682.2769488581",
    "rd": "55.589687298425",
    "rpr": "1676.6337086801",
    "rprd": "54.388862788958",
    "rpsigma": "0",
    "rptime": "1667984400",
    "sigma": "0",
    "t": "0",
    "userid": "showdextester",
    "username": "showdex_tester",
    "w": "33"
  }
]

At this point, it should be very straightforward to obtain the user's Elo by Arpad Elo for the current format.

doshidak commented 1 year ago

Done!

calcdex-player-rating

The screenshot above shows a private gen8randombattle, whose battle object normally does not report each player's rating.

Note that gen8unratedrandombattle is mapped to gen8randombattle, so you will see players' gen8randombattle Elo ratings in gen8unratedrandombattle.

Additionally, the tooltip that shows the additional GXE and Glicko-1 ratings are only available if the Calcdex had to fetch the player's rating.

Will close this via reference on the next PR.

doshidak commented 1 year ago

Update:

Just tested this on smogtours.psim.us using Chrome and seems to pull the rating from https://play.pokemonshowdown.com/~~showdown/action.php just fine, despite being on different domains (i.e., seems to be no CORS issues).

May have to separately test this on Firefox since fetch() is called directly from the injected bundle, as opposed to the background service worker on Chrome (which could possibly evade CORS since the fetching URL is specified in host_permissions of the manifest).

doshidak commented 1 year ago

Update:

As expected, does not work from Firefox due to CORS. action.php does not respond with the Access-Control-Allow-Origin header, so it only works on play.pokemonshowdown.com.

Interestingly, app.user.getActionPHP() on smogtours.psim.us returns:

/~~smogtours/action.php

resulting in the full URL:

https://smogtours.psim.us/~~smogtours/action.php

However, this redirects to:

https://play.pokemonshowdown.com/~~smogtours/action.php

which does not exist.

Fetching from:

/~~showdown/action.php

from smogtours.psim.us will result in the correct URL:

https://smogtours.psim.us/~~showdown/action.php

which redirects to:

https://play.pokemonshowdown.com/~~showdown/action.php

however, this results in an error due to Mixed Content, specifically active content due to this being used in a fetch() call.

Unfortunately for now, Firefox users will not get this feature on other Showdown sites (other than the main one), like smogtours.psim.us. Thankfully, this does not crash the Calcdex as the fetch() error is absorbed by RTK.

Workaround is to use some kind of CORS proxy, similar to how the fetch() call in background script on Chrome works as a proxy. Will research more workarounds, but won't be able to fix this for the upcoming patch.