feat(wall-posts): add wall post reading/writing with comments (v1.3.0)
- Add get_wall_posts: read recent wall posts with like/comment counts - Add create_wall_post: publish new status posts to the wall - Add add_comment: add comments to wall posts and activities - like_post already supports both wall posts and activities (v1.2.0) - Update README.md with new Wall & Activities section - Update CLAUDE.md with v1.3.0 and tool reorganization - Update CHANGELOG.md with v1.3.0 release notes - Add wallpublish and walladdComment documentation to SPEC.md Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1974,6 +1974,218 @@ def like_post(post_id: str, like: bool = True, mood: str = "STAR") -> str:
|
||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: get_wall_posts
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_wall_posts(limit: int = 20) -> str:
|
||||
"""Return recent Family Wall posts as JSON.
|
||||
|
||||
Returns posts from the primary circle's wall, sorted by date descending.
|
||||
Includes status posts, activity entries, comments, and like information.
|
||||
|
||||
Args:
|
||||
limit: Maximum number of posts to return (default 20).
|
||||
|
||||
Returns:
|
||||
JSON list of post objects with keys:
|
||||
id, type, text, date, author, author_id,
|
||||
liked_by_me, like_count, comment_count.
|
||||
"""
|
||||
try:
|
||||
email, password = get_credentials()
|
||||
except RuntimeError as exc:
|
||||
return _err(str(exc))
|
||||
|
||||
author_map: dict[str, str] = {}
|
||||
try:
|
||||
for circle in _famlistfamily():
|
||||
for member in circle.get("members") or []:
|
||||
acc_id: str = member.get("accountId", "")
|
||||
display = member.get("firstName") or member.get("name") or acc_id
|
||||
if acc_id:
|
||||
author_map[acc_id] = display
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
with FamilyWallClient() as client:
|
||||
client.login(email, password)
|
||||
data = client.call("wallget", {"nb": str(limit)})
|
||||
client.logout()
|
||||
except FamilyWallError as exc:
|
||||
return _err(str(exc))
|
||||
except Exception as exc:
|
||||
return _err(f"Connection error: {exc}")
|
||||
|
||||
raw_posts: list[dict[str, Any]] | None = None
|
||||
try:
|
||||
candidate = data["a00"]["r"]["r"]
|
||||
if isinstance(candidate, list):
|
||||
raw_posts = candidate
|
||||
elif isinstance(candidate, dict) and isinstance(candidate.get("updatedCreated"), list):
|
||||
raw_posts = candidate["updatedCreated"]
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
if raw_posts is None:
|
||||
return json.dumps(
|
||||
{"warning": "Unexpected wallget response structure", "raw": data},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
result = []
|
||||
for item in raw_posts:
|
||||
raw_author: str = item.get("accountId", "")
|
||||
mood_map: dict[str, Any] = item.get("moodMap") or {}
|
||||
|
||||
liked_by_me = (
|
||||
item.get("moodStarShortcut") == "true"
|
||||
or any("STAR" in moods for moods in mood_map.values())
|
||||
)
|
||||
|
||||
like_count = sum(len(moods) for moods in mood_map.values() if isinstance(moods, list))
|
||||
|
||||
comments: list[dict[str, Any]] = item.get("comments") or []
|
||||
comment_count = len(comments)
|
||||
|
||||
result.append(
|
||||
{
|
||||
"id": item.get("metaId"),
|
||||
"type": item.get("refType"),
|
||||
"text": item.get("text") or item.get("tagline"),
|
||||
"date": item.get("creationDate"),
|
||||
"author": author_map.get(raw_author, raw_author),
|
||||
"author_id": raw_author,
|
||||
"liked_by_me": liked_by_me,
|
||||
"like_count": like_count,
|
||||
"comment_count": comment_count,
|
||||
}
|
||||
)
|
||||
|
||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: create_wall_post
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def create_wall_post(text: str) -> str:
|
||||
"""Create a new status post on the Family Wall.
|
||||
|
||||
IMPORTANT: Ask the user for confirmation before calling this tool.
|
||||
|
||||
Args:
|
||||
text: Text content of the post.
|
||||
|
||||
Returns:
|
||||
JSON with the new post's metaId on success, or an error message.
|
||||
"""
|
||||
try:
|
||||
email, password = get_credentials()
|
||||
except RuntimeError as exc:
|
||||
return _err(str(exc))
|
||||
|
||||
try:
|
||||
with FamilyWallClient() as client:
|
||||
client.login(email, password)
|
||||
data = client.call("wallpublish", {"tagline": text})
|
||||
client.logout()
|
||||
except FamilyWallError as exc:
|
||||
return _err(str(exc))
|
||||
except Exception as exc:
|
||||
return _err(f"Connection error: {exc}")
|
||||
|
||||
try:
|
||||
post = data["a00"]["r"]["r"]
|
||||
if not isinstance(post, dict) or "metaId" not in post:
|
||||
raise TypeError("unexpected shape")
|
||||
except (KeyError, TypeError):
|
||||
return json.dumps(
|
||||
{"warning": "Unexpected wallpublish response structure", "raw": data},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"id": post.get("metaId"),
|
||||
"text": post.get("tagline") or post.get("text"),
|
||||
"date": post.get("creationDate"),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: add_comment
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def add_comment(post_id: str, comment: str) -> str:
|
||||
"""Add a comment to a wall post or activity.
|
||||
|
||||
IMPORTANT: Ask the user for confirmation before calling this tool.
|
||||
|
||||
Args:
|
||||
post_id: Post metaId from get_wall_posts
|
||||
(e.g. ``"wall/16282169_31119189"``).
|
||||
comment: Comment text.
|
||||
|
||||
Returns:
|
||||
JSON success indicator or an error message.
|
||||
"""
|
||||
try:
|
||||
email, password = get_credentials()
|
||||
except RuntimeError as exc:
|
||||
return _err(str(exc))
|
||||
|
||||
try:
|
||||
with FamilyWallClient() as client:
|
||||
client.login(email, password)
|
||||
data = client.call(
|
||||
"walladdComment",
|
||||
{
|
||||
"wall_message_id": post_id,
|
||||
"comment": comment,
|
||||
},
|
||||
)
|
||||
client.logout()
|
||||
except FamilyWallError as exc:
|
||||
return _err(str(exc))
|
||||
except Exception as exc:
|
||||
return _err(f"Connection error: {exc}")
|
||||
|
||||
try:
|
||||
comment_obj = data["a00"]["r"]["r"]
|
||||
if not isinstance(comment_obj, dict) or "metaId" not in comment_obj:
|
||||
raise TypeError("unexpected shape")
|
||||
except (KeyError, TypeError):
|
||||
return json.dumps(
|
||||
{"warning": "Unexpected walladdComment response structure", "raw": data},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{
|
||||
"id": comment_obj.get("metaId"),
|
||||
"post_id": post_id,
|
||||
"text": comment_obj.get("text") or comment_obj.get("comment"),
|
||||
"date": comment_obj.get("creationDate"),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helper: fetch all raw recipes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user