ps3838api 1.1.0__py3-none-any.whl
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.
- ps3838api/__init__.py +7 -0
- ps3838api/api/__init__.py +45 -0
- ps3838api/api/client.py +516 -0
- ps3838api/api/default_client.py +231 -0
- ps3838api/api/v4client.py +107 -0
- ps3838api/matching.py +141 -0
- ps3838api/models/__init__.py +0 -0
- ps3838api/models/bets.py +250 -0
- ps3838api/models/client.py +48 -0
- ps3838api/models/errors.py +43 -0
- ps3838api/models/event.py +61 -0
- ps3838api/models/fixtures.py +53 -0
- ps3838api/models/lines.py +27 -0
- ps3838api/models/odds.py +221 -0
- ps3838api/models/sports.py +256 -0
- ps3838api/models/tank.py +6 -0
- ps3838api/py.typed +0 -0
- ps3838api/tank.py +93 -0
- ps3838api/totals.py +62 -0
- ps3838api/utils/match_leagues.py +90 -0
- ps3838api/utils/ops.py +146 -0
- ps3838api-1.1.0.dist-info/METADATA +176 -0
- ps3838api-1.1.0.dist-info/RECORD +26 -0
- ps3838api-1.1.0.dist-info/WHEEL +5 -0
- ps3838api-1.1.0.dist-info/licenses/LICENSE +8 -0
- ps3838api-1.1.0.dist-info/top_level.txt +1 -0
ps3838api/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PACKAGE: ps3838api.api
|
|
3
|
+
|
|
4
|
+
This package exposes the :class:`Client` and the legacy convenience helpers that
|
|
5
|
+
use a shared default client (imported from :mod:`ps3838api.api.default_client`).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ps3838api.models.client import BalanceData, LeagueV3, PeriodData
|
|
9
|
+
from ps3838api.models.sports import BASEBALL_SPORT_ID, SOCCER_SPORT_ID
|
|
10
|
+
|
|
11
|
+
from .client import DEFAULT_API_BASE_URL, PinnacleClient
|
|
12
|
+
from .default_client import (
|
|
13
|
+
export_my_bets, # pyright: ignore[reportDeprecated]
|
|
14
|
+
get_betting_status, # pyright: ignore[reportDeprecated]
|
|
15
|
+
get_client_balance, # pyright: ignore[reportDeprecated]
|
|
16
|
+
get_fixtures, # pyright: ignore[reportDeprecated]
|
|
17
|
+
get_leagues, # pyright: ignore[reportDeprecated]
|
|
18
|
+
get_line, # pyright: ignore[reportDeprecated]
|
|
19
|
+
get_odds, # pyright: ignore[reportDeprecated]
|
|
20
|
+
get_periods, # pyright: ignore[reportDeprecated]
|
|
21
|
+
get_special_fixtures, # pyright: ignore[reportDeprecated]
|
|
22
|
+
get_sports, # pyright: ignore[reportDeprecated]
|
|
23
|
+
place_straigh_bet, # pyright: ignore[reportDeprecated]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"PinnacleClient",
|
|
28
|
+
"DEFAULT_API_BASE_URL", # legacy
|
|
29
|
+
"SOCCER_SPORT_ID", # legacy
|
|
30
|
+
"BASEBALL_SPORT_ID", # legacy
|
|
31
|
+
"get_client_balance",
|
|
32
|
+
"get_periods",
|
|
33
|
+
"get_sports",
|
|
34
|
+
"get_leagues",
|
|
35
|
+
"get_fixtures",
|
|
36
|
+
"get_odds",
|
|
37
|
+
"get_special_fixtures",
|
|
38
|
+
"get_line",
|
|
39
|
+
"place_straigh_bet",
|
|
40
|
+
"get_betting_status",
|
|
41
|
+
"BalanceData", # legacy, was in ps3838api.api
|
|
42
|
+
"PeriodData", # was in ps3838api.api
|
|
43
|
+
"LeagueV3", # was in ps3838api.api
|
|
44
|
+
"export_my_bets",
|
|
45
|
+
]
|
ps3838api/api/client.py
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Client implementation for the PS3838 API.
|
|
3
|
+
|
|
4
|
+
The Client exposes all endpoints required by the public helper functions while
|
|
5
|
+
encapsulating session management, credentials, and error handling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import base64
|
|
9
|
+
import os
|
|
10
|
+
import uuid
|
|
11
|
+
from datetime import datetime, timedelta, timezone
|
|
12
|
+
from typing import Any, Literal, cast, overload
|
|
13
|
+
|
|
14
|
+
import requests
|
|
15
|
+
from requests import Response, Session
|
|
16
|
+
|
|
17
|
+
from ps3838api.api.v4client import V4PinnacleClient
|
|
18
|
+
from ps3838api.models.bets import (
|
|
19
|
+
BetList,
|
|
20
|
+
BetsResponse,
|
|
21
|
+
BetStatus,
|
|
22
|
+
BetType,
|
|
23
|
+
FillType,
|
|
24
|
+
OddsFormat,
|
|
25
|
+
PlaceStraightBetResponse,
|
|
26
|
+
Side,
|
|
27
|
+
SortDir,
|
|
28
|
+
Team,
|
|
29
|
+
)
|
|
30
|
+
from ps3838api.models.client import BalanceData, BettingStatusResponse, LeagueV3, PeriodData
|
|
31
|
+
from ps3838api.models.errors import (
|
|
32
|
+
AccessBlockedError,
|
|
33
|
+
BaseballOnlyArgumentError,
|
|
34
|
+
PS3838APIError,
|
|
35
|
+
WrongEndpoint,
|
|
36
|
+
)
|
|
37
|
+
from ps3838api.models.fixtures import FixturesResponse
|
|
38
|
+
from ps3838api.models.lines import LineResponse
|
|
39
|
+
from ps3838api.models.odds import OddsResponse
|
|
40
|
+
from ps3838api.models.sports import BASEBALL_SPORT_ID, SOCCER_SPORT_ID, Sport
|
|
41
|
+
|
|
42
|
+
DEFAULT_API_BASE_URL = "https://api.ps3838.com"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PinnacleClient:
|
|
46
|
+
"""Stateful PS3838 API client backed by ``requests.Session``."""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
login: str | None = None,
|
|
51
|
+
password: str | None = None,
|
|
52
|
+
api_base_url: str | None = None,
|
|
53
|
+
default_sport: Sport = SOCCER_SPORT_ID,
|
|
54
|
+
*,
|
|
55
|
+
session: Session | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
# prepare login and password
|
|
58
|
+
self.default_sport = default_sport
|
|
59
|
+
self._login = login or os.environ.get("PS3838_LOGIN") or os.environ.get("PINNACLE_LOGIN")
|
|
60
|
+
self._password = password or os.environ.get("PS3838_PASSWORD") or os.environ.get("PINNACLE_PASSWORD")
|
|
61
|
+
if not self._login or not self._password:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
"login and password must be provided either via "
|
|
64
|
+
"Client() arguments or PINNACLE_LOGIN/PS3838_LOGIN and PINNACLE_PASSWORD/PS3838_PASSWORD"
|
|
65
|
+
"environment variables."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
env_base_url = os.environ.get("PS3838_API_BASE_URL") or os.environ.get("PINNACLE_API_BASE_URL")
|
|
69
|
+
resolved_base_url = api_base_url or env_base_url or DEFAULT_API_BASE_URL
|
|
70
|
+
self._base_url = resolved_base_url.rstrip("/")
|
|
71
|
+
# prepare session and headers
|
|
72
|
+
token = base64.b64encode(f"{self._login}:{self._password}".encode("utf-8"))
|
|
73
|
+
self._headers = {
|
|
74
|
+
"Authorization": f"Basic {token.decode('utf-8')}",
|
|
75
|
+
"User-Agent": "ps3838api (https://github.com/iliyasone/ps3838api)",
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
self._session = session or requests.Session()
|
|
80
|
+
self._session.headers.update(self._headers)
|
|
81
|
+
# init v4 subclient
|
|
82
|
+
self.v4 = V4PinnacleClient(self)
|
|
83
|
+
|
|
84
|
+
# ------------------------------------------------------------------ #
|
|
85
|
+
# Core request helpers
|
|
86
|
+
# ------------------------------------------------------------------ #
|
|
87
|
+
def _handle_response(self, response: Response) -> Any:
|
|
88
|
+
try:
|
|
89
|
+
response.raise_for_status()
|
|
90
|
+
result: Any = response.json()
|
|
91
|
+
except requests.exceptions.HTTPError as exc:
|
|
92
|
+
if exc.response and exc.response.status_code == 405:
|
|
93
|
+
raise WrongEndpoint() from exc
|
|
94
|
+
|
|
95
|
+
payload: Any | None = None
|
|
96
|
+
if exc.response is not None:
|
|
97
|
+
try:
|
|
98
|
+
payload = exc.response.json()
|
|
99
|
+
except requests.exceptions.JSONDecodeError:
|
|
100
|
+
payload = None
|
|
101
|
+
|
|
102
|
+
if isinstance(payload, dict):
|
|
103
|
+
match payload:
|
|
104
|
+
case {"code": str(code), "message": str(message)}:
|
|
105
|
+
raise AccessBlockedError(message) from exc
|
|
106
|
+
case object():
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
status_code = exc.response.status_code if exc.response else "Unknown"
|
|
110
|
+
raise AccessBlockedError(status_code) from exc
|
|
111
|
+
except requests.exceptions.JSONDecodeError as exc:
|
|
112
|
+
raise AccessBlockedError("Empty response") from exc
|
|
113
|
+
|
|
114
|
+
match result:
|
|
115
|
+
case {"code": str(code), "message": str(message)}:
|
|
116
|
+
raise PS3838APIError(code=code, message=message)
|
|
117
|
+
case _:
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
def _request(
|
|
121
|
+
self,
|
|
122
|
+
method: Literal["GET", "POST"],
|
|
123
|
+
endpoint: str,
|
|
124
|
+
*,
|
|
125
|
+
params: dict[str, Any] | None = None,
|
|
126
|
+
body: dict[str, Any] | None = None,
|
|
127
|
+
) -> Any:
|
|
128
|
+
url = f"{self._base_url}{endpoint}"
|
|
129
|
+
response = self._session.request(method, url, params=params, json=body)
|
|
130
|
+
return self._handle_response(response)
|
|
131
|
+
|
|
132
|
+
def _get(self, endpoint: str, params: dict[str, Any] | None = None) -> Any:
|
|
133
|
+
return self._request("GET", endpoint, params=params)
|
|
134
|
+
|
|
135
|
+
def _post(self, endpoint: str, body: dict[str, Any]) -> Any:
|
|
136
|
+
return self._request("POST", endpoint, body=body)
|
|
137
|
+
|
|
138
|
+
# ------------------------------------------------------------------ #
|
|
139
|
+
# API endpoints
|
|
140
|
+
# ------------------------------------------------------------------ #
|
|
141
|
+
def get_client_balance(self) -> BalanceData:
|
|
142
|
+
endpoint = "/v1/client/balance"
|
|
143
|
+
data = self._get(endpoint)
|
|
144
|
+
return cast(BalanceData, data)
|
|
145
|
+
|
|
146
|
+
def get_periods(self, sport_id: int | None = None) -> list[PeriodData]:
|
|
147
|
+
resolved_sport_id = sport_id if sport_id is not None else self.default_sport
|
|
148
|
+
endpoint = "/v1/periods"
|
|
149
|
+
response = self._get(endpoint, params={"sportId": str(resolved_sport_id)})
|
|
150
|
+
periods_data = response.get("periods", [])
|
|
151
|
+
return cast(list[PeriodData], periods_data)
|
|
152
|
+
|
|
153
|
+
def get_sports(self) -> Any:
|
|
154
|
+
endpoint = "/v3/sports"
|
|
155
|
+
return self._get(endpoint)
|
|
156
|
+
|
|
157
|
+
def get_leagues(self, sport_id: int | None = None) -> list[LeagueV3]:
|
|
158
|
+
resolved_sport_id = sport_id if sport_id is not None else self.default_sport
|
|
159
|
+
endpoint = "/v3/leagues"
|
|
160
|
+
data = self._get(endpoint, params={"sportId": resolved_sport_id})
|
|
161
|
+
leagues_data = data.get("leagues", [])
|
|
162
|
+
return cast(list[LeagueV3], leagues_data)
|
|
163
|
+
|
|
164
|
+
def get_fixtures(
|
|
165
|
+
self,
|
|
166
|
+
sport_id: int | None = None,
|
|
167
|
+
league_ids: list[int] | None = None,
|
|
168
|
+
is_live: bool | None = None,
|
|
169
|
+
since: int | None = None,
|
|
170
|
+
event_ids: list[int] | None = None,
|
|
171
|
+
settled: bool = False,
|
|
172
|
+
) -> FixturesResponse:
|
|
173
|
+
subpath = "/v3/fixtures/settled" if settled else "/v3/fixtures"
|
|
174
|
+
endpoint = f"{subpath}"
|
|
175
|
+
|
|
176
|
+
resolved_sport_id = sport_id if sport_id is not None else self.default_sport
|
|
177
|
+
|
|
178
|
+
params: dict[str, Any] = {"sportId": resolved_sport_id}
|
|
179
|
+
if league_ids:
|
|
180
|
+
params["leagueIds"] = ",".join(map(str, league_ids))
|
|
181
|
+
if is_live is not None:
|
|
182
|
+
params["isLive"] = int(is_live)
|
|
183
|
+
if since is not None:
|
|
184
|
+
params["since"] = since
|
|
185
|
+
if event_ids:
|
|
186
|
+
params["eventIds"] = ",".join(map(str, event_ids))
|
|
187
|
+
|
|
188
|
+
return cast(FixturesResponse, self._get(endpoint, params))
|
|
189
|
+
|
|
190
|
+
def get_odds(
|
|
191
|
+
self,
|
|
192
|
+
sport_id: int | None = None,
|
|
193
|
+
is_special: bool = False,
|
|
194
|
+
league_ids: list[int] | None = None,
|
|
195
|
+
odds_format: OddsFormat = "DECIMAL",
|
|
196
|
+
since: int | None = None,
|
|
197
|
+
is_live: bool | None = None,
|
|
198
|
+
event_ids: list[int] | None = None,
|
|
199
|
+
) -> OddsResponse:
|
|
200
|
+
endpoint = "/v2/odds/special" if is_special else "/v3/odds"
|
|
201
|
+
|
|
202
|
+
resolved_sport_id = sport_id if sport_id is not None else self.default_sport
|
|
203
|
+
|
|
204
|
+
params: dict[str, Any] = {
|
|
205
|
+
"sportId": resolved_sport_id,
|
|
206
|
+
"oddsFormat": odds_format,
|
|
207
|
+
}
|
|
208
|
+
if league_ids:
|
|
209
|
+
params["leagueIds"] = ",".join(map(str, league_ids))
|
|
210
|
+
if since is not None:
|
|
211
|
+
params["since"] = since
|
|
212
|
+
if is_live is not None:
|
|
213
|
+
params["isLive"] = int(is_live)
|
|
214
|
+
if event_ids:
|
|
215
|
+
params["eventIds"] = ",".join(map(str, event_ids))
|
|
216
|
+
|
|
217
|
+
return cast(OddsResponse, self._get(endpoint, params))
|
|
218
|
+
|
|
219
|
+
def get_special_fixtures(
|
|
220
|
+
self,
|
|
221
|
+
sport_id: int | None = None,
|
|
222
|
+
league_ids: list[int] | None = None,
|
|
223
|
+
event_id: int | None = None,
|
|
224
|
+
) -> Any:
|
|
225
|
+
endpoint = "/v2/fixtures/special"
|
|
226
|
+
resolved_sport_id = sport_id if sport_id is not None else self.default_sport
|
|
227
|
+
params: dict[str, Any] = {"sportId": resolved_sport_id, "oddsFormat": "Decimal"}
|
|
228
|
+
|
|
229
|
+
if league_ids:
|
|
230
|
+
params["leagueIds"] = ",".join(map(str, league_ids))
|
|
231
|
+
if event_id is not None:
|
|
232
|
+
params["eventId"] = event_id
|
|
233
|
+
|
|
234
|
+
return self._get(endpoint, params)
|
|
235
|
+
|
|
236
|
+
def get_line(
|
|
237
|
+
self,
|
|
238
|
+
league_id: int,
|
|
239
|
+
event_id: int,
|
|
240
|
+
period_number: int,
|
|
241
|
+
bet_type: Literal["SPREAD", "MONEYLINE", "TOTAL_POINTS", "TEAM_TOTAL_POINTS"],
|
|
242
|
+
handicap: float,
|
|
243
|
+
team: Literal["Team1", "Team2", "Draw"] | None = None,
|
|
244
|
+
side: Literal["OVER", "UNDER"] | None = None,
|
|
245
|
+
sport_id: int | None = None,
|
|
246
|
+
odds_format: str = "Decimal",
|
|
247
|
+
) -> LineResponse:
|
|
248
|
+
endpoint = "/v2/line"
|
|
249
|
+
resolved_sport_id = sport_id if sport_id is not None else self.default_sport
|
|
250
|
+
params: dict[str, Any] = {
|
|
251
|
+
"sportId": resolved_sport_id,
|
|
252
|
+
"leagueId": league_id,
|
|
253
|
+
"eventId": event_id,
|
|
254
|
+
"periodNumber": period_number,
|
|
255
|
+
"betType": bet_type,
|
|
256
|
+
"handicap": handicap,
|
|
257
|
+
"oddsFormat": odds_format,
|
|
258
|
+
}
|
|
259
|
+
if team:
|
|
260
|
+
params["team"] = team
|
|
261
|
+
if side:
|
|
262
|
+
params["side"] = side
|
|
263
|
+
|
|
264
|
+
return cast(LineResponse, self._get(endpoint, params))
|
|
265
|
+
|
|
266
|
+
def place_straight_bet(
|
|
267
|
+
self,
|
|
268
|
+
*,
|
|
269
|
+
stake: float,
|
|
270
|
+
event_id: int,
|
|
271
|
+
bet_type: BetType,
|
|
272
|
+
line_id: int | None,
|
|
273
|
+
period_number: int = 0,
|
|
274
|
+
sport_id: int | None = None,
|
|
275
|
+
alt_line_id: int | None = None,
|
|
276
|
+
unique_request_id: str | None = None,
|
|
277
|
+
odds_format: OddsFormat = "DECIMAL",
|
|
278
|
+
fill_type: FillType = "NORMAL",
|
|
279
|
+
accept_better_line: bool = True,
|
|
280
|
+
win_risk_stake: Literal["WIN", "RISK"] = "RISK",
|
|
281
|
+
pitcher1_must_start: bool = True,
|
|
282
|
+
pitcher2_must_start: bool = True,
|
|
283
|
+
team: Team | None = None,
|
|
284
|
+
side: Side | None = None,
|
|
285
|
+
handicap: float | None = None,
|
|
286
|
+
) -> PlaceStraightBetResponse:
|
|
287
|
+
if unique_request_id is None:
|
|
288
|
+
unique_request_id = str(uuid.uuid1())
|
|
289
|
+
|
|
290
|
+
resolved_sport_id = sport_id if sport_id is not None else self.default_sport
|
|
291
|
+
|
|
292
|
+
if resolved_sport_id != BASEBALL_SPORT_ID:
|
|
293
|
+
if not pitcher1_must_start or not pitcher2_must_start:
|
|
294
|
+
raise BaseballOnlyArgumentError()
|
|
295
|
+
params: dict[str, Any] = {
|
|
296
|
+
"oddsFormat": odds_format,
|
|
297
|
+
"uniqueRequestId": unique_request_id,
|
|
298
|
+
"acceptBetterLine": accept_better_line,
|
|
299
|
+
"stake": stake,
|
|
300
|
+
"winRiskStake": win_risk_stake,
|
|
301
|
+
"pitcher1MustStart": pitcher1_must_start,
|
|
302
|
+
"pitcher2MustStart": pitcher2_must_start,
|
|
303
|
+
"fillType": fill_type,
|
|
304
|
+
"sportId": resolved_sport_id,
|
|
305
|
+
"eventId": event_id,
|
|
306
|
+
"periodNumber": period_number,
|
|
307
|
+
"betType": bet_type,
|
|
308
|
+
}
|
|
309
|
+
if team is not None:
|
|
310
|
+
params["team"] = team
|
|
311
|
+
if line_id is not None:
|
|
312
|
+
params["lineId"] = line_id
|
|
313
|
+
if alt_line_id is not None:
|
|
314
|
+
params["altLineId"] = alt_line_id
|
|
315
|
+
if side is not None:
|
|
316
|
+
params["side"] = side
|
|
317
|
+
if handicap is not None:
|
|
318
|
+
params["handicap"] = handicap
|
|
319
|
+
|
|
320
|
+
endpoint = "/v2/bets/place"
|
|
321
|
+
data = self._post(endpoint, params)
|
|
322
|
+
return cast(PlaceStraightBetResponse, data)
|
|
323
|
+
|
|
324
|
+
@overload
|
|
325
|
+
def get_bets(
|
|
326
|
+
self,
|
|
327
|
+
*,
|
|
328
|
+
unique_request_ids: list[str],
|
|
329
|
+
) -> "BetsResponse":
|
|
330
|
+
"""Get bets by unique request IDs.
|
|
331
|
+
|
|
332
|
+
A comma separated list of `uniqueRequestId` from the place bet request.
|
|
333
|
+
If specified, it's highest priority, all other parameters are ignored.
|
|
334
|
+
Maximum is 10 ids. If client has bet id, preferred way is to use `betIds`
|
|
335
|
+
query parameter, you can use `uniqueRequestIds` when you do not have bet id.
|
|
336
|
+
|
|
337
|
+
There are 2 cases when client may not have a bet id:
|
|
338
|
+
|
|
339
|
+
1. When you bet on live event with live delay, place bet response in that
|
|
340
|
+
case does not return bet id, so client can query bet status by
|
|
341
|
+
`uniqueRequestIds`.
|
|
342
|
+
2. In case of any network issues when client is not sure what happened
|
|
343
|
+
with his place bet request. Empty response means that the bet was not
|
|
344
|
+
placed.
|
|
345
|
+
|
|
346
|
+
Note that there is a restriction: querying by uniqueRequestIds is supported
|
|
347
|
+
for straight and special bets and only up to 30 min from the moment the
|
|
348
|
+
bet was placed.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
unique_request_ids: List of unique request IDs. Maximum is 10 ids.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
BetsResponse containing matching bets.
|
|
355
|
+
"""
|
|
356
|
+
...
|
|
357
|
+
|
|
358
|
+
@overload
|
|
359
|
+
def get_bets(
|
|
360
|
+
self,
|
|
361
|
+
*,
|
|
362
|
+
bet_ids: list[int],
|
|
363
|
+
) -> "BetsResponse":
|
|
364
|
+
"""Get bets by bet IDs.
|
|
365
|
+
|
|
366
|
+
A comma separated list of bet ids. When betids is submitted, no other
|
|
367
|
+
parameter is necessary. Maximum is 100 ids. Works for all non settled
|
|
368
|
+
bets and all bets settled in the last 30 days.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
bet_ids: List of bet IDs. Maximum is 100 ids.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
BetsResponse containing matching bets.
|
|
375
|
+
"""
|
|
376
|
+
...
|
|
377
|
+
|
|
378
|
+
@overload
|
|
379
|
+
def get_bets(
|
|
380
|
+
self,
|
|
381
|
+
*,
|
|
382
|
+
betlist: BetList,
|
|
383
|
+
from_date: datetime,
|
|
384
|
+
to_date: datetime,
|
|
385
|
+
bet_statuses: list[BetStatus] | None = ...,
|
|
386
|
+
sort_dir: SortDir = ...,
|
|
387
|
+
page_size: int = ...,
|
|
388
|
+
from_record: int = ...,
|
|
389
|
+
bet_type: list[BetType] | None = ...,
|
|
390
|
+
) -> "BetsResponse":
|
|
391
|
+
"""Get bets by date range and bet list type.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
betlist: Type of bet list to return (SETTLED, RUNNING, ALL).
|
|
395
|
+
from_date: Start date of the requested period. Required when betlist
|
|
396
|
+
parameter is submitted. Start date can be up to 30 days in the past.
|
|
397
|
+
Expected format is ISO8601 - can be set to just date or date and time.
|
|
398
|
+
to_date: End date of the requested period. Required when betlist
|
|
399
|
+
parameter is submitted. Expected format is ISO8601 - can be set to
|
|
400
|
+
just date or date and time. toDate value is exclusive, meaning it
|
|
401
|
+
cannot be equal to fromDate.
|
|
402
|
+
bet_statuses: Type of bet statuses to return (WON, LOSE, CANCELLED,
|
|
403
|
+
REFUNDED, NOT_ACCEPTED, ACCEPTED, PENDING_ACCEPTANCE). This works
|
|
404
|
+
only in conjunction with betlist, as additional filter.
|
|
405
|
+
sort_dir: Sort direction by postedAt/settledAt (ASC, DESC). Respected
|
|
406
|
+
only when querying by date range. Defaults to ASC.
|
|
407
|
+
page_size: Page size. Max is 1000. Respected only when querying by date
|
|
408
|
+
range. Defaults to 1000.
|
|
409
|
+
from_record: Starting record (inclusive) of the result. Respected only
|
|
410
|
+
when querying by date range. To fetch next page set it to toRecord+1.
|
|
411
|
+
Defaults to 0.
|
|
412
|
+
bet_type: A comma separated list of bet types (SPREAD, MONEYLINE,
|
|
413
|
+
TOTAL_POINTS, TEAM_TOTAL_POINTS, SPECIAL, PARLAY, TEASER, MANUAL).
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
BetsResponse containing matching bets.
|
|
417
|
+
"""
|
|
418
|
+
...
|
|
419
|
+
|
|
420
|
+
def get_bets(
|
|
421
|
+
self,
|
|
422
|
+
*,
|
|
423
|
+
bet_ids: list[int] | None = None,
|
|
424
|
+
unique_request_ids: list[str] | None = None,
|
|
425
|
+
betlist: BetList | None = None,
|
|
426
|
+
bet_statuses: list[BetStatus] | None = None,
|
|
427
|
+
from_date: datetime | None = None,
|
|
428
|
+
to_date: datetime | None = None,
|
|
429
|
+
sort_dir: SortDir = "ASC",
|
|
430
|
+
page_size: int = 1000,
|
|
431
|
+
from_record: int = 0,
|
|
432
|
+
bet_type: list[BetType] | None = None,
|
|
433
|
+
) -> "BetsResponse":
|
|
434
|
+
endpoint = "/v3/bets"
|
|
435
|
+
params: dict[str, Any] = {}
|
|
436
|
+
|
|
437
|
+
if unique_request_ids is not None:
|
|
438
|
+
if not unique_request_ids:
|
|
439
|
+
raise ValueError("uniqueRequestIds must not be empty")
|
|
440
|
+
if len(unique_request_ids) > 10:
|
|
441
|
+
raise ValueError("uniqueRequestIds max is 10")
|
|
442
|
+
params["uniqueRequestIds"] = ",".join(unique_request_ids)
|
|
443
|
+
return cast("BetsResponse", self._get(endpoint, params))
|
|
444
|
+
|
|
445
|
+
if bet_ids is not None:
|
|
446
|
+
if not bet_ids:
|
|
447
|
+
raise ValueError("betIds must not be empty")
|
|
448
|
+
if len(bet_ids) > 100:
|
|
449
|
+
raise ValueError("betIds max is 100")
|
|
450
|
+
params["betIds"] = ",".join(map(str, bet_ids))
|
|
451
|
+
return cast("BetsResponse", self._get(endpoint, params))
|
|
452
|
+
|
|
453
|
+
if betlist is None:
|
|
454
|
+
raise ValueError("betlist is required when betIds and uniqueRequestIds are not provided")
|
|
455
|
+
if from_date is None or to_date is None:
|
|
456
|
+
raise ValueError("fromDate and toDate are required when betlist is submitted")
|
|
457
|
+
if to_date <= from_date:
|
|
458
|
+
raise ValueError("toDate must be exclusive and greater than fromDate")
|
|
459
|
+
if from_date < datetime.now(timezone.utc) - timedelta(days=30):
|
|
460
|
+
raise ValueError("fromDate cannot be more than 30 days in the past")
|
|
461
|
+
if not (1 <= page_size <= 1000):
|
|
462
|
+
raise ValueError("pageSize must be between 1 and 1000")
|
|
463
|
+
if from_record < 0:
|
|
464
|
+
raise ValueError("fromRecord must be >= 0")
|
|
465
|
+
|
|
466
|
+
params["betlist"] = betlist
|
|
467
|
+
params["fromDate"] = from_date.isoformat()
|
|
468
|
+
params["toDate"] = to_date.isoformat()
|
|
469
|
+
params["sortDir"] = sort_dir
|
|
470
|
+
params["pageSize"] = page_size
|
|
471
|
+
params["fromRecord"] = from_record
|
|
472
|
+
|
|
473
|
+
if bet_statuses:
|
|
474
|
+
params["betStatuses"] = ",".join(bet_statuses)
|
|
475
|
+
if bet_type:
|
|
476
|
+
params["betType"] = ",".join(bet_type)
|
|
477
|
+
|
|
478
|
+
return cast("BetsResponse", self._get(endpoint, params))
|
|
479
|
+
|
|
480
|
+
def get_betting_status(self) -> BettingStatusResponse:
|
|
481
|
+
endpoint = "/v1/bets/betting-status"
|
|
482
|
+
return cast(BettingStatusResponse, self._get(endpoint, {}))
|
|
483
|
+
|
|
484
|
+
def export_my_bets(
|
|
485
|
+
self,
|
|
486
|
+
*,
|
|
487
|
+
from_datetime: datetime,
|
|
488
|
+
to_datetime: datetime,
|
|
489
|
+
d: int = -1,
|
|
490
|
+
status: Literal["UNSETTLED", "SETTLED"] = "SETTLED",
|
|
491
|
+
sd: bool = False,
|
|
492
|
+
bet_type: str = "WAGER",
|
|
493
|
+
product: str = "SB,PP,BG",
|
|
494
|
+
locale: str = "en_US",
|
|
495
|
+
timezone: str = "GMT-4",
|
|
496
|
+
) -> bytes:
|
|
497
|
+
url = "https://www.ps3838.com/member-service/v2/export/my-bets/all"
|
|
498
|
+
|
|
499
|
+
params: dict[str, Any] = {
|
|
500
|
+
"f": from_datetime.strftime("%Y-%m-%d %H:%M:%S"),
|
|
501
|
+
"t": to_datetime.strftime("%Y-%m-%d %H:%M:%S"),
|
|
502
|
+
"d": d,
|
|
503
|
+
"s": status,
|
|
504
|
+
"sd": str(sd).lower(),
|
|
505
|
+
"type": bet_type,
|
|
506
|
+
"product": product,
|
|
507
|
+
"locale": locale,
|
|
508
|
+
"timezone": timezone,
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
response = self._session.get(url, headers=self._headers, params=params)
|
|
512
|
+
response.raise_for_status()
|
|
513
|
+
return response.content
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
__all__ = ["PinnacleClient", "DEFAULT_API_BASE_URL"]
|