# 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"),
)