12 tools for the new Confluence toolkit. | Name | Description | |----------------------------------|------------------------------------------------------------------------------------| | Confluence.CreatePage | Create a new page at the root of the given space. | | Confluence.UpdatePageContent | Update a page's content. | | Confluence.RenamePage | Rename a page by changing its title. | | Confluence.GetPage | Retrieve a SINGLE page's content by its ID or title. | | Confluence.GetPagesById | Get the content of MULTIPLE pages by their ID in a single efficient request. | | Confluence.ListPages | Get the content of multiple pages by their ID. | | Confluence.ListAttachments | List attachments in a workspace. | | Confluence.GetAttachmentsForPage | Get attachments for a page by its ID or title. | | Confluence.SearchContent | Search for content in Confluence. | | Confluence.GetSpace | Get the details of a space by its ID or key. | | Confluence.ListSpaces | List all spaces sorted by name in ascending order. | | Confluence.GetSpaceHierarchy | Retrieve the full hierarchical structure of a Confluence space as a tree structure.| ### Confluence Clients Confluence has deprecated most of their V1 endpoints, so most of the tools use V2. However, we still need a V1 client to support search. The V1 search API has not been deprecated yet, because there is no V2 equivalent. But we need to be aware in the future for when this deprecation happens. ### Future work * Content of pages are returned in the Confluence `storage` format. This is the format that Confluence uses to store pages internally. We should understand the storage format more deeply, and write utility function to transform this format into plain text. * Better protections against extremely large pages. I've tested up to 6,000 word pages. * Tools for blog posts * Tool for getting the children of a page. (`get_space_hierarchy` will suffice for now) * Allow for numerical titles
89 lines
3.1 KiB
Python
89 lines
3.1 KiB
Python
import re
|
|
|
|
from arcade.sdk.errors import RetryableToolError, ToolExecutionError
|
|
|
|
|
|
def remove_none_values(data: dict) -> dict:
|
|
"""Remove all keys with None values from the dictionary."""
|
|
return {k: v for k, v in data.items() if v is not None}
|
|
|
|
|
|
def validate_ids(ids: list[str] | None, max_length: int) -> None:
|
|
"""Validate a list of IDs. The ids can be page ids, space ids, etc.
|
|
|
|
A valid id is a string that is a number.
|
|
|
|
Args:
|
|
ids: A list of IDs to validate.
|
|
|
|
Returns:
|
|
None
|
|
|
|
Raises:
|
|
ToolExecutionError: If any of the IDs are not valid.
|
|
RetryableToolError: If the number of IDs is greater than the max length.
|
|
"""
|
|
if not ids:
|
|
return
|
|
if len(ids) > max_length:
|
|
raise RetryableToolError(
|
|
message=f"The 'ids' parameter must have less than {max_length} items. Got {len(ids)}"
|
|
)
|
|
if any(not id_.isdigit() for id_ in ids):
|
|
raise ToolExecutionError(message="Invalid ID provided. IDs are numeric")
|
|
|
|
|
|
def build_child_url(base_url: str, child: dict) -> str | None:
|
|
"""Build URL for a child node based on its type and status.
|
|
|
|
Args:
|
|
base_url: The base URL for the Confluence space
|
|
child: A dictionary representing a Confluence content item
|
|
|
|
Returns:
|
|
The URL for the child, or None if it can't be determined
|
|
"""
|
|
if child["type"] in ("whiteboard", "database", "embed"):
|
|
return f"{base_url}/{child['type']}/{child['id']}"
|
|
elif child["type"] == "folder":
|
|
return None
|
|
elif child["type"] == "page":
|
|
parsed_title = re.sub(r"[ '\s]+", "+", child["title"].strip())
|
|
if child.get("status") == "draft":
|
|
return f"{base_url}/{child['type']}s/edit-v2/{child['id']}"
|
|
else:
|
|
return f"{base_url}/{child['type']}s/{child['id']}/{parsed_title}"
|
|
return None
|
|
|
|
|
|
def build_hierarchy(transformed_children: list, parent_id: str, parent_node: dict) -> None:
|
|
"""Build parent-child hierarchy from a flat list of descendants.
|
|
|
|
This function takes a flat list of items that have parent_id references and
|
|
builds a hierarchical tree structure. It modifies the parent_node in place.
|
|
|
|
Args:
|
|
transformed_children: List of child nodes with parent_id fields
|
|
parent_id: The ID of the parent node
|
|
parent_node: The parent node to attach direct children to
|
|
|
|
Returns:
|
|
None (modifies parent_node in place)
|
|
"""
|
|
# Create a map of children by their ID for efficient lookups
|
|
child_map = {child["id"]: child for child in transformed_children}
|
|
|
|
# Find all direct children of the given parent_id
|
|
direct_children = []
|
|
for child in transformed_children:
|
|
if child.get("parent_id") == parent_id:
|
|
direct_children.append(child)
|
|
elif child.get("parent_id") in child_map:
|
|
# Add child to its parent's children list
|
|
parent = child_map[child.get("parent_id")]
|
|
if "children" not in parent:
|
|
parent["children"] = []
|
|
parent["children"].append(child)
|
|
|
|
# Set the direct children on the parent node
|
|
parent_node["children"] = direct_children
|