ps3838api 0.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.
Potentially problematic release.
This version of ps3838api might be problematic. Click here for more details.
- ps3838api/__init__.py +5 -0
- ps3838api/api.py +528 -0
- ps3838api/logic.py +146 -0
- ps3838api/matching.py +106 -0
- ps3838api/models/__init__.py +0 -0
- ps3838api/models/bets.py +249 -0
- ps3838api/models/errors.py +42 -0
- ps3838api/models/event.py +57 -0
- ps3838api/models/fixtures.py +53 -0
- ps3838api/models/lines.py +27 -0
- ps3838api/models/odds.py +107 -0
- ps3838api/models/tank.py +5 -0
- ps3838api/tank.py +237 -0
- ps3838api/totals.py +52 -0
- ps3838api/utils/match_leagues.py +71 -0
- ps3838api-0.1.0.dist-info/METADATA +192 -0
- ps3838api-0.1.0.dist-info/RECORD +20 -0
- ps3838api-0.1.0.dist-info/WHEEL +5 -0
- ps3838api-0.1.0.dist-info/licenses/LICENSE +8 -0
- ps3838api-0.1.0.dist-info/top_level.txt +1 -0
ps3838api/tank.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
import datetime
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from time import time
|
|
6
|
+
|
|
7
|
+
import ps3838api.api as ps
|
|
8
|
+
|
|
9
|
+
from ps3838api.logic import (
|
|
10
|
+
filter_odds,
|
|
11
|
+
find_fixtureV3_in_league,
|
|
12
|
+
find_league_in_fixtures,
|
|
13
|
+
merge_fixtures,
|
|
14
|
+
merge_odds_response,
|
|
15
|
+
)
|
|
16
|
+
from ps3838api.matching import find_event_in_league, match_league, MATCHED_LEAGUES
|
|
17
|
+
|
|
18
|
+
from ps3838api.models.tank import EventInfo
|
|
19
|
+
from ps3838api.models.fixtures import FixturesResponse
|
|
20
|
+
from ps3838api.models.odds import OddsEventV3, OddsResponse
|
|
21
|
+
from ps3838api.models.event import (
|
|
22
|
+
EventTooFarInFuture,
|
|
23
|
+
Failure,
|
|
24
|
+
NoResult,
|
|
25
|
+
NoSuchEvent,
|
|
26
|
+
NoSuchLeague,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
SNAPSHOT_INTERVAL = 60 # 1 minute
|
|
31
|
+
DELTA_INTERVAL = 5 # 5 seconds
|
|
32
|
+
|
|
33
|
+
RESPONSES_DIR = Path("temp/responses")
|
|
34
|
+
RESPONSES_DIR.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
TOP_LEAGUES = [league["ps3838_id"] for league in MATCHED_LEAGUES if league["ps3838_id"]]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FixtureTank:
|
|
40
|
+
"""
|
|
41
|
+
Stores ps3838 fixtures in a local JSON file, updating either via
|
|
42
|
+
a full snapshot or a delta call, depending on time elapsed since last call.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
league_ids: list[int] | None = None,
|
|
48
|
+
file_path: str | Path = "temp/fixtures.json",
|
|
49
|
+
) -> None:
|
|
50
|
+
self.file_path = Path(file_path)
|
|
51
|
+
self.last_call_time = 0.0
|
|
52
|
+
|
|
53
|
+
# Load local cache or pull a snapshot from the API if file not found
|
|
54
|
+
try:
|
|
55
|
+
with open(self.file_path) as file:
|
|
56
|
+
self.data: FixturesResponse = json.load(file)
|
|
57
|
+
except FileNotFoundError:
|
|
58
|
+
self.data: FixturesResponse = ps.get_fixtures(league_ids=league_ids)
|
|
59
|
+
self.last_call_time = time()
|
|
60
|
+
self._save_response(self.data, snapshot=True)
|
|
61
|
+
|
|
62
|
+
def _save_response(self, response_data: FixturesResponse, snapshot: bool) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Save fixture response to the temp/responses folder for future testing.
|
|
65
|
+
"""
|
|
66
|
+
kind = "snapshot" if snapshot else "delta"
|
|
67
|
+
timestamp = int(time())
|
|
68
|
+
filename = RESPONSES_DIR / f"fixtures_{kind}_{timestamp}.json"
|
|
69
|
+
with open(filename, "w") as f:
|
|
70
|
+
json.dump(response_data, f, indent=4)
|
|
71
|
+
|
|
72
|
+
def update(self):
|
|
73
|
+
"""
|
|
74
|
+
Decide whether to make a snapshot call, delta call, or do nothing,
|
|
75
|
+
based on how much time has elapsed since the last call.
|
|
76
|
+
"""
|
|
77
|
+
now = time()
|
|
78
|
+
elapsed = now - self.last_call_time
|
|
79
|
+
|
|
80
|
+
if elapsed < DELTA_INTERVAL:
|
|
81
|
+
# Less than 5 seconds → do nothing
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
if elapsed >= SNAPSHOT_INTERVAL:
|
|
85
|
+
# More than 1 minute → snapshot call
|
|
86
|
+
response = ps.get_fixtures(ps.SOCCER_SPORT_ID)
|
|
87
|
+
self.data = response
|
|
88
|
+
self._save_response(response, snapshot=True)
|
|
89
|
+
|
|
90
|
+
else:
|
|
91
|
+
# [5, 60) → delta call
|
|
92
|
+
delta = ps.get_fixtures(ps.SOCCER_SPORT_ID, since=self.data["last"])
|
|
93
|
+
self.data = merge_fixtures(self.data, delta)
|
|
94
|
+
self._save_response(delta, snapshot=False)
|
|
95
|
+
|
|
96
|
+
self.last_call_time = now
|
|
97
|
+
|
|
98
|
+
def save(self):
|
|
99
|
+
"""
|
|
100
|
+
Save the current fixture data to fixtures.json (the local cache).
|
|
101
|
+
"""
|
|
102
|
+
with open(self.file_path, "w") as file:
|
|
103
|
+
json.dump(self.data, file, indent=4)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class OddsTank:
|
|
107
|
+
"""
|
|
108
|
+
Stores ps3838 odds in a local JSON file, updating either via
|
|
109
|
+
a full snapshot or a delta call, depending on time elapsed since last call.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
league_ids: list[int] | None = None,
|
|
115
|
+
file_path: str | Path = "temp/odds.json",
|
|
116
|
+
) -> None:
|
|
117
|
+
self.file_path = Path(file_path)
|
|
118
|
+
self.last_call_time = 0.0
|
|
119
|
+
self.is_live: bool | None = None
|
|
120
|
+
|
|
121
|
+
# Load local cache or pull a snapshot from the API if file not found
|
|
122
|
+
try:
|
|
123
|
+
with open(file_path) as file:
|
|
124
|
+
self.data: OddsResponse = json.load(file)
|
|
125
|
+
except FileNotFoundError:
|
|
126
|
+
self.data: OddsResponse = ps.get_odds(league_ids=league_ids)
|
|
127
|
+
self.last_call_time = time()
|
|
128
|
+
self._save_response(self.data, snapshot=True)
|
|
129
|
+
|
|
130
|
+
def _save_response(self, response_data: OddsResponse, snapshot: bool) -> None:
|
|
131
|
+
"""
|
|
132
|
+
Save odds response to the temp/responses folder for future testing.
|
|
133
|
+
"""
|
|
134
|
+
kind = "snapshot" if snapshot else "delta"
|
|
135
|
+
timestamp = int(time())
|
|
136
|
+
filename = RESPONSES_DIR / f"odds_{kind}_{timestamp}.json"
|
|
137
|
+
with open(filename, "w") as f:
|
|
138
|
+
json.dump(response_data, f, indent=4)
|
|
139
|
+
|
|
140
|
+
def update(self):
|
|
141
|
+
"""
|
|
142
|
+
Decide whether to make a snapshot call, delta call, or do nothing,
|
|
143
|
+
based on how much time has elapsed since the last call.
|
|
144
|
+
"""
|
|
145
|
+
now = time()
|
|
146
|
+
elapsed = now - self.last_call_time
|
|
147
|
+
|
|
148
|
+
if elapsed < DELTA_INTERVAL:
|
|
149
|
+
# Less than 5 seconds → do nothing
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
if elapsed >= SNAPSHOT_INTERVAL:
|
|
153
|
+
# More than 1 minute → snapshot call
|
|
154
|
+
response = ps.get_odds(ps.SOCCER_SPORT_ID, is_live=self.is_live)
|
|
155
|
+
self.data = response
|
|
156
|
+
self._save_response(response, snapshot=True)
|
|
157
|
+
|
|
158
|
+
else:
|
|
159
|
+
# [5, 60) → delta call
|
|
160
|
+
delta = ps.get_odds(
|
|
161
|
+
ps.SOCCER_SPORT_ID, is_live=self.is_live, since=self.data["last"]
|
|
162
|
+
)
|
|
163
|
+
self.data = merge_odds_response(self.data, delta)
|
|
164
|
+
self._save_response(delta, snapshot=False)
|
|
165
|
+
|
|
166
|
+
self.last_call_time = now
|
|
167
|
+
|
|
168
|
+
def save(self):
|
|
169
|
+
"""
|
|
170
|
+
Save the current odds data to odds.json (the local cache).
|
|
171
|
+
"""
|
|
172
|
+
with open(self.file_path, "w") as file:
|
|
173
|
+
json.dump(self.data, file, indent=4)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass
|
|
178
|
+
class EventMatcher:
|
|
179
|
+
fixtures: FixtureTank = field(init=False)
|
|
180
|
+
odds: OddsTank = field(init=False)
|
|
181
|
+
league_ids: list[int] | None = None
|
|
182
|
+
|
|
183
|
+
def __post_init__(self):
|
|
184
|
+
self.fixtures = FixtureTank(league_ids=self.league_ids)
|
|
185
|
+
self.odds = OddsTank(league_ids=self.league_ids)
|
|
186
|
+
|
|
187
|
+
def save(self):
|
|
188
|
+
self.fixtures.save()
|
|
189
|
+
self.odds.save()
|
|
190
|
+
|
|
191
|
+
def get_league_id_and_event_id(
|
|
192
|
+
self, league: str, home: str, away: str, force_local: bool = False
|
|
193
|
+
) -> EventInfo | Failure:
|
|
194
|
+
match match_league(league_betsapi=league):
|
|
195
|
+
case NoSuchLeague() as f:
|
|
196
|
+
return f
|
|
197
|
+
case matched_league:
|
|
198
|
+
league_id = matched_league["ps3838_id"]
|
|
199
|
+
assert league_id is not None
|
|
200
|
+
|
|
201
|
+
leagueV3 = find_league_in_fixtures(self.fixtures.data, league, league_id)
|
|
202
|
+
|
|
203
|
+
if isinstance(leagueV3, NoSuchLeague):
|
|
204
|
+
if force_local:
|
|
205
|
+
return leagueV3
|
|
206
|
+
print("updating fixtures...")
|
|
207
|
+
self.fixtures.update()
|
|
208
|
+
leagueV3 = find_league_in_fixtures(self.fixtures.data, league, league_id)
|
|
209
|
+
if isinstance(leagueV3, NoSuchLeague):
|
|
210
|
+
return leagueV3
|
|
211
|
+
|
|
212
|
+
match find_event_in_league(leagueV3, league, home, away):
|
|
213
|
+
case NoSuchEvent() as f:
|
|
214
|
+
return f
|
|
215
|
+
case event:
|
|
216
|
+
event = event
|
|
217
|
+
fixture = find_fixtureV3_in_league(leagueV3, event['eventId'])
|
|
218
|
+
|
|
219
|
+
if 'starts' in fixture:
|
|
220
|
+
fixture_start = datetime.datetime.fromisoformat(fixture['starts'])
|
|
221
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
222
|
+
time_diff = fixture_start - now
|
|
223
|
+
# Check if the event starts in 60 minutes or less, but not in the past
|
|
224
|
+
if datetime.timedelta(0) <= time_diff <= datetime.timedelta(minutes=60):
|
|
225
|
+
return event
|
|
226
|
+
return EventTooFarInFuture(league, home, away)
|
|
227
|
+
|
|
228
|
+
def get_odds(
|
|
229
|
+
self, event: EventInfo, force_local: bool = False
|
|
230
|
+
) -> OddsEventV3 | NoResult:
|
|
231
|
+
"""
|
|
232
|
+
Update the odds tank and then look up the odds for the given event.
|
|
233
|
+
"""
|
|
234
|
+
if not force_local:
|
|
235
|
+
self.odds.update()
|
|
236
|
+
self.save()
|
|
237
|
+
return filter_odds(self.odds.data, event["eventId"])
|
ps3838api/totals.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
from typing import NotRequired
|
|
3
|
+
from ps3838api.models.odds import OddsTotalV3, OddsEventV3
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class OddsTotal(OddsTotalV3):
|
|
7
|
+
"""Either has line or alt line id"""
|
|
8
|
+
|
|
9
|
+
lineId: NotRequired[int]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def calculate_margin(total: OddsTotalV3) -> float:
|
|
13
|
+
return (1 / total["over"] + 1 / total["under"]) - 1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_all_total_lines(
|
|
17
|
+
odds: OddsEventV3,
|
|
18
|
+
periods: list[int] = [
|
|
19
|
+
0,
|
|
20
|
+
],
|
|
21
|
+
) -> list[OddsTotal]:
|
|
22
|
+
result: list[OddsTotal] = []
|
|
23
|
+
for period in odds["periods"]: # type: ignore
|
|
24
|
+
if (
|
|
25
|
+
"number" not in period
|
|
26
|
+
or period["number"] not in periods
|
|
27
|
+
or "totals" not in period
|
|
28
|
+
):
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
lineId = period["lineId"] if "lineId" in period else None
|
|
32
|
+
maxTotal = period["maxTotal"] if "maxTotal" in period else None
|
|
33
|
+
|
|
34
|
+
for total in period["totals"]:
|
|
35
|
+
if "altLineId" not in total and lineId is not None:
|
|
36
|
+
odds_total = cast(OddsTotal, total.copy())
|
|
37
|
+
odds_total["lineId"] = lineId
|
|
38
|
+
if maxTotal is not None:
|
|
39
|
+
odds_total["max"] = maxTotal
|
|
40
|
+
result.append(odds_total)
|
|
41
|
+
else:
|
|
42
|
+
result.append(cast(OddsTotal, total))
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_best_total_line(
|
|
47
|
+
odds: OddsEventV3, periods: list[int] = [0, 1]
|
|
48
|
+
) -> OddsTotal | None:
|
|
49
|
+
try:
|
|
50
|
+
return min(get_all_total_lines(odds, periods=periods), key=calculate_margin)
|
|
51
|
+
except Exception:
|
|
52
|
+
return None
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ps3838api import ROOT_DIR
|
|
7
|
+
|
|
8
|
+
def normalize_to_set(name: str) -> set[str]:
|
|
9
|
+
return set(
|
|
10
|
+
name.replace(" II", " 2").replace(" I", "").lower().replace("-", " ").split()
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_json(path: str | Path) -> list[Any] | dict[str, Any]:
|
|
15
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
16
|
+
return json.load(f)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
# Paths
|
|
21
|
+
betsapi_path = ROOT_DIR / Path("out/betsapi_leagues.json")
|
|
22
|
+
ps3838_path = ROOT_DIR / Path("out/ps3838_leagues.json")
|
|
23
|
+
output_path = ROOT_DIR / Path("out/matched_leagues.json")
|
|
24
|
+
|
|
25
|
+
# Load files
|
|
26
|
+
betsapi_leagues = load_json(betsapi_path)
|
|
27
|
+
ps3838_data = load_json(ps3838_path)["leagues"]
|
|
28
|
+
|
|
29
|
+
# Build normalized index for ps3838
|
|
30
|
+
ps3838_index = []
|
|
31
|
+
for league in ps3838_data:
|
|
32
|
+
ps3838_index.append(
|
|
33
|
+
{
|
|
34
|
+
"name": league["name"],
|
|
35
|
+
"id": league["id"],
|
|
36
|
+
"norm_set": normalize_to_set(league["name"]),
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Match
|
|
41
|
+
matched = []
|
|
42
|
+
for betsapi_league in betsapi_leagues:
|
|
43
|
+
norm_betsapi = normalize_to_set(betsapi_league)
|
|
44
|
+
match = next(
|
|
45
|
+
(
|
|
46
|
+
{
|
|
47
|
+
"betsapi_league": betsapi_league,
|
|
48
|
+
"ps3838_league": ps["name"],
|
|
49
|
+
"ps3838_id": ps["id"],
|
|
50
|
+
}
|
|
51
|
+
for ps in ps3838_index
|
|
52
|
+
if ps["norm_set"] == norm_betsapi
|
|
53
|
+
),
|
|
54
|
+
{
|
|
55
|
+
"betsapi_league": betsapi_league,
|
|
56
|
+
"ps3838_league": None,
|
|
57
|
+
"ps3838_id": None,
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
matched.append(match)
|
|
61
|
+
|
|
62
|
+
# Save output
|
|
63
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
65
|
+
json.dump(matched, f, indent=2, ensure_ascii=False)
|
|
66
|
+
|
|
67
|
+
print(f"✅ Matching complete. Output saved to: {output_path}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
main()
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ps3838api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modern PS3838 API
|
|
5
|
+
Author-email: Ilias Dzhabbarov <ilias.dzabbarov@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/iliyasone/ps3838api
|
|
8
|
+
Project-URL: Issues, https://github.com/iliyasone/ps3838api/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: requests
|
|
16
|
+
Requires-Dist: rapidfuzz
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# Modern PS3838 API
|
|
20
|
+
|
|
21
|
+
A lightweight Python library to interact with the PS3838 API.
|
|
22
|
+
|
|
23
|
+
## 🔑 Key Idea
|
|
24
|
+
|
|
25
|
+
This project aims to keep all method names and behavior as close as possible to the official [PS3838 API documentation](https://ps3838api.github.io/docs/). No abstraction layers that get in your way — just a clean, Pythonic functional interface to the raw API.
|
|
26
|
+
|
|
27
|
+
If you need assistance, contact me directly on Telegram: [@iliyasone](https://t.me/iliyasone) 💬
|
|
28
|
+
|
|
29
|
+
## ✨ Features
|
|
30
|
+
|
|
31
|
+
### `ps3838api.api` — Minimalist, Typed API Wrapper
|
|
32
|
+
|
|
33
|
+
- **Simple & Clear:** All commonly used endpoints are exposed as straightforward Python functions.
|
|
34
|
+
- **Type-Safe:** Responses are structured using precise `TypedDict` definitions based directly on the official docs.
|
|
35
|
+
- **Clean Data:** Say goodbye to messy, undocumented JSON blobs.
|
|
36
|
+
- **Lightweight:** No bloated ORMs or clunky third-party wrappers — just clean, readable code.
|
|
37
|
+
|
|
38
|
+
### Event & Odds Matching
|
|
39
|
+
|
|
40
|
+
- Utility functions like `magic_find_event` and `filter_odds` help you match events and filter odds quickly and effortlessly 🔍.
|
|
41
|
+
|
|
42
|
+
### Bet Placement
|
|
43
|
+
|
|
44
|
+
- Place bets with simple functions that eliminate unnecessary overhead. Fast and efficient, just as you like it!
|
|
45
|
+
|
|
46
|
+
## 🚀 Setup
|
|
47
|
+
|
|
48
|
+
> You can also check out the [📓 examples.ipynb](https://github.com/iliyasone/ps3838api/blob/release/0.1.0/examples/examples.ipynb) for a quick start!
|
|
49
|
+
|
|
50
|
+
### 1. Set Environment Variables
|
|
51
|
+
|
|
52
|
+
Before using the library, set your API credentials via environment variables:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import os
|
|
56
|
+
|
|
57
|
+
os.environ["PS3838_LOGIN"] = "your_username"
|
|
58
|
+
os.environ["PS3838_PASSWORD"] = "your_password"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
> **Note:** After version 1.0, the library will transition to a client-based API instead of just functions.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### 2. Check Client Balance
|
|
66
|
+
|
|
67
|
+
Quickly check your account balance by calling the API:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import ps3838api.api as ps
|
|
71
|
+
|
|
72
|
+
balance = ps.get_client_balance()
|
|
73
|
+
print("Client Balance:", balance)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Expected output:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"availableBalance": 200.0,
|
|
81
|
+
"outstandingTransactions": 0.0,
|
|
82
|
+
"givenCredit": 0.0,
|
|
83
|
+
"currency": "USD"
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 🎯 Retrieve Event and Place Bet
|
|
88
|
+
|
|
89
|
+
Find and use events with ease:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
league = 'Russia - Cup'
|
|
93
|
+
home = 'Lokomotiv Moscow'
|
|
94
|
+
away = 'Akhmat Grozny'
|
|
95
|
+
|
|
96
|
+
fixtures = ps.get_fixtures() # sport_id: int = SOCCER_SPORT_ID by default
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Match the event using utility functions:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from ps3838api.matching import magic_find_event
|
|
103
|
+
|
|
104
|
+
event = magic_find_event(fixtures, league, home, away)
|
|
105
|
+
print(event)
|
|
106
|
+
# Example output: {'eventId': 1607937909, 'leagueId': 2409}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Make sure to validate the event:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
assert isinstance(event, dict) # also could be Failure
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Filter odds for the selected event:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from ps3838api.logic import filter_odds
|
|
119
|
+
|
|
120
|
+
odds_response = ps.get_odds()
|
|
121
|
+
odds_eventV3 = filter_odds(odds_response, event_id=event['eventId'])
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Select the best total line:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from ps3838api.totals import get_best_total_line
|
|
128
|
+
|
|
129
|
+
total_line = get_best_total_line(odds_eventV3)
|
|
130
|
+
print(total_line)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
An example response:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"points": 4.5,
|
|
138
|
+
"over": 2.08,
|
|
139
|
+
"under": 1.775,
|
|
140
|
+
"lineId": 3058623866,
|
|
141
|
+
"max": 3750.0
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 💸 Place a Bet
|
|
146
|
+
|
|
147
|
+
Once you have your event and total line, place your bet:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
assert isinstance(event, dict) # also could be Failure
|
|
151
|
+
assert total_line is not None
|
|
152
|
+
|
|
153
|
+
import ps3838api.api as ps
|
|
154
|
+
|
|
155
|
+
stake_usdt = 1.0
|
|
156
|
+
|
|
157
|
+
place_bet_response = ps.place_straigh_bet(
|
|
158
|
+
stake=stake_usdt,
|
|
159
|
+
event_id=event['eventId'],
|
|
160
|
+
bet_type='TOTAL_POINTS',
|
|
161
|
+
line_id=total_line.get('lineId', None),
|
|
162
|
+
alt_line_id=total_line.get('altLineId', None),
|
|
163
|
+
side='OVER',
|
|
164
|
+
handicap=total_line['points']
|
|
165
|
+
)
|
|
166
|
+
print("Unique Request ID:", place_bet_response['uniqueRequestId'])
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
You can also check your bet status:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
bets = ps.get_bets(unique_request_ids=[place_bet_response['uniqueRequestId']])
|
|
173
|
+
# Verify the bet status
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## ⚠️ Known Issues
|
|
177
|
+
|
|
178
|
+
- **Logging:** Not implemented yet.
|
|
179
|
+
- **Testing:** Still missing.
|
|
180
|
+
- **CI/CD:** No GitHub CI/CD integration at the moment.
|
|
181
|
+
|
|
182
|
+
## 🛠️ Local Installation
|
|
183
|
+
|
|
184
|
+
To install the library locally, run the following commands:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
git clone https://github.com/iliyasone/ps3838api.git
|
|
188
|
+
cd ps3838api
|
|
189
|
+
pip install . -r dev-requirements.txt
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Happy coding
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
ps3838api/__init__.py,sha256=GUa02DNN5yCnNNlKtV9BgBvBknjKDyALU7GaWKLAW9A,113
|
|
2
|
+
ps3838api/api.py,sha256=JEGn3n4ZEqJAPNcruI3lpYOC5C4VOZwfj9ZyoPcql3I,16356
|
|
3
|
+
ps3838api/logic.py,sha256=_6TTV35zTf-m-klj2cfy6LXjAJE7Gji9WVMKmqslzpw,5688
|
|
4
|
+
ps3838api/matching.py,sha256=yCw3N45NMoPlsWGeXVdJ84SQf6me6qGwk-rovnDyHeQ,3664
|
|
5
|
+
ps3838api/tank.py,sha256=-7VVbB9B7oE5Wm8J3ToBjO4wujxRvTUjrg8Evlrbx24,8211
|
|
6
|
+
ps3838api/totals.py,sha256=3t75TDY4xoF8eC6zNpsao_yOXqFESWvzZgsSFNgvifQ,1551
|
|
7
|
+
ps3838api/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
ps3838api/models/bets.py,sha256=xa2zub-8GbRvVGXguZg3SEy1Kq4a1hUYztHx8ufM12o,6721
|
|
9
|
+
ps3838api/models/errors.py,sha256=VUtqposauK_-t9mW1d6vq986lLyjt-Kj-BupnCn3VIc,918
|
|
10
|
+
ps3838api/models/event.py,sha256=LxW0FXZzxfS_Q8diFcWNGNiN2iZGWcn48xG2CZ9Mvxs,1013
|
|
11
|
+
ps3838api/models/fixtures.py,sha256=5JL5Op8LOGn-tvbLQP65Y0S27fbclRkDnZPNlJZUNbU,1434
|
|
12
|
+
ps3838api/models/lines.py,sha256=6Nh5yxMY00BZV72mvS5yHF2Uasb4-KoOutxX9wqwTkY,615
|
|
13
|
+
ps3838api/models/odds.py,sha256=qHhjcR_glrnka7GrBPvDfwjF9TGGESaHGwH9n5zUYc8,4483
|
|
14
|
+
ps3838api/models/tank.py,sha256=O5JoYaq5FyYrXNELobJQWH5siEnCZHbgT4eHOn5zdI0,96
|
|
15
|
+
ps3838api/utils/match_leagues.py,sha256=vSAMbPDj0sFufIlTTh0PwCLXgcV8hp0eRiGP6FwY-uk,2047
|
|
16
|
+
ps3838api-0.1.0.dist-info/licenses/LICENSE,sha256=7PyWO4q1z3n4SX9uPkti_UH9O1v1p_m7Q6JUSgHr3kg,1073
|
|
17
|
+
ps3838api-0.1.0.dist-info/METADATA,sha256=rrnhV0L4ANBqC7KjMLFqazc2CQ1Y5FWArt0MHB2lMsA,4809
|
|
18
|
+
ps3838api-0.1.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
19
|
+
ps3838api-0.1.0.dist-info/top_level.txt,sha256=ec8CzKfcDq10-rMF_IM-9kxxxlnT4euiLfVrwQrmO0s,10
|
|
20
|
+
ps3838api-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Copyright 2025 Ilias Dzhabbarov
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
8
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ps3838api
|