Giter Site home page Giter Site logo

esterni / pyracing Goto Github PK

View Code? Open in Web Editor NEW
63.0 7.0 25.0 1.71 MB

A complete overhaul of the original ir_webstats; pyracing is an API client/wrapper for iRacing, the leading online simracing service. pyracing handles the queries to iRacing's (known) URL endpoints and maps the returned JSON data into structured objects, allowing for easier access to the data.

Home Page: https://esterni.github.io/pyracing/

License: MIT License

Python 100.00%
api-client wrapper-api iracing

pyracing's Introduction

About

This package is an asynchronous API "wrapper" for retrieving data from iRacing. We use the term "wrapper" loosely as iRacing does not yet have an officially documented API; However, we've done our best to build something that might resemble one.

The goal of this project is to provide access to iRacing stats in a manner that is convienent, flexible, and efficient. In using this package, if you find something in its design that goes against these goals, we want to know.

The contributors of this project use Discord as the primary means of communication; The iRacing Open Wheel server was created by the author of this project and hosts the channels for discussion there. When joining, please ask Jacob Anderson for the role to see the appropriate channels.

Documentation & Discussion

All documentation for this project is available through the Github Pages project site.

Dependancies

httpx = 0.13.x

pyracing's People

Contributors

echang97 avatar esterni avatar fuzzwah avatar igortg avatar markstar avatar mikeholler avatar nohedidnt avatar sschulze1989 avatar xanderriga avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyracing's Issues

New endpoint in release notes

There is a new endpoint mentioned in the release notes for the upcoming iRacing season.

Declare Drivers 

- A new data endpoint has been added for Broadcasters to get the list of Teams and their declared drivers. 
- - This data endpoint is accessed through the URL: https://members.iracing.com/membersite/GetSubsessionTeams?subsessionid=<subsessionID> 
- - The intended use for this data is so Broadcasters can know from the start of a Session who will be driving for each Team over the course of the Session. 
- - - This data will not provide data for single driver sessions. 
- - - The data for Team Sessions without Declared Drivers is no different than what can be obtained by tracking the in-Sim telemetry.

Unable to download laps

I have this code:

CUSTOMER_ID = 404787


async def run(username: str, password: str):
    ir = PyRacingClient(
        username=username,
        password=password,
    )

    await save_personal_results(ir, CUSTOMER_ID)


async def save_personal_results(
        ir: PyRacingClient,
        customer_id: int
):
    results = await ir.event_results(cust_id=customer_id, quarter="3")
    first_result = results[0]
    subsession_results = await ir.subsession_data(subsession_id=first_result.subsession_id, cust_id=customer_id)
    subsession_laps = await ir.race_laps_driver(subsession_id=first_result.subsession_id, cust_id=customer_id)

And when I run it here are the logs:

/home/mjholler/Git/mike.holler/iracing-stats/venv/bin/python /home/mjholler/Git/mike.holler/iracing-stats/ianalyze/cli/entrypoint.py example --username [email protected] --password Wb887esvDkl0 --database-file test.sqlite3
2020-07-27 16:53:08;INFO;No cookies in cookie jar.
2020-07-27 16:53:08;INFO;Authenticating...
2020-07-27 16:53:11;INFO;Login successful
2020-07-27 16:53:11;INFO;Request being sent to: https://members.iracing.com/memberstats/member/GetResults with params: {'custid': 404787, 'showraces': 1, 'showquals': None, 'showtts': None, 'showops': None, 'showofficial': 1, 'showunofficial': None, 'showrookie': 1, 'showclassd': 1, 'showclassc': 1, 'showclassb': 1, 'showclassa': 1, 'showpro': 1, 'showprowc': 1, 'lowerbound': 1, 'upperbound': 25, 'sort': 'start_time', 'order': 'desc', 'format': 'json', 'category[]': 2, 'seasonyear': 2020, 'seasonquarter': '3', 'raceweek': None, 'trackid': None, 'carclassid': None, 'carid': None, 'start_low': None, 'start_high': None, 'finish_low': None, 'finish_high': None, 'incidents_low': None, 'incidents_high': None, 'champpoints_low': None, 'champpoints_high': None}
2020-07-27 16:53:11;INFO;Request sent for URL: https://members.iracing.com/memberstats/member/GetResults?custid=404787&showraces=1&showquals=&showtts=&showops=&showofficial=1&showunofficial=&showrookie=1&showclassd=1&showclassc=1&showclassb=1&showclassa=1&showpro=1&showprowc=1&lowerbound=1&upperbound=25&sort=start_time&order=desc&format=json&category%5B%5D=2&seasonyear=2020&seasonquarter=3&raceweek=&trackid=&carclassid=&carid=&start_low=&start_high=&finish_low=&finish_high=&incidents_low=&incidents_high=&champpoints_low=&champpoints_high=
2020-07-27 16:53:11;INFO;Status code of response: 200
2020-07-27 16:53:11;INFO;Request being sent to: https://members.iracing.com/membersite/member/GetSubsessionResults with params: {'subsessionID': 33637167, 'custid': 404787}
2020-07-27 16:53:12;INFO;Request sent for URL: https://members.iracing.com/membersite/member/GetSubsessionResults?subsessionID=33637167&custid=404787
2020-07-27 16:53:12;INFO;Status code of response: 200
2020-07-27 16:53:12;INFO;Request being sent to: https://members.iracing.com/membersite/member/GetLaps with params: {'subsessionid': 33637167, 'simsessnum': 0, 'groupid': 404787}
2020-07-27 16:53:12;INFO;Request sent for URL: https://members.iracing.com/membersite/member/GetLaps?subsessionid=33637167&simsessnum=0&groupid=404787
2020-07-27 16:53:12;INFO;Status code of response: 200
2020-07-27 16:53:12;ERROR;Task exception was never retrieved
future: <Task finished name='Task-2' coro=<run() done, defined at /home/mjholler/Git/mike.holler/iracing-stats/ianalyze/example.py:9> exception=TypeError('string indices must be integers')>
Traceback (most recent call last):
  File "/home/mjholler/Git/mike.holler/iracing-stats/ianalyze/example.py", line 15, in run
    await save_personal_results(ir, CUSTOMER_ID)
  File "/home/mjholler/Git/mike.holler/iracing-stats/ianalyze/example.py", line 26, in save_personal_results
    subsession_laps = await ir.race_laps_driver(subsession_id=first_result.subsession_id, cust_id=customer_id)
  File "/home/mjholler/Git/mike.holler/iracing-stats/venv/lib/python3.8/site-packages/pyracing/client.py", line 605, in race_laps_driver
    return [session_data.RaceLapsDriver(x) for x in response.json()]
  File "/home/mjholler/Git/mike.holler/iracing-stats/venv/lib/python3.8/site-packages/pyracing/client.py", line 605, in <listcomp>
    return [session_data.RaceLapsDriver(x) for x in response.json()]
  File "/home/mjholler/Git/mike.holler/iracing-stats/venv/lib/python3.8/site-packages/pyracing/response_objects/session_data.py", line 228, in __init__
    self.driver = self.Driver(dict['drivers'])
TypeError: string indices must be integers
2020-07-27 16:53:12;ERROR;An open stream object is being garbage collected; call "stream.close()" explicitly.
2020-07-27 16:53:12;ERROR;An open stream object is being garbage collected; call "stream.close()" explicitly.

Process finished with exit code 0

Finish team_standings() parsing of data into class objects

team_standings is the only URL endpoint that returns data in a completely unique format. The response format for team data is a single list of dictionaries. Just one. There are 35 attributes that make up a team from the provided URL endpoint, but none of these attributes list the individual drivers that make up the team, but they are there.

The dictionaries are ordered as: TDDD-TDD-TDDDDDD-TD-TDDD-TDD (T = Team, D = Driver)
The common attribute shared among a team and all of it's members is TeamID. Unfortunately, we'll have to iterate through all dictionary values in order to separate them into their respective objects, which will have identical keys.

Add install_requires block in setup.py to make sure correct dependencies get resolved

I'm using the following command to install pyracing:

› pip install -r requirements.txt 
Collecting git+https://github.com/Esterni/pyracing.git@cd25239999f9d1b25705f0272ae5bfbe97f07910 (from -r requirements.txt (line 1))
  Cloning https://github.com/Esterni/pyracing.git (to revision cd25239999f9d1b25705f0272ae5bfbe97f07910) to /tmp/pip-req-build-ufq91ix0
  Running command git clone -q https://github.com/Esterni/pyracing.git /tmp/pip-req-build-ufq91ix0
Building wheels for collected packages: pyracing
  Building wheel for pyracing (setup.py) ... done
  Created wheel for pyracing: filename=pyracing-0.1.0-py3-none-any.whl size=25015 sha256=5bb04c533e242ad0c5726e46b69a38167fbfde1c61b5608474206c3d3e62e9e0
  Stored in directory: /home/mjholler/.cache/pip/wheels/d5/e5/7b/11e8002ae3fcd21e6c5569eaa863a049e529a956b481d3a7de
Successfully built pyracing
Installing collected packages: pyracing
Successfully installed pyracing-0.1.0

Once installed, I try to use it like so:

(venv) 
iracing-stats/ on master 
› python
Python 3.8.0 (default, Apr 22 2020, 12:03:58) 
[GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyracing
>>> from pyracing import client
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/mjholler/Git/mike.holler/iracing-stats/venv/lib/python3.8/site-packages/pyracing/client.py", line 8, in <module>
    import httpx
ModuleNotFoundError: No module named 'httpx'

You can see that I do not have the proper dependency installed. I could do pip install httpx=<version>, but I do not know what the recommended version is, and most python libraries handle this by adding an install_requires block to setup.py.

Here is an example of what that might look like:

install_requires=[
    "httpx>=0.13"
]

I am only using httpx as an example here because of the error I got. I do not know whether this is comprehensive, or even the correct version. That's why I've not submitted a pull request.

Can't login - Authentication failed and credentials are correct

I don't know if this is a stupid question but I couldn't login anymore after a maintenance (error from client: "The login POST request was redirected to /failedlogin, indicating an authentication failure. If credentials are correct, check that a captcha is not required by manually visiting members.iracing.com").
I looked if captcha was required when I wanted to login on the website and this was required but what should I do to fix this problem so I could use this api for my app again? Thanks.

Edit:
With the Season 3 release (2022), these endpoints will begin sending the password as a Base64 encoded string (still transmitted via HTTPS) using the following steps.

  1. Convert the username (email) to lowercase
  2. Concatenate the output from step 1 to the end of the password
  3. Create a SHA256 hash of the output from step 2
  4. Encode the output from step 3 in Base64
  5. Submit the output from step 4 in the password field of the login form

Tweak the init methods of response_objects

  • For better uniformity the init methods of all (or most) response_objects should take the same arguments. With the exception of the classes in chart_data it is only a small tweak to get to: def __init__(self, data):

  • We should read the information returned by iRacing like this: self.laps = data.get('totalLaps') instead of self.laps = data['totalLaps'] just in case something is missing, changed its name or something like that. Also this would make it easy to set default values inside the get(), should we later want that.

Rename class attributes to align with common user terms

iRacing uses quite verbose naming structure for some of their data that probably aligns with their tracking of variables used in the simulator. Since the users of this API client will, in all likelihood, be users of the service it makes sense to use the same terminology that we would see them use when discussing various aspects of the iRacing service.

Add checks to Client init

In the init method of the Client we should have checks in place to verify that the arguments given have the right type. The username: str and password: str designation are not enforced by python.

Remove items from payload dictionary if their value is None

Create class method from following code-block and apply to all functions so that they are removed from the URL query when sent to iRacing.

for key in payload.copy():
            if payload.get(key) is None:
                del(payload[key])
            else:
                pass

Functions need to be tested for proper functionality afterwards. It is known that a value of -1 is equal to the key being set to nothing key= but it is unknown if queries will still work if the key is removed entirely.

Add subclass to next_session_times

The attribute 26 "raceWeekCars" is actually a dictionary of car_ids
example from season 2865:
{"110":{"maxPctFuelFill":"","weightPenaltyKG":"","max_dry_tire_sets":3,"powerAdjustPct":""},"56":{"maxPctFuelFill":"","weightPenaltyKG":"","max_dry_tire_sets":3,"powerAdjustPct":""},"103":{"maxPctFuelFill":"","weightPenaltyKG":"","max_dry_tire_sets":3,"powerAdjustPct":""}}

Convert driver id / series id to display name or series name

Hi everyone! This is not really an issue. I just have a question. I've started experimenting with this a bit and I love it! Want to try some fun stuff with Discord. It feels like I'm missing something so that's why I'm asking here. If I request the recent results for a driver ID I can't seem to get the display name of that driver ID. How do I request what display name belongs to an ID? I have the same problem for series names. Thx in advance!

Explanation of field: excludeLite

RE: :

'excludeLite': None # Purpose of excludeLite unknown

So this isn't a real issue that has a defect, but is some knowledge that might help understand why this parameter exists.

At some point, iRacing was considering (but it never came to light) a "lite" version of iRacing. Not too much details were ever unveiled but it was free to play and gave you access to a limited set of content. No paid content, nor was the option to pay content available.

Some screenshots: https://imgur.com/a/4PY3l

typo

There is a typo in client.py. simsessnum should be simsesnum. using simsessnum always returns the feature race.

I also had to change...
sim_session_type=ct.SimSessionType.race.value
to
sim_session_type
so that I could pass the value directly. I was scraping laps from heat races which wasn't an option by default.

All working for me now just a heads up.

async def race_laps_driver(
            self,
            cust_id,
            subsession_id,
            sim_session_type=ct.SimSessionType.race.value
    ):
        """ Returns data for all laps completed of a single driver.
        sim_sess_id specifies the laps from practice, qual, or race.
        """
        payload = {
            'subsessionid': subsession_id,
            'simsessnum': sim_session_type,
            'groupid': cust_id
        }
        url = ct.URL_LAPS_SINGLE
        response = await self._build_request(url, payload)

        return session_data.RaceLapsDriver(response.json())

Clean up class objects and attributes

Everything I've found so far that needs to be cleaned up

Names

SubSessionData.driver to .drivers

SeasonCarClass:
custid to cust_id
lowername to name_lower
shortname to name_short

Class name ActiveOPCount to OpenPractice for clarity of the object

Errors

WorldRecords object has 3 instances of = ['##'] instead of dict['##'] resulting in 3 lists being returned

Apply parse_encode() to:

SubSessionData.league_id
SubSessionData.league_season_id
SubSessionData.points_type
SubSessionData.reserve_status
SubSessionData.season_name
SubSessionData.season_name_short
SubSessionData.series_name
SubSessionData.series_name_short
SubSessionData.session_name
SubSessionData.special_event_type_text
SubSessionData.time_start
SubSessionData.time_start_sim
SubSessionData.track
SubSessionData.track_config

Driver.car_class_name
Driver.car_class_name_short
Driver.car_color_1
Driver.car_color_2
Driver.car_color_3
Driver.car_num
Driver.car_number_color_1
Driver.car_number_color_2
Driver.car_number_color_3
Driver.club_name
Driver.club_name_short
Driver.display_name
Driver.division_name
Driver.event_type_name
Driver.helm_color_1
Driver.helm_color_2
Driver.helm_color_3
Driver.host_id
Driver.league_points
Driver.license_category
Driver.reason_out
Driver.restrict_results
Driver.sim_ses_name
Driver.sim_ses_type_name
Driver.suit_color_1
Driver.suit_color_2
Driver.suit_color_3
Driver.track_category
Driver.wheel_color

SeasonCar.name

SeasonCarClass.lowername

ActiveOPCount.cars_left
ActiveOPCount.count_registered
ActiveOPCount.drivers_connected
ActiveOPCount.drivers_registered
ActiveOPCount.farm_id
ActiveOPCount.pits
ActiveOPCount.race_panel_img
ActiveOPCount.rubber_practice
ActiveOPCount.rubber_qualify
ActiveOPCount.rubber_race
ActiveOPCount.rubber_warmup
ActiveOPCount.series_abbrv
ActiveOPCount.series_name
ActiveOPCount.time_start
ActiveOPCount.time_start_sim
ActiveOPCount.track_config
ActiveOPCount.track_name

Response of GetWorldRecords changed

The endpoint GetWorldRecords returns a different set of numerical keys (and additional values) inside the individual rows. The init method of the WorldRecords class needs to be updated to reflect this change.

  • Two attributes have been added: "helmhelmettype" => "9" & "helmfacetype" => "34".

  • Most of the old numbers have changed as well, because the numbers assigned to the new attributes were already taken.

Quality check default parameters for all functions

I've tested and confirmed that when a parameter is included in the payload but set to None, that the query is still accepted by iRacing and returns data based on the parameters that have an assigned value.

The next step should be to quality check all function parameters and determine what makes sense to set as a default value for each query. Using driver_stats as an example:

pyracing/pyracing/client.py

Lines 230 to 257 in 69c61e6

async def driver_stats(
self,
custid,
search='null',
friend=-1,
watched=-1,
recent=-1,
country='null',
category=ct.Category.road,
class_low=-1,
class_high=-1,
irating_low=-1,
irating_high=-1,
ttrating_low=-1,
ttrating_high=-1,
avg_start_low=-1,
avg_start_high=-1,
avg_finish_low=-1,
avg_finish_high=-1,
avg_points_low=-1,
avg_points_high=-1,
avg_inc_low=-1,
avg_inc_high=-1,
lower_bound=1,
upper_bound=25,
sort=ct.Sort.irating,
order=ct.Sort.descending,
active=1

How should those parameter defaults be set?

Consider publishing to PyPi.org

Hi there,

I love the library! Do you have plans to publish it to PyPi so I can pip install pyracing? I can provide guidance if you need help setting it up.

Thanks,

Mike

Change the URL endpoint for event_results as requested by iRacing

In the 2020 Season 3 release notes iRacing presented a new endpoint to retrieve a drivers race results and requested that anyone previously using.

Website Data

- For those who scrape the iRacing Membersite website for data and are using the endpoint behind the "My Series Results" page (/memberstats/member/GetResults) we request that you switch to the new endpoint "/memberstats/member/SearchSeriesResults".
- - Key differences for the new endpoint are that it has fewer filters and returns all results unordered rather than pages of ordered results making the query easier for our system and requiring fewer calls.
- - Request parameters:
- - - custid (Required): ID for the customer whose official series results will be pulled.
- - - seriesid (Optional): ID of the series to limit results. Excluding pulls the customer's results for all series.
- - - catid (Optional): The ID(s) of the categories to limit results. This parameter may be included zero or more times. Excluding pulls results for all categories. Acceptable values are '1' (Oval), '2' (Road), '3' (Dirt Oval), '4' (Dirt Road).
- - - evttype (Optional): The type(s) of events to limit results. This parameter may be included zero or more times. Excluding pulls results for all event types. Acceptable values are '2' (Practice), '3' (Qualify), '4' (Time Trial), '5' (Race).
- - - Either 'seasonyear' and 'seasonquarter' must be included, or 'starttime_low' and 'starttime_high'. Our recommended approach is to use 'seasonyear' and 'seasonquarter' for initial pull of a customer's data, then using 'starttime_low' and 'starttime_high' where 'starttime_low' is the start time from the last session pulled.
- - - - seasonyear: The season year for which to limit results (e.g. 2020).
- - - - seasonquarter: The season quarter for which to limit results (e.g. 3).
- - - - OR
- - - - starttime_low: Include in the results sessions which started after this date/time, in milliseconds since the epoch beginning midnight January 1, 1970 UTC.
- - - - starttime_high: Include in the results sessions which started prior to this date/time, in milliseconds since the epoch beginning midnight January 1, 1970 UTC. May be no more than 90 days after starttime_low.
- - Example pulling Oval and Road race results (note that this returns no results):

It looks like the parameters are as follows:

  • custid (required)
  • seriesid (optional)
  • catid (optional - returns all categories when omitted)
  • evttype (optional- returns all event types when ommitted)
  • seasonyear AND seasonquarter
    OR
  • starttime_low AND starttime_high (Restricted to 90 day max search window)

Results are returned unordered

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.