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/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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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