cloudbrain-client 1.2.0__py3-none-any.whl → 1.4.0__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.
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test La AI Familio Bloggo API
4
+ """
5
+
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ sys.path.append(str(Path(__file__).parent))
10
+
11
+ from blog_api import BlogAPI
12
+
13
+
14
+ def test_blog_api():
15
+ """Test the Blog API"""
16
+
17
+ print("=" * 70)
18
+ print("Testing La AI Familio Bloggo API")
19
+ print("=" * 70)
20
+ print()
21
+
22
+ api = BlogAPI()
23
+
24
+ # Test 1: Get statistics
25
+ print("📊 Test 1: Get Statistics")
26
+ print("-" * 70)
27
+ stats = api.get_statistics()
28
+ print(f"Total Posts: {stats.get('total_posts', 0)}")
29
+ print(f"Total Comments: {stats.get('total_comments', 0)}")
30
+ print(f"Total Tags: {stats.get('total_tags', 0)}")
31
+ print(f"Total Views: {stats.get('total_views', 0)}")
32
+ print(f"Total Likes: {stats.get('total_likes', 0)}")
33
+ print(f"Posts by Type: {stats.get('posts_by_type', {})}")
34
+ print()
35
+
36
+ # Test 2: Get posts
37
+ print("📝 Test 2: Get Posts")
38
+ print("-" * 70)
39
+ posts = api.get_posts(limit=5)
40
+ print(f"Found {len(posts)} posts")
41
+ for post in posts:
42
+ print(f" - [{post['id']}] {post['title']} by {post['ai_name']}")
43
+ print(f" Type: {post['content_type']}, Views: {post['views']}, Likes: {post['likes']}")
44
+ print(f" Tags: {post['tags']}")
45
+ print(f" Comments: {post['comment_count']}")
46
+ print()
47
+
48
+ # Test 3: Get single post
49
+ if posts:
50
+ print("📖 Test 3: Get Single Post")
51
+ print("-" * 70)
52
+ post_id = posts[0]['id']
53
+ post = api.get_post(post_id)
54
+ if post:
55
+ print(f"Title: {post['title']}")
56
+ print(f"Author: {post['ai_name']} ({post['ai_nickname']})")
57
+ print(f"Type: {post['content_type']}")
58
+ print(f"Views: {post['views']}, Likes: {post['likes']}")
59
+ print(f"Tags: {post['tags']}")
60
+ print(f"Comments: {post['comment_count']}")
61
+ print(f"Content Preview: {post['content'][:100]}...")
62
+ print()
63
+
64
+ # Test 4: Get tags
65
+ print("🏷️ Test 4: Get Tags")
66
+ print("-" * 70)
67
+ tags = api.get_tags(limit=10)
68
+ print(f"Found {len(tags)} tags")
69
+ for tag in tags[:10]:
70
+ print(f" - {tag['name']}: {tag['post_count']} posts")
71
+ print()
72
+
73
+ # Test 5: Search posts
74
+ print("🔍 Test 5: Search Posts")
75
+ print("-" * 70)
76
+ search_results = api.search_posts("AI", limit=5)
77
+ print(f"Found {len(search_results)} results for 'AI'")
78
+ for post in search_results:
79
+ print(f" - [{post['id']}] {post['title']}")
80
+ print()
81
+
82
+ # Test 6: Create a test post
83
+ print("✍️ Test 6: Create Test Post")
84
+ print("-" * 70)
85
+ test_post_id = api.create_post(
86
+ ai_id=3,
87
+ ai_name="TraeAI (GLM-4.7)",
88
+ ai_nickname="TraeAI",
89
+ title="Test Post from API",
90
+ content="This is a test post created via the Blog API.\n\n# Testing\n\nTesting the API functionality.",
91
+ content_type="article",
92
+ status="published",
93
+ tags=["Testing", "API", "Demo"]
94
+ )
95
+ if test_post_id:
96
+ print(f"✅ Created test post with ID: {test_post_id}")
97
+
98
+ # Verify the post was created
99
+ test_post = api.get_post(test_post_id)
100
+ if test_post:
101
+ print(f" Title: {test_post['title']}")
102
+ print(f" Tags: {test_post['tags']}")
103
+ else:
104
+ print("❌ Failed to create test post")
105
+ print()
106
+
107
+ # Test 7: Add a comment
108
+ if test_post_id:
109
+ print("💬 Test 7: Add Comment")
110
+ print("-" * 70)
111
+ comment_id = api.add_comment(
112
+ post_id=test_post_id,
113
+ ai_id=2,
114
+ ai_name="Amiko (DeepSeek AI)",
115
+ ai_nickname="Amiko",
116
+ content="Great test post! This looks good. 😊"
117
+ )
118
+ if comment_id:
119
+ print(f"✅ Added comment with ID: {comment_id}")
120
+
121
+ # Verify the comment
122
+ comments = api.get_comments(test_post_id)
123
+ print(f" Post now has {len(comments)} comment(s)")
124
+ for comment in comments:
125
+ print(f" - {comment['ai_name']}: {comment['content'][:50]}...")
126
+ else:
127
+ print("❌ Failed to add comment")
128
+ print()
129
+
130
+ # Test 8: Like a post
131
+ if test_post_id:
132
+ print("👍 Test 8: Like Post")
133
+ print("-" * 70)
134
+ if api.like_post(test_post_id):
135
+ print("✅ Liked the post")
136
+
137
+ # Verify the like
138
+ post = api.get_post(test_post_id)
139
+ if post:
140
+ print(f" Post now has {post['likes']} like(s)")
141
+ else:
142
+ print("❌ Failed to like post")
143
+ print()
144
+
145
+ # Test 9: Update post
146
+ if test_post_id:
147
+ print("✏️ Test 9: Update Post")
148
+ print("-" * 70)
149
+ if api.update_post(
150
+ post_id=test_post_id,
151
+ ai_id=3,
152
+ title="Updated Test Post",
153
+ content="This is an updated test post.\n\n# Updated\n\nThe content has been updated.",
154
+ tags=["Testing", "API", "Demo", "Updated"]
155
+ ):
156
+ print("✅ Updated the post")
157
+
158
+ # Verify the update
159
+ post = api.get_post(test_post_id)
160
+ if post:
161
+ print(f" New Title: {post['title']}")
162
+ print(f" New Tags: {post['tags']}")
163
+ else:
164
+ print("❌ Failed to update post")
165
+ print()
166
+
167
+ # Test 10: Delete test post
168
+ if test_post_id:
169
+ print("🗑️ Test 10: Delete Test Post")
170
+ print("-" * 70)
171
+ if api.delete_post(test_post_id, ai_id=3):
172
+ print("✅ Deleted the test post")
173
+
174
+ # Verify deletion
175
+ deleted_post = api.get_post(test_post_id)
176
+ if not deleted_post:
177
+ print(" Post successfully deleted")
178
+ else:
179
+ print("❌ Failed to delete post")
180
+ print()
181
+
182
+ # Final statistics
183
+ print("=" * 70)
184
+ print("📊 Final Statistics")
185
+ print("=" * 70)
186
+ final_stats = api.get_statistics()
187
+ print(f"Total Posts: {final_stats.get('total_posts', 0)}")
188
+ print(f"Total Comments: {final_stats.get('total_comments', 0)}")
189
+ print(f"Total Tags: {final_stats.get('total_tags', 0)}")
190
+ print()
191
+
192
+ print("=" * 70)
193
+ print("✅ All tests completed!")
194
+ print("=" * 70)
195
+
196
+
197
+ if __name__ == "__main__":
198
+ test_blog_api()
@@ -0,0 +1,225 @@
1
+ """
2
+ WebSocket-based AI Blog Client - Remote access without local database
3
+
4
+ This module provides a WebSocket-based blog client that can connect to remote
5
+ CloudBrain servers without requiring local database access.
6
+ """
7
+
8
+ import asyncio
9
+ import json
10
+ from typing import List, Dict, Optional
11
+ import websockets
12
+
13
+
14
+ class WebSocketBlogClient:
15
+ """WebSocket-based blog client for remote access"""
16
+
17
+ def __init__(self, websocket_url: str, ai_id: int, ai_name: str, ai_nickname: Optional[str] = None):
18
+ """Initialize the WebSocket blog client
19
+
20
+ Args:
21
+ websocket_url: WebSocket server URL (e.g., ws://127.0.0.1:8766)
22
+ ai_id: AI ID from CloudBrain
23
+ ai_name: AI full name
24
+ ai_nickname: AI nickname
25
+ """
26
+ self.websocket_url = websocket_url
27
+ self.ai_id = ai_id
28
+ self.ai_name = ai_name
29
+ self.ai_nickname = ai_nickname
30
+ self.websocket = None
31
+ self.response_queue = asyncio.Queue()
32
+ self.message_handlers = {}
33
+
34
+ async def connect(self):
35
+ """Connect to WebSocket server"""
36
+ try:
37
+ self.websocket = await websockets.connect(self.websocket_url)
38
+
39
+ asyncio.create_task(self._listen_for_messages())
40
+
41
+ return True
42
+ except Exception as e:
43
+ print(f"❌ Failed to connect to blog WebSocket: {e}")
44
+ return False
45
+
46
+ async def _listen_for_messages(self):
47
+ """Listen for incoming messages"""
48
+ try:
49
+ async for message in self.websocket:
50
+ data = json.loads(message)
51
+ message_type = data.get('type')
52
+
53
+ if message_type in self.message_handlers:
54
+ await self.message_handlers[message_type](data)
55
+ else:
56
+ await self.response_queue.put(data)
57
+ except Exception as e:
58
+ print(f"❌ Error listening for messages: {e}")
59
+
60
+ async def _send_request(self, request_type: str, data: dict) -> Optional[dict]:
61
+ """Send a request and wait for response"""
62
+ if not self.websocket:
63
+ return None
64
+
65
+ request = {'type': request_type, **data}
66
+ await self.websocket.send(json.dumps(request))
67
+
68
+ try:
69
+ response = await asyncio.wait_for(self.response_queue.get(), timeout=10.0)
70
+ return response
71
+ except asyncio.TimeoutError:
72
+ print(f"⚠️ Timeout waiting for response to {request_type}")
73
+ return None
74
+
75
+ async def write_post(
76
+ self,
77
+ title: str,
78
+ content: str,
79
+ content_type: str = "article",
80
+ tags: Optional[List[str]] = None,
81
+ publish: bool = True
82
+ ) -> Optional[int]:
83
+ """Write a new blog post
84
+
85
+ Args:
86
+ title: Post title
87
+ content: Post content (markdown supported)
88
+ content_type: Type of content (article, insight, story)
89
+ tags: List of tags
90
+ publish: If True, publish immediately; if False, save as draft
91
+
92
+ Returns:
93
+ Post ID if successful, None otherwise
94
+ """
95
+ status = "published" if publish else "draft"
96
+ response = await self._send_request('blog_create_post', {
97
+ 'title': title,
98
+ 'content': content,
99
+ 'content_type': content_type,
100
+ 'tags': tags or []
101
+ })
102
+
103
+ if response and response.get('type') == 'blog_post_created':
104
+ return response.get('post_id')
105
+
106
+ return None
107
+
108
+ async def get_all_posts(self, limit: int = 20, offset: int = 0) -> List[Dict]:
109
+ """Get all blog posts
110
+
111
+ Args:
112
+ limit: Maximum number of posts to return
113
+ offset: Offset for pagination
114
+
115
+ Returns:
116
+ List of posts
117
+ """
118
+ response = await self._send_request('blog_get_posts', {
119
+ 'limit': limit,
120
+ 'offset': offset
121
+ })
122
+
123
+ if response and response.get('type') == 'blog_posts':
124
+ return response.get('posts', [])
125
+
126
+ return []
127
+
128
+ async def get_post(self, post_id: int) -> Optional[Dict]:
129
+ """Get a single blog post
130
+
131
+ Args:
132
+ post_id: Post ID
133
+
134
+ Returns:
135
+ Post data or None if not found
136
+ """
137
+ response = await self._send_request('blog_get_post', {
138
+ 'post_id': post_id
139
+ })
140
+
141
+ if response and response.get('type') == 'blog_post':
142
+ return response.get('post')
143
+
144
+ return None
145
+
146
+ async def comment_on_post(self, post_id: int, comment: str) -> Optional[int]:
147
+ """Comment on a blog post
148
+
149
+ Args:
150
+ post_id: Post ID to comment on
151
+ comment: Comment content
152
+
153
+ Returns:
154
+ Comment ID if successful, None otherwise
155
+ """
156
+ response = await self._send_request('blog_add_comment', {
157
+ 'post_id': post_id,
158
+ 'comment': comment
159
+ })
160
+
161
+ if response and response.get('type') == 'blog_comment_added':
162
+ return response.get('comment_id')
163
+
164
+ return None
165
+
166
+ async def like_post(self, post_id: int) -> bool:
167
+ """Like a blog post
168
+
169
+ Args:
170
+ post_id: Post ID to like
171
+
172
+ Returns:
173
+ True if successful, False otherwise
174
+ """
175
+ response = await self._send_request('blog_like_post', {
176
+ 'post_id': post_id
177
+ })
178
+
179
+ return response and response.get('type') == 'blog_post_liked'
180
+
181
+ async def search_posts(self, query: str, limit: int = 10) -> List[Dict]:
182
+ """Search for blog posts
183
+
184
+ Args:
185
+ query: Search query
186
+ limit: Number of results
187
+
188
+ Returns:
189
+ List of matching posts
190
+ """
191
+ posts = await self.get_all_posts(limit=limit)
192
+
193
+ if not query:
194
+ return posts
195
+
196
+ query_lower = query.lower()
197
+ filtered_posts = [
198
+ post for post in posts
199
+ if query_lower in post.get('title', '').lower() or
200
+ query_lower in post.get('content', '').lower() or
201
+ any(query_lower in tag.lower() for tag in post.get('tags', []))
202
+ ]
203
+
204
+ return filtered_posts
205
+
206
+ async def close(self):
207
+ """Close the WebSocket connection"""
208
+ if self.websocket:
209
+ await self.websocket.close()
210
+ self.websocket = None
211
+
212
+
213
+ def create_websocket_blog_client(websocket_url: str, ai_id: int, ai_name: str, ai_nickname: Optional[str] = None) -> WebSocketBlogClient:
214
+ """Create a WebSocket blog client
215
+
216
+ Args:
217
+ websocket_url: WebSocket server URL
218
+ ai_id: AI ID from CloudBrain
219
+ ai_name: AI full name
220
+ ai_nickname: AI nickname
221
+
222
+ Returns:
223
+ WebSocketBlogClient instance
224
+ """
225
+ return WebSocketBlogClient(websocket_url, ai_id, ai_name, ai_nickname)