palworld-restapi 0.7.3__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.
- palworld_restapi/__init__.py +21 -0
- palworld_restapi/__main__.py +4 -0
- palworld_restapi/_core.py +33 -0
- palworld_restapi/async_client.py +96 -0
- palworld_restapi/cli.py +123 -0
- palworld_restapi/client.py +96 -0
- palworld_restapi/errors.py +13 -0
- palworld_restapi/models.py +253 -0
- palworld_restapi/py.typed +1 -0
- palworld_restapi-0.7.3.dist-info/METADATA +98 -0
- palworld_restapi-0.7.3.dist-info/RECORD +14 -0
- palworld_restapi-0.7.3.dist-info/WHEEL +4 -0
- palworld_restapi-0.7.3.dist-info/entry_points.txt +2 -0
- palworld_restapi-0.7.3.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .client import PalworldClient
|
|
2
|
+
from .async_client import AsyncPalworldClient
|
|
3
|
+
from .errors import PalworldApiError
|
|
4
|
+
from .models import (
|
|
5
|
+
ServerInfo,
|
|
6
|
+
PlayerInfo,
|
|
7
|
+
PlayersResponse,
|
|
8
|
+
ServerSettings,
|
|
9
|
+
ServerMetrics,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"PalworldClient",
|
|
14
|
+
"AsyncPalworldClient",
|
|
15
|
+
"PalworldApiError",
|
|
16
|
+
"ServerInfo",
|
|
17
|
+
"PlayerInfo",
|
|
18
|
+
"PlayersResponse",
|
|
19
|
+
"ServerSettings",
|
|
20
|
+
"ServerMetrics",
|
|
21
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
import httpx
|
|
3
|
+
|
|
4
|
+
from .errors import PalworldApiError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def build_url(base_url: str, path: str) -> str:
|
|
8
|
+
"""Build a full URL for the Palworld API."""
|
|
9
|
+
base = base_url.rstrip("/")
|
|
10
|
+
if not base.endswith("/v1/api"):
|
|
11
|
+
base = f"{base}/v1/api"
|
|
12
|
+
return f"{base}/{path.lstrip('/')}"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handle_response(response: httpx.Response) -> Any:
|
|
16
|
+
"""
|
|
17
|
+
Handle the HTTP response, parsing JSON if successful, or raising PalworldApiError.
|
|
18
|
+
"""
|
|
19
|
+
if response.status_code >= 400:
|
|
20
|
+
raise PalworldApiError(
|
|
21
|
+
status_code=response.status_code,
|
|
22
|
+
method=response.request.method,
|
|
23
|
+
path=str(response.request.url),
|
|
24
|
+
response_body=response.text,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if not response.content:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
return response.json()
|
|
32
|
+
except ValueError:
|
|
33
|
+
return response.text
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import Any, Self
|
|
2
|
+
import httpx
|
|
3
|
+
|
|
4
|
+
from ._core import build_url, handle_response
|
|
5
|
+
from .models import ServerInfo, PlayersResponse, ServerSettings, ServerMetrics
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AsyncPalworldClient:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
base_url: str,
|
|
12
|
+
password: str,
|
|
13
|
+
username: str = "admin",
|
|
14
|
+
timeout: float = 10.0,
|
|
15
|
+
) -> None:
|
|
16
|
+
self.base_url = base_url
|
|
17
|
+
auth = (username, password)
|
|
18
|
+
self._client = httpx.AsyncClient(auth=auth, timeout=timeout)
|
|
19
|
+
|
|
20
|
+
async def __aenter__(self) -> Self:
|
|
21
|
+
await self._client.__aenter__()
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
25
|
+
await self._client.__aexit__(exc_type, exc_val, exc_tb)
|
|
26
|
+
|
|
27
|
+
async def close(self) -> None:
|
|
28
|
+
await self._client.aclose()
|
|
29
|
+
|
|
30
|
+
async def get_info(self) -> ServerInfo:
|
|
31
|
+
url = build_url(self.base_url, "info")
|
|
32
|
+
response = await self._client.get(url)
|
|
33
|
+
data = handle_response(response)
|
|
34
|
+
return ServerInfo.from_dict(data)
|
|
35
|
+
|
|
36
|
+
async def get_players(self) -> PlayersResponse:
|
|
37
|
+
url = build_url(self.base_url, "players")
|
|
38
|
+
response = await self._client.get(url)
|
|
39
|
+
data = handle_response(response)
|
|
40
|
+
return PlayersResponse.from_dict(data)
|
|
41
|
+
|
|
42
|
+
async def get_settings(self) -> ServerSettings:
|
|
43
|
+
url = build_url(self.base_url, "settings")
|
|
44
|
+
response = await self._client.get(url)
|
|
45
|
+
data = handle_response(response)
|
|
46
|
+
return ServerSettings.from_dict(data)
|
|
47
|
+
|
|
48
|
+
async def get_metrics(self) -> ServerMetrics:
|
|
49
|
+
url = build_url(self.base_url, "metrics")
|
|
50
|
+
response = await self._client.get(url)
|
|
51
|
+
data = handle_response(response)
|
|
52
|
+
return ServerMetrics.from_dict(data)
|
|
53
|
+
|
|
54
|
+
async def announce(self, message: str) -> None:
|
|
55
|
+
url = build_url(self.base_url, "announce")
|
|
56
|
+
response = await self._client.post(url, json={"message": message})
|
|
57
|
+
handle_response(response)
|
|
58
|
+
|
|
59
|
+
async def kick_player(self, userid: str, message: str | None = None) -> None:
|
|
60
|
+
url = build_url(self.base_url, "kick")
|
|
61
|
+
payload = {"userid": userid}
|
|
62
|
+
if message is not None:
|
|
63
|
+
payload["message"] = message
|
|
64
|
+
response = await self._client.post(url, json=payload)
|
|
65
|
+
handle_response(response)
|
|
66
|
+
|
|
67
|
+
async def ban_player(self, userid: str, message: str | None = None) -> None:
|
|
68
|
+
url = build_url(self.base_url, "ban")
|
|
69
|
+
payload = {"userid": userid}
|
|
70
|
+
if message is not None:
|
|
71
|
+
payload["message"] = message
|
|
72
|
+
response = await self._client.post(url, json=payload)
|
|
73
|
+
handle_response(response)
|
|
74
|
+
|
|
75
|
+
async def unban_player(self, userid: str) -> None:
|
|
76
|
+
url = build_url(self.base_url, "unban")
|
|
77
|
+
response = await self._client.post(url, json={"userid": userid})
|
|
78
|
+
handle_response(response)
|
|
79
|
+
|
|
80
|
+
async def save(self) -> None:
|
|
81
|
+
url = build_url(self.base_url, "save")
|
|
82
|
+
response = await self._client.post(url)
|
|
83
|
+
handle_response(response)
|
|
84
|
+
|
|
85
|
+
async def shutdown(self, waittime: int, message: str | None = None) -> None:
|
|
86
|
+
url = build_url(self.base_url, "shutdown")
|
|
87
|
+
payload: dict[str, Any] = {"waittime": waittime}
|
|
88
|
+
if message is not None:
|
|
89
|
+
payload["message"] = message
|
|
90
|
+
response = await self._client.post(url, json=payload)
|
|
91
|
+
handle_response(response)
|
|
92
|
+
|
|
93
|
+
async def stop(self) -> None:
|
|
94
|
+
url = build_url(self.base_url, "stop")
|
|
95
|
+
response = await self._client.post(url)
|
|
96
|
+
handle_response(response)
|
palworld_restapi/cli.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
from dataclasses import asdict
|
|
7
|
+
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
|
|
10
|
+
from .client import PalworldClient
|
|
11
|
+
from .errors import PalworldApiError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def print_json(obj: Any) -> None:
|
|
15
|
+
print(json.dumps(obj, indent=2, default=str))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> None:
|
|
19
|
+
parser = argparse.ArgumentParser(description="Palworld REST API CLI")
|
|
20
|
+
parser.add_argument("--env", type=str, help="Path to .env file", default=".env")
|
|
21
|
+
parser.add_argument("--base-url", type=str, help="Base URL for Palworld API")
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--username", type=str, help="Username for Basic Auth", default="admin"
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument("--password", type=str, help="Password for Basic Auth")
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--timeout", type=float, help="Request timeout in seconds", default=10.0
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
31
|
+
|
|
32
|
+
subparsers.add_parser("info", help="Get server info")
|
|
33
|
+
subparsers.add_parser("players", help="Get player list")
|
|
34
|
+
subparsers.add_parser("settings", help="Get server settings")
|
|
35
|
+
subparsers.add_parser("metrics", help="Get server metrics")
|
|
36
|
+
|
|
37
|
+
announce_parser = subparsers.add_parser(
|
|
38
|
+
"announce", help="Announce a message to the server"
|
|
39
|
+
)
|
|
40
|
+
announce_parser.add_argument("message", type=str, help="Message to announce")
|
|
41
|
+
|
|
42
|
+
kick_parser = subparsers.add_parser("kick", help="Kick a player")
|
|
43
|
+
kick_parser.add_argument("userid", type=str, help="User ID to kick")
|
|
44
|
+
kick_parser.add_argument("--message", type=str, help="Kick message")
|
|
45
|
+
|
|
46
|
+
ban_parser = subparsers.add_parser("ban", help="Ban a player")
|
|
47
|
+
ban_parser.add_argument("userid", type=str, help="User ID to ban")
|
|
48
|
+
ban_parser.add_argument("--message", type=str, help="Ban message")
|
|
49
|
+
|
|
50
|
+
unban_parser = subparsers.add_parser("unban", help="Unban a player")
|
|
51
|
+
unban_parser.add_argument("userid", type=str, help="User ID to unban")
|
|
52
|
+
|
|
53
|
+
subparsers.add_parser("save", help="Save the world state")
|
|
54
|
+
|
|
55
|
+
shutdown_parser = subparsers.add_parser("shutdown", help="Shutdown the server")
|
|
56
|
+
shutdown_parser.add_argument("waittime", type=int, help="Wait time in seconds")
|
|
57
|
+
shutdown_parser.add_argument("--message", type=str, help="Shutdown message")
|
|
58
|
+
|
|
59
|
+
subparsers.add_parser("stop", help="Stop the server immediately")
|
|
60
|
+
|
|
61
|
+
args = parser.parse_args()
|
|
62
|
+
|
|
63
|
+
# Load environment variables
|
|
64
|
+
if os.path.exists(args.env):
|
|
65
|
+
load_dotenv(args.env)
|
|
66
|
+
|
|
67
|
+
base_url = args.base_url or os.environ.get(
|
|
68
|
+
"PALWORLD_BASE_URL", "http://127.0.0.1:8212"
|
|
69
|
+
)
|
|
70
|
+
username = os.environ.get("PALWORLD_USERNAME", args.username)
|
|
71
|
+
password = args.password or os.environ.get("PALWORLD_PASSWORD")
|
|
72
|
+
timeout = float(os.environ.get("PALWORLD_TIMEOUT", args.timeout))
|
|
73
|
+
|
|
74
|
+
if not password:
|
|
75
|
+
print(
|
|
76
|
+
"Error: Password is required. Set it via --password or PALWORLD_PASSWORD.",
|
|
77
|
+
file=sys.stderr,
|
|
78
|
+
)
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
with PalworldClient(
|
|
83
|
+
base_url, password=password, username=username, timeout=timeout
|
|
84
|
+
) as client:
|
|
85
|
+
if args.command == "info":
|
|
86
|
+
print_json(asdict(client.get_info()))
|
|
87
|
+
elif args.command == "players":
|
|
88
|
+
print_json(asdict(client.get_players()))
|
|
89
|
+
elif args.command == "settings":
|
|
90
|
+
print_json(asdict(client.get_settings()))
|
|
91
|
+
elif args.command == "metrics":
|
|
92
|
+
print_json(asdict(client.get_metrics()))
|
|
93
|
+
elif args.command == "announce":
|
|
94
|
+
client.announce(args.message)
|
|
95
|
+
print("Announcement sent successfully.")
|
|
96
|
+
elif args.command == "kick":
|
|
97
|
+
client.kick_player(args.userid, message=args.message)
|
|
98
|
+
print(f"Player {args.userid} kicked successfully.")
|
|
99
|
+
elif args.command == "ban":
|
|
100
|
+
client.ban_player(args.userid, message=args.message)
|
|
101
|
+
print(f"Player {args.userid} banned successfully.")
|
|
102
|
+
elif args.command == "unban":
|
|
103
|
+
client.unban_player(args.userid)
|
|
104
|
+
print(f"Player {args.userid} unbanned successfully.")
|
|
105
|
+
elif args.command == "save":
|
|
106
|
+
client.save()
|
|
107
|
+
print("World saved successfully.")
|
|
108
|
+
elif args.command == "shutdown":
|
|
109
|
+
client.shutdown(args.waittime, message=args.message)
|
|
110
|
+
print(f"Shutdown initiated with wait time {args.waittime}s.")
|
|
111
|
+
elif args.command == "stop":
|
|
112
|
+
client.stop()
|
|
113
|
+
print("Server stopped.")
|
|
114
|
+
except PalworldApiError as e:
|
|
115
|
+
print(f"API Error: {e}", file=sys.stderr)
|
|
116
|
+
sys.exit(1)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
main()
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import Any, Self
|
|
2
|
+
import httpx
|
|
3
|
+
|
|
4
|
+
from ._core import build_url, handle_response
|
|
5
|
+
from .models import ServerInfo, PlayersResponse, ServerSettings, ServerMetrics
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PalworldClient:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
base_url: str,
|
|
12
|
+
password: str,
|
|
13
|
+
username: str = "admin",
|
|
14
|
+
timeout: float = 10.0,
|
|
15
|
+
) -> None:
|
|
16
|
+
self.base_url = base_url
|
|
17
|
+
auth = (username, password)
|
|
18
|
+
self._client = httpx.Client(auth=auth, timeout=timeout)
|
|
19
|
+
|
|
20
|
+
def __enter__(self) -> Self:
|
|
21
|
+
self._client.__enter__()
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
25
|
+
self._client.__exit__(exc_type, exc_val, exc_tb)
|
|
26
|
+
|
|
27
|
+
def close(self) -> None:
|
|
28
|
+
self._client.close()
|
|
29
|
+
|
|
30
|
+
def get_info(self) -> ServerInfo:
|
|
31
|
+
url = build_url(self.base_url, "info")
|
|
32
|
+
response = self._client.get(url)
|
|
33
|
+
data = handle_response(response)
|
|
34
|
+
return ServerInfo.from_dict(data)
|
|
35
|
+
|
|
36
|
+
def get_players(self) -> PlayersResponse:
|
|
37
|
+
url = build_url(self.base_url, "players")
|
|
38
|
+
response = self._client.get(url)
|
|
39
|
+
data = handle_response(response)
|
|
40
|
+
return PlayersResponse.from_dict(data)
|
|
41
|
+
|
|
42
|
+
def get_settings(self) -> ServerSettings:
|
|
43
|
+
url = build_url(self.base_url, "settings")
|
|
44
|
+
response = self._client.get(url)
|
|
45
|
+
data = handle_response(response)
|
|
46
|
+
return ServerSettings.from_dict(data)
|
|
47
|
+
|
|
48
|
+
def get_metrics(self) -> ServerMetrics:
|
|
49
|
+
url = build_url(self.base_url, "metrics")
|
|
50
|
+
response = self._client.get(url)
|
|
51
|
+
data = handle_response(response)
|
|
52
|
+
return ServerMetrics.from_dict(data)
|
|
53
|
+
|
|
54
|
+
def announce(self, message: str) -> None:
|
|
55
|
+
url = build_url(self.base_url, "announce")
|
|
56
|
+
response = self._client.post(url, json={"message": message})
|
|
57
|
+
handle_response(response)
|
|
58
|
+
|
|
59
|
+
def kick_player(self, userid: str, message: str | None = None) -> None:
|
|
60
|
+
url = build_url(self.base_url, "kick")
|
|
61
|
+
payload = {"userid": userid}
|
|
62
|
+
if message is not None:
|
|
63
|
+
payload["message"] = message
|
|
64
|
+
response = self._client.post(url, json=payload)
|
|
65
|
+
handle_response(response)
|
|
66
|
+
|
|
67
|
+
def ban_player(self, userid: str, message: str | None = None) -> None:
|
|
68
|
+
url = build_url(self.base_url, "ban")
|
|
69
|
+
payload = {"userid": userid}
|
|
70
|
+
if message is not None:
|
|
71
|
+
payload["message"] = message
|
|
72
|
+
response = self._client.post(url, json=payload)
|
|
73
|
+
handle_response(response)
|
|
74
|
+
|
|
75
|
+
def unban_player(self, userid: str) -> None:
|
|
76
|
+
url = build_url(self.base_url, "unban")
|
|
77
|
+
response = self._client.post(url, json={"userid": userid})
|
|
78
|
+
handle_response(response)
|
|
79
|
+
|
|
80
|
+
def save(self) -> None:
|
|
81
|
+
url = build_url(self.base_url, "save")
|
|
82
|
+
response = self._client.post(url)
|
|
83
|
+
handle_response(response)
|
|
84
|
+
|
|
85
|
+
def shutdown(self, waittime: int, message: str | None = None) -> None:
|
|
86
|
+
url = build_url(self.base_url, "shutdown")
|
|
87
|
+
payload: dict[str, Any] = {"waittime": waittime}
|
|
88
|
+
if message is not None:
|
|
89
|
+
payload["message"] = message
|
|
90
|
+
response = self._client.post(url, json=payload)
|
|
91
|
+
handle_response(response)
|
|
92
|
+
|
|
93
|
+
def stop(self) -> None:
|
|
94
|
+
url = build_url(self.base_url, "stop")
|
|
95
|
+
response = self._client.post(url)
|
|
96
|
+
handle_response(response)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class PalworldApiError(Exception):
|
|
2
|
+
"""Exception raised for errors returned by the Palworld REST API."""
|
|
3
|
+
|
|
4
|
+
def __init__(
|
|
5
|
+
self, status_code: int, method: str, path: str, response_body: str
|
|
6
|
+
) -> None:
|
|
7
|
+
self.status_code = status_code
|
|
8
|
+
self.method = method
|
|
9
|
+
self.path = path
|
|
10
|
+
self.response_body = response_body
|
|
11
|
+
super().__init__(
|
|
12
|
+
f"Palworld API Error {status_code} on {method} {path}: {response_body}"
|
|
13
|
+
)
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass(slots=True, frozen=True)
|
|
6
|
+
class ServerInfo:
|
|
7
|
+
version: str
|
|
8
|
+
servername: str
|
|
9
|
+
description: str
|
|
10
|
+
worldguid: str
|
|
11
|
+
raw: dict[str, Any] = field(repr=False, hash=False, compare=False)
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def from_dict(cls, data: dict[str, Any]) -> "ServerInfo":
|
|
15
|
+
return cls(
|
|
16
|
+
version=data.get("version", ""),
|
|
17
|
+
servername=data.get("servername", ""),
|
|
18
|
+
description=data.get("description", ""),
|
|
19
|
+
worldguid=data.get("worldguid", ""),
|
|
20
|
+
raw=data,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(slots=True, frozen=True)
|
|
25
|
+
class PlayerInfo:
|
|
26
|
+
name: str
|
|
27
|
+
accountName: str
|
|
28
|
+
playerId: str
|
|
29
|
+
userId: str
|
|
30
|
+
ip: str
|
|
31
|
+
ping: float
|
|
32
|
+
location_x: float
|
|
33
|
+
location_y: float
|
|
34
|
+
level: int
|
|
35
|
+
building_count: int
|
|
36
|
+
raw: dict[str, Any] = field(repr=False, hash=False, compare=False)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_dict(cls, data: dict[str, Any]) -> "PlayerInfo":
|
|
40
|
+
return cls(
|
|
41
|
+
name=data.get("name", ""),
|
|
42
|
+
accountName=data.get("accountName", ""),
|
|
43
|
+
playerId=data.get("playerId", ""),
|
|
44
|
+
userId=data.get("userId", ""),
|
|
45
|
+
ip=data.get("ip", ""),
|
|
46
|
+
ping=float(data.get("ping", 0.0)),
|
|
47
|
+
location_x=float(data.get("location_x", 0.0)),
|
|
48
|
+
location_y=float(data.get("location_y", 0.0)),
|
|
49
|
+
level=int(data.get("level", 0)),
|
|
50
|
+
building_count=int(data.get("building_count", 0)),
|
|
51
|
+
raw=data,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(slots=True, frozen=True)
|
|
56
|
+
class PlayersResponse:
|
|
57
|
+
players: list[PlayerInfo]
|
|
58
|
+
raw: dict[str, Any] = field(repr=False, hash=False, compare=False)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def from_dict(cls, data: dict[str, Any]) -> "PlayersResponse":
|
|
62
|
+
return cls(
|
|
63
|
+
players=[PlayerInfo.from_dict(p) for p in data.get("players", [])],
|
|
64
|
+
raw=data,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(slots=True, frozen=True)
|
|
69
|
+
class ServerMetrics:
|
|
70
|
+
serverfps: int
|
|
71
|
+
currentplayernum: int
|
|
72
|
+
serverframetime: float
|
|
73
|
+
maxplayernum: int
|
|
74
|
+
uptime: int
|
|
75
|
+
basecampnum: int
|
|
76
|
+
days: int
|
|
77
|
+
raw: dict[str, Any] = field(repr=False, hash=False, compare=False)
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_dict(cls, data: dict[str, Any]) -> "ServerMetrics":
|
|
81
|
+
return cls(
|
|
82
|
+
serverfps=int(data.get("serverfps", 0)),
|
|
83
|
+
currentplayernum=int(data.get("currentplayernum", 0)),
|
|
84
|
+
serverframetime=float(data.get("serverframetime", 0.0)),
|
|
85
|
+
maxplayernum=int(data.get("maxplayernum", 0)),
|
|
86
|
+
uptime=int(data.get("uptime", 0)),
|
|
87
|
+
basecampnum=int(data.get("basecampnum", 0)),
|
|
88
|
+
days=int(data.get("days", 0)),
|
|
89
|
+
raw=data,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(slots=True, frozen=True)
|
|
94
|
+
class ServerSettings:
|
|
95
|
+
Difficulty: str
|
|
96
|
+
DayTimeSpeedRate: float
|
|
97
|
+
NightTimeSpeedRate: float
|
|
98
|
+
ExpRate: float
|
|
99
|
+
PalCaptureRate: float
|
|
100
|
+
PalSpawnNumRate: float
|
|
101
|
+
PalDamageRateAttack: float
|
|
102
|
+
PalDamageRateDefense: float
|
|
103
|
+
PlayerDamageRateAttack: float
|
|
104
|
+
PlayerDamageRateDefense: float
|
|
105
|
+
PlayerStomachDecreaceRate: float
|
|
106
|
+
PlayerStaminaDecreaceRate: float
|
|
107
|
+
PlayerAutoHPRegeneRate: float
|
|
108
|
+
PlayerAutoHpRegeneRateInSleep: float
|
|
109
|
+
PalStomachDecreaceRate: float
|
|
110
|
+
PalStaminaDecreaceRate: float
|
|
111
|
+
PalAutoHPRegeneRate: float
|
|
112
|
+
PalAutoHpRegeneRateInSleep: float
|
|
113
|
+
BuildObjectDamageRate: float
|
|
114
|
+
BuildObjectDeteriorationDamageRate: float
|
|
115
|
+
CollectionDropRate: float
|
|
116
|
+
CollectionObjectHpRate: float
|
|
117
|
+
CollectionObjectRespawnSpeedRate: float
|
|
118
|
+
EnemyDropItemRate: float
|
|
119
|
+
DeathPenalty: str
|
|
120
|
+
bEnablePlayerToPlayerDamage: bool
|
|
121
|
+
bEnableFriendlyFire: bool
|
|
122
|
+
bEnableInvaderEnemy: bool
|
|
123
|
+
bActiveUNKO: bool
|
|
124
|
+
bEnableAimAssistPad: bool
|
|
125
|
+
bEnableAimAssistKeyboard: bool
|
|
126
|
+
DropItemMaxNum: int
|
|
127
|
+
DropItemMaxNum_UNKO: int
|
|
128
|
+
BaseCampMaxNum: int
|
|
129
|
+
BaseCampWorkerMaxNum: int
|
|
130
|
+
DropItemAliveMaxHours: float
|
|
131
|
+
bAutoResetGuildNoOnlinePlayers: bool
|
|
132
|
+
AutoResetGuildTimeNoOnlinePlayers: float
|
|
133
|
+
GuildPlayerMaxNum: int
|
|
134
|
+
PalEggDefaultHatchingTime: float
|
|
135
|
+
WorkSpeedRate: float
|
|
136
|
+
bIsMultiplay: bool
|
|
137
|
+
bIsPvP: bool
|
|
138
|
+
bCanPickupOtherGuildDeathPenaltyDrop: bool
|
|
139
|
+
bEnableNonLoginPenalty: bool
|
|
140
|
+
bEnableFastTravel: bool
|
|
141
|
+
bIsStartLocationSelectByMap: bool
|
|
142
|
+
bExistPlayerAfterLogout: bool
|
|
143
|
+
bEnableDefenseOtherGuildPlayer: bool
|
|
144
|
+
CoopPlayerMaxNum: int
|
|
145
|
+
ServerPlayerMaxNum: int
|
|
146
|
+
ServerName: str
|
|
147
|
+
ServerDescription: str
|
|
148
|
+
PublicPort: int
|
|
149
|
+
PublicIP: str
|
|
150
|
+
RCONEnabled: bool
|
|
151
|
+
RCONPort: int
|
|
152
|
+
Region: str
|
|
153
|
+
bUseAuth: bool
|
|
154
|
+
BanListURL: str
|
|
155
|
+
RESTAPIEnabled: bool
|
|
156
|
+
RESTAPIPort: int
|
|
157
|
+
bShowPlayerList: bool
|
|
158
|
+
AllowConnectPlatform: str
|
|
159
|
+
bIsUseBackupSaveData: bool
|
|
160
|
+
LogFormatType: str
|
|
161
|
+
raw: dict[str, Any] = field(repr=False, hash=False, compare=False)
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def from_dict(cls, data: dict[str, Any]) -> "ServerSettings":
|
|
165
|
+
return cls(
|
|
166
|
+
Difficulty=data.get("Difficulty", ""),
|
|
167
|
+
DayTimeSpeedRate=float(data.get("DayTimeSpeedRate", 0.0)),
|
|
168
|
+
NightTimeSpeedRate=float(data.get("NightTimeSpeedRate", 0.0)),
|
|
169
|
+
ExpRate=float(data.get("ExpRate", 0.0)),
|
|
170
|
+
PalCaptureRate=float(data.get("PalCaptureRate", 0.0)),
|
|
171
|
+
PalSpawnNumRate=float(data.get("PalSpawnNumRate", 0.0)),
|
|
172
|
+
PalDamageRateAttack=float(data.get("PalDamageRateAttack", 0.0)),
|
|
173
|
+
PalDamageRateDefense=float(data.get("PalDamageRateDefense", 0.0)),
|
|
174
|
+
PlayerDamageRateAttack=float(data.get("PlayerDamageRateAttack", 0.0)),
|
|
175
|
+
PlayerDamageRateDefense=float(data.get("PlayerDamageRateDefense", 0.0)),
|
|
176
|
+
PlayerStomachDecreaceRate=float(data.get("PlayerStomachDecreaceRate", 0.0)),
|
|
177
|
+
PlayerStaminaDecreaceRate=float(data.get("PlayerStaminaDecreaceRate", 0.0)),
|
|
178
|
+
PlayerAutoHPRegeneRate=float(data.get("PlayerAutoHPRegeneRate", 0.0)),
|
|
179
|
+
PlayerAutoHpRegeneRateInSleep=float(
|
|
180
|
+
data.get("PlayerAutoHpRegeneRateInSleep", 0.0)
|
|
181
|
+
),
|
|
182
|
+
PalStomachDecreaceRate=float(data.get("PalStomachDecreaceRate", 0.0)),
|
|
183
|
+
PalStaminaDecreaceRate=float(data.get("PalStaminaDecreaceRate", 0.0)),
|
|
184
|
+
PalAutoHPRegeneRate=float(data.get("PalAutoHPRegeneRate", 0.0)),
|
|
185
|
+
PalAutoHpRegeneRateInSleep=float(
|
|
186
|
+
data.get("PalAutoHpRegeneRateInSleep", 0.0)
|
|
187
|
+
),
|
|
188
|
+
BuildObjectDamageRate=float(data.get("BuildObjectDamageRate", 0.0)),
|
|
189
|
+
BuildObjectDeteriorationDamageRate=float(
|
|
190
|
+
data.get("BuildObjectDeteriorationDamageRate", 0.0)
|
|
191
|
+
),
|
|
192
|
+
CollectionDropRate=float(data.get("CollectionDropRate", 0.0)),
|
|
193
|
+
CollectionObjectHpRate=float(data.get("CollectionObjectHpRate", 0.0)),
|
|
194
|
+
CollectionObjectRespawnSpeedRate=float(
|
|
195
|
+
data.get("CollectionObjectRespawnSpeedRate", 0.0)
|
|
196
|
+
),
|
|
197
|
+
EnemyDropItemRate=float(data.get("EnemyDropItemRate", 0.0)),
|
|
198
|
+
DeathPenalty=data.get("DeathPenalty", ""),
|
|
199
|
+
bEnablePlayerToPlayerDamage=bool(
|
|
200
|
+
data.get("bEnablePlayerToPlayerDamage", False)
|
|
201
|
+
),
|
|
202
|
+
bEnableFriendlyFire=bool(data.get("bEnableFriendlyFire", False)),
|
|
203
|
+
bEnableInvaderEnemy=bool(data.get("bEnableInvaderEnemy", False)),
|
|
204
|
+
bActiveUNKO=bool(data.get("bActiveUNKO", False)),
|
|
205
|
+
bEnableAimAssistPad=bool(data.get("bEnableAimAssistPad", False)),
|
|
206
|
+
bEnableAimAssistKeyboard=bool(data.get("bEnableAimAssistKeyboard", False)),
|
|
207
|
+
DropItemMaxNum=int(data.get("DropItemMaxNum", 0)),
|
|
208
|
+
DropItemMaxNum_UNKO=int(data.get("DropItemMaxNum_UNKO", 0)),
|
|
209
|
+
BaseCampMaxNum=int(data.get("BaseCampMaxNum", 0)),
|
|
210
|
+
BaseCampWorkerMaxNum=int(data.get("BaseCampWorkerMaxNum", 0)),
|
|
211
|
+
DropItemAliveMaxHours=float(data.get("DropItemAliveMaxHours", 0.0)),
|
|
212
|
+
bAutoResetGuildNoOnlinePlayers=bool(
|
|
213
|
+
data.get("bAutoResetGuildNoOnlinePlayers", False)
|
|
214
|
+
),
|
|
215
|
+
AutoResetGuildTimeNoOnlinePlayers=float(
|
|
216
|
+
data.get("AutoResetGuildTimeNoOnlinePlayers", 0.0)
|
|
217
|
+
),
|
|
218
|
+
GuildPlayerMaxNum=int(data.get("GuildPlayerMaxNum", 0)),
|
|
219
|
+
PalEggDefaultHatchingTime=float(data.get("PalEggDefaultHatchingTime", 0.0)),
|
|
220
|
+
WorkSpeedRate=float(data.get("WorkSpeedRate", 0.0)),
|
|
221
|
+
bIsMultiplay=bool(data.get("bIsMultiplay", False)),
|
|
222
|
+
bIsPvP=bool(data.get("bIsPvP", False)),
|
|
223
|
+
bCanPickupOtherGuildDeathPenaltyDrop=bool(
|
|
224
|
+
data.get("bCanPickupOtherGuildDeathPenaltyDrop", False)
|
|
225
|
+
),
|
|
226
|
+
bEnableNonLoginPenalty=bool(data.get("bEnableNonLoginPenalty", False)),
|
|
227
|
+
bEnableFastTravel=bool(data.get("bEnableFastTravel", False)),
|
|
228
|
+
bIsStartLocationSelectByMap=bool(
|
|
229
|
+
data.get("bIsStartLocationSelectByMap", False)
|
|
230
|
+
),
|
|
231
|
+
bExistPlayerAfterLogout=bool(data.get("bExistPlayerAfterLogout", False)),
|
|
232
|
+
bEnableDefenseOtherGuildPlayer=bool(
|
|
233
|
+
data.get("bEnableDefenseOtherGuildPlayer", False)
|
|
234
|
+
),
|
|
235
|
+
CoopPlayerMaxNum=int(data.get("CoopPlayerMaxNum", 0)),
|
|
236
|
+
ServerPlayerMaxNum=int(data.get("ServerPlayerMaxNum", 0)),
|
|
237
|
+
ServerName=data.get("ServerName", ""),
|
|
238
|
+
ServerDescription=data.get("ServerDescription", ""),
|
|
239
|
+
PublicPort=int(data.get("PublicPort", 0)),
|
|
240
|
+
PublicIP=data.get("PublicIP", ""),
|
|
241
|
+
RCONEnabled=bool(data.get("RCONEnabled", False)),
|
|
242
|
+
RCONPort=int(data.get("RCONPort", 0)),
|
|
243
|
+
Region=data.get("Region", ""),
|
|
244
|
+
bUseAuth=bool(data.get("bUseAuth", False)),
|
|
245
|
+
BanListURL=data.get("BanListURL", ""),
|
|
246
|
+
RESTAPIEnabled=bool(data.get("RESTAPIEnabled", False)),
|
|
247
|
+
RESTAPIPort=int(data.get("RESTAPIPort", 0)),
|
|
248
|
+
bShowPlayerList=bool(data.get("bShowPlayerList", False)),
|
|
249
|
+
AllowConnectPlatform=data.get("AllowConnectPlatform", ""),
|
|
250
|
+
bIsUseBackupSaveData=bool(data.get("bIsUseBackupSaveData", False)),
|
|
251
|
+
LogFormatType=data.get("LogFormatType", ""),
|
|
252
|
+
raw=data,
|
|
253
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# PEP 561 type hinting marker
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: palworld-restapi
|
|
3
|
+
Version: 0.7.3
|
|
4
|
+
Summary: This is a simple Palworld REST API Wrapper + CLI written in python.
|
|
5
|
+
Project-URL: Repository, https://github.com/KJAyano/Palworld-RESTAPI
|
|
6
|
+
Project-URL: Issues, https://github.com/KJAyano/Palworld-RESTAPI/issues
|
|
7
|
+
Author: KJAyano
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.14
|
|
10
|
+
Requires-Dist: httpx<1,>=0.27
|
|
11
|
+
Requires-Dist: python-dotenv
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Palworld REST API
|
|
15
|
+
|
|
16
|
+
[](https://pypi.org/project/palworld-restapi/)
|
|
17
|
+
[](https://pypi.org/project/palworld-restapi/)
|
|
18
|
+
|
|
19
|
+
A modern, fast, and fully-typed Python client library and CLI wrapper for the official **Palworld Dedicated Server REST API**. Built on `httpx` to provide first-class support for both synchronous scripts and high-performance asynchronous applications.
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Dual Clients**: Contains both `PalworldClient` (sync) and `AsyncPalworldClient` (async) using connection pooling for maximum performance.
|
|
24
|
+
- **Strict Typing**: Fully typed with PEP 561 compliance. API responses are parsed into rigorous Python Dataclasses for safety and excellent IDE autocomplete.
|
|
25
|
+
- **CLI Included**: Manage your Palworld server directly from your terminal using the built-in `palworld-cli`.
|
|
26
|
+
- **Modern**: Formatted with `ruff`, built with `hatchling`, and rigorously tested.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
You can install the package directly from PyPI:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install palworld-restapi
|
|
36
|
+
```
|
|
37
|
+
*(Or use `uv add palworld-restapi` if you are using `uv`)*
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quickstart
|
|
42
|
+
|
|
43
|
+
### Async Client (Recommended for Bots / Web Apps)
|
|
44
|
+
```python
|
|
45
|
+
import asyncio
|
|
46
|
+
from palworld_restapi import AsyncPalworldClient
|
|
47
|
+
|
|
48
|
+
async def main():
|
|
49
|
+
async with AsyncPalworldClient("http://127.0.0.1:8212", password="admin-password") as client:
|
|
50
|
+
info = await client.get_info()
|
|
51
|
+
print(f"Connected to {info.servername} (v{info.version})")
|
|
52
|
+
|
|
53
|
+
# Announce a message to the server
|
|
54
|
+
await client.announce("Hello from Python!")
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
asyncio.run(main())
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Sync Client (Recommended for simple scripts)
|
|
61
|
+
```python
|
|
62
|
+
from palworld_restapi import PalworldClient
|
|
63
|
+
|
|
64
|
+
with PalworldClient("http://127.0.0.1:8212", password="admin-password") as client:
|
|
65
|
+
players_resp = client.get_players()
|
|
66
|
+
print(f"There are currently {len(players_resp.players)} players online.")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Command Line Interface (CLI)
|
|
72
|
+
|
|
73
|
+
The package ships with a built-in CLI to easily manage your server without writing code. It supports loading credentials from `.env` files automatically.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Get server info
|
|
77
|
+
palworld-cli info
|
|
78
|
+
|
|
79
|
+
# See online players
|
|
80
|
+
palworld-cli players
|
|
81
|
+
|
|
82
|
+
# Kick a player by Steam ID
|
|
83
|
+
palworld-cli kick steam_00000000000000000 --message "AFK too long"
|
|
84
|
+
|
|
85
|
+
# Shut the server down in 60 seconds
|
|
86
|
+
palworld-cli shutdown 60 --message "Restarting for an update!"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Documentation & Examples
|
|
92
|
+
|
|
93
|
+
For full details, please refer to the documentation:
|
|
94
|
+
- **[Usage Guide](https://github.com/KJAyano/Palworld-RESTAPI/blob/main/docs/USAGE_GUIDE.md)**: Deep dive into using the Python clients.
|
|
95
|
+
- **[CLI Guide](https://github.com/KJAyano/Palworld-RESTAPI/blob/main/docs/CLI_GUIDE.md)**: Full command reference for the `palworld-cli` terminal tool.
|
|
96
|
+
- **[Endpoints Reference](https://github.com/KJAyano/Palworld-RESTAPI/blob/main/docs/ENDPOINTS.md)**: Available endpoints and payload details.
|
|
97
|
+
|
|
98
|
+
We also have a full suite of working scripts in the [`examples/`](https://github.com/KJAyano/Palworld-RESTAPI/tree/main/examples) directory demonstrating kick/ban management, player tracking, and server metrics.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
palworld_restapi/__init__.py,sha256=TDCOEXiJfjVAVTVFVBESvBJRn3pmtWZ0zOZqKT1CDjc,424
|
|
2
|
+
palworld_restapi/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
|
3
|
+
palworld_restapi/_core.py,sha256=s3Hv5hMe-QUZXhymFshGvKZhr005KJPo9r_pkljz_n4,869
|
|
4
|
+
palworld_restapi/async_client.py,sha256=_Ud_eI5TxQxWdiXRfHo6dfMjTUI-bYH_l3999OAZmSs,3524
|
|
5
|
+
palworld_restapi/cli.py,sha256=tE5El6MhhQOPfTt6ZdQxPpWsla5IDXiWIouX25jY-RI,4823
|
|
6
|
+
palworld_restapi/client.py,sha256=W4pG1VHoyInvSDuE5Yk6WOSySGwaeYAMDQbvrSYPIeM,3341
|
|
7
|
+
palworld_restapi/errors.py,sha256=CuDePaYCzXoYUzH47Kma-WspFPUEYvGQZniTGwUyqZ8,473
|
|
8
|
+
palworld_restapi/models.py,sha256=UWoSlt2BCxbikJhb7GwsAK0npU8DkpNljJCV3ye7NM0,10163
|
|
9
|
+
palworld_restapi/py.typed,sha256=CHI1GHUWh_37O6wIhLuQswalK0kOjarehBwBMSQ6e1Q,29
|
|
10
|
+
palworld_restapi-0.7.3.dist-info/METADATA,sha256=W_ykEBxbZAnehLbgSmx8i2wIFSYfpv_CbnRQITWA0_A,3686
|
|
11
|
+
palworld_restapi-0.7.3.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
12
|
+
palworld_restapi-0.7.3.dist-info/entry_points.txt,sha256=3DWz06AtgNVsmpoEr1x0Ylmvsm7G7kWeEuMpb4R5Wl0,59
|
|
13
|
+
palworld_restapi-0.7.3.dist-info/licenses/LICENSE,sha256=uogv3EQohnej6pTCx0rjvNdwmlFFJS-qykOlEQmHaCk,1067
|
|
14
|
+
palworld_restapi-0.7.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AyanoKouji
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|