notionary 0.1.11__py3-none-any.whl → 0.1.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +13 -2
- notionary/core/converters/elements/audio_element.py +6 -4
- notionary/core/converters/elements/embed_element.py +2 -4
- notionary/core/converters/elements/toggle_element.py +28 -20
- notionary/core/converters/markdown_to_notion_converter.py +70 -109
- notionary/core/converters/registry/block_element_registry.py +3 -3
- notionary/core/database/database_discovery.py +140 -0
- notionary/core/database/notion_database_manager.py +26 -49
- notionary/core/database/notion_database_manager_factory.py +10 -4
- notionary/core/notion_client.py +4 -2
- notionary/core/page/content/notion_page_content_chunker.py +84 -0
- notionary/core/page/content/page_content_manager.py +26 -8
- notionary/core/page/metadata/metadata_editor.py +57 -44
- notionary/core/page/metadata/notion_icon_manager.py +9 -11
- notionary/core/page/metadata/notion_page_cover_manager.py +15 -20
- notionary/core/page/notion_page_manager.py +139 -149
- notionary/core/page/properites/database_property_service.py +114 -98
- notionary/core/page/properites/page_property_manager.py +78 -49
- notionary/core/page/properites/property_formatter.py +1 -1
- notionary/core/page/properites/property_operation_result.py +43 -30
- notionary/core/page/properites/property_value_extractor.py +26 -8
- notionary/core/page/relations/notion_page_relation_manager.py +71 -52
- notionary/core/page/relations/notion_page_title_resolver.py +11 -11
- notionary/core/page/relations/page_database_relation.py +14 -14
- notionary/core/page/relations/relation_operation_result.py +50 -41
- notionary/util/page_id_utils.py +11 -7
- {notionary-0.1.11.dist-info → notionary-0.1.12.dist-info}/METADATA +1 -1
- {notionary-0.1.11.dist-info → notionary-0.1.12.dist-info}/RECORD +31 -30
- notionary/core/database/notion_database_schema.py +0 -104
- {notionary-0.1.11.dist-info → notionary-0.1.12.dist-info}/WHEEL +0 -0
- {notionary-0.1.11.dist-info → notionary-0.1.12.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.1.11.dist-info → notionary-0.1.12.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,7 @@ from notionary.util.page_id_utils import format_uuid
|
|
9
9
|
class NotionDatabaseManager(LoggingMixin):
|
10
10
|
"""
|
11
11
|
Minimal manager for Notion databases.
|
12
|
-
Focused exclusively on creating basic pages and retrieving page managers
|
12
|
+
Focused exclusively on creating basic pages and retrieving page managers
|
13
13
|
for further page operations.
|
14
14
|
"""
|
15
15
|
|
@@ -24,61 +24,33 @@ class NotionDatabaseManager(LoggingMixin):
|
|
24
24
|
self.database_id = format_uuid(database_id) or database_id
|
25
25
|
self._client = NotionClient(token=token)
|
26
26
|
|
27
|
-
|
28
|
-
async def create_blank_page(self) -> Optional[str]:
|
27
|
+
async def create_blank_page(self) -> Optional[NotionPageManager]:
|
29
28
|
"""
|
30
29
|
Create a new blank page in the database with minimal properties.
|
31
|
-
|
30
|
+
|
32
31
|
Returns:
|
33
|
-
|
32
|
+
NotionPageManager for the created page, or None if creation failed
|
34
33
|
"""
|
35
34
|
try:
|
36
35
|
response = await self._client.post(
|
37
|
-
"pages",
|
38
|
-
{
|
39
|
-
"parent": {"database_id": self.database_id},
|
40
|
-
"properties": {}
|
41
|
-
}
|
36
|
+
"pages", {"parent": {"database_id": self.database_id}, "properties": {}}
|
42
37
|
)
|
43
|
-
|
38
|
+
|
44
39
|
if response and "id" in response:
|
45
40
|
page_id = response["id"]
|
46
|
-
self.logger.info(
|
47
|
-
|
48
|
-
|
41
|
+
self.logger.info(
|
42
|
+
"Created blank page %s in database %s", page_id, self.database_id
|
43
|
+
)
|
44
|
+
|
45
|
+
return NotionPageManager(page_id=page_id)
|
46
|
+
|
49
47
|
self.logger.warning("Page creation failed: invalid response")
|
50
48
|
return None
|
51
|
-
|
49
|
+
|
52
50
|
except Exception as e:
|
53
51
|
self.logger.error("Error creating blank page: %s", str(e))
|
54
52
|
return None
|
55
|
-
|
56
|
-
async def get_page_manager(self, page_id: str) -> Optional[NotionPageManager]:
|
57
|
-
"""
|
58
|
-
Get a NotionPageManager for a specific page.
|
59
|
-
|
60
|
-
Args:
|
61
|
-
page_id: The ID of the page
|
62
|
-
|
63
|
-
Returns:
|
64
|
-
NotionPageManager instance or None if the page wasn't found
|
65
|
-
"""
|
66
|
-
self.logger.debug("Getting page manager for page %s", page_id)
|
67
53
|
|
68
|
-
try:
|
69
|
-
# Check if the page exists
|
70
|
-
page_data = await self._client.get_page(page_id)
|
71
|
-
|
72
|
-
if not page_data:
|
73
|
-
self.logger.error("Page %s not found", page_id)
|
74
|
-
return None
|
75
|
-
|
76
|
-
return NotionPageManager(page_id=page_id)
|
77
|
-
|
78
|
-
except Exception as e:
|
79
|
-
self.logger.error("Error getting page manager: %s", str(e))
|
80
|
-
return None
|
81
|
-
|
82
54
|
async def get_pages(
|
83
55
|
self,
|
84
56
|
limit: int = 100,
|
@@ -174,10 +146,12 @@ class NotionDatabaseManager(LoggingMixin):
|
|
174
146
|
for page in result["results"]:
|
175
147
|
page_id: str = page.get("id", "")
|
176
148
|
title = self._extract_page_title(page)
|
177
|
-
|
149
|
+
|
178
150
|
page_url = f"https://notion.so/{page_id.replace('-', '')}"
|
179
151
|
|
180
|
-
notion_page_manager = NotionPageManager(
|
152
|
+
notion_page_manager = NotionPageManager(
|
153
|
+
page_id=page_id, title=title, url=page_url
|
154
|
+
)
|
181
155
|
yield notion_page_manager
|
182
156
|
|
183
157
|
# Update pagination parameters
|
@@ -222,10 +196,10 @@ class NotionDatabaseManager(LoggingMixin):
|
|
222
196
|
"""
|
223
197
|
try:
|
224
198
|
formatted_page_id = format_uuid(page_id) or page_id
|
225
|
-
|
199
|
+
|
226
200
|
# Archive the page (Notion's way of deleting)
|
227
201
|
data = {"archived": True}
|
228
|
-
|
202
|
+
|
229
203
|
result = await self._client.patch(f"pages/{formatted_page_id}", data)
|
230
204
|
if not result:
|
231
205
|
self.logger.error("Error deleting page %s", formatted_page_id)
|
@@ -233,14 +207,17 @@ class NotionDatabaseManager(LoggingMixin):
|
|
233
207
|
"success": False,
|
234
208
|
"message": f"Failed to delete page {formatted_page_id}",
|
235
209
|
}
|
236
|
-
|
237
|
-
self.logger.info(
|
210
|
+
|
211
|
+
self.logger.info(
|
212
|
+
"Page %s successfully deleted (archived)", formatted_page_id
|
213
|
+
)
|
238
214
|
return {"success": True, "page_id": formatted_page_id}
|
239
|
-
|
215
|
+
|
240
216
|
except Exception as e:
|
241
217
|
self.logger.error("Error in delete_page: %s", str(e))
|
242
218
|
return {"success": False, "message": f"Error: {str(e)}"}
|
243
219
|
|
244
220
|
async def close(self) -> None:
|
245
221
|
"""Close the client connection."""
|
246
|
-
await self._client.close()
|
222
|
+
await self._client.close()
|
223
|
+
|
@@ -44,11 +44,12 @@ class NotionDatabaseFactory(LoggingMixin):
|
|
44
44
|
|
45
45
|
try:
|
46
46
|
formatted_id = format_uuid(database_id) or database_id
|
47
|
-
|
47
|
+
|
48
48
|
manager = NotionDatabaseManager(formatted_id, token)
|
49
|
-
|
50
49
|
|
51
|
-
logger.info(
|
50
|
+
logger.info(
|
51
|
+
"Successfully created database manager for ID: %s", formatted_id
|
52
|
+
)
|
52
53
|
return manager
|
53
54
|
|
54
55
|
except DatabaseInitializationError:
|
@@ -136,7 +137,12 @@ class NotionDatabaseFactory(LoggingMixin):
|
|
136
137
|
|
137
138
|
matched_name = cls._extract_title_from_database(best_match)
|
138
139
|
|
139
|
-
logger.info(
|
140
|
+
logger.info(
|
141
|
+
"Found matching database: '%s' (ID: %s) with score: %.2f",
|
142
|
+
matched_name,
|
143
|
+
database_id,
|
144
|
+
best_score,
|
145
|
+
)
|
140
146
|
|
141
147
|
manager = NotionDatabaseManager(database_id, token)
|
142
148
|
|
notionary/core/notion_client.py
CHANGED
@@ -7,6 +7,7 @@ import httpx
|
|
7
7
|
from dotenv import load_dotenv
|
8
8
|
from notionary.util.logging_mixin import LoggingMixin
|
9
9
|
|
10
|
+
|
10
11
|
class HttpMethod(Enum):
|
11
12
|
"""Enum für HTTP-Methoden."""
|
12
13
|
|
@@ -15,6 +16,7 @@ class HttpMethod(Enum):
|
|
15
16
|
PATCH = "patch"
|
16
17
|
DELETE = "delete"
|
17
18
|
|
19
|
+
|
18
20
|
class NotionClient(LoggingMixin):
|
19
21
|
"""Verbesserter Notion-Client mit automatischer Ressourcenverwaltung."""
|
20
22
|
|
@@ -50,7 +52,7 @@ class NotionClient(LoggingMixin):
|
|
50
52
|
|
51
53
|
async def get(self, endpoint: str) -> Optional[Dict[str, Any]]:
|
52
54
|
return await self._make_request(HttpMethod.GET, endpoint)
|
53
|
-
|
55
|
+
|
54
56
|
async def get_page(self, page_id: str) -> Optional[Dict[str, Any]]:
|
55
57
|
return await self.get(f"pages/{page_id}")
|
56
58
|
|
@@ -126,4 +128,4 @@ class NotionClient(LoggingMixin):
|
|
126
128
|
loop.create_task(self.close())
|
127
129
|
self.logger.debug("Created cleanup task for NotionClient")
|
128
130
|
except RuntimeError:
|
129
|
-
self.logger.warning("No event loop available for auto-closing NotionClient")
|
131
|
+
self.logger.warning("No event loop available for auto-closing NotionClient")
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Any, Dict, List
|
3
|
+
from notionary.util.logging_mixin import LoggingMixin
|
4
|
+
|
5
|
+
|
6
|
+
class NotionPageContentChunker(LoggingMixin):
|
7
|
+
"""
|
8
|
+
Handles markdown text processing to comply with Notion API length limitations.
|
9
|
+
|
10
|
+
This class specifically addresses the Notion API constraint that limits
|
11
|
+
rich_text elements to a maximum of 2000 characters. This particularly affects
|
12
|
+
paragraph blocks within toggle blocks or other nested structures.
|
13
|
+
|
14
|
+
Resolves the following typical API error:
|
15
|
+
"validation_error - body.children[79].toggle.children[2].paragraph.rich_text[0].text.content.length
|
16
|
+
should be ≤ 2000, instead was 2162."
|
17
|
+
|
18
|
+
The class provides methods for:
|
19
|
+
1. Automatically truncating text that exceeds the limit
|
20
|
+
2. Splitting markdown into smaller units for separate API requests
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, max_text_length: int = 1900):
|
24
|
+
self.max_text_length = max_text_length
|
25
|
+
|
26
|
+
def fix_blocks_content_length(
|
27
|
+
self, blocks: List[Dict[str, Any]]
|
28
|
+
) -> List[Dict[str, Any]]:
|
29
|
+
"""Check each block and ensure text content doesn't exceed Notion's limit."""
|
30
|
+
return [self._fix_single_block_content(block) for block in blocks]
|
31
|
+
|
32
|
+
def _fix_single_block_content(self, block: Dict[str, Any]) -> Dict[str, Any]:
|
33
|
+
"""Fix content length in a single block and its children recursively."""
|
34
|
+
block_copy = block.copy()
|
35
|
+
|
36
|
+
block_type = block.get("type")
|
37
|
+
if not block_type:
|
38
|
+
return block_copy
|
39
|
+
|
40
|
+
content = block.get(block_type)
|
41
|
+
if not content:
|
42
|
+
return block_copy
|
43
|
+
|
44
|
+
if "rich_text" in content:
|
45
|
+
self._fix_rich_text_content(block_copy, block_type, content)
|
46
|
+
|
47
|
+
if "children" in content and content["children"]:
|
48
|
+
block_copy[block_type]["children"] = [
|
49
|
+
self._fix_single_block_content(child) for child in content["children"]
|
50
|
+
]
|
51
|
+
|
52
|
+
return block_copy
|
53
|
+
|
54
|
+
def _fix_rich_text_content(
|
55
|
+
self, block_copy: Dict[str, Any], block_type: str, content: Dict[str, Any]
|
56
|
+
) -> None:
|
57
|
+
"""Fix rich text content that exceeds the length limit."""
|
58
|
+
rich_text = content["rich_text"]
|
59
|
+
for i, text_item in enumerate(rich_text):
|
60
|
+
if "text" not in text_item or "content" not in text_item["text"]:
|
61
|
+
continue
|
62
|
+
|
63
|
+
text_content = text_item["text"]["content"]
|
64
|
+
if len(text_content) <= self.max_text_length:
|
65
|
+
continue
|
66
|
+
|
67
|
+
self.logger.warning(
|
68
|
+
"Truncating text content from %d to %d chars",
|
69
|
+
len(text_content),
|
70
|
+
self.max_text_length,
|
71
|
+
)
|
72
|
+
block_copy[block_type]["rich_text"][i]["text"]["content"] = text_content[
|
73
|
+
: self.max_text_length
|
74
|
+
]
|
75
|
+
|
76
|
+
def split_to_paragraphs(self, markdown_text: str) -> List[str]:
|
77
|
+
"""Split markdown into paragraphs."""
|
78
|
+
paragraphs = re.split(r"\n\s*\n", markdown_text)
|
79
|
+
return [p for p in paragraphs if p.strip()]
|
80
|
+
|
81
|
+
def split_to_sentences(self, paragraph: str) -> List[str]:
|
82
|
+
"""Split a paragraph into sentences."""
|
83
|
+
sentences = re.split(r"(?<=[.!?])\s+", paragraph)
|
84
|
+
return [s for s in sentences if s.strip()]
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import re
|
1
2
|
from typing import Any, Dict, List, Optional
|
2
3
|
|
3
4
|
from notionary.core.converters.markdown_to_notion_converter import (
|
@@ -10,6 +11,9 @@ from notionary.core.converters.registry.block_element_registry import (
|
|
10
11
|
BlockElementRegistry,
|
11
12
|
)
|
12
13
|
from notionary.core.notion_client import NotionClient
|
14
|
+
from notionary.core.page.content.notion_page_content_chunker import (
|
15
|
+
NotionPageContentChunker,
|
16
|
+
)
|
13
17
|
from notionary.util.logging_mixin import LoggingMixin
|
14
18
|
|
15
19
|
|
@@ -28,15 +32,29 @@ class PageContentManager(LoggingMixin):
|
|
28
32
|
self._notion_to_markdown_converter = NotionToMarkdownConverter(
|
29
33
|
block_registry=block_registry
|
30
34
|
)
|
35
|
+
self._chunker = NotionPageContentChunker()
|
31
36
|
|
32
37
|
async def append_markdown(self, markdown_text: str) -> str:
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
"""
|
39
|
+
Append markdown text to a Notion page, automatically handling content length limits.
|
40
|
+
"""
|
41
|
+
try:
|
42
|
+
blocks = self._markdown_to_notion_converter.convert(markdown_text)
|
43
|
+
|
44
|
+
# Fix any blocks that exceed Notion's content length limits
|
45
|
+
fixed_blocks = self._chunker.fix_blocks_content_length(blocks)
|
46
|
+
|
47
|
+
result = await self._client.patch(
|
48
|
+
f"blocks/{self.page_id}/children", {"children": fixed_blocks}
|
49
|
+
)
|
50
|
+
return (
|
51
|
+
"Successfully added text to the page."
|
52
|
+
if result
|
53
|
+
else "Failed to add text."
|
54
|
+
)
|
55
|
+
except Exception as e:
|
56
|
+
self.logger.error("Error appending markdown: %s", str(e))
|
57
|
+
raise
|
40
58
|
|
41
59
|
async def clear(self) -> str:
|
42
60
|
blocks = await self._client.get(f"blocks/{self.page_id}/children")
|
@@ -53,7 +71,7 @@ class PageContentManager(LoggingMixin):
|
|
53
71
|
if block.get("type") in ["child_database", "database", "linked_database"]:
|
54
72
|
skipped += 1
|
55
73
|
continue
|
56
|
-
|
74
|
+
|
57
75
|
if await self._client.delete(f"blocks/{block['id']}"):
|
58
76
|
deleted += 1
|
59
77
|
|
@@ -20,90 +20,103 @@ class MetadataEditor(LoggingMixin):
|
|
20
20
|
},
|
21
21
|
)
|
22
22
|
|
23
|
-
async def set_property(
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
"properties": {
|
45
|
-
property_name: property_payload
|
46
|
-
}
|
47
|
-
},
|
23
|
+
async def set_property(
|
24
|
+
self, property_name: str, property_value: Any, property_type: str
|
25
|
+
) -> Optional[Dict[str, Any]]:
|
26
|
+
"""
|
27
|
+
Generic method to set any property on a Notion page.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
property_name: The name of the property in Notion
|
31
|
+
property_value: The value to set
|
32
|
+
property_type: The type of property ('select', 'multi_select', 'status', 'relation', etc.)
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
Optional[Dict[str, Any]]: The API response or None if the operation fails
|
36
|
+
"""
|
37
|
+
property_payload = self._property_formatter.format_value(
|
38
|
+
property_type, property_value
|
39
|
+
)
|
40
|
+
|
41
|
+
if not property_payload:
|
42
|
+
self.logger.warning(
|
43
|
+
"Could not create payload for property type: %s", property_type
|
48
44
|
)
|
45
|
+
return None
|
49
46
|
|
47
|
+
return await self._client.patch(
|
48
|
+
f"pages/{self.page_id}",
|
49
|
+
{"properties": {property_name: property_payload}},
|
50
|
+
)
|
50
51
|
|
51
52
|
async def get_property_schema(self) -> Dict[str, Dict[str, Any]]:
|
52
53
|
"""
|
53
54
|
Retrieves the schema for all properties of the page.
|
54
|
-
|
55
|
+
|
55
56
|
Returns:
|
56
57
|
Dict[str, Dict[str, Any]]: A dictionary mapping property names to their schema
|
57
58
|
"""
|
58
59
|
page_data = await self._client.get_page(self.page_id)
|
59
60
|
property_schema = {}
|
60
|
-
|
61
|
+
|
61
62
|
if not page_data or "properties" not in page_data:
|
62
63
|
return property_schema
|
63
|
-
|
64
|
+
|
64
65
|
for prop_name, prop_data in page_data["properties"].items():
|
65
66
|
prop_type = prop_data.get("type")
|
66
67
|
property_schema[prop_name] = {
|
67
68
|
"id": prop_data.get("id"),
|
68
69
|
"type": prop_type,
|
69
|
-
"name": prop_name
|
70
|
+
"name": prop_name,
|
70
71
|
}
|
71
|
-
|
72
|
+
|
72
73
|
try:
|
73
74
|
if prop_type == "select" and "select" in prop_data:
|
74
75
|
# Make sure prop_data["select"] is a dictionary before calling .get()
|
75
76
|
if isinstance(prop_data["select"], dict):
|
76
|
-
property_schema[prop_name]["options"] = prop_data["select"].get(
|
77
|
+
property_schema[prop_name]["options"] = prop_data["select"].get(
|
78
|
+
"options", []
|
79
|
+
)
|
77
80
|
elif prop_type == "multi_select" and "multi_select" in prop_data:
|
78
81
|
# Make sure prop_data["multi_select"] is a dictionary before calling .get()
|
79
82
|
if isinstance(prop_data["multi_select"], dict):
|
80
|
-
property_schema[prop_name]["options"] = prop_data[
|
83
|
+
property_schema[prop_name]["options"] = prop_data[
|
84
|
+
"multi_select"
|
85
|
+
].get("options", [])
|
81
86
|
elif prop_type == "status" and "status" in prop_data:
|
82
87
|
# Make sure prop_data["status"] is a dictionary before calling .get()
|
83
88
|
if isinstance(prop_data["status"], dict):
|
84
|
-
property_schema[prop_name]["options"] = prop_data["status"].get(
|
89
|
+
property_schema[prop_name]["options"] = prop_data["status"].get(
|
90
|
+
"options", []
|
91
|
+
)
|
85
92
|
except Exception as e:
|
86
|
-
if hasattr(self,
|
87
|
-
self.logger.warning(
|
88
|
-
|
93
|
+
if hasattr(self, "logger") and self.logger:
|
94
|
+
self.logger.warning(
|
95
|
+
"Error processing property schema for '%s': %s", prop_name, e
|
96
|
+
)
|
97
|
+
|
89
98
|
return property_schema
|
90
|
-
|
91
|
-
async def set_property_by_name(
|
99
|
+
|
100
|
+
async def set_property_by_name(
|
101
|
+
self, property_name: str, value: Any
|
102
|
+
) -> Optional[Dict[str, Any]]:
|
92
103
|
"""
|
93
104
|
Sets a property value based on the property name, automatically detecting the property type.
|
94
|
-
|
105
|
+
|
95
106
|
Args:
|
96
107
|
property_name: The name of the property in Notion
|
97
108
|
value: The value to set
|
98
|
-
|
109
|
+
|
99
110
|
Returns:
|
100
111
|
Optional[Dict[str, Any]]: The API response or None if the operation fails
|
101
112
|
"""
|
102
113
|
property_schema = await self.get_property_schema()
|
103
|
-
|
114
|
+
|
104
115
|
if property_name not in property_schema:
|
105
|
-
self.logger.warning(
|
116
|
+
self.logger.warning(
|
117
|
+
"Property '%s' not found in database schema", property_name
|
118
|
+
)
|
106
119
|
return None
|
107
|
-
|
120
|
+
|
108
121
|
property_type = property_schema[property_name]["type"]
|
109
|
-
return await self.set_property(property_name, value, property_type)
|
122
|
+
return await self.set_property(property_name, value, property_type)
|
@@ -3,11 +3,12 @@ from typing import Any, Dict, Optional
|
|
3
3
|
from notionary.core.notion_client import NotionClient
|
4
4
|
from notionary.util.logging_mixin import LoggingMixin
|
5
5
|
|
6
|
+
|
6
7
|
class NotionPageIconManager(LoggingMixin):
|
7
8
|
def __init__(self, page_id: str, client: NotionClient):
|
8
9
|
self.page_id = page_id
|
9
10
|
self._client = client
|
10
|
-
|
11
|
+
|
11
12
|
async def set_icon(
|
12
13
|
self, emoji: Optional[str] = None, external_url: Optional[str] = None
|
13
14
|
) -> Optional[Dict[str, Any]]:
|
@@ -19,28 +20,25 @@ class NotionPageIconManager(LoggingMixin):
|
|
19
20
|
return None
|
20
21
|
|
21
22
|
return await self._client.patch(f"pages/{self.page_id}", {"icon": icon})
|
22
|
-
|
23
|
-
|
23
|
+
|
24
24
|
async def get_icon(self) -> Optional[str]:
|
25
25
|
"""
|
26
26
|
Retrieves the page icon - either emoji or external URL.
|
27
|
-
|
27
|
+
|
28
28
|
Returns:
|
29
29
|
str: Emoji character or URL if set, None if no icon
|
30
30
|
"""
|
31
31
|
page_data = await self._client.get_page(self.page_id)
|
32
|
-
|
32
|
+
|
33
33
|
if not page_data or "icon" not in page_data:
|
34
34
|
return None
|
35
|
-
|
35
|
+
|
36
36
|
icon_data = page_data.get("icon", {})
|
37
37
|
icon_type = icon_data.get("type")
|
38
|
-
|
38
|
+
|
39
39
|
if icon_type == "emoji":
|
40
40
|
return icon_data.get("emoji")
|
41
41
|
elif icon_type == "external":
|
42
42
|
return icon_data.get("external", {}).get("url")
|
43
|
-
|
44
|
-
return None
|
45
|
-
|
46
|
-
|
43
|
+
|
44
|
+
return None
|
@@ -1,48 +1,43 @@
|
|
1
|
-
|
2
1
|
import random
|
3
2
|
from typing import Any, Dict, Optional
|
4
3
|
from notionary.core.notion_client import NotionClient
|
5
4
|
from notionary.util.logging_mixin import LoggingMixin
|
6
5
|
|
6
|
+
|
7
7
|
class NotionPageCoverManager(LoggingMixin):
|
8
8
|
def __init__(self, page_id: str, client: NotionClient):
|
9
9
|
self.page_id = page_id
|
10
10
|
self._client = client
|
11
|
-
|
11
|
+
|
12
12
|
async def set_cover(self, external_url: str) -> Optional[Dict[str, Any]]:
|
13
|
-
"""Sets a cover image from an external URL.
|
14
|
-
|
15
|
-
|
13
|
+
"""Sets a cover image from an external URL."""
|
14
|
+
|
16
15
|
return await self._client.patch(
|
17
16
|
f"pages/{self.page_id}",
|
18
17
|
{"cover": {"type": "external", "external": {"url": external_url}}},
|
19
18
|
)
|
20
|
-
|
19
|
+
|
21
20
|
async def set_random_gradient_cover(self) -> Optional[Dict[str, Any]]:
|
22
|
-
"""
|
23
|
-
"""
|
21
|
+
"""Sets a random gradient cover from Notion's default gradient covers."""
|
24
22
|
default_notion_covers = [
|
25
|
-
"https://www.notion.so/images/page-cover/gradients_8.png",
|
23
|
+
"https://www.notion.so/images/page-cover/gradients_8.png",
|
26
24
|
"https://www.notion.so/images/page-cover/gradients_2.png",
|
27
25
|
"https://www.notion.so/images/page-cover/gradients_11.jpg",
|
28
26
|
"https://www.notion.so/images/page-cover/gradients_10.jpg",
|
29
27
|
"https://www.notion.so/images/page-cover/gradients_5.png",
|
30
|
-
"https://www.notion.so/images/page-cover/gradients_3.png"
|
28
|
+
"https://www.notion.so/images/page-cover/gradients_3.png",
|
31
29
|
]
|
32
|
-
|
30
|
+
|
33
31
|
random_cover_url = random.choice(default_notion_covers)
|
34
|
-
|
32
|
+
|
35
33
|
return await self.set_cover(random_cover_url)
|
36
|
-
|
37
|
-
|
34
|
+
|
38
35
|
async def get_cover_url(self) -> str:
|
39
|
-
"""Retrieves the current cover image URL of the page.
|
40
|
-
|
41
|
-
|
36
|
+
"""Retrieves the current cover image URL of the page."""
|
37
|
+
|
42
38
|
page_data = await self._client.get_page(self.page_id)
|
43
|
-
|
39
|
+
|
44
40
|
if not page_data:
|
45
41
|
return ""
|
46
|
-
|
42
|
+
|
47
43
|
return page_data.get("cover", {}).get("external", {}).get("url", "")
|
48
|
-
|