mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-03 02:38:43 +02:00
113 lines
4.9 KiB
Python
113 lines
4.9 KiB
Python
"""Write operations: the things a logged-in human does on Reddit.
|
|
|
|
Posts, comments, edits, deletes, votes, saves, subscriptions, and DMs, all via
|
|
the user's own session. Each call goes through the rate limiter's write buckets.
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
from typing import Any, Dict, Optional
|
|
|
|
from backend.apps.reddit_mcp_shim.reddit_http import RedditError, api
|
|
|
|
# Reddit's own id shapes echoed back in a write response: a "fullname" (t1_ comment,
|
|
# t3_ post, ...) and a comment/post permalink. Used to recover a real receipt when the
|
|
# structured envelope is absent (the legacy "jquery" response shape, see p_receipt).
|
|
P_FULLNAME_RE = re.compile(r"t[1-6]_[0-9a-z]+", re.I)
|
|
P_PERMALINK_RE = re.compile(r"/r/[A-Za-z0-9_]+/comments/[A-Za-z0-9_/\-]+")
|
|
|
|
|
|
def p_check(resp: dict) -> dict:
|
|
"""Raise on Reddit's json.errors envelope; return the inner data otherwise."""
|
|
j = (resp or {}).get("json", resp or {})
|
|
errors = j.get("errors") if isinstance(j, dict) else None
|
|
if errors:
|
|
raise RedditError("; ".join(" ".join(str(p) for p in e) for e in errors))
|
|
return j.get("data", {}) if isinstance(j, dict) else {}
|
|
|
|
|
|
def p_receipt(resp: Any, kind: str, exclude: str = "") -> Dict[str, Optional[str]]:
|
|
"""The just-created thing's own fullname + permalink, robust to Reddit's TWO write
|
|
response shapes. Modern api_type=json returns data.things[0].data (comment) or the
|
|
fields at data top-level (submit); the LEGACY web endpoint returns a 'jquery' command
|
|
array with neither, so the naive parse came back empty = the receipt='ok' bug. Prefer
|
|
the structured field; else scan the echoed response for a fullname of the right kind
|
|
(t1 comment / t3 post), never the parent id we're replying to."""
|
|
data = p_check(resp) # raises on Reddit's real error envelope
|
|
d: Dict[str, Any] = {}
|
|
if isinstance(data, dict):
|
|
things = data.get("things")
|
|
if things and isinstance(things[0], dict):
|
|
d = things[0].get("data", {}) or {}
|
|
elif data.get("name") or data.get("id") or data.get("url"):
|
|
d = data
|
|
if d.get("name") or d.get("permalink") or d.get("url"):
|
|
return {"id": d.get("name") or d.get("id"),
|
|
"permalink": d.get("permalink") or d.get("url")}
|
|
blob = json.dumps(resp, default=str)
|
|
ex = (exclude or "").lower()
|
|
name = next((m for m in P_FULLNAME_RE.findall(blob)
|
|
if m.lower().startswith(kind.lower()) and m.lower() != ex), None)
|
|
pm = P_PERMALINK_RE.search(blob)
|
|
return {"id": name, "permalink": pm.group(0) if pm else None}
|
|
|
|
|
|
def p_dir(direction: str) -> int:
|
|
return {"up": 1, "upvote": 1, "down": -1, "downvote": -1, "clear": 0, "none": 0, "unvote": 0}.get(
|
|
(direction or "").lower(), 0
|
|
)
|
|
|
|
|
|
def submit(subreddit: str, title: str, kind: str, text: str, url: str, nsfw: bool, spoiler: bool, send_replies: bool) -> dict:
|
|
form = {
|
|
"sr": subreddit,
|
|
"title": title,
|
|
"kind": "self" if kind != "link" else "link",
|
|
"nsfw": "true" if nsfw else "false",
|
|
"spoiler": "true" if spoiler else "false",
|
|
"sendreplies": "true" if send_replies else "false",
|
|
"resubmit": "true",
|
|
"api_type": "json",
|
|
}
|
|
form["url" if kind == "link" else "text"] = url if kind == "link" else text
|
|
r = p_receipt(api("POST", "/api/submit", form=form, action="submit"), kind="t3")
|
|
return {"id": r["id"], "url": r["permalink"]}
|
|
|
|
|
|
def comment(parent_id: str, text: str) -> dict:
|
|
resp = api("POST", "/api/comment", form={"thing_id": parent_id, "text": text, "api_type": "json"}, action="comment")
|
|
r = p_receipt(resp, kind="t1", exclude=parent_id)
|
|
return {"id": r["id"], "permalink": r["permalink"]}
|
|
|
|
|
|
def edit(thing_id: str, text: str) -> dict:
|
|
resp = api("POST", "/api/editusertext", form={"thing_id": thing_id, "text": text, "api_type": "json"}, action="comment")
|
|
r = p_receipt(resp, kind=(thing_id[:2] or "t1"), exclude="")
|
|
return {"id": r["id"] or thing_id, "edited": True}
|
|
|
|
|
|
def delete(thing_id: str) -> dict:
|
|
api("POST", "/api/del", form={"id": thing_id}, action="save")
|
|
return {"id": thing_id, "deleted": True}
|
|
|
|
|
|
def vote(thing_id: str, direction: str) -> dict:
|
|
d = p_dir(direction)
|
|
api("POST", "/api/vote", form={"id": thing_id, "dir": d}, action="vote")
|
|
return {"id": thing_id, "dir": d}
|
|
|
|
|
|
def save(thing_id: str, unsave: bool) -> dict:
|
|
api("POST", "/api/unsave" if unsave else "/api/save", form={"id": thing_id}, action="save")
|
|
return {"id": thing_id, "saved": not unsave}
|
|
|
|
|
|
def subscribe(subreddit: str, unsubscribe: bool) -> dict:
|
|
api("POST", "/api/subscribe", form={"sr_name": subreddit, "action": "unsub" if unsubscribe else "sub"}, action="subscribe")
|
|
return {"subreddit": subreddit, "subscribed": not unsubscribe}
|
|
|
|
|
|
def compose(to: str, subject: str, text: str) -> dict:
|
|
p_check(api("POST", "/api/compose", form={"to": to, "subject": subject, "text": text, "api_type": "json"}, action="compose"))
|
|
return {"to": to, "sent": True}
|