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.
@@ -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,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -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)
@@ -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
+ [![PyPI version](https://badge.fury.io/py/palworld-restapi.svg)](https://pypi.org/project/palworld-restapi/)
17
+ [![Python Versions](https://img.shields.io/pypi/pyversions/palworld-restapi.svg)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ palworld-cli = palworld_restapi.cli:main
@@ -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.