mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-13 21:27:41 +02:00
79 lines
3.4 KiB
Python
79 lines
3.4 KiB
Python
"""Write operations for X, driven through the user's own logged-in x.com card.
|
|
|
|
Navigate the real card and click/type via the perform_action bridge, so X's own browser
|
|
generates the request signature we can't forge from HTTP. Targets are tweet URLs from the
|
|
read tools. tweet/reply/quote/like/retweet/follow are wired; DM stays a card-only action.
|
|
"""
|
|
|
|
from typing import Any, Dict
|
|
|
|
from backend.apps.social_shims.browser_action import BrowserActionError, last_json, perform
|
|
from backend.apps.x_mcp_shim.x_dom import (
|
|
click_action_js,
|
|
follow_js,
|
|
open_reply_js,
|
|
post_text_js,
|
|
retweet_js,
|
|
)
|
|
from backend.apps.x_mcp_shim.x_reads import tweet_id_of
|
|
|
|
DOMAIN = "x.com"
|
|
|
|
|
|
def p_url(target: str) -> str:
|
|
return target if str(target).startswith("http") else f"https://x.com/i/status/{tweet_id_of(target)}"
|
|
|
|
|
|
def tweet(text: str, reply_to: str, quote_id: str) -> Dict[str, Any]:
|
|
if reply_to:
|
|
steps = [
|
|
{"op": "navigate", "url": p_url(reply_to)},
|
|
{"op": "wait", "ms": 2800},
|
|
{"op": "evaluate", "expression": open_reply_js()},
|
|
{"op": "evaluate", "expression": post_text_js(text, "tweetButton")},
|
|
]
|
|
return {"replied_to": reply_to, "result": last_json(perform(DOMAIN, steps))}
|
|
body = text if not quote_id else f"{text} {p_url(quote_id)}".strip()
|
|
steps = [
|
|
{"op": "navigate", "url": "https://x.com/compose/post"},
|
|
{"op": "wait", "ms": 2500},
|
|
{"op": "evaluate", "expression": post_text_js(body, "tweetButton")},
|
|
]
|
|
return {"posted": True, "quote": bool(quote_id), "result": last_json(perform(DOMAIN, steps))}
|
|
|
|
|
|
def like(target: str, unlike: bool) -> Dict[str, Any]:
|
|
js = click_action_js(["unlike"] if unlike else ["like"], "unlike" if not unlike else "like", "unlike" if unlike else "like")
|
|
steps = [{"op": "navigate", "url": p_url(target)}, {"op": "wait", "ms": 2600}, {"op": "evaluate", "expression": js}]
|
|
return {"target": target, "liked": not unlike, "result": last_json(perform(DOMAIN, steps))}
|
|
|
|
|
|
def retweet(target: str, undo: bool) -> Dict[str, Any]:
|
|
steps = [{"op": "navigate", "url": p_url(target)}, {"op": "wait", "ms": 2600}, {"op": "evaluate", "expression": retweet_js(undo)}]
|
|
return {"target": target, "retweeted": not undo, "result": last_json(perform(DOMAIN, steps))}
|
|
|
|
|
|
def follow(username: str, unfollow: bool) -> Dict[str, Any]:
|
|
h = username.lstrip("@")
|
|
steps = [{"op": "navigate", "url": f"https://x.com/{h}"}, {"op": "wait", "ms": 2600}, {"op": "evaluate", "expression": follow_js(unfollow)}]
|
|
return {"username": h, "following": not unfollow, "result": last_json(perform(DOMAIN, steps))}
|
|
|
|
|
|
def delete_tweet(target: str) -> Dict[str, Any]:
|
|
raise BrowserActionError(
|
|
f"Deleting a tweet needs the caret menu + a confirm dialog that's risky to click blind. "
|
|
f"Open {p_url(target)} in your X card and delete it there."
|
|
)
|
|
|
|
|
|
def send_dm(recipient: str, text: str) -> Dict[str, Any]:
|
|
raise BrowserActionError(
|
|
f"DMs aren't wired for browser-driving yet. Open https://x.com/messages in your X card to DM {recipient!r}."
|
|
)
|
|
|
|
|
|
def bookmark(target: str, remove: bool) -> Dict[str, Any]:
|
|
js = click_action_js(["removeBookmark"] if remove else ["bookmark"], "bookmark", "bookmark")
|
|
steps = [{"op": "navigate", "url": p_url(target)}, {"op": "wait", "ms": 2600}, {"op": "evaluate", "expression": js}]
|
|
return {"target": target, "bookmarked": not remove, "result": last_json(perform(DOMAIN, steps))}
|