stakeapi-codestats 0.2.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.
- stakeapi/__init__.py +31 -0
- stakeapi/_version.py +1 -0
- stakeapi/auth.py +138 -0
- stakeapi/client.py +695 -0
- stakeapi/endpoints.py +916 -0
- stakeapi/exceptions.py +43 -0
- stakeapi/models.py +285 -0
- stakeapi/utils.py +141 -0
- stakeapi_codestats-0.2.0.dist-info/METADATA +230 -0
- stakeapi_codestats-0.2.0.dist-info/RECORD +13 -0
- stakeapi_codestats-0.2.0.dist-info/WHEEL +5 -0
- stakeapi_codestats-0.2.0.dist-info/licenses/LICENSE +21 -0
- stakeapi_codestats-0.2.0.dist-info/top_level.txt +1 -0
stakeapi/exceptions.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Custom exceptions for StakeAPI."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StakeAPIError(Exception):
|
|
5
|
+
"""Base exception for StakeAPI errors."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AuthenticationError(StakeAPIError):
|
|
11
|
+
"""Raised when authentication fails."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RateLimitError(StakeAPIError):
|
|
17
|
+
"""Raised when rate limit is exceeded."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ValidationError(StakeAPIError):
|
|
23
|
+
"""Raised when input validation fails."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class NetworkError(StakeAPIError):
|
|
29
|
+
"""Raised when network requests fail."""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class GameNotFoundError(StakeAPIError):
|
|
35
|
+
"""Raised when a requested game is not found."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class InsufficientFundsError(StakeAPIError):
|
|
41
|
+
"""Raised when user has insufficient funds for an operation."""
|
|
42
|
+
|
|
43
|
+
pass
|
stakeapi/models.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Data models for StakeAPI.
|
|
2
|
+
|
|
3
|
+
Models reflect the actual GraphQL API response shapes as verified
|
|
4
|
+
against stake.com and stake.us on 2025-05-09/10.
|
|
5
|
+
|
|
6
|
+
Pydantic v2 model_config handles camelCase (from API) <-> snake_case (Python)
|
|
7
|
+
conversion via alias_generator.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from decimal import Decimal
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel as _BaseModel
|
|
14
|
+
from pydantic import ConfigDict, Field
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _to_camel(name: str) -> str:
|
|
18
|
+
first, *rest = name.split("_")
|
|
19
|
+
return first + "".join(r.capitalize() for r in rest)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StakeModel(_BaseModel):
|
|
23
|
+
"""Base model with camelCase alias support."""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(
|
|
26
|
+
populate_by_name=True,
|
|
27
|
+
alias_generator=_to_camel,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_dict(cls, data: Dict[str, Any]) -> "StakeModel":
|
|
32
|
+
return cls(**data)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BalanceEntry(StakeModel):
|
|
36
|
+
"""Available or vault balance for a single currency."""
|
|
37
|
+
|
|
38
|
+
amount: Decimal = Decimal("0")
|
|
39
|
+
currency: str = ""
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_dict(cls, data: Dict[str, Any]) -> "BalanceEntry":
|
|
43
|
+
return cls(**data)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class User(StakeModel):
|
|
47
|
+
"""User model matching the real GraphQL user shape."""
|
|
48
|
+
|
|
49
|
+
id: str = ""
|
|
50
|
+
name: str = ""
|
|
51
|
+
email: Optional[str] = None
|
|
52
|
+
has_email_verified: bool = False
|
|
53
|
+
is_muted: bool = False
|
|
54
|
+
is_rainproof: bool = False
|
|
55
|
+
is_banned: bool = False
|
|
56
|
+
created_at: Optional[str] = None
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_dict(cls, data: Dict[str, Any]) -> "User":
|
|
60
|
+
return cls(**data)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SessionInfo(StakeModel):
|
|
64
|
+
"""Session entry from user.sessionList."""
|
|
65
|
+
|
|
66
|
+
id: str = ""
|
|
67
|
+
session_name: Optional[str] = None
|
|
68
|
+
ip: Optional[str] = None
|
|
69
|
+
active: bool = False
|
|
70
|
+
country: Optional[str] = None
|
|
71
|
+
city: Optional[str] = None
|
|
72
|
+
created_at: Optional[str] = None
|
|
73
|
+
updated_at: Optional[str] = None
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_dict(cls, data: Dict[str, Any]) -> "SessionInfo":
|
|
77
|
+
return cls(**data)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ApiKeyInfo(StakeModel):
|
|
81
|
+
"""API key entry from user.apiKeys."""
|
|
82
|
+
|
|
83
|
+
id: str = ""
|
|
84
|
+
ip: Optional[str] = None
|
|
85
|
+
active: bool = False
|
|
86
|
+
session_name: Optional[str] = None
|
|
87
|
+
type: Optional[str] = None
|
|
88
|
+
created_at: Optional[str] = None
|
|
89
|
+
updated_at: Optional[str] = None
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ApiKeyInfo":
|
|
93
|
+
return cls(**data)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class StatisticEntry(StakeModel):
|
|
97
|
+
"""Per-currency wagering statistic from user.statistic."""
|
|
98
|
+
|
|
99
|
+
id: str = ""
|
|
100
|
+
bet_amount: Decimal = Decimal("0")
|
|
101
|
+
profit: Decimal = Decimal("0")
|
|
102
|
+
amount: Decimal = Decimal("0")
|
|
103
|
+
currency: str = ""
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def from_dict(cls, data: Dict[str, Any]) -> "StatisticEntry":
|
|
107
|
+
return cls(**data)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class SeedPair(StakeModel):
|
|
111
|
+
"""Active client/server seed pair."""
|
|
112
|
+
|
|
113
|
+
client_seed: str = ""
|
|
114
|
+
server_seed_hash: str = ""
|
|
115
|
+
nonce: int = 0
|
|
116
|
+
next_seed_hash: Optional[str] = None
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def from_dict(cls, data: Dict[str, Any]) -> "SeedPair":
|
|
120
|
+
return cls(**data)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class FaucetInfo(StakeModel):
|
|
124
|
+
"""Reload/faucet status."""
|
|
125
|
+
|
|
126
|
+
id: str = ""
|
|
127
|
+
active: bool = False
|
|
128
|
+
value: Decimal = Decimal("0")
|
|
129
|
+
claim_interval: Optional[int] = None
|
|
130
|
+
last_claim: Optional[str] = None
|
|
131
|
+
expire_at: Optional[str] = None
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def from_dict(cls, data: Dict[str, Any]) -> "FaucetInfo":
|
|
135
|
+
return cls(**data)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TransactionEntry(StakeModel):
|
|
139
|
+
"""Single transaction from user.transaction."""
|
|
140
|
+
|
|
141
|
+
id: str = ""
|
|
142
|
+
amount: Decimal = Decimal("0")
|
|
143
|
+
currency: str = ""
|
|
144
|
+
type: str = ""
|
|
145
|
+
created_at: Optional[str] = None
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def from_dict(cls, data: Dict[str, Any]) -> "TransactionEntry":
|
|
149
|
+
return cls(**data)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class BlackjackCard(StakeModel):
|
|
153
|
+
"""Card in a blackjack hand."""
|
|
154
|
+
|
|
155
|
+
rank: str = ""
|
|
156
|
+
suit: str = ""
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def from_dict(cls, data: Dict[str, Any]) -> "BlackjackCard":
|
|
160
|
+
return cls(**data)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class BlackjackHand(StakeModel):
|
|
164
|
+
"""Blackjack hand (player or dealer)."""
|
|
165
|
+
|
|
166
|
+
value: int = 0
|
|
167
|
+
actions: List[str] = Field(default_factory=list)
|
|
168
|
+
cards: List[BlackjackCard] = Field(default_factory=list)
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def from_dict(cls, data: Dict[str, Any]) -> "BlackjackHand":
|
|
172
|
+
return cls(**data)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class BlackjackBet(StakeModel):
|
|
176
|
+
"""Active blackjack bet."""
|
|
177
|
+
|
|
178
|
+
id: str = ""
|
|
179
|
+
active: bool = False
|
|
180
|
+
nonce: int = 0
|
|
181
|
+
payout_multiplier: Decimal = Decimal("0")
|
|
182
|
+
amount_multiplier: Decimal = Decimal("0")
|
|
183
|
+
amount: Decimal = Decimal("0")
|
|
184
|
+
payout: Decimal = Decimal("0")
|
|
185
|
+
updated_at: Optional[str] = None
|
|
186
|
+
currency: str = ""
|
|
187
|
+
game: str = ""
|
|
188
|
+
player: Optional[BlackjackHand] = None
|
|
189
|
+
dealer: Optional[BlackjackHand] = None
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def from_dict(cls, data: Dict[str, Any]) -> "BlackjackBet":
|
|
193
|
+
return cls(**data)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class BonusCodeInfo(StakeModel):
|
|
197
|
+
"""Bonus code availability info."""
|
|
198
|
+
|
|
199
|
+
availability_status: str = ""
|
|
200
|
+
bonus_value: Optional[Decimal] = None
|
|
201
|
+
crypto_multiplier: Optional[Decimal] = None
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def from_dict(cls, data: Dict[str, Any]) -> "BonusCodeInfo":
|
|
205
|
+
return cls(**data)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class KuratorGame(StakeModel):
|
|
209
|
+
"""Game entry in a kurator collection/group."""
|
|
210
|
+
|
|
211
|
+
id: str = ""
|
|
212
|
+
name: str = ""
|
|
213
|
+
slug: str = ""
|
|
214
|
+
provider: Optional[str] = None
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def from_dict(cls, data: Dict[str, Any]) -> "KuratorGame":
|
|
218
|
+
return cls(**data)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class KuratorCollection(StakeModel):
|
|
222
|
+
"""Kurator game collection."""
|
|
223
|
+
|
|
224
|
+
id: str = ""
|
|
225
|
+
name: str = ""
|
|
226
|
+
slug: str = ""
|
|
227
|
+
games: List[KuratorGame] = Field(default_factory=list)
|
|
228
|
+
|
|
229
|
+
@classmethod
|
|
230
|
+
def from_dict(cls, data: Dict[str, Any]) -> "KuratorCollection":
|
|
231
|
+
return cls(**data)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class CurrencyInfo(StakeModel):
|
|
235
|
+
"""Currency configuration entry."""
|
|
236
|
+
|
|
237
|
+
name: str = ""
|
|
238
|
+
rates: List[Dict[str, Any]] = Field(default_factory=list)
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def from_dict(cls, data: Dict[str, Any]) -> "CurrencyInfo":
|
|
242
|
+
return cls(**data)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class SportItem(StakeModel):
|
|
246
|
+
"""Sport menu entry."""
|
|
247
|
+
|
|
248
|
+
id: str = ""
|
|
249
|
+
name: str = ""
|
|
250
|
+
slug: str = ""
|
|
251
|
+
icon: Optional[str] = None
|
|
252
|
+
active: bool = False
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def from_dict(cls, data: Dict[str, Any]) -> "SportItem":
|
|
256
|
+
return cls(**data)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class RaceInfo(StakeModel):
|
|
260
|
+
"""Active race entry."""
|
|
261
|
+
|
|
262
|
+
id: str = ""
|
|
263
|
+
name: str = ""
|
|
264
|
+
start_date: Optional[str] = None
|
|
265
|
+
end_date: Optional[str] = None
|
|
266
|
+
prize: Optional[Decimal] = None
|
|
267
|
+
currency: str = ""
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def from_dict(cls, data: Dict[str, Any]) -> "RaceInfo":
|
|
271
|
+
return cls(**data)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class NotificationEntry(StakeModel):
|
|
275
|
+
"""Notification entry."""
|
|
276
|
+
|
|
277
|
+
id: str = ""
|
|
278
|
+
type: Optional[str] = None
|
|
279
|
+
message: Optional[str] = None
|
|
280
|
+
read: bool = False
|
|
281
|
+
created_at: Optional[str] = None
|
|
282
|
+
|
|
283
|
+
@classmethod
|
|
284
|
+
def from_dict(cls, data: Dict[str, Any]) -> "NotificationEntry":
|
|
285
|
+
return cls(**data)
|
stakeapi/utils.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Utility functions for StakeAPI."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from decimal import Decimal, InvalidOperation
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def validate_api_key(api_key: str) -> bool:
|
|
10
|
+
"""
|
|
11
|
+
Validate API key format.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
api_key: The API key to validate
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
True if valid format
|
|
18
|
+
"""
|
|
19
|
+
if not api_key or not isinstance(api_key, str):
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
# Basic format validation (adjust based on actual format)
|
|
23
|
+
pattern = r"^[a-zA-Z0-9]{32,64}$"
|
|
24
|
+
return bool(re.match(pattern, api_key))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def safe_decimal(value: Any) -> Optional[Decimal]:
|
|
28
|
+
"""
|
|
29
|
+
Safely convert value to Decimal.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
value: Value to convert
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Decimal value or None if conversion fails
|
|
36
|
+
"""
|
|
37
|
+
if value is None:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
return Decimal(str(value))
|
|
42
|
+
except (InvalidOperation, ValueError, TypeError):
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parse_datetime(date_string: str) -> Optional[datetime]:
|
|
47
|
+
"""
|
|
48
|
+
Parse datetime string to datetime object.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
date_string: ISO format datetime string
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Datetime object or None if parsing fails
|
|
55
|
+
"""
|
|
56
|
+
if not date_string:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
# Try parsing ISO format with timezone
|
|
61
|
+
return datetime.fromisoformat(date_string.replace("Z", "+00:00"))
|
|
62
|
+
except ValueError:
|
|
63
|
+
try:
|
|
64
|
+
# Try parsing without timezone
|
|
65
|
+
dt = datetime.fromisoformat(date_string)
|
|
66
|
+
return dt.replace(tzinfo=timezone.utc)
|
|
67
|
+
except ValueError:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def format_currency(amount: Decimal, currency: str = "USD") -> str:
|
|
72
|
+
"""
|
|
73
|
+
Format currency amount for display.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
amount: Amount to format
|
|
77
|
+
currency: Currency code
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Formatted currency string
|
|
81
|
+
"""
|
|
82
|
+
if currency.upper() == "USD":
|
|
83
|
+
return f"${amount:.2f}"
|
|
84
|
+
elif currency.upper() == "EUR":
|
|
85
|
+
return f"€{amount:.2f}"
|
|
86
|
+
elif currency.upper() == "GBP":
|
|
87
|
+
return f"£{amount:.2f}"
|
|
88
|
+
else:
|
|
89
|
+
return f"{amount:.2f} {currency.upper()}"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def calculate_win_rate(wins: int, total_bets: int) -> float:
|
|
93
|
+
"""
|
|
94
|
+
Calculate win rate percentage.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
wins: Number of wins
|
|
98
|
+
total_bets: Total number of bets
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Win rate as percentage (0-100)
|
|
102
|
+
"""
|
|
103
|
+
if total_bets == 0:
|
|
104
|
+
return 0.0
|
|
105
|
+
|
|
106
|
+
return (wins / total_bets) * 100
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def validate_bet_amount(amount: Decimal, min_bet: Decimal, max_bet: Decimal) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Validate bet amount is within limits.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
amount: Bet amount
|
|
115
|
+
min_bet: Minimum bet amount
|
|
116
|
+
max_bet: Maximum bet amount
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
True if amount is valid
|
|
120
|
+
"""
|
|
121
|
+
return min_bet <= amount <= max_bet
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def sanitize_game_name(name: str) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Sanitize game name for safe usage.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
name: Game name to sanitize
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Sanitized game name
|
|
133
|
+
"""
|
|
134
|
+
if not name:
|
|
135
|
+
return ""
|
|
136
|
+
|
|
137
|
+
# Remove special characters and normalize spaces
|
|
138
|
+
sanitized = re.sub(r"[^\w\s-]", "", name)
|
|
139
|
+
sanitized = re.sub(r"\s+", " ", sanitized).strip()
|
|
140
|
+
|
|
141
|
+
return sanitized
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stakeapi-codestats
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Unofficial async Python wrapper for the stake.com / stake.us GraphQL API — CodeStats.gg edition
|
|
5
|
+
Author: brokechubb
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://codestats.gg
|
|
8
|
+
Project-URL: Documentation, https://brokechubb.github.io/StakeAPI/
|
|
9
|
+
Project-URL: Repository, https://github.com/brokechubb/StakeAPI
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/brokechubb/StakeAPI/issues
|
|
11
|
+
Keywords: stake,gambling,api,casino,betting,codestats,graphql,stake.us
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
27
|
+
Requires-Dist: pydantic>=2.0.0
|
|
28
|
+
Requires-Dist: python-dotenv>=0.19.0
|
|
29
|
+
Requires-Dist: websockets>=10.0
|
|
30
|
+
Requires-Dist: cryptography>=3.4.8
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: black>=22.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: isort>=5.10.0; extra == "dev"
|
|
37
|
+
Requires-Dist: flake8>=4.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: mypy>=0.910; extra == "dev"
|
|
39
|
+
Requires-Dist: pre-commit>=2.15.0; extra == "dev"
|
|
40
|
+
Provides-Extra: docs
|
|
41
|
+
Requires-Dist: sphinx>=4.0.0; extra == "docs"
|
|
42
|
+
Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "docs"
|
|
43
|
+
Requires-Dist: myst-parser>=0.15.0; extra == "docs"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# StakeAPI
|
|
47
|
+
|
|
48
|
+
### UPDATED MAY 2026
|
|
49
|
+
|
|
50
|
+
An unofficial async Python wrapper for the stake.com / stake.us GraphQL API.
|
|
51
|
+
|
|
52
|
+
## Disclaimer
|
|
53
|
+
|
|
54
|
+
This is an unofficial wrapper, not affiliated with or endorsed by Stake.com or Stake.us. Use at your own risk and ensure compliance with all applicable laws and the platform's terms of service.
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- Async/await with `aiohttp`
|
|
59
|
+
- All GraphQL operations verified against live stake.com and stake.us APIs
|
|
60
|
+
- Pydantic v2 models with camelCase alias support
|
|
61
|
+
- Cloudflare bypass via `cf_clearance` cookie + matching User-Agent
|
|
62
|
+
- stake.us works with access token only — no Cloudflare cookie required
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install stakeapi-codestats
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import asyncio
|
|
74
|
+
from stakeapi import StakeAPI
|
|
75
|
+
|
|
76
|
+
async def main():
|
|
77
|
+
# stake.us — access token only
|
|
78
|
+
async with StakeAPI(
|
|
79
|
+
access_token="your_token",
|
|
80
|
+
base_url="https://stake.us",
|
|
81
|
+
) as client:
|
|
82
|
+
balance = await client.get_user_balance()
|
|
83
|
+
print(balance)
|
|
84
|
+
|
|
85
|
+
asyncio.run(main())
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### stake.com (requires Cloudflare clearance)
|
|
89
|
+
|
|
90
|
+
stake.com blocks requests without a valid `cf_clearance` cookie. Get it from your browser's DevTools (Application → Cookies → `cf_clearance`) or extract it with Playwright:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
async with StakeAPI(
|
|
94
|
+
access_token="your_token",
|
|
95
|
+
cf_clearance="your_cf_clearance_cookie",
|
|
96
|
+
user_agent="Mozilla/5.0 ... Chrome/147.0.0.0 ...", # must match cookie
|
|
97
|
+
base_url="https://stake.com",
|
|
98
|
+
) as client:
|
|
99
|
+
balance = await client.get_user_balance()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Getting Your Access Token
|
|
103
|
+
|
|
104
|
+
1. Log in to stake.com in your browser
|
|
105
|
+
2. Open DevTools (F12) → Network tab
|
|
106
|
+
3. Make any action that triggers a request to `/_api/graphql`
|
|
107
|
+
4. Find the `x-access-token` request header — that's your token
|
|
108
|
+
|
|
109
|
+
## API Methods
|
|
110
|
+
|
|
111
|
+
### User
|
|
112
|
+
|
|
113
|
+
| Method | Description |
|
|
114
|
+
| ------------------------------------------- | --------------------------------------- |
|
|
115
|
+
| `get_user_balance()` | Available + vault balances per currency |
|
|
116
|
+
| `get_user_profile()` | Name, email, verification status |
|
|
117
|
+
| `get_user_meta(name=None)` | Lightweight user info with balances |
|
|
118
|
+
| `get_user_meta_extended(name, signup_code)` | Extended info including self-exclude |
|
|
119
|
+
| `get_user_account_info()` | Name, email, createdAt |
|
|
120
|
+
| `get_user_kyc_status()` | KYC status (stake.com only) |
|
|
121
|
+
| `get_user_sessions()` | Active sessions with IP and location |
|
|
122
|
+
| `get_user_api_keys()` | API key list |
|
|
123
|
+
| `get_user_statistic()` | Per-currency wagering stats |
|
|
124
|
+
| `get_user_seed_pair()` | Provably fair client/server seeds |
|
|
125
|
+
| `is_user_tfa_enabled()` | 2FA status |
|
|
126
|
+
| `get_user_preferences()` | User preferences object |
|
|
127
|
+
| `get_user_recent_games(limit)` | Recently played games |
|
|
128
|
+
|
|
129
|
+
### VIP / Reload / Faucet
|
|
130
|
+
|
|
131
|
+
| Method | Description |
|
|
132
|
+
| ----------------------- | ------------------------------------- |
|
|
133
|
+
| `get_vip_meta()` | Balances + reload status combined |
|
|
134
|
+
| `get_faucet()` | Reload/faucet status |
|
|
135
|
+
| `get_active_rakeback()` | Rakeback info (permission-restricted) |
|
|
136
|
+
| `get_tip_list(limit)` | User tip history |
|
|
137
|
+
|
|
138
|
+
### Currency / Config
|
|
139
|
+
|
|
140
|
+
| Method | Description |
|
|
141
|
+
| ------------------------------------------ | ------------------------------------------------- |
|
|
142
|
+
| `get_currency_configuration(is_acp)` | Currency rates; `is_acp=True` for stake.us |
|
|
143
|
+
| `get_conversion_rates(display_currencies)` | Fiat conversion rates (lowercase enum: `["usd"]`) |
|
|
144
|
+
|
|
145
|
+
### Bonuses / Promos
|
|
146
|
+
|
|
147
|
+
| Method | Description |
|
|
148
|
+
| ------------------------------------- | ----------------------- |
|
|
149
|
+
| `check_bonus_code(code, coupon_type)` | Check code availability |
|
|
150
|
+
| `get_racing_list()` | Race/campaign list |
|
|
151
|
+
| `get_campaign_balances()` | User campaign balances |
|
|
152
|
+
|
|
153
|
+
### Transactions / History
|
|
154
|
+
|
|
155
|
+
| Method | Description |
|
|
156
|
+
| ---------------------------------------- | -------------------------------------------------- |
|
|
157
|
+
| `get_transactions(offset, limit, types)` | Transaction history with optional type filter |
|
|
158
|
+
| `get_deposits(offset, limit)` | Deposit history |
|
|
159
|
+
| `get_withdrawals(offset, limit)` | Withdrawal history |
|
|
160
|
+
| `get_my_bets(limit)` | Chat list (direct bet history unavailable via API) |
|
|
161
|
+
|
|
162
|
+
### Casino / Games
|
|
163
|
+
|
|
164
|
+
| Method | Description |
|
|
165
|
+
| ----------------------------------------- | --------------------------------------------- |
|
|
166
|
+
| `get_blackjack_active_bet()` | Active BJ bet or null |
|
|
167
|
+
| `get_kurator_collection(collection_type)` | Game collection by enum type |
|
|
168
|
+
| `get_kurator_group(slug)` | Game group by slug (e.g. `"stake-originals"`) |
|
|
169
|
+
|
|
170
|
+
### Sports (stake.com only)
|
|
171
|
+
|
|
172
|
+
| Method | Description |
|
|
173
|
+
| ----------------------- | -------------------------------------- |
|
|
174
|
+
| `get_sport_list_menu()` | Sport list (region-locked on stake.us) |
|
|
175
|
+
|
|
176
|
+
### Social / Misc
|
|
177
|
+
|
|
178
|
+
| Method | Description |
|
|
179
|
+
| ---------------------------------- | --------------------------------------- |
|
|
180
|
+
| `get_active_races()` | Active races |
|
|
181
|
+
| `get_notifications(offset, limit)` | Notification list |
|
|
182
|
+
| `get_public_chats()` | Public chat entries |
|
|
183
|
+
| `get_banned_countries()` | Banned countries (CSV in `value` field) |
|
|
184
|
+
| `get_player_count()` | Player count by scope |
|
|
185
|
+
| `get_feature_flags()` | Feature flag list |
|
|
186
|
+
|
|
187
|
+
### Mutations
|
|
188
|
+
|
|
189
|
+
| Method | Description |
|
|
190
|
+
| --------------------------------------------------- | -------------------------- |
|
|
191
|
+
| `claim_bonus_code(code, currency, turnstile_token)` | Claim a bonus code |
|
|
192
|
+
| `claim_faucet(currency, turnstile_token)` | Claim reload bonus |
|
|
193
|
+
| `claim_rakeback()` | Claim rakeback |
|
|
194
|
+
| `create_vault_deposit(currency, amount)` | Deposit to vault |
|
|
195
|
+
| `rotate_seed_pair(seed)` | Rotate provably fair seeds |
|
|
196
|
+
| `blackjack_bet(amount, currency, identifier)` | Place BJ bet |
|
|
197
|
+
| `blackjack_next(action, identifier)` | Hit/stand/double |
|
|
198
|
+
|
|
199
|
+
> **Note:** `claim_bonus_code` and `claim_faucet` require a Cloudflare Turnstile token (sitekey `0x4AAAAAAAGD4gMGOTFnvupz`), which must be generated in a browser context.
|
|
200
|
+
|
|
201
|
+
## Custom GraphQL Queries
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
async with StakeAPI(access_token="token", base_url="https://stake.us") as client:
|
|
205
|
+
data = await client._graphql_request(
|
|
206
|
+
query="""
|
|
207
|
+
query UserSeedPair {
|
|
208
|
+
user {
|
|
209
|
+
id
|
|
210
|
+
activeClientSeed { seed }
|
|
211
|
+
activeServerSeed { seedHash nonce }
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
""",
|
|
215
|
+
operation_name="UserSeedPair",
|
|
216
|
+
)
|
|
217
|
+
print(data)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Development
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
make install-dev # install with dev deps
|
|
224
|
+
make format # black + isort
|
|
225
|
+
make check # lint + typecheck + tests
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
MIT
|