sportsball 0.3.29__tar.gz → 0.3.31__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sportsball-0.3.29/sportsball.egg-info → sportsball-0.3.31}/PKG-INFO +1 -1
- {sportsball-0.3.29 → sportsball-0.3.31}/setup.py +1 -1
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/__init__.py +1 -1
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/afltables/afl_afltables_league_model.py +3 -1
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/combined/afl_combined_league_model.py +18 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/google/google_address_model.py +21 -18
- sportsball-0.3.31/sportsball/data/oddsportal/decrypt.py +98 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/oddsportal/oddsportal_game_model.py +7 -38
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/oddsportal/oddsportal_league_model.py +50 -69
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/oddsportal/oddsportal_team_model.py +7 -71
- {sportsball-0.3.29 → sportsball-0.3.31/sportsball.egg-info}/PKG-INFO +1 -1
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball.egg-info/SOURCES.txt +1 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/LICENSE +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/MANIFEST.in +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/README.md +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/requirements.txt +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/setup.cfg +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/__main__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/args.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/cache.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/address_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/afltables/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/afltables/afl_afltables_game_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/afltables/afl_afltables_player_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/afltables/afl_afltables_team_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/afltables/afl_afltables_venue_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/aussportsbetting/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/aussportsbetting/afl_aussportsbetting_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/combined/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/espn/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/espn/afl_espn_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/oddsportal/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/oddsportal/afl_oddsportal_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/aussportsbetting/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/aussportsbetting/aussportsbetting_bookie_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/aussportsbetting/aussportsbetting_game_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/aussportsbetting/aussportsbetting_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/aussportsbetting/aussportsbetting_odds_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/aussportsbetting/aussportsbetting_team_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/aussportsbetting/aussportsbetting_venue_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/bookie_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/combined/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/combined/combined_address_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/combined/combined_game_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/combined/combined_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/combined/combined_player_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/combined/combined_team_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/combined/combined_venue_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/combined/combined_weather_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/espn/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/espn/espn_bookie_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/espn/espn_game_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/espn/espn_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/espn/espn_odds_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/espn/espn_player_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/espn/espn_team_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/espn/espn_venue_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/field_type.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/game_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/google/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/google/google_news_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/league.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/combined/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/combined/nba_combined_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/espn/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/espn/nba_espn_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/nba/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/nba/nba_nba_game_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/nba/nba_nba_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/nba/nba_nba_player_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/nba/nba_nba_team_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/oddsportal/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/oddsportal/nba_oddsportal_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/sportsdb/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/sportsdb/nba_sportsdb_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/sportsreference/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nba/sportsreference/nba_sportsreference_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaab/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaab/combined/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaab/combined/ncaab_combined_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaab/espn/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaab/espn/ncaab_espn_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaab/oddsportal/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaab/oddsportal/ncaab_oddsportal_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaab/sportsreference/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaab/sportsreference/ncaab_sportsreference_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaaf/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaaf/combined/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaaf/combined/ncaaf_combined_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaaf/espn/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaaf/espn/ncaaf_espn_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaaf/oddsportal/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/ncaaf/oddsportal/ncaaf_oddsportal_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/news_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/aussportsbetting/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/aussportsbetting/nfl_aussportsbetting_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/combined/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/combined/nfl_combined_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/espn/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/espn/nfl_espn_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/oddsportal/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/oddsportal/nfl_oddsportal_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/sportsdb/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/nfl/sportsdb/nfl_sportsdb_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/odds_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/oddsportal/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/oddsportal/oddsportal_bookie_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/oddsportal/oddsportal_odds_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/oddsportal/oddsportal_venue_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/player_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/season_type.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/social_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsdb/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsdb/sportsdb_game_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsdb/sportsdb_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsdb/sportsdb_team_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsdb/sportsdb_venue_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsreference/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsreference/sportsreference_game_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsreference/sportsreference_league_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsreference/sportsreference_player_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsreference/sportsreference_team_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/sportsreference/sportsreference_venue_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/team_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/venue_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/weather/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/weather/gribstream/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/weather/gribstream/gribstream_weather_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/weather/multi_weather_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/weather/openmeteo/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/weather/openmeteo/openmeteo_weather_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/weather_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/wikipedia/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/wikipedia/wikipedia_venue_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/x/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/x/x_social_model.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/logger.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/loglevel.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/proxy_session.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/session.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/sportsball.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/vendor/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball/vendor/pygooglenews/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball.egg-info/dependency_links.txt +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball.egg-info/entry_points.txt +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball.egg-info/not-zip-safe +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball.egg-info/requires.txt +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/sportsball.egg-info/top_level.txt +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/afl/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/afl/afltables/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/afl/afltables/afl_afltables_league_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/afl/afltables/afl_afltables_player_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/aussportsbetting/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/aussportsbetting/aussportsbetting_game_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/combined/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/combined/combined_game_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/combined/combined_player_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/espn/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/espn/espn_game_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/espn/espn_player_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/game_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/google/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/google/google_address_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/google/google_news_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nba/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nba/nba/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nba/nba/nba_nba_game_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nba/nba/nba_nba_league_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nba/nba/nba_nba_player_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nba/nba/nba_nba_team_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nba/sportsdb/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nba/sportsdb/nba_sportsdb_league_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nba/sportsreference/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nba/sportsreference/nba_sportsreference_league_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/ncaab/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/ncaab/sportsreference/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/ncaab/sportsreference/ncaab_sportsreference_league_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nfl/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nfl/sportsdb/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/nfl/sportsdb/nfl_sportsdb_league_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/oddsportal/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/oddsportal/oddsportal_game_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/player_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/social_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsdb/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsdb/sportsdb_game_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsdb/sportsdb_league_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsdb/sportsdb_team_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsdb/sportsdb_venue_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsreference/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsreference/sportsreference_game_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsreference/sportsreference_league_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsreference/sportsreference_player_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsreference/sportsreference_team_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/sportsreference/sportsreference_venue_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/team_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/x/__init__.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/data/x/x_social_model_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/proxy_session_test.py +0 -0
- {sportsball-0.3.29 → sportsball-0.3.31}/tests/sportsball_test.py +0 -0
@@ -26,7 +26,7 @@ def install_requires() -> typing.List[str]:
|
|
26
26
|
|
27
27
|
setup(
|
28
28
|
name='sportsball',
|
29
|
-
version='0.3.
|
29
|
+
version='0.3.31',
|
30
30
|
description='A library for pulling in and normalising sports stats.',
|
31
31
|
long_description=long_description,
|
32
32
|
long_description_content_type='text/markdown',
|
{sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/afltables/afl_afltables_league_model.py
RENAMED
@@ -145,7 +145,9 @@ class AFLAFLTablesLeagueModel(LeagueModel):
|
|
145
145
|
|
146
146
|
@property
|
147
147
|
def games(self) -> Iterator[GameModel]:
|
148
|
-
|
148
|
+
with self.session.cache_disabled():
|
149
|
+
response = self.session.get(_SEASON_URL)
|
150
|
+
response.raise_for_status()
|
149
151
|
soup = BeautifulSoup(response.text, "html.parser")
|
150
152
|
with tqdm.tqdm(position=self.position) as pbar:
|
151
153
|
for table in soup.find_all("table"):
|
{sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/afl/combined/afl_combined_league_model.py
RENAMED
@@ -71,6 +71,22 @@ AFL_TEAM_IDENTITY_MAP = {
|
|
71
71
|
"North Melbourne": KANGAROOS,
|
72
72
|
"Melbourne": MELBOURNE,
|
73
73
|
"Collingwood": COLLINGWOOD,
|
74
|
+
# OddsPortal
|
75
|
+
"Collingwood Magpies": COLLINGWOOD,
|
76
|
+
"Port Adelaide Power": PORT_ADELAIDE,
|
77
|
+
"Richmond Tigers": RICHMOND,
|
78
|
+
"Carlton Blues": CARLTON,
|
79
|
+
"Hawthorn Hawks": HAWTHORN,
|
80
|
+
"Essendon Bombers": ESSENDON,
|
81
|
+
"Sydney Swans": SWANS,
|
82
|
+
"Adelaide Crows": ADELAIDE,
|
83
|
+
"St Kilda Saints": STKILDA,
|
84
|
+
"Brisbane Lions": BRISBANE_LIONS,
|
85
|
+
"Melbourne Demons": MELBOURNE,
|
86
|
+
"Geelong Cats": GEELONG,
|
87
|
+
"Fremantle Dockers": FREMANTLE,
|
88
|
+
"West Coast Eagles": WESTCOAST,
|
89
|
+
"Gold Coast Suns": GOLDCOAST,
|
74
90
|
}
|
75
91
|
BRUNSWICK_ST = "brunswick_st"
|
76
92
|
VICTORIA_PARK = "victoria_park"
|
@@ -199,6 +215,8 @@ AFL_VENUE_IDENTITY_MAP = {
|
|
199
215
|
"Westpac Stadium": WELLINGTON,
|
200
216
|
"AAMI Stadium": FOOTBALL_PARK,
|
201
217
|
"Blacktown Park": BLACKTOWN,
|
218
|
+
# OddsPortal
|
219
|
+
"Melbourne Cricket Ground": MCG,
|
202
220
|
}
|
203
221
|
|
204
222
|
|
@@ -1441,6 +1441,15 @@ TED_CONSTANT_CONVOCATION_CENTER = SportsballGeocodeTuple(
|
|
1441
1441
|
housenumber="4320",
|
1442
1442
|
country="USA",
|
1443
1443
|
)
|
1444
|
+
STONY_BROOK_ARENA = SportsballGeocodeTuple(
|
1445
|
+
city="Stony Brook",
|
1446
|
+
state="NY",
|
1447
|
+
postal="11790",
|
1448
|
+
lat=40.91741,
|
1449
|
+
lng=-73.125955,
|
1450
|
+
housenumber="100",
|
1451
|
+
country="USA",
|
1452
|
+
)
|
1444
1453
|
_CACHED_GEOCODES: dict[str, Any] = {
|
1445
1454
|
"S.C.G. - Australia": SCG,
|
1446
1455
|
"Victoria Park - Australia": SportsballGeocodeTuple(
|
@@ -5554,15 +5563,7 @@ _CACHED_GEOCODES: dict[str, Any] = {
|
|
5554
5563
|
housenumber="4000",
|
5555
5564
|
country="USA",
|
5556
5565
|
),
|
5557
|
-
"Island Federal Credit Union, Stony Brook, New York":
|
5558
|
-
city="Stony Brook",
|
5559
|
-
state="NY",
|
5560
|
-
postal="11790",
|
5561
|
-
lat=40.91741,
|
5562
|
-
lng=-73.125955,
|
5563
|
-
housenumber="100",
|
5564
|
-
country="USA",
|
5565
|
-
),
|
5566
|
+
"Island Federal Credit Union, Stony Brook, New York": STONY_BROOK_ARENA,
|
5566
5567
|
"Cajundome, Lafayette, Louisiana": SportsballGeocodeTuple(
|
5567
5568
|
city="Lafayette",
|
5568
5569
|
state="LA",
|
@@ -7588,15 +7589,7 @@ _CACHED_GEOCODES: dict[str, Any] = {
|
|
7588
7589
|
housenumber="11200",
|
7589
7590
|
country="USA",
|
7590
7591
|
),
|
7591
|
-
"Island Federal Credit Union Arena, Stony Brook, New York":
|
7592
|
-
city="Stony Brook",
|
7593
|
-
state="NY",
|
7594
|
-
postal="11790",
|
7595
|
-
lat=40.91741,
|
7596
|
-
lng=-73.125955,
|
7597
|
-
housenumber="100",
|
7598
|
-
country="USA",
|
7599
|
-
),
|
7592
|
+
"Island Federal Credit Union Arena, Stony Brook, New York": STONY_BROOK_ARENA,
|
7600
7593
|
"Phoenix, Arizona": SportsballGeocodeTuple(
|
7601
7594
|
city="Phoenix",
|
7602
7595
|
state="AZ",
|
@@ -7825,6 +7818,16 @@ _CACHED_GEOCODES: dict[str, Any] = {
|
|
7825
7818
|
housenumber="35",
|
7826
7819
|
country="USA",
|
7827
7820
|
),
|
7821
|
+
"Corpus Christi, Texas": SportsballGeocodeTuple(
|
7822
|
+
city="Corpus Christi",
|
7823
|
+
state="TX",
|
7824
|
+
postal="",
|
7825
|
+
lat=27.742778,
|
7826
|
+
lng=-97.401944,
|
7827
|
+
housenumber="",
|
7828
|
+
country="USA",
|
7829
|
+
),
|
7830
|
+
"Island Federal Credit Union Arena, New York, NY, USA": STONY_BROOK_ARENA,
|
7828
7831
|
}
|
7829
7832
|
|
7830
7833
|
|
@@ -0,0 +1,98 @@
|
|
1
|
+
"""A function for decrypting an odds portal dat file."""
|
2
|
+
|
3
|
+
# pylint: disable=too-many-locals
|
4
|
+
import base64
|
5
|
+
import json
|
6
|
+
import urllib.parse
|
7
|
+
from typing import Any
|
8
|
+
|
9
|
+
import requests_cache
|
10
|
+
from bs4 import BeautifulSoup
|
11
|
+
from cryptography.hazmat.backends import default_backend
|
12
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
13
|
+
from cryptography.hazmat.primitives.hashes import SHA256
|
14
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
15
|
+
|
16
|
+
from ...proxy_session import X_NO_WAYBACK
|
17
|
+
|
18
|
+
|
19
|
+
def _find_decryption_data(
|
20
|
+
session: requests_cache.CachedSession,
|
21
|
+
soup: BeautifulSoup,
|
22
|
+
referer_url: str,
|
23
|
+
user_agent: str | None = None,
|
24
|
+
) -> tuple[bytes, bytes]:
|
25
|
+
salt: bytes | None = None
|
26
|
+
password: bytes | None = None
|
27
|
+
for script in soup.find_all("script"):
|
28
|
+
src = script.get("src")
|
29
|
+
if src is None:
|
30
|
+
continue
|
31
|
+
if "/app.js" in src:
|
32
|
+
src_url = urllib.parse.urljoin(referer_url, src)
|
33
|
+
headers = {X_NO_WAYBACK: "1"}
|
34
|
+
if user_agent is not None:
|
35
|
+
headers["User-Agent"] = user_agent
|
36
|
+
src_response = session.get(src_url, headers=headers)
|
37
|
+
src_response.raise_for_status()
|
38
|
+
variables = src_response.text
|
39
|
+
sentinel = 'break}return e.next=9,g(r.data,"'
|
40
|
+
variables = variables[variables.find(sentinel) + len(sentinel) :]
|
41
|
+
variables = variables[
|
42
|
+
: variables.find('");case 9:return s=e.sent,l=JSON.parse(s),e.abrupt')
|
43
|
+
]
|
44
|
+
password_str, salt_str = variables.split('","')
|
45
|
+
salt = str.encode(salt_str)
|
46
|
+
password = str.encode(password_str)
|
47
|
+
break
|
48
|
+
if salt is None:
|
49
|
+
raise ValueError("salt is null.")
|
50
|
+
if password is None:
|
51
|
+
raise ValueError("password is null.")
|
52
|
+
return salt, password
|
53
|
+
|
54
|
+
|
55
|
+
def fetch_data(
|
56
|
+
url: str,
|
57
|
+
session: requests_cache.CachedSession,
|
58
|
+
referer_url: str,
|
59
|
+
soup: BeautifulSoup,
|
60
|
+
user_agent: str | None = None,
|
61
|
+
) -> dict[str, Any]:
|
62
|
+
"""Fetch the data from the URL and decrypt it."""
|
63
|
+
salt, password = _find_decryption_data(
|
64
|
+
session, soup, referer_url, user_agent=user_agent
|
65
|
+
)
|
66
|
+
headers = {
|
67
|
+
"X-Requested-With": "XMLHttpRequest",
|
68
|
+
"Referer": referer_url,
|
69
|
+
}
|
70
|
+
if user_agent is not None:
|
71
|
+
headers["User-Agent"] = user_agent
|
72
|
+
response = session.get(
|
73
|
+
url,
|
74
|
+
headers=headers,
|
75
|
+
)
|
76
|
+
response.raise_for_status()
|
77
|
+
decoded_data = base64.b64decode(response.content).decode()
|
78
|
+
encrypted, key = decoded_data.split(":")
|
79
|
+
encrypted_bytes = base64.urlsafe_b64decode(encrypted)
|
80
|
+
key_bytes = bytes.fromhex(key)
|
81
|
+
kdf = PBKDF2HMAC(
|
82
|
+
algorithm=SHA256(),
|
83
|
+
length=32,
|
84
|
+
salt=salt,
|
85
|
+
iterations=1000,
|
86
|
+
backend=default_backend(),
|
87
|
+
)
|
88
|
+
aes_key = kdf.derive(password)
|
89
|
+
cipher = Cipher(
|
90
|
+
algorithms.AES(aes_key), modes.CBC(key_bytes), backend=default_backend()
|
91
|
+
)
|
92
|
+
decryptor = cipher.decryptor()
|
93
|
+
decrypted_bytes = decryptor.update(encrypted_bytes) + decryptor.finalize()
|
94
|
+
decrypted_data = decrypted_bytes.decode("utf-8")
|
95
|
+
end_of_json = decrypted_data.rfind("}")
|
96
|
+
if end_of_json != -1:
|
97
|
+
decrypted_data = decrypted_data[: end_of_json + 1]
|
98
|
+
return json.loads(decrypted_data)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""OddsPortal game model."""
|
2
2
|
|
3
|
-
# pylint: disable=too-many-locals,too-many-statements
|
3
|
+
# pylint: disable=too-many-locals,too-many-statements,line-too-long
|
4
4
|
import datetime
|
5
5
|
import json
|
6
6
|
import urllib.parse
|
@@ -13,6 +13,7 @@ from ...cache import MEMORY
|
|
13
13
|
from ...proxy_session import X_NO_WAYBACK
|
14
14
|
from ..game_model import GameModel
|
15
15
|
from ..league import League
|
16
|
+
from .decrypt import fetch_data
|
16
17
|
from .oddsportal_team_model import create_oddsportal_team_model
|
17
18
|
from .oddsportal_venue_model import create_oddsportal_venue_model
|
18
19
|
|
@@ -49,31 +50,6 @@ def _create_oddsportal_game_model(
|
|
49
50
|
default_scope_id = str(event_data["defaultScopeId"])
|
50
51
|
xhash = urllib.parse.unquote(event_data["xhash"])
|
51
52
|
|
52
|
-
salt: bytes | None = None
|
53
|
-
password: bytes | None = None
|
54
|
-
for script in soup.find_all("script"):
|
55
|
-
src = script.get("src")
|
56
|
-
if src is None:
|
57
|
-
continue
|
58
|
-
if "/app.js" in src:
|
59
|
-
src_url = urllib.parse.urljoin(url, src)
|
60
|
-
src_response = session.get(src_url, headers={X_NO_WAYBACK: "1"})
|
61
|
-
src_response.raise_for_status()
|
62
|
-
variables = src_response.text
|
63
|
-
sentinel = 'break}return e.next=9,g(r.data,"'
|
64
|
-
variables = variables[variables.find(sentinel) + len(sentinel) :]
|
65
|
-
variables = variables[
|
66
|
-
: variables.find('");case 9:return s=e.sent,l=JSON.parse(s),e.abrupt')
|
67
|
-
]
|
68
|
-
password_str, salt_str = variables.split('","')
|
69
|
-
salt = str.encode(salt_str)
|
70
|
-
password = str.encode(password_str)
|
71
|
-
break
|
72
|
-
if salt is None:
|
73
|
-
raise ValueError("salt is null.")
|
74
|
-
if password is None:
|
75
|
-
raise ValueError("password is null.")
|
76
|
-
|
77
53
|
bookie_names = event_body["providersNames"]
|
78
54
|
home_points = None
|
79
55
|
if event_body["homeResult"] != "":
|
@@ -96,6 +72,9 @@ def _create_oddsportal_game_model(
|
|
96
72
|
event_body["venueCountry"],
|
97
73
|
)
|
98
74
|
|
75
|
+
url = f"https://www.oddsportal.com/match-event/{version_id}-{sport_id}-{unique_id}-{default_bet_id}-{default_scope_id}-{xhash}.dat"
|
76
|
+
parsed_data = fetch_data(url, session, url, soup)
|
77
|
+
|
99
78
|
return GameModel(
|
100
79
|
dt=dt,
|
101
80
|
week=None,
|
@@ -108,16 +87,11 @@ def _create_oddsportal_game_model(
|
|
108
87
|
event_data["home"],
|
109
88
|
league,
|
110
89
|
home_points,
|
111
|
-
version_id,
|
112
|
-
sport_id,
|
113
|
-
unique_id,
|
114
90
|
default_bet_id,
|
115
91
|
default_scope_id,
|
116
|
-
xhash,
|
117
|
-
salt,
|
118
|
-
password,
|
119
92
|
bookie_names,
|
120
93
|
0,
|
94
|
+
parsed_data,
|
121
95
|
),
|
122
96
|
create_oddsportal_team_model(
|
123
97
|
session,
|
@@ -125,16 +99,11 @@ def _create_oddsportal_game_model(
|
|
125
99
|
event_data["away"],
|
126
100
|
league,
|
127
101
|
away_points,
|
128
|
-
version_id,
|
129
|
-
sport_id,
|
130
|
-
unique_id,
|
131
102
|
default_bet_id,
|
132
103
|
default_scope_id,
|
133
|
-
xhash,
|
134
|
-
salt,
|
135
|
-
password,
|
136
104
|
bookie_names,
|
137
105
|
1,
|
106
|
+
parsed_data,
|
138
107
|
),
|
139
108
|
],
|
140
109
|
end_dt=end_dt,
|
{sportsball-0.3.29 → sportsball-0.3.31}/sportsball/data/oddsportal/oddsportal_league_model.py
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
"""Odds Portal league model."""
|
2
2
|
|
3
|
-
# pylint: disable=too-many-locals,too-many-branches,too-many-statements,protected-access
|
3
|
+
# pylint: disable=too-many-locals,too-many-branches,too-many-statements,protected-access,too-many-arguments,line-too-long
|
4
4
|
import http
|
5
5
|
import json
|
6
6
|
import logging
|
@@ -9,12 +9,15 @@ from typing import Iterator
|
|
9
9
|
|
10
10
|
import extruct # type: ignore
|
11
11
|
import requests
|
12
|
+
import requests_cache
|
12
13
|
import tqdm
|
13
|
-
from bs4 import BeautifulSoup
|
14
|
+
from bs4 import BeautifulSoup
|
14
15
|
|
16
|
+
from ...proxy_session import X_NO_WAYBACK
|
15
17
|
from ..game_model import GameModel
|
16
18
|
from ..league import League
|
17
19
|
from ..league_model import LeagueModel
|
20
|
+
from .decrypt import fetch_data
|
18
21
|
from .oddsportal_game_model import create_oddsportal_game_model
|
19
22
|
|
20
23
|
# Sports
|
@@ -28,6 +31,40 @@ USA = "usa"
|
|
28
31
|
NCAA = "ncaa"
|
29
32
|
|
30
33
|
|
34
|
+
def _process_results_pages(
|
35
|
+
url: str,
|
36
|
+
session: requests_cache.CachedSession,
|
37
|
+
soup: BeautifulSoup,
|
38
|
+
league: League,
|
39
|
+
pbar: tqdm.tqdm,
|
40
|
+
response: requests.Response,
|
41
|
+
) -> Iterator[GameModel]:
|
42
|
+
# Fetch first page
|
43
|
+
sanitised_text = response.text[response.text.find("var pageOutrightsVar = '") :]
|
44
|
+
sanitised_text = sanitised_text[: sanitised_text.find("'")]
|
45
|
+
page_outrights = json.loads(sanitised_text)
|
46
|
+
sports_id = page_outrights["sid"]
|
47
|
+
oddsportal_id = page_outrights["id"]
|
48
|
+
|
49
|
+
current_page = 1
|
50
|
+
total_pages = None
|
51
|
+
while (current_page == 1 and total_pages is None) or (
|
52
|
+
current_page <= 0 if total_pages is None else total_pages
|
53
|
+
):
|
54
|
+
dat_url = f"https://www.oddsportal.com/ajax-sport-country-tournament-archive_/{sports_id}/{oddsportal_id}/X134529032X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X512X32X0X0X0X0X0X0X131072X0X2048/1/-5/page/{current_page}//"
|
55
|
+
parsed_data = fetch_data(dat_url, session, url, soup)
|
56
|
+
d = parsed_data["d"]
|
57
|
+
for row in d["rows"]:
|
58
|
+
game_model = create_oddsportal_game_model(
|
59
|
+
session, urllib.parse.urljoin(url, row["url"]), league, False
|
60
|
+
)
|
61
|
+
pbar.update(1)
|
62
|
+
pbar.set_description(f"OddsPortal {game_model.dt}")
|
63
|
+
yield game_model
|
64
|
+
total_pages = d["pagination"]["pages"]
|
65
|
+
current_page += 1
|
66
|
+
|
67
|
+
|
31
68
|
class OddsPortalLeagueModel(LeagueModel):
|
32
69
|
"""Odds Portal implementation of the league model."""
|
33
70
|
|
@@ -85,14 +122,10 @@ class OddsPortalLeagueModel(LeagueModel):
|
|
85
122
|
logging.debug("processing url: %s", url)
|
86
123
|
seen_urls.add(url)
|
87
124
|
|
88
|
-
|
89
|
-
with self.session.cache_disabled():
|
90
|
-
response = self.session.get(url)
|
91
|
-
else:
|
92
|
-
response = self.session.get(url)
|
125
|
+
response = self.session.get(url, headers={X_NO_WAYBACK: "1"})
|
93
126
|
response.raise_for_status()
|
94
127
|
|
95
|
-
soup = BeautifulSoup(response.text, "
|
128
|
+
soup = BeautifulSoup(response.text, "xml")
|
96
129
|
|
97
130
|
# Find next URLs
|
98
131
|
for option in soup.find_all("option"):
|
@@ -100,69 +133,17 @@ class OddsPortalLeagueModel(LeagueModel):
|
|
100
133
|
if next_url.endswith("/results/") and self._path[:-1] in next_url:
|
101
134
|
queued_urls.add(next_url)
|
102
135
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
component = matches.get(
|
112
|
-
"tournamentGamesComponent", matches.get("d")
|
113
|
-
)
|
114
|
-
if component.get("total") != 0:
|
115
|
-
game_urls = {
|
116
|
-
urllib.parse.urljoin(url, x["url"])
|
117
|
-
for x in component.get("rows", [])
|
118
|
-
}
|
119
|
-
if not game_urls:
|
120
|
-
for a in soup.find_all("a", href=True):
|
121
|
-
game_url = urllib.parse.urljoin(url, a.get("href"))
|
122
|
-
if (
|
123
|
-
game_url.endswith("results/")
|
124
|
-
or game_url.endswith("standings/")
|
125
|
-
or game_url.endswith(self._path)
|
126
|
-
or game_url.endswith("outrights/")
|
127
|
-
):
|
128
|
-
continue
|
129
|
-
if self._path[:-1] in game_url:
|
130
|
-
game_urls.add(game_url)
|
131
|
-
|
132
|
-
for game_url in game_urls:
|
133
|
-
if "#" in game_url:
|
134
|
-
continue
|
135
|
-
if (
|
136
|
-
game_url.replace("results/", "") == url
|
137
|
-
or game_url.replace("results/", "") in queued_urls
|
138
|
-
):
|
139
|
-
continue
|
140
|
-
if game_url.endswith("/archive/"):
|
141
|
-
continue
|
142
|
-
if len(game_url.split("/")) != 8:
|
143
|
-
continue
|
144
|
-
game_model = create_oddsportal_game_model(
|
145
|
-
self.session,
|
146
|
-
game_url,
|
147
|
-
self.league,
|
148
|
-
False,
|
149
|
-
)
|
150
|
-
pbar.update(1)
|
151
|
-
pbar.set_description(f"OddsPortal {game_model.dt}")
|
152
|
-
yield game_model
|
153
|
-
|
154
|
-
next_a = soup.find("a", text="Next", href=True)
|
155
|
-
if next_a is None:
|
156
|
-
break
|
157
|
-
if not isinstance(next_a, Tag):
|
158
|
-
raise ValueError("next_a is not a tag.")
|
159
|
-
next_url = urllib.parse.urljoin(url, str(next_a.get("href")))
|
160
|
-
response = self.session.get(next_url)
|
161
|
-
response.raise_for_status()
|
162
|
-
soup = BeautifulSoup(response.text, "html.parser")
|
136
|
+
yield from _process_results_pages(
|
137
|
+
url,
|
138
|
+
self.session,
|
139
|
+
soup,
|
140
|
+
self.league,
|
141
|
+
pbar,
|
142
|
+
response,
|
143
|
+
)
|
163
144
|
|
164
145
|
@property
|
165
146
|
def games(self) -> Iterator[GameModel]:
|
166
147
|
with tqdm.tqdm(position=self.position) as pbar:
|
167
|
-
|
148
|
+
yield from self._find_next(pbar)
|
168
149
|
yield from self._find_previous(pbar)
|
@@ -1,16 +1,11 @@
|
|
1
1
|
"""Odds Portal team model."""
|
2
2
|
|
3
3
|
# pylint: disable=too-many-arguments,duplicate-code,line-too-long,too-many-locals
|
4
|
-
import base64
|
5
4
|
import datetime
|
6
|
-
import
|
5
|
+
from typing import Any
|
7
6
|
|
8
7
|
import pytest_is_running
|
9
8
|
import requests_cache
|
10
|
-
from cryptography.hazmat.backends import default_backend
|
11
|
-
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
12
|
-
from cryptography.hazmat.primitives.hashes import SHA256
|
13
|
-
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
14
9
|
|
15
10
|
from ...cache import MEMORY
|
16
11
|
from ..google.google_news_model import create_google_news_models
|
@@ -26,46 +21,12 @@ def _create_oddsportal_team_model(
|
|
26
21
|
team_name: str,
|
27
22
|
league: League,
|
28
23
|
points: float | None,
|
29
|
-
version_id: str,
|
30
|
-
sport_id: str,
|
31
|
-
unique_id: str,
|
32
24
|
default_bet_id: str,
|
33
25
|
default_scope_id: str,
|
34
|
-
xhash: str,
|
35
|
-
salt: bytes,
|
36
|
-
password: bytes,
|
37
26
|
bookie_names: dict[str, str],
|
38
27
|
team_idx: int,
|
28
|
+
parsed_data: dict[str, Any],
|
39
29
|
) -> TeamModel:
|
40
|
-
response = session.get(
|
41
|
-
f"https://www.oddsportal.com/match-event/{version_id}-{sport_id}-{unique_id}-{default_bet_id}-{default_scope_id}-{xhash}.dat",
|
42
|
-
headers={
|
43
|
-
"X-Requested-With": "XMLHttpRequest",
|
44
|
-
},
|
45
|
-
)
|
46
|
-
response.raise_for_status()
|
47
|
-
decoded_data = base64.b64decode(response.content).decode()
|
48
|
-
encrypted, key = decoded_data.split(":")
|
49
|
-
encrypted_bytes = base64.urlsafe_b64decode(encrypted)
|
50
|
-
key_bytes = bytes.fromhex(key)
|
51
|
-
kdf = PBKDF2HMAC(
|
52
|
-
algorithm=SHA256(),
|
53
|
-
length=32,
|
54
|
-
salt=salt,
|
55
|
-
iterations=1000,
|
56
|
-
backend=default_backend(),
|
57
|
-
)
|
58
|
-
aes_key = kdf.derive(password)
|
59
|
-
cipher = Cipher(
|
60
|
-
algorithms.AES(aes_key), modes.CBC(key_bytes), backend=default_backend()
|
61
|
-
)
|
62
|
-
decryptor = cipher.decryptor()
|
63
|
-
decrypted_bytes = decryptor.update(encrypted_bytes) + decryptor.finalize()
|
64
|
-
decrypted_data = decrypted_bytes.decode("utf-8")
|
65
|
-
end_of_json = decrypted_data.rfind("}")
|
66
|
-
if end_of_json != -1:
|
67
|
-
decrypted_data = decrypted_data[: end_of_json + 1]
|
68
|
-
parsed_data = json.loads(decrypted_data)
|
69
30
|
try:
|
70
31
|
odds_data = parsed_data["d"]["oddsdata"]["back"][
|
71
32
|
f"E-{default_bet_id}-{default_scope_id}-0-0-0"
|
@@ -111,16 +72,11 @@ def _cached_create_oddsportal_team_model(
|
|
111
72
|
team_name: str,
|
112
73
|
league: League,
|
113
74
|
points: float | None,
|
114
|
-
version_id: str,
|
115
|
-
sport_id: str,
|
116
|
-
unique_id: str,
|
117
75
|
default_bet_id: str,
|
118
76
|
default_scope_id: str,
|
119
|
-
xhash: str,
|
120
|
-
salt: bytes,
|
121
|
-
password: bytes,
|
122
77
|
bookie_names: dict[str, str],
|
123
78
|
team_idx: int,
|
79
|
+
parsed_data: dict[str, Any],
|
124
80
|
) -> TeamModel:
|
125
81
|
return _create_oddsportal_team_model(
|
126
82
|
session,
|
@@ -128,16 +84,11 @@ def _cached_create_oddsportal_team_model(
|
|
128
84
|
team_name,
|
129
85
|
league,
|
130
86
|
points,
|
131
|
-
version_id,
|
132
|
-
sport_id,
|
133
|
-
unique_id,
|
134
87
|
default_bet_id,
|
135
88
|
default_scope_id,
|
136
|
-
xhash,
|
137
|
-
salt,
|
138
|
-
password,
|
139
89
|
bookie_names,
|
140
90
|
team_idx,
|
91
|
+
parsed_data,
|
141
92
|
)
|
142
93
|
|
143
94
|
|
@@ -147,16 +98,11 @@ def create_oddsportal_team_model(
|
|
147
98
|
team_name: str,
|
148
99
|
league: League,
|
149
100
|
points: float | None,
|
150
|
-
version_id: str,
|
151
|
-
sport_id: str,
|
152
|
-
unique_id: str,
|
153
101
|
default_bet_id: str,
|
154
102
|
default_scope_id: str,
|
155
|
-
xhash: str,
|
156
|
-
salt: bytes,
|
157
|
-
password: bytes,
|
158
103
|
bookie_names: dict[str, str],
|
159
104
|
team_idx: int,
|
105
|
+
parsed_data: dict[str, Any],
|
160
106
|
) -> TeamModel:
|
161
107
|
"""Create a team model based off the odds portal response."""
|
162
108
|
if not pytest_is_running.is_running() and dt < datetime.datetime.now().replace(
|
@@ -168,16 +114,11 @@ def create_oddsportal_team_model(
|
|
168
114
|
team_name,
|
169
115
|
league,
|
170
116
|
points,
|
171
|
-
version_id,
|
172
|
-
sport_id,
|
173
|
-
unique_id,
|
174
117
|
default_bet_id,
|
175
118
|
default_scope_id,
|
176
|
-
xhash,
|
177
|
-
salt,
|
178
|
-
password,
|
179
119
|
bookie_names,
|
180
120
|
team_idx,
|
121
|
+
parsed_data,
|
181
122
|
)
|
182
123
|
with session.cache_disabled():
|
183
124
|
return _create_oddsportal_team_model(
|
@@ -186,14 +127,9 @@ def create_oddsportal_team_model(
|
|
186
127
|
team_name,
|
187
128
|
league,
|
188
129
|
points,
|
189
|
-
version_id,
|
190
|
-
sport_id,
|
191
|
-
unique_id,
|
192
130
|
default_bet_id,
|
193
131
|
default_scope_id,
|
194
|
-
xhash,
|
195
|
-
salt,
|
196
|
-
password,
|
197
132
|
bookie_names,
|
198
133
|
team_idx,
|
134
|
+
parsed_data,
|
199
135
|
)
|
@@ -120,6 +120,7 @@ sportsball/data/nfl/oddsportal/nfl_oddsportal_league_model.py
|
|
120
120
|
sportsball/data/nfl/sportsdb/__init__.py
|
121
121
|
sportsball/data/nfl/sportsdb/nfl_sportsdb_league_model.py
|
122
122
|
sportsball/data/oddsportal/__init__.py
|
123
|
+
sportsball/data/oddsportal/decrypt.py
|
123
124
|
sportsball/data/oddsportal/oddsportal_bookie_model.py
|
124
125
|
sportsball/data/oddsportal/oddsportal_game_model.py
|
125
126
|
sportsball/data/oddsportal/oddsportal_league_model.py
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|