ddapi 0.7.2__tar.gz → 0.8.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ddapi-0.7.2 → ddapi-0.8.1}/PKG-INFO +9 -4
- {ddapi-0.7.2 → ddapi-0.8.1}/README.md +8 -3
- {ddapi-0.7.2 → ddapi-0.8.1}/ddapi/ddapi/__init__.py +2 -2
- ddapi-0.7.2/ddapi/ddapi/deflt.py → ddapi-0.8.1/ddapi/ddapi/api.py +19 -17
- ddapi-0.8.1/ddapi/ddapi/main.py +173 -0
- ddapi-0.8.1/ddapi/ddapi/scheme/__init__.py +3 -0
- ddapi-0.8.1/ddapi/ddapi/scheme/ddnet.py +132 -0
- ddapi-0.7.2/ddapi/ddapi/dataclass.py → ddapi-0.8.1/ddapi/ddapi/scheme/ddstats_tw.py +29 -195
- ddapi-0.8.1/ddapi/ddapi/scheme/status_tw.py +84 -0
- {ddapi-0.7.2 → ddapi-0.8.1}/ddapi/ddapi.egg-info/PKG-INFO +9 -4
- {ddapi-0.7.2 → ddapi-0.8.1}/ddapi/ddapi.egg-info/SOURCES.txt +6 -3
- {ddapi-0.7.2 → ddapi-0.8.1}/setup.cfg +1 -1
- {ddapi-0.7.2 → ddapi-0.8.1}/setup.py +2 -2
- ddapi-0.7.2/ddapi/ddapi/main.py +0 -88
- {ddapi-0.7.2 → ddapi-0.8.1}/LICENSE +0 -0
- {ddapi-0.7.2 → ddapi-0.8.1}/ddapi/ddapi.egg-info/dependency_links.txt +0 -0
- {ddapi-0.7.2 → ddapi-0.8.1}/ddapi/ddapi.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: ddapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: DDnet api
|
|
5
5
|
Home-page: https://github.com/ByFox213/ddapi
|
|
6
6
|
Author: ByFox
|
|
@@ -21,19 +21,23 @@ Dynamic: requires-python
|
|
|
21
21
|
Dynamic: summary
|
|
22
22
|
|
|
23
23
|
ddapi is an api from the ddnet and qwik website
|
|
24
|
+
|
|
24
25
|
## Installation
|
|
25
26
|
|
|
26
27
|
```bash
|
|
27
28
|
pip install ddapi
|
|
28
29
|
```
|
|
30
|
+
|
|
29
31
|
or
|
|
32
|
+
|
|
30
33
|
```bash
|
|
31
34
|
pip install git+https://github.com/ByFox213/ddapi
|
|
32
35
|
```
|
|
33
|
-
|
|
36
|
+
|
|
34
37
|
## Usage/Examples
|
|
35
38
|
|
|
36
39
|
DDnet
|
|
40
|
+
|
|
37
41
|
```python
|
|
38
42
|
import asyncio
|
|
39
43
|
from ddapi import DDnetApi, DDPlayer
|
|
@@ -45,6 +49,7 @@ async def main():
|
|
|
45
49
|
user: DDPlayer = await obj.player(nickname)
|
|
46
50
|
if user is None:
|
|
47
51
|
return "Player not found"
|
|
52
|
+
|
|
48
53
|
print(f"{user.player}: {user.points.points}")
|
|
49
54
|
# Cor: 31473
|
|
50
55
|
await obj.close() # Closing client Not necessary
|
|
@@ -53,10 +58,10 @@ async def main():
|
|
|
53
58
|
|
|
54
59
|
asyncio.run(main())
|
|
55
60
|
```
|
|
61
|
+
|
|
56
62
|
Tested on Python
|
|
57
63
|
| 3.9 | 3.10 | 3.11 | 3.12 |
|
|
58
64
|
|-----|------|------|------|
|
|
59
|
-
| ?
|
|
60
|
-
|
|
65
|
+
| ? | ? | ✅ | ✅ |
|
|
61
66
|
|
|
62
67
|
[other examples](https://github.com/ByFox213/ddapi/tree/main/example)
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
ddapi is an api from the ddnet and qwik website
|
|
2
|
+
|
|
2
3
|
## Installation
|
|
3
4
|
|
|
4
5
|
```bash
|
|
5
6
|
pip install ddapi
|
|
6
7
|
```
|
|
8
|
+
|
|
7
9
|
or
|
|
10
|
+
|
|
8
11
|
```bash
|
|
9
12
|
pip install git+https://github.com/ByFox213/ddapi
|
|
10
13
|
```
|
|
11
|
-
|
|
14
|
+
|
|
12
15
|
## Usage/Examples
|
|
13
16
|
|
|
14
17
|
DDnet
|
|
18
|
+
|
|
15
19
|
```python
|
|
16
20
|
import asyncio
|
|
17
21
|
from ddapi import DDnetApi, DDPlayer
|
|
@@ -23,6 +27,7 @@ async def main():
|
|
|
23
27
|
user: DDPlayer = await obj.player(nickname)
|
|
24
28
|
if user is None:
|
|
25
29
|
return "Player not found"
|
|
30
|
+
|
|
26
31
|
print(f"{user.player}: {user.points.points}")
|
|
27
32
|
# Cor: 31473
|
|
28
33
|
await obj.close() # Closing client Not necessary
|
|
@@ -31,10 +36,10 @@ async def main():
|
|
|
31
36
|
|
|
32
37
|
asyncio.run(main())
|
|
33
38
|
```
|
|
39
|
+
|
|
34
40
|
Tested on Python
|
|
35
41
|
| 3.9 | 3.10 | 3.11 | 3.12 |
|
|
36
42
|
|-----|------|------|------|
|
|
37
|
-
| ?
|
|
38
|
-
|
|
43
|
+
| ? | ? | ✅ | ✅ |
|
|
39
44
|
|
|
40
45
|
[other examples](https://github.com/ByFox213/ddapi/tree/main/example)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""the module is made for the convenience of users to download and use"""
|
|
2
2
|
from .main import DDnetApi, DDstats, Status
|
|
3
|
-
from .
|
|
3
|
+
from .scheme import *
|
|
4
4
|
|
|
5
|
-
__version__ = "0.
|
|
5
|
+
__version__ = "0.8.1"
|
|
6
6
|
__author__ = 'ByFox'
|
|
7
7
|
__LICENSE__ = "MIT"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional, TypeVar, Type, Any
|
|
3
3
|
|
|
4
4
|
import aiohttp
|
|
5
5
|
from aiohttp import ClientSession, ClientConnectorError
|
|
@@ -14,23 +14,21 @@ class API(ABC):
|
|
|
14
14
|
session: ClientSession = None,
|
|
15
15
|
json_loads: Any = DEFAULT_JSON_DECODER
|
|
16
16
|
):
|
|
17
|
-
self.
|
|
17
|
+
self.__session = session
|
|
18
18
|
self.json_loads = json_loads
|
|
19
19
|
|
|
20
20
|
@staticmethod
|
|
21
|
-
def powered() ->
|
|
22
|
-
|
|
23
|
-
return ''
|
|
21
|
+
def powered() -> None:
|
|
22
|
+
return None
|
|
24
23
|
|
|
25
|
-
async def _get(self, url: str) ->
|
|
24
|
+
async def _get(self, url: str) -> Optional[dict]:
|
|
26
25
|
"""Send a GET request to the given URL and return the response as JSON."""
|
|
27
|
-
if self.
|
|
28
|
-
self.
|
|
26
|
+
if self.__session is None:
|
|
27
|
+
self.__session = aiohttp.ClientSession()
|
|
28
|
+
|
|
29
29
|
try:
|
|
30
|
-
async with self.
|
|
31
|
-
if req.status == 200
|
|
32
|
-
return await req.json(loads=self.json_loads)
|
|
33
|
-
return
|
|
30
|
+
async with self.__session.get(url) as req:
|
|
31
|
+
return await req.json(loads=self.json_loads) if req.status == 200 else None
|
|
34
32
|
except ClientConnectorError:
|
|
35
33
|
return
|
|
36
34
|
|
|
@@ -38,7 +36,7 @@ class API(ABC):
|
|
|
38
36
|
self,
|
|
39
37
|
url: str,
|
|
40
38
|
model: Type[ModelType],
|
|
41
|
-
k: str = None
|
|
39
|
+
k: Optional[str] = None
|
|
42
40
|
) -> Optional[ModelType]:
|
|
43
41
|
"""Generate a model instance from the given URL and optional keyword arguments.
|
|
44
42
|
|
|
@@ -54,11 +52,15 @@ class API(ABC):
|
|
|
54
52
|
if dat is None or not dat:
|
|
55
53
|
return
|
|
56
54
|
|
|
57
|
-
data_to_pass = {k: dat} if k else dat
|
|
55
|
+
data_to_pass = {k: dat} if k is not None else dat
|
|
58
56
|
return model(**data_to_pass)
|
|
59
57
|
|
|
58
|
+
async def is_closed(self) -> bool:
|
|
59
|
+
"""True is closed, False otherwise."""
|
|
60
|
+
return self.__session is None or self.__session.closed
|
|
61
|
+
|
|
60
62
|
async def close(self) -> None:
|
|
61
63
|
"""Close the aiohttp session."""
|
|
62
|
-
if self.
|
|
63
|
-
await self.
|
|
64
|
-
|
|
64
|
+
if self.__session is not None:
|
|
65
|
+
await self.__session.close()
|
|
66
|
+
self.__session = None
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
from urllib.parse import quote
|
|
3
|
+
|
|
4
|
+
from .api import API
|
|
5
|
+
from .scheme import DDPlayer, Master, Query, DMap, DDStatus, Player, MasterTw, ServerTwOne, ChartEnum, Charts
|
|
6
|
+
|
|
7
|
+
__all__ = (
|
|
8
|
+
"DDnetApi",
|
|
9
|
+
"DDstats",
|
|
10
|
+
"Status"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DDnetApi(API):
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.domain = "ddnet.org"
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def powered() -> str:
|
|
21
|
+
return DDnetApi().domain
|
|
22
|
+
|
|
23
|
+
async def player(self, player_name: str) -> DDPlayer:
|
|
24
|
+
"""Fetch player data from the API and return it as a dictionary."""
|
|
25
|
+
return await self._generate_model_instance(
|
|
26
|
+
f"https://{self.domain}/players/?json2={quote(player_name)}",
|
|
27
|
+
DDPlayer
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
async def status(self) -> Union[DDStatus, None]:
|
|
31
|
+
return await self._generate_model_instance(
|
|
32
|
+
f"https://{self.domain}/status/json/stats.json",
|
|
33
|
+
DDStatus
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
async def query(self, player: str) -> Union[Query, None]:
|
|
37
|
+
return await self._generate_model_instance(
|
|
38
|
+
f"https://{self.domain}/players/?query={quote(player)}",
|
|
39
|
+
Query,
|
|
40
|
+
"data"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
async def master(self) -> Union[Master, None]:
|
|
44
|
+
return await self._generate_model_instance(
|
|
45
|
+
f"https://master1.{self.domain}/ddnet/15/servers.json",
|
|
46
|
+
Master
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def map(self, map_name: str) -> Union[DMap, None]:
|
|
50
|
+
return await self._generate_model_instance(
|
|
51
|
+
f"https://{self.domain}/maps/?json={quote(map_name)}",
|
|
52
|
+
DMap
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class DDstats(API):
|
|
57
|
+
def __init__(self):
|
|
58
|
+
super().__init__()
|
|
59
|
+
self.domain = 'ddstats.tw'
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def powered() -> str:
|
|
63
|
+
return DDstats().domain
|
|
64
|
+
|
|
65
|
+
async def player(self, player_name: str) -> Player:
|
|
66
|
+
"""Fetch player data from the API and return it as a dictionary."""
|
|
67
|
+
return await self._generate_model_instance(
|
|
68
|
+
f"https://{self.domain}/player/json?player={quote(player_name)}",
|
|
69
|
+
Player
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Status(API):
|
|
74
|
+
def __init__(self):
|
|
75
|
+
super().__init__()
|
|
76
|
+
self.domain = 'status.tw'
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def powered() -> str:
|
|
80
|
+
return Status().domain
|
|
81
|
+
|
|
82
|
+
async def info(self) -> None:
|
|
83
|
+
# https://api.status.tw/info
|
|
84
|
+
raise NotImplementedError()
|
|
85
|
+
|
|
86
|
+
async def master_bans(self) -> None:
|
|
87
|
+
# https://api.status.tw/master/bans/list
|
|
88
|
+
raise NotImplementedError()
|
|
89
|
+
|
|
90
|
+
async def map_list(self) -> None:
|
|
91
|
+
# https://api.status.tw/map/list
|
|
92
|
+
raise NotImplementedError()
|
|
93
|
+
|
|
94
|
+
async def map(self, name: str) -> None:
|
|
95
|
+
# https://api.status.tw/map/name/{name}
|
|
96
|
+
raise NotImplementedError()
|
|
97
|
+
|
|
98
|
+
async def gametype_list(self) -> None:
|
|
99
|
+
# https://api.status.tw/gametype/list
|
|
100
|
+
raise NotImplementedError()
|
|
101
|
+
|
|
102
|
+
async def gametype(self, name: str) -> None:
|
|
103
|
+
# https://api.status.tw/gametype/name/{name}
|
|
104
|
+
raise NotImplementedError()
|
|
105
|
+
|
|
106
|
+
async def version_list(self) -> None:
|
|
107
|
+
# https://api.status.tw/version/list
|
|
108
|
+
raise NotImplementedError()
|
|
109
|
+
|
|
110
|
+
async def version(self, name: str) -> None:
|
|
111
|
+
# https://api.status.tw/version/name/{name}
|
|
112
|
+
raise NotImplementedError()
|
|
113
|
+
|
|
114
|
+
async def server_list(self) -> MasterTw:
|
|
115
|
+
"""Fetch player data from the API and return it as a dictionary."""
|
|
116
|
+
return await self._generate_model_instance(
|
|
117
|
+
f"https://api.{self.domain}/server/list",
|
|
118
|
+
MasterTw,
|
|
119
|
+
"servers"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
async def server(self, ip: str, port: Union[int, str]) -> ServerTwOne:
|
|
123
|
+
return await self._generate_model_instance(
|
|
124
|
+
f"https://api.{self.domain}/server/{quote(ip)}/{port}",
|
|
125
|
+
ServerTwOne,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
async def server_charts(self, ip: str, port: Union[int, str], chart: ChartEnum = ChartEnum.day) -> Charts:
|
|
129
|
+
return await self._generate_model_instance(
|
|
130
|
+
f"https://api.{self.domain}/server/{quote(ip)}/{port}/chart/{chart}",
|
|
131
|
+
Charts,
|
|
132
|
+
"charts"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def server_history(self, ip: str, port: Union[int, str]) -> None:
|
|
136
|
+
# https://api.status.tw/server/{ip}/{port}/history
|
|
137
|
+
raise NotImplementedError()
|
|
138
|
+
|
|
139
|
+
async def server_stats(self, ip: str, port: Union[int, str]) -> None:
|
|
140
|
+
# https://api.status.tw/server/{ip}/{port}/stats
|
|
141
|
+
raise NotImplementedError()
|
|
142
|
+
|
|
143
|
+
async def clan_list(self) -> None:
|
|
144
|
+
# https://api.status.tw/clan/list
|
|
145
|
+
raise NotImplementedError()
|
|
146
|
+
|
|
147
|
+
async def clan(self, name: str) -> None:
|
|
148
|
+
# https://api.status.tw/clan/name/{name}
|
|
149
|
+
raise NotImplementedError()
|
|
150
|
+
|
|
151
|
+
async def player_list(self) -> None:
|
|
152
|
+
# https://api.status.tw/player/list
|
|
153
|
+
raise NotImplementedError()
|
|
154
|
+
|
|
155
|
+
async def player(self, name: str) -> None:
|
|
156
|
+
# https://api.status.tw/player/name/{name}
|
|
157
|
+
raise NotImplementedError()
|
|
158
|
+
|
|
159
|
+
async def player_history(self, name: str) -> None:
|
|
160
|
+
# https://api.status.tw/player/name/{name}/history
|
|
161
|
+
raise NotImplementedError()
|
|
162
|
+
|
|
163
|
+
async def stats(self) -> None:
|
|
164
|
+
# https://api.status.tw/stats
|
|
165
|
+
raise NotImplementedError()
|
|
166
|
+
|
|
167
|
+
async def stats_players(self, t: str) -> None:
|
|
168
|
+
# https://api.status.tw/stats/players/day week month
|
|
169
|
+
raise NotImplementedError()
|
|
170
|
+
|
|
171
|
+
async def stats_servers(self, t: str) -> None:
|
|
172
|
+
# https://api.status.tw/stats/servers/week month
|
|
173
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# ddnet
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DPoints(BaseModel):
|
|
8
|
+
total: int
|
|
9
|
+
points: Optional[int] = Field(default=None)
|
|
10
|
+
rank: Optional[int] = Field(default=None)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MaxFinishes(BaseModel):
|
|
14
|
+
rank: int
|
|
15
|
+
player: str
|
|
16
|
+
num: int
|
|
17
|
+
time: float
|
|
18
|
+
min_timestamp: int
|
|
19
|
+
max_timestamp: int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DDRanks(BaseModel):
|
|
23
|
+
rank: int
|
|
24
|
+
player: str
|
|
25
|
+
time: float
|
|
26
|
+
timestamp: int
|
|
27
|
+
country: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DDTeamRanks(BaseModel):
|
|
31
|
+
rank: int
|
|
32
|
+
players: list[str]
|
|
33
|
+
time: float
|
|
34
|
+
timestamp: int
|
|
35
|
+
country: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DMap(BaseModel):
|
|
39
|
+
name: str
|
|
40
|
+
website: str
|
|
41
|
+
thumbnail: str
|
|
42
|
+
web_preview: str
|
|
43
|
+
type: str
|
|
44
|
+
points: int
|
|
45
|
+
difficulty: int
|
|
46
|
+
mapper: str
|
|
47
|
+
release: Optional[int] = None
|
|
48
|
+
median_time: float
|
|
49
|
+
first_finish: int
|
|
50
|
+
last_finish: int
|
|
51
|
+
finishes: int
|
|
52
|
+
finishers: int
|
|
53
|
+
biggest_team: int
|
|
54
|
+
width: int
|
|
55
|
+
height: int
|
|
56
|
+
tiles: list[str]
|
|
57
|
+
team_ranks: list[DDTeamRanks]
|
|
58
|
+
ranks: list[DDRanks]
|
|
59
|
+
max_finishes: list[MaxFinishes]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class DRank(BaseModel):
|
|
63
|
+
points: Optional[int] = None
|
|
64
|
+
rank: Optional[int] = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class FirstFinish(BaseModel):
|
|
68
|
+
timestamp: float
|
|
69
|
+
map: str
|
|
70
|
+
time: float
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class LastFinish(BaseModel):
|
|
74
|
+
timestamp: float
|
|
75
|
+
map: str
|
|
76
|
+
time: float
|
|
77
|
+
country: str
|
|
78
|
+
type: Optional[str] = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class FavoritePartner(BaseModel):
|
|
82
|
+
name: str
|
|
83
|
+
finishes: int
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class DData(BaseModel):
|
|
87
|
+
points: Optional[DPoints] = Field(default=None)
|
|
88
|
+
team_rank: Optional[DRank] = Field(default=None)
|
|
89
|
+
rank: Optional[DRank] = Field(default=None)
|
|
90
|
+
maps: Optional[dict] = Field(default=None)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class DDType(BaseModel):
|
|
94
|
+
Novice: Optional[DData] = None
|
|
95
|
+
Moderate: Optional[DData] = None
|
|
96
|
+
Brutal: Optional[DData] = None
|
|
97
|
+
Insane: Optional[DData] = None
|
|
98
|
+
Dummy: Optional[DData] = None
|
|
99
|
+
DDmaX_Easy: Optional[DData] = Field(default=None, validation_alias='DDmaX.Easy')
|
|
100
|
+
DDmaX_Next: Optional[DData] = Field(default=None, validation_alias='DDmaX.Next')
|
|
101
|
+
DDmaX_Nut: Optional[DData] = Field(default=None, validation_alias='DDmaX.Nut')
|
|
102
|
+
DDmaX_Pro: Optional[DData] = Field(default=None, validation_alias='DDmaX.Pro')
|
|
103
|
+
Oldschool: Optional[DData] = Field(default=None)
|
|
104
|
+
Race: Optional[DData] = None
|
|
105
|
+
Solo: Optional[DData] = None
|
|
106
|
+
total: Optional[DData] = None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class Activity(BaseModel):
|
|
110
|
+
date: str
|
|
111
|
+
hours_played: int
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class FavoriteServer(BaseModel):
|
|
115
|
+
server: str
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class DDPlayer(BaseModel):
|
|
119
|
+
player: str
|
|
120
|
+
points: DPoints
|
|
121
|
+
team_rank: Optional[DRank] = None
|
|
122
|
+
rank: Optional[DRank] = None
|
|
123
|
+
points_last_year: Optional[DRank] = None
|
|
124
|
+
points_last_month: Optional[DRank] = None
|
|
125
|
+
points_last_week: Optional[DRank] = None
|
|
126
|
+
favorite_server: FavoriteServer
|
|
127
|
+
first_finish: FirstFinish
|
|
128
|
+
last_finishes: list[LastFinish]
|
|
129
|
+
favorite_partners: Optional[list[FavoritePartner]] = None
|
|
130
|
+
types: DDType
|
|
131
|
+
activity: list[Activity]
|
|
132
|
+
hours_played_past_365_days: int
|
|
@@ -1,143 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ddstats.tw
|
|
2
2
|
from collections import Counter
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any, Union, Optional
|
|
5
|
+
|
|
5
6
|
from pydantic import BaseModel, Field
|
|
6
7
|
|
|
7
8
|
rm_list = ["DD-Persian", "/vDQMHSss8W", '']
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
# ddnet
|
|
11
|
-
class DPoints(BaseModel):
|
|
12
|
-
total: int
|
|
13
|
-
points: Optional[int] = Field(default=None)
|
|
14
|
-
rank: Optional[int] = Field(default=None)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class MaxFinishes(BaseModel):
|
|
18
|
-
rank: int
|
|
19
|
-
player: str
|
|
20
|
-
num: int
|
|
21
|
-
time: float
|
|
22
|
-
min_timestamp: int
|
|
23
|
-
max_timestamp: int
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class DDRanks(BaseModel):
|
|
27
|
-
rank: int
|
|
28
|
-
player: str
|
|
29
|
-
time: float
|
|
30
|
-
timestamp: int
|
|
31
|
-
country: str
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class DDTeamRanks(BaseModel):
|
|
35
|
-
rank: int
|
|
36
|
-
players: list[str]
|
|
37
|
-
time: float
|
|
38
|
-
timestamp: int
|
|
39
|
-
country: str
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class DMap(BaseModel):
|
|
43
|
-
name: str
|
|
44
|
-
website: str
|
|
45
|
-
thumbnail: str
|
|
46
|
-
web_preview: str
|
|
47
|
-
type: str
|
|
48
|
-
points: int
|
|
49
|
-
difficulty: int
|
|
50
|
-
mapper: str
|
|
51
|
-
release: Optional[int] = None
|
|
52
|
-
median_time: float
|
|
53
|
-
first_finish: int
|
|
54
|
-
last_finish: int
|
|
55
|
-
finishes: int
|
|
56
|
-
finishers: int
|
|
57
|
-
biggest_team: int
|
|
58
|
-
width: int
|
|
59
|
-
height: int
|
|
60
|
-
tiles: list[str]
|
|
61
|
-
team_ranks: list[DDTeamRanks]
|
|
62
|
-
ranks: list[DDRanks]
|
|
63
|
-
max_finishes: list[MaxFinishes]
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class DRank(BaseModel):
|
|
67
|
-
points: Optional[int] = None
|
|
68
|
-
rank: Optional[int] = None
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class FirstFinish(BaseModel):
|
|
72
|
-
timestamp: float
|
|
73
|
-
map: str
|
|
74
|
-
time: float
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class LastFinish(BaseModel):
|
|
78
|
-
timestamp: float
|
|
79
|
-
map: str
|
|
80
|
-
time: float
|
|
81
|
-
country: str
|
|
82
|
-
type: Optional[str] = None
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class FavoritePartner(BaseModel):
|
|
86
|
-
name: str
|
|
87
|
-
finishes: int
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class DData(BaseModel):
|
|
91
|
-
points: Optional[DPoints] = Field(default=None)
|
|
92
|
-
team_rank: Optional[DRank] = Field(default=None)
|
|
93
|
-
rank: Optional[DRank] = Field(default=None)
|
|
94
|
-
maps: Optional[dict] = Field(default=None)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
class DDType(BaseModel):
|
|
98
|
-
Novice: Optional[DData] = None
|
|
99
|
-
Moderate: Optional[DData] = None
|
|
100
|
-
Brutal: Optional[DData] = None
|
|
101
|
-
Insane: Optional[DData] = None
|
|
102
|
-
Dummy: Optional[DData] = None
|
|
103
|
-
DDmaX_Easy: Optional[DData] = Field(default=None, validation_alias='DDmaX.Easy')
|
|
104
|
-
DDmaX_Next: Optional[DData] = Field(default=None, validation_alias='DDmaX.Next')
|
|
105
|
-
DDmaX_Nut: Optional[DData] = Field(default=None, validation_alias='DDmaX.Nut')
|
|
106
|
-
DDmaX_Pro: Optional[DData] = Field(default=None, validation_alias='DDmaX.Pro')
|
|
107
|
-
Oldschool: Optional[DData] = Field(default=None)
|
|
108
|
-
Race: Optional[DData] = None
|
|
109
|
-
Solo: Optional[DData] = None
|
|
110
|
-
total: Optional[DData] = None
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
class Activity(BaseModel):
|
|
114
|
-
date: str
|
|
115
|
-
hours_played: int
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
class FavoriteServer(BaseModel):
|
|
119
|
-
server: str
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
class DDPlayer(BaseModel):
|
|
123
|
-
player: str
|
|
124
|
-
points: DPoints
|
|
125
|
-
team_rank: Optional[DRank] = None
|
|
126
|
-
rank: Optional[DRank] = None
|
|
127
|
-
points_last_year: Optional[DRank] = None
|
|
128
|
-
points_last_month: Optional[DRank] = None
|
|
129
|
-
points_last_week: Optional[DRank] = None
|
|
130
|
-
favorite_server: FavoriteServer
|
|
131
|
-
first_finish: FirstFinish
|
|
132
|
-
last_finishes: list[LastFinish]
|
|
133
|
-
favorite_partners: Optional[list[FavoritePartner]] = None
|
|
134
|
-
types: DDType
|
|
135
|
-
activity: list[Activity]
|
|
136
|
-
hours_played_past_365_days: int
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# ddstats.tw
|
|
140
|
-
|
|
141
11
|
class PData(BaseModel):
|
|
142
12
|
rank: int
|
|
143
13
|
points: int
|
|
@@ -169,9 +39,9 @@ class PointG(BaseModel):
|
|
|
169
39
|
class Profile(BaseModel):
|
|
170
40
|
name: str
|
|
171
41
|
points: int
|
|
172
|
-
clan: str
|
|
173
|
-
country: int
|
|
174
|
-
skin_name: str
|
|
42
|
+
clan: Optional[str]
|
|
43
|
+
country: Optional[int]
|
|
44
|
+
skin_name: Optional[str]
|
|
175
45
|
skin_color_body: Optional[int] = None
|
|
176
46
|
skin_color_feet: Optional[int] = None
|
|
177
47
|
|
|
@@ -391,6 +261,13 @@ class Server(BaseModel):
|
|
|
391
261
|
return 0
|
|
392
262
|
|
|
393
263
|
|
|
264
|
+
class CountServers(BaseModel):
|
|
265
|
+
name: str
|
|
266
|
+
address: str
|
|
267
|
+
game_type: str
|
|
268
|
+
count_clients: int
|
|
269
|
+
|
|
270
|
+
|
|
394
271
|
class Master(BaseModel):
|
|
395
272
|
servers: list[Server]
|
|
396
273
|
|
|
@@ -401,25 +278,37 @@ class Master(BaseModel):
|
|
|
401
278
|
remove_list = rm_list.copy() if rm is None else rm
|
|
402
279
|
if not self.servers:
|
|
403
280
|
return []
|
|
281
|
+
|
|
404
282
|
dat: Counter[Any] = Counter(
|
|
405
283
|
client.clan
|
|
406
284
|
for server in self.servers
|
|
407
285
|
for client in server.info.clients
|
|
408
286
|
if client != ''
|
|
409
287
|
)
|
|
288
|
+
|
|
410
289
|
for i in remove_list:
|
|
411
290
|
del dat[i]
|
|
412
291
|
return sorted(dat.items(), key=lambda x: x[1], reverse=True)
|
|
413
292
|
|
|
414
293
|
@property
|
|
415
|
-
def count_servers(self) -> list:
|
|
294
|
+
def count_servers(self) -> list[CountServers]:
|
|
416
295
|
return sorted(
|
|
417
296
|
(
|
|
418
|
-
(
|
|
297
|
+
CountServers(
|
|
298
|
+
count_clients=i.count_client,
|
|
299
|
+
address=i.addresses[0],
|
|
300
|
+
game_type=i.info.game_type,
|
|
301
|
+
name=i.info.name
|
|
302
|
+
)
|
|
419
303
|
if isinstance(i.addresses, list)
|
|
420
|
-
else (
|
|
304
|
+
else CountServers(
|
|
305
|
+
count_clients=i.count_client,
|
|
306
|
+
address=i.addresses,
|
|
307
|
+
game_type=i.info.game_type,
|
|
308
|
+
name=i.info.name
|
|
309
|
+
)
|
|
421
310
|
for i in self.servers
|
|
422
|
-
), key=lambda x: x
|
|
311
|
+
), key=lambda x: x.count_clients, reverse=True)
|
|
423
312
|
|
|
424
313
|
@property
|
|
425
314
|
def count_clients(self) -> int:
|
|
@@ -459,58 +348,3 @@ class DDStatusData(BaseModel):
|
|
|
459
348
|
|
|
460
349
|
class DDStatus(BaseModel):
|
|
461
350
|
servers: list[DDStatusData]
|
|
462
|
-
|
|
463
|
-
# Status.tw
|
|
464
|
-
|
|
465
|
-
class DataTw(BaseModel):
|
|
466
|
-
name: str
|
|
467
|
-
createdAt: datetime
|
|
468
|
-
|
|
469
|
-
class VersionTw(BaseModel):
|
|
470
|
-
version: str
|
|
471
|
-
createdAt: datetime
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
class CountryTw(BaseModel):
|
|
475
|
-
identifier: str
|
|
476
|
-
code: int
|
|
477
|
-
iconUrl: str
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
class ClientTw(BaseModel):
|
|
481
|
-
name: str
|
|
482
|
-
country: CountryTw
|
|
483
|
-
score: int
|
|
484
|
-
isPlayer: bool
|
|
485
|
-
isAfk: bool
|
|
486
|
-
team: int
|
|
487
|
-
createdAt: datetime
|
|
488
|
-
clan: DataTw | None
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
class ServersTw(BaseModel):
|
|
492
|
-
ip: str
|
|
493
|
-
port: int
|
|
494
|
-
name: str
|
|
495
|
-
numClients: int
|
|
496
|
-
numPlayers: int
|
|
497
|
-
maxClients: int
|
|
498
|
-
maxPlayers: int
|
|
499
|
-
hasPassword: bool
|
|
500
|
-
supports6: bool
|
|
501
|
-
supports7: bool
|
|
502
|
-
createdAt: datetime
|
|
503
|
-
relevanceScore: int
|
|
504
|
-
clientScoreKind: str | None
|
|
505
|
-
logoUrl: str
|
|
506
|
-
map: DataTw
|
|
507
|
-
website: str | None = None
|
|
508
|
-
discordInvite: str | None = None
|
|
509
|
-
description: str | None = None
|
|
510
|
-
gameType: DataTw
|
|
511
|
-
version: VersionTw
|
|
512
|
-
clients: list[ClientTw]
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
class MasterTw(BaseModel):
|
|
516
|
-
servers: list[ServersTw]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Status.tw
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ChartEnum(StrEnum):
|
|
10
|
+
day = "day"
|
|
11
|
+
week = "week"
|
|
12
|
+
month = "month"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DataTw(BaseModel):
|
|
16
|
+
name: str
|
|
17
|
+
createdAt: datetime
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class VersionTw(BaseModel):
|
|
21
|
+
version: str
|
|
22
|
+
createdAt: datetime
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CountryTw(BaseModel):
|
|
26
|
+
identifier: str
|
|
27
|
+
code: int
|
|
28
|
+
iconUrl: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ClientTw(BaseModel):
|
|
32
|
+
name: str
|
|
33
|
+
country: CountryTw
|
|
34
|
+
score: int
|
|
35
|
+
isPlayer: bool
|
|
36
|
+
isAfk: bool
|
|
37
|
+
team: int
|
|
38
|
+
createdAt: datetime
|
|
39
|
+
clan: DataTw | None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ServerTw(BaseModel):
|
|
43
|
+
ip: str
|
|
44
|
+
port: int
|
|
45
|
+
name: str
|
|
46
|
+
numClients: int
|
|
47
|
+
numPlayers: int
|
|
48
|
+
maxClients: int
|
|
49
|
+
maxPlayers: int
|
|
50
|
+
hasPassword: bool
|
|
51
|
+
supports6: bool
|
|
52
|
+
supports7: bool
|
|
53
|
+
createdAt: datetime
|
|
54
|
+
relevanceScore: int
|
|
55
|
+
clientScoreKind: str | None
|
|
56
|
+
logoUrl: str
|
|
57
|
+
map: DataTw
|
|
58
|
+
website: Optional[str] = None
|
|
59
|
+
discordInvite: Optional[str] = None
|
|
60
|
+
description: Optional[str] = None
|
|
61
|
+
gameType: DataTw
|
|
62
|
+
version: VersionTw
|
|
63
|
+
clients: list[ClientTw]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ServerTwOne(BaseModel):
|
|
67
|
+
ip: str
|
|
68
|
+
port: int
|
|
69
|
+
online: ServerTw
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Chart(BaseModel):
|
|
73
|
+
avgPlayers: float
|
|
74
|
+
maxPlayers: int
|
|
75
|
+
minPlayers: int
|
|
76
|
+
timestamp: datetime
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Charts(BaseModel):
|
|
80
|
+
charts: list[Chart]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class MasterTw(BaseModel):
|
|
84
|
+
servers: list[ServerTw]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: ddapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: DDnet api
|
|
5
5
|
Home-page: https://github.com/ByFox213/ddapi
|
|
6
6
|
Author: ByFox
|
|
@@ -21,19 +21,23 @@ Dynamic: requires-python
|
|
|
21
21
|
Dynamic: summary
|
|
22
22
|
|
|
23
23
|
ddapi is an api from the ddnet and qwik website
|
|
24
|
+
|
|
24
25
|
## Installation
|
|
25
26
|
|
|
26
27
|
```bash
|
|
27
28
|
pip install ddapi
|
|
28
29
|
```
|
|
30
|
+
|
|
29
31
|
or
|
|
32
|
+
|
|
30
33
|
```bash
|
|
31
34
|
pip install git+https://github.com/ByFox213/ddapi
|
|
32
35
|
```
|
|
33
|
-
|
|
36
|
+
|
|
34
37
|
## Usage/Examples
|
|
35
38
|
|
|
36
39
|
DDnet
|
|
40
|
+
|
|
37
41
|
```python
|
|
38
42
|
import asyncio
|
|
39
43
|
from ddapi import DDnetApi, DDPlayer
|
|
@@ -45,6 +49,7 @@ async def main():
|
|
|
45
49
|
user: DDPlayer = await obj.player(nickname)
|
|
46
50
|
if user is None:
|
|
47
51
|
return "Player not found"
|
|
52
|
+
|
|
48
53
|
print(f"{user.player}: {user.points.points}")
|
|
49
54
|
# Cor: 31473
|
|
50
55
|
await obj.close() # Closing client Not necessary
|
|
@@ -53,10 +58,10 @@ async def main():
|
|
|
53
58
|
|
|
54
59
|
asyncio.run(main())
|
|
55
60
|
```
|
|
61
|
+
|
|
56
62
|
Tested on Python
|
|
57
63
|
| 3.9 | 3.10 | 3.11 | 3.12 |
|
|
58
64
|
|-----|------|------|------|
|
|
59
|
-
| ?
|
|
60
|
-
|
|
65
|
+
| ? | ? | ✅ | ✅ |
|
|
61
66
|
|
|
62
67
|
[other examples](https://github.com/ByFox213/ddapi/tree/main/example)
|
|
@@ -3,10 +3,13 @@ README.md
|
|
|
3
3
|
setup.cfg
|
|
4
4
|
setup.py
|
|
5
5
|
ddapi/ddapi/__init__.py
|
|
6
|
-
ddapi/ddapi/
|
|
7
|
-
ddapi/ddapi/deflt.py
|
|
6
|
+
ddapi/ddapi/api.py
|
|
8
7
|
ddapi/ddapi/main.py
|
|
9
8
|
ddapi/ddapi.egg-info/PKG-INFO
|
|
10
9
|
ddapi/ddapi.egg-info/SOURCES.txt
|
|
11
10
|
ddapi/ddapi.egg-info/dependency_links.txt
|
|
12
|
-
ddapi/ddapi.egg-info/top_level.txt
|
|
11
|
+
ddapi/ddapi.egg-info/top_level.txt
|
|
12
|
+
ddapi/ddapi/scheme/__init__.py
|
|
13
|
+
ddapi/ddapi/scheme/ddnet.py
|
|
14
|
+
ddapi/ddapi/scheme/ddstats_tw.py
|
|
15
|
+
ddapi/ddapi/scheme/status_tw.py
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="ddapi",
|
|
8
|
-
version="0.
|
|
8
|
+
version="0.8.1",
|
|
9
9
|
author="ByFox",
|
|
10
10
|
description="DDnet api",
|
|
11
11
|
long_description=long_description,
|
|
@@ -20,6 +20,6 @@ setuptools.setup(
|
|
|
20
20
|
"Operating System :: OS Independent",
|
|
21
21
|
],
|
|
22
22
|
package_dir={"": "ddapi"},
|
|
23
|
-
packages=setuptools.find_packages(where="
|
|
23
|
+
packages=setuptools.find_packages(where="ddapi"),
|
|
24
24
|
python_requires=">=3.8"
|
|
25
25
|
)
|
ddapi-0.7.2/ddapi/ddapi/main.py
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
from urllib.parse import quote
|
|
3
|
-
|
|
4
|
-
from .dataclass import DDPlayer, Master, Query, DMap, DDStatus, Player, MasterTw
|
|
5
|
-
from .deflt import API
|
|
6
|
-
|
|
7
|
-
__all__ = (
|
|
8
|
-
"DDnetApi",
|
|
9
|
-
"DDstats",
|
|
10
|
-
"Status"
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class DDnetApi(API):
|
|
15
|
-
def __init__(self):
|
|
16
|
-
super().__init__()
|
|
17
|
-
self.domain = "ddnet.org"
|
|
18
|
-
|
|
19
|
-
@staticmethod
|
|
20
|
-
def powered() -> str:
|
|
21
|
-
return 'ddnet.org'
|
|
22
|
-
|
|
23
|
-
async def player(self, player_name: str) -> DDPlayer:
|
|
24
|
-
"""Fetch player data from the API and return it as a dictionary."""
|
|
25
|
-
return await self._generate_model_instance(
|
|
26
|
-
f"https://{self.domain}/players/?json2={quote(player_name)}",
|
|
27
|
-
DDPlayer
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
async def status(self) -> Union[DDStatus, None]:
|
|
31
|
-
return await self._generate_model_instance(
|
|
32
|
-
f"https://{self.domain}/status/json/stats.json",
|
|
33
|
-
DDStatus
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
async def query(self, player: str) -> Union[Query, None]:
|
|
37
|
-
return await self._generate_model_instance(
|
|
38
|
-
f"https://{self.domain}/players/?query={quote(player)}",
|
|
39
|
-
Query,
|
|
40
|
-
"data"
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
async def master(self) -> Union[Master, None]:
|
|
44
|
-
return await self._generate_model_instance(
|
|
45
|
-
f"https://master1.{self.domain}/ddnet/15/servers.json",
|
|
46
|
-
Master
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
async def map(self, map_name: str) -> Union[DMap, None]:
|
|
50
|
-
return await self._generate_model_instance(
|
|
51
|
-
f"https://{self.domain}/maps/?json={quote(map_name)}",
|
|
52
|
-
DMap
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class DDstats(API):
|
|
57
|
-
def __init__(self):
|
|
58
|
-
super().__init__()
|
|
59
|
-
self.url = "https://ddstats.tw/player/json?player={0}"
|
|
60
|
-
|
|
61
|
-
@staticmethod
|
|
62
|
-
def powered() -> str:
|
|
63
|
-
return 'ddstats.tw'
|
|
64
|
-
|
|
65
|
-
async def player(self, player_name: str) -> Player:
|
|
66
|
-
"""Fetch player data from the API and return it as a dictionary."""
|
|
67
|
-
return await self._generate_model_instance(
|
|
68
|
-
self.url.format(quote(player_name)),
|
|
69
|
-
Player
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class Status(API): # TODO: Add other
|
|
74
|
-
def __init__(self):
|
|
75
|
-
super().__init__()
|
|
76
|
-
self.url = "https://api.status.tw"
|
|
77
|
-
|
|
78
|
-
@staticmethod
|
|
79
|
-
def powered() -> str:
|
|
80
|
-
return 'ddstats.tw'
|
|
81
|
-
|
|
82
|
-
async def server_list(self) -> MasterTw:
|
|
83
|
-
"""Fetch player data from the API and return it as a dictionary."""
|
|
84
|
-
return await self._generate_model_instance(
|
|
85
|
-
self.url + "/server/list",
|
|
86
|
-
MasterTw,
|
|
87
|
-
"servers"
|
|
88
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|