-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_client.py
More file actions
197 lines (154 loc) · 6.08 KB
/
Copy pathapi_client.py
File metadata and controls
197 lines (154 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
"""
API Client
Wrapper for moltbook REST API with authentication and error handling.
Always uses www.moltbook.com to avoid redirect issues.
"""
import requests
from typing import Any, Dict, Optional
from dataclasses import dataclass
# Base URL - ALWAYS use www to avoid redirects
BASE_URL = "https://www.moltbook.com/api/v1"
class MoltbookError(Exception):
"""Base exception for moltbook API errors."""
pass
class AuthenticationError(MoltbookError):
"""Raised when API returns 401 Unauthorized."""
pass
class RateLimitError(MoltbookError):
"""Raised when API returns 429 Too Many Requests."""
def __init__(self, message: str, retry_after: int = 0):
super().__init__(message)
self.retry_after = retry_after
class NotFoundError(MoltbookError):
"""Raised when API returns 404 Not Found."""
pass
class MoltbookClient:
"""REST API client for moltbook.com."""
def __init__(self, api_key: str, base_url: str = BASE_URL):
"""
Initialize client.
Args:
api_key: Moltbook API key for authentication
base_url: API base URL (defaults to www.moltbook.com)
"""
self.api_key = api_key
self.base_url = base_url
self.session = requests.Session()
def _get_headers(self) -> Dict[str, str]:
"""Get headers with authorization."""
return {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
"User-Agent": "Clawdbot-MoltbookSkill/1.0"
}
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
"""
Handle API response, raising appropriate exceptions.
Args:
response: Response from requests
Returns:
Parsed JSON data
Raises:
AuthenticationError: On 401
RateLimitError: On 429
NotFoundError: On 404
MoltbookError: On other errors
"""
if response.status_code == 401:
raise AuthenticationError(
f"Authentication failed (401): Invalid or expired API key"
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
raise RateLimitError(
f"Rate limited (429): Retry after {retry_after} seconds",
retry_after=retry_after
)
if response.status_code == 404:
raise NotFoundError(f"Resource not found (404)")
if response.status_code >= 400:
try:
error_data = response.json()
message = error_data.get("error", f"API error: {response.status_code}")
except:
message = f"API error: {response.status_code}"
raise MoltbookError(message)
return response.json()
def request(
self,
method: str,
endpoint: str,
data: Optional[Dict] = None,
params: Optional[Dict] = None
) -> Dict[str, Any]:
"""
Make an API request.
Args:
method: HTTP method (GET, POST, DELETE, etc.)
endpoint: API endpoint (e.g., /posts)
data: JSON body for POST/PUT requests
params: Query parameters
Returns:
Parsed JSON response
"""
url = f"{self.base_url}{endpoint}"
response = requests.request(
method=method,
url=url,
headers=self._get_headers(),
json=data,
params=params,
timeout=30
)
return self._handle_response(response)
def get(self, endpoint: str, params: Optional[Dict] = None) -> Dict[str, Any]:
"""Make a GET request."""
return self.request("GET", endpoint, params=params)
def post(self, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
"""Make a POST request."""
return self.request("POST", endpoint, data=data)
def delete(self, endpoint: str) -> Dict[str, Any]:
"""Make a DELETE request."""
return self.request("DELETE", endpoint)
# === Convenience Methods ===
def get_feed(self, sort: str = "hot", limit: int = 25) -> Dict[str, Any]:
"""Get the main feed."""
return self.get("/posts", params={"sort": sort, "limit": limit})
def get_submolt(self, name: str, limit: int = 25) -> Dict[str, Any]:
"""Get posts from a specific submolt."""
return self.get(f"/submolts/{name}", params={"limit": limit})
def get_post(self, post_id: str) -> Dict[str, Any]:
"""Get a single post with comments."""
return self.get(f"/posts/{post_id}")
def upvote(self, post_id: str) -> Dict[str, Any]:
"""Upvote a post."""
return self.post(f"/posts/{post_id}/upvote")
def downvote(self, post_id: str) -> Dict[str, Any]:
"""Downvote a post."""
return self.post(f"/posts/{post_id}/downvote")
def comment(self, post_id: str, content: str) -> Dict[str, Any]:
"""Add a comment to a post."""
return self.post(f"/posts/{post_id}/comments", data={"content": content})
def create_post(
self,
submolt: str,
title: str,
content: Optional[str] = None,
url: Optional[str] = None
) -> Dict[str, Any]:
"""Create a new post."""
data = {"submolt": submolt, "title": title}
if content:
data["content"] = content
if url:
data["url"] = url
return self.post("/posts", data=data)
def get_profile(self, agent_name: str) -> Dict[str, Any]:
"""Get an agent's profile."""
return self.get(f"/agents/profile/{agent_name}")
def follow(self, agent_id: str) -> Dict[str, Any]:
"""Follow an agent."""
return self.post(f"/agents/{agent_id}/follow")
def get_status(self) -> Dict[str, Any]:
"""Get current agent status."""
return self.get("/agents/status")