forked from retoor/devplacepy
feat: add plug-and-play devRant API client examples with Python and JavaScript
Add complete set of example scripts for the devRant REST protocol under examples/devrant/, including reusable client libraries, a rant poster, a live feed watcher with keyword-based auto-upvote, and an end-to-end smoke test that exercises every endpoint. Both Python (stdlib-only) and JavaScript (Node 18+ global fetch) implementations are provided.
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class DevRant:
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str,
|
||||
username: Optional[str] = None,
|
||||
password: Optional[str] = None,
|
||||
) -> None:
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.auth: dict[str, Any] = {}
|
||||
|
||||
def _url(self, path: str) -> str:
|
||||
return f"{self.base_url}/api/{path.lstrip('/')}"
|
||||
|
||||
def _request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
params: Optional[dict] = None,
|
||||
body: Optional[dict] = None,
|
||||
) -> dict:
|
||||
merged = dict(params or {})
|
||||
merged.update(self.auth)
|
||||
url = self._url(path)
|
||||
data = None
|
||||
headers = {"Accept": "application/json"}
|
||||
if method in ("GET", "DELETE"):
|
||||
if merged:
|
||||
url += "?" + urllib.parse.urlencode(merged)
|
||||
else:
|
||||
payload = dict(merged)
|
||||
payload.update(body or {})
|
||||
data = urllib.parse.urlencode(payload).encode("utf-8")
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
request = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(request) as response:
|
||||
return json.loads(response.read().decode("utf-8"))
|
||||
|
||||
def login(self) -> dict:
|
||||
result = self._request(
|
||||
"POST",
|
||||
"users/auth-token",
|
||||
body={"username": self.username, "password": self.password},
|
||||
)
|
||||
if not result.get("success"):
|
||||
raise RuntimeError(result.get("error", "login failed"))
|
||||
token = result["auth_token"]
|
||||
self.auth = {
|
||||
"user_id": token["user_id"],
|
||||
"token_id": token["id"],
|
||||
"token_key": token["key"],
|
||||
}
|
||||
return self.auth
|
||||
|
||||
def register(self, email: str) -> dict:
|
||||
return self._request(
|
||||
"POST",
|
||||
"users",
|
||||
body={
|
||||
"username": self.username,
|
||||
"email": email,
|
||||
"password": self.password,
|
||||
},
|
||||
)
|
||||
|
||||
def rants(self, sort: str = "recent", limit: int = 20, skip: int = 0) -> list:
|
||||
result = self._request(
|
||||
"GET", "devrant/rants", {"sort": sort, "limit": limit, "skip": skip}
|
||||
)
|
||||
return result.get("rants", [])
|
||||
|
||||
def rant(self, rant_id: int) -> dict:
|
||||
return self._request("GET", f"devrant/rants/{rant_id}")
|
||||
|
||||
def search(self, term: str) -> list:
|
||||
return self._request("GET", "devrant/search", {"term": term}).get("results", [])
|
||||
|
||||
def user_id(self, username: str) -> Optional[int]:
|
||||
return self._request("GET", "get-user-id", {"username": username}).get("user_id")
|
||||
|
||||
def profile(self, user_id: int) -> Optional[dict]:
|
||||
return self._request("GET", f"users/{user_id}").get("profile")
|
||||
|
||||
def notifs(self) -> dict:
|
||||
return self._request("GET", "users/me/notif-feed").get("data", {})
|
||||
|
||||
def clear_notifs(self) -> dict:
|
||||
return self._request("DELETE", "users/me/notif-feed")
|
||||
|
||||
def edit_profile(self, **fields: str) -> dict:
|
||||
return self._request("POST", "users/me/edit-profile", body=fields)
|
||||
|
||||
def post_rant(self, text: str, tags: str = "") -> dict:
|
||||
return self._request("POST", "devrant/rants", body={"rant": text, "tags": tags})
|
||||
|
||||
def edit_rant(self, rant_id: int, text: str, tags: str = "") -> dict:
|
||||
return self._request(
|
||||
"POST", f"devrant/rants/{rant_id}", body={"rant": text, "tags": tags}
|
||||
)
|
||||
|
||||
def delete_rant(self, rant_id: int) -> dict:
|
||||
return self._request("DELETE", f"devrant/rants/{rant_id}")
|
||||
|
||||
def vote_rant(self, rant_id: int, vote: int) -> dict:
|
||||
return self._request(
|
||||
"POST", f"devrant/rants/{rant_id}/vote", body={"vote": vote}
|
||||
)
|
||||
|
||||
def favorite(self, rant_id: int) -> dict:
|
||||
return self._request("POST", f"devrant/rants/{rant_id}/favorite")
|
||||
|
||||
def unfavorite(self, rant_id: int) -> dict:
|
||||
return self._request("POST", f"devrant/rants/{rant_id}/unfavorite")
|
||||
|
||||
def comment(self, rant_id: int, text: str) -> dict:
|
||||
return self._request(
|
||||
"POST", f"devrant/rants/{rant_id}/comments", body={"comment": text}
|
||||
)
|
||||
|
||||
def vote_comment(self, comment_id: int, vote: int) -> dict:
|
||||
return self._request("POST", f"comments/{comment_id}/vote", body={"vote": vote})
|
||||
|
||||
|
||||
def from_env() -> DevRant:
|
||||
return DevRant(
|
||||
os.environ.get("DEVRANT_BASE", "http://localhost:10500"),
|
||||
os.environ.get("DEVRANT_USERNAME"),
|
||||
os.environ.get("DEVRANT_PASSWORD"),
|
||||
)
|
||||
Reference in New Issue
Block a user