pararamio-aio 2.1.1__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.
- pararamio_aio/__init__.py +78 -0
- pararamio_aio/_core/__init__.py +125 -0
- pararamio_aio/_core/_types.py +120 -0
- pararamio_aio/_core/base.py +143 -0
- pararamio_aio/_core/client_protocol.py +90 -0
- pararamio_aio/_core/constants/__init__.py +7 -0
- pararamio_aio/_core/constants/base.py +9 -0
- pararamio_aio/_core/constants/endpoints.py +84 -0
- pararamio_aio/_core/cookie_decorator.py +208 -0
- pararamio_aio/_core/cookie_manager.py +1222 -0
- pararamio_aio/_core/endpoints.py +67 -0
- pararamio_aio/_core/exceptions/__init__.py +6 -0
- pararamio_aio/_core/exceptions/auth.py +91 -0
- pararamio_aio/_core/exceptions/base.py +124 -0
- pararamio_aio/_core/models/__init__.py +17 -0
- pararamio_aio/_core/models/base.py +66 -0
- pararamio_aio/_core/models/chat.py +92 -0
- pararamio_aio/_core/models/post.py +65 -0
- pararamio_aio/_core/models/user.py +54 -0
- pararamio_aio/_core/py.typed +2 -0
- pararamio_aio/_core/utils/__init__.py +73 -0
- pararamio_aio/_core/utils/async_requests.py +417 -0
- pararamio_aio/_core/utils/auth_flow.py +202 -0
- pararamio_aio/_core/utils/authentication.py +235 -0
- pararamio_aio/_core/utils/captcha.py +92 -0
- pararamio_aio/_core/utils/helpers.py +336 -0
- pararamio_aio/_core/utils/http_client.py +199 -0
- pararamio_aio/_core/utils/requests.py +424 -0
- pararamio_aio/_core/validators.py +78 -0
- pararamio_aio/_types.py +29 -0
- pararamio_aio/client.py +989 -0
- pararamio_aio/constants/__init__.py +16 -0
- pararamio_aio/cookie_manager.py +15 -0
- pararamio_aio/exceptions/__init__.py +31 -0
- pararamio_aio/exceptions/base.py +1 -0
- pararamio_aio/file_operations.py +232 -0
- pararamio_aio/models/__init__.py +32 -0
- pararamio_aio/models/activity.py +127 -0
- pararamio_aio/models/attachment.py +141 -0
- pararamio_aio/models/base.py +83 -0
- pararamio_aio/models/bot.py +274 -0
- pararamio_aio/models/chat.py +722 -0
- pararamio_aio/models/deferred_post.py +174 -0
- pararamio_aio/models/file.py +103 -0
- pararamio_aio/models/group.py +361 -0
- pararamio_aio/models/poll.py +275 -0
- pararamio_aio/models/post.py +643 -0
- pararamio_aio/models/team.py +403 -0
- pararamio_aio/models/user.py +239 -0
- pararamio_aio/py.typed +2 -0
- pararamio_aio/utils/__init__.py +18 -0
- pararamio_aio/utils/authentication.py +383 -0
- pararamio_aio/utils/requests.py +75 -0
- pararamio_aio-2.1.1.dist-info/METADATA +269 -0
- pararamio_aio-2.1.1.dist-info/RECORD +57 -0
- pararamio_aio-2.1.1.dist-info/WHEEL +5 -0
- pararamio_aio-2.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
"""Async DeferredPost model."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from datetime import datetime
|
6
|
+
from typing import TYPE_CHECKING, Any
|
7
|
+
|
8
|
+
# Imports from core
|
9
|
+
from pararamio_aio._core import PararamNotFound
|
10
|
+
from pararamio_aio._core.utils.helpers import format_datetime
|
11
|
+
|
12
|
+
from .base import BaseModel
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from ..client import AsyncPararamio
|
16
|
+
|
17
|
+
__all__ = ("DeferredPost",)
|
18
|
+
|
19
|
+
|
20
|
+
class DeferredPost(BaseModel):
|
21
|
+
"""Async DeferredPost model for scheduled posts."""
|
22
|
+
|
23
|
+
_data: dict[str, Any] # Type hint for mypy
|
24
|
+
|
25
|
+
def __init__(self, client: AsyncPararamio, id: int, **kwargs):
|
26
|
+
"""Initialize async deferred post.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
client: AsyncPararamio client
|
30
|
+
id: Deferred post ID
|
31
|
+
**kwargs: Additional post data
|
32
|
+
"""
|
33
|
+
super().__init__(client, id=id, **kwargs)
|
34
|
+
self.id = id
|
35
|
+
|
36
|
+
@property
|
37
|
+
def user_id(self) -> int:
|
38
|
+
"""Get post author user ID."""
|
39
|
+
return self._data.get("user_id", 0)
|
40
|
+
|
41
|
+
@property
|
42
|
+
def chat_id(self) -> int:
|
43
|
+
"""Get target chat ID."""
|
44
|
+
return self._data.get("chat_id", 0)
|
45
|
+
|
46
|
+
@property
|
47
|
+
def text(self) -> str:
|
48
|
+
"""Get post text."""
|
49
|
+
# Check both top-level and nested data
|
50
|
+
text = self._data.get("text")
|
51
|
+
if text is None and "data" in self._data:
|
52
|
+
text = self._data["data"].get("text")
|
53
|
+
return text or ""
|
54
|
+
|
55
|
+
@property
|
56
|
+
def reply_no(self) -> int | None:
|
57
|
+
"""Get reply post number if any."""
|
58
|
+
reply_no = self._data.get("reply_no")
|
59
|
+
if reply_no is None and "data" in self._data:
|
60
|
+
reply_no = self._data["data"].get("reply_no")
|
61
|
+
return reply_no
|
62
|
+
|
63
|
+
@property
|
64
|
+
def time_created(self) -> datetime | None:
|
65
|
+
"""Get creation time."""
|
66
|
+
return self._data.get("time_created")
|
67
|
+
|
68
|
+
@property
|
69
|
+
def time_sending(self) -> datetime | None:
|
70
|
+
"""Get scheduled sending time."""
|
71
|
+
return self._data.get("time_sending")
|
72
|
+
|
73
|
+
@property
|
74
|
+
def data(self) -> dict[str, Any]:
|
75
|
+
"""Get additional post data."""
|
76
|
+
return self._data.get("data", {})
|
77
|
+
|
78
|
+
async def load(self) -> DeferredPost:
|
79
|
+
"""Load full deferred post data from API.
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
Self with updated data
|
83
|
+
|
84
|
+
Raises:
|
85
|
+
PararamNotFound: If post not found
|
86
|
+
"""
|
87
|
+
posts = await self.get_deferred_posts(self.client)
|
88
|
+
|
89
|
+
for post in posts:
|
90
|
+
if post.id == self.id:
|
91
|
+
self._data = post._data
|
92
|
+
return self
|
93
|
+
|
94
|
+
raise PararamNotFound(f"Deferred post with id {self.id} not found")
|
95
|
+
|
96
|
+
async def delete(self) -> bool:
|
97
|
+
"""Delete this deferred post.
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
True if successful
|
101
|
+
"""
|
102
|
+
url = f"/msg/deferred/{self.id}"
|
103
|
+
await self.client.api_delete(url)
|
104
|
+
return True
|
105
|
+
|
106
|
+
@classmethod
|
107
|
+
async def create(
|
108
|
+
cls,
|
109
|
+
client: AsyncPararamio,
|
110
|
+
chat_id: int,
|
111
|
+
text: str,
|
112
|
+
*,
|
113
|
+
time_sending: datetime,
|
114
|
+
reply_no: int | None = None,
|
115
|
+
quote_range: tuple[int, int] | None = None,
|
116
|
+
) -> DeferredPost:
|
117
|
+
"""Create a new deferred (scheduled) post.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
client: AsyncPararamio client
|
121
|
+
chat_id: Target chat ID
|
122
|
+
text: Post text
|
123
|
+
time_sending: When to send the post
|
124
|
+
reply_no: Optional post number to reply to
|
125
|
+
quote_range: Optional quote range as (start, end) tuple
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
Created DeferredPost object
|
129
|
+
"""
|
130
|
+
url = "/msg/deferred"
|
131
|
+
data = {
|
132
|
+
"chat_id": chat_id,
|
133
|
+
"text": text,
|
134
|
+
"time_sending": format_datetime(time_sending),
|
135
|
+
"reply_no": reply_no,
|
136
|
+
"quote_range": quote_range,
|
137
|
+
}
|
138
|
+
|
139
|
+
response = await client.api_post(url, data)
|
140
|
+
|
141
|
+
return cls(
|
142
|
+
client,
|
143
|
+
id=int(response["deferred_post_id"]),
|
144
|
+
chat_id=chat_id,
|
145
|
+
data=data,
|
146
|
+
time_sending=time_sending,
|
147
|
+
**response,
|
148
|
+
)
|
149
|
+
|
150
|
+
@classmethod
|
151
|
+
async def get_deferred_posts(cls, client: AsyncPararamio) -> list[DeferredPost]:
|
152
|
+
"""Get all deferred posts for the current user.
|
153
|
+
|
154
|
+
Args:
|
155
|
+
client: AsyncPararamio client
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
List of DeferredPost objects
|
159
|
+
"""
|
160
|
+
url = "/msg/deferred"
|
161
|
+
response = await client.api_get(url)
|
162
|
+
posts_data = response.get("posts", [])
|
163
|
+
|
164
|
+
return [cls(client, **post_data) for post_data in posts_data]
|
165
|
+
|
166
|
+
def __str__(self) -> str:
|
167
|
+
"""String representation."""
|
168
|
+
return self.text or f"DeferredPost({self.id})"
|
169
|
+
|
170
|
+
def __eq__(self, other) -> bool:
|
171
|
+
"""Check equality."""
|
172
|
+
if not isinstance(other, DeferredPost):
|
173
|
+
return False
|
174
|
+
return self.id == other.id
|
@@ -0,0 +1,103 @@
|
|
1
|
+
"""Async File model."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
from urllib.parse import quote
|
7
|
+
|
8
|
+
from .base import BaseModel
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from ..client import AsyncPararamio
|
12
|
+
|
13
|
+
__all__ = ("File",)
|
14
|
+
|
15
|
+
|
16
|
+
class File(BaseModel):
|
17
|
+
"""Async File model with explicit loading."""
|
18
|
+
|
19
|
+
def __init__(self, client: AsyncPararamio, guid: str, name: str | None = None, **kwargs):
|
20
|
+
"""Initialize async file.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
client: AsyncPararamio client
|
24
|
+
guid: File GUID
|
25
|
+
name: Optional file name
|
26
|
+
**kwargs: Additional file data
|
27
|
+
"""
|
28
|
+
super().__init__(client, guid=guid, name=name, **kwargs)
|
29
|
+
self.guid = guid
|
30
|
+
|
31
|
+
@property
|
32
|
+
def name(self) -> str | None:
|
33
|
+
"""Get file name."""
|
34
|
+
return self._data.get("name") or self._data.get("filename")
|
35
|
+
|
36
|
+
@property
|
37
|
+
def mime_type(self) -> str | None:
|
38
|
+
"""Get file MIME type."""
|
39
|
+
return self._data.get("mime_type") or self._data.get("type")
|
40
|
+
|
41
|
+
@property
|
42
|
+
def size(self) -> int | None:
|
43
|
+
"""Get file size in bytes."""
|
44
|
+
return self._data.get("size")
|
45
|
+
|
46
|
+
@property
|
47
|
+
def chat_id(self) -> int | None:
|
48
|
+
"""Get associated chat ID."""
|
49
|
+
return self._data.get("chat_id")
|
50
|
+
|
51
|
+
@property
|
52
|
+
def organization_id(self) -> int | None:
|
53
|
+
"""Get associated organization ID."""
|
54
|
+
return self._data.get("organization_id")
|
55
|
+
|
56
|
+
@property
|
57
|
+
def reply_no(self) -> int | None:
|
58
|
+
"""Get associated reply number."""
|
59
|
+
return self._data.get("reply_no")
|
60
|
+
|
61
|
+
async def download(self) -> bytes:
|
62
|
+
"""Download file content.
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
File content as bytes
|
66
|
+
"""
|
67
|
+
if not self.name:
|
68
|
+
raise ValueError("File name is required for download")
|
69
|
+
|
70
|
+
# Use the client's download_file method
|
71
|
+
bio = await self.client.download_file(self.guid, self.name)
|
72
|
+
return bio.read()
|
73
|
+
|
74
|
+
async def delete(self) -> bool:
|
75
|
+
"""Delete this file.
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
True if successful
|
79
|
+
"""
|
80
|
+
try:
|
81
|
+
result = await self.client.delete_file(self.guid)
|
82
|
+
return result.get("success", True)
|
83
|
+
except (AttributeError, KeyError, TypeError, ValueError):
|
84
|
+
return False
|
85
|
+
|
86
|
+
def get_download_url(self) -> str:
|
87
|
+
"""Get file download URL.
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
Download URL string
|
91
|
+
"""
|
92
|
+
if not self.name:
|
93
|
+
raise ValueError("File name is required for download URL")
|
94
|
+
|
95
|
+
return f"https://file.pararam.io/download/{self.guid}/{quote(self.name)}"
|
96
|
+
|
97
|
+
def __str__(self) -> str:
|
98
|
+
"""String representation."""
|
99
|
+
return self.name or f"File({self.guid})"
|
100
|
+
|
101
|
+
def __repr__(self) -> str:
|
102
|
+
"""Detailed representation."""
|
103
|
+
return f"<File(guid={self.guid}, name={self.name}, size={self.size})>"
|
@@ -0,0 +1,361 @@
|
|
1
|
+
"""Async Group model."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from datetime import datetime
|
6
|
+
from typing import TYPE_CHECKING
|
7
|
+
|
8
|
+
# Imports from core
|
9
|
+
from pararamio_aio._core import (
|
10
|
+
PararamioRequestException,
|
11
|
+
PararamioValidationException,
|
12
|
+
PararamNotFound,
|
13
|
+
)
|
14
|
+
from pararamio_aio._core.utils.helpers import join_ids
|
15
|
+
|
16
|
+
from .base import BaseModel
|
17
|
+
|
18
|
+
if TYPE_CHECKING:
|
19
|
+
from ..client import AsyncPararamio
|
20
|
+
|
21
|
+
__all__ = ("Group",)
|
22
|
+
|
23
|
+
|
24
|
+
class Group(BaseModel):
|
25
|
+
"""Async Group model with explicit loading."""
|
26
|
+
|
27
|
+
def __init__(self, client: AsyncPararamio, id: int, name: str | None = None, **kwargs):
|
28
|
+
"""Initialize async group.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
client: AsyncPararamio client
|
32
|
+
id: Group ID
|
33
|
+
name: Optional group name
|
34
|
+
**kwargs: Additional group data
|
35
|
+
"""
|
36
|
+
super().__init__(client, id=id, name=name, **kwargs)
|
37
|
+
self.id = id
|
38
|
+
|
39
|
+
@property
|
40
|
+
def name(self) -> str | None:
|
41
|
+
"""Get group name."""
|
42
|
+
return self._data.get("name")
|
43
|
+
|
44
|
+
@property
|
45
|
+
def unique_name(self) -> str | None:
|
46
|
+
"""Get group unique name."""
|
47
|
+
return self._data.get("unique_name")
|
48
|
+
|
49
|
+
@property
|
50
|
+
def description(self) -> str | None:
|
51
|
+
"""Get group description."""
|
52
|
+
return self._data.get("description")
|
53
|
+
|
54
|
+
@property
|
55
|
+
def email_domain(self) -> str | None:
|
56
|
+
"""Get group email domain."""
|
57
|
+
return self._data.get("email_domain")
|
58
|
+
|
59
|
+
@property
|
60
|
+
def time_created(self) -> datetime | None:
|
61
|
+
"""Get group creation time."""
|
62
|
+
return self._data.get("time_created")
|
63
|
+
|
64
|
+
@property
|
65
|
+
def time_updated(self) -> datetime | None:
|
66
|
+
"""Get group last update time."""
|
67
|
+
return self._data.get("time_updated")
|
68
|
+
|
69
|
+
@property
|
70
|
+
def is_active(self) -> bool:
|
71
|
+
"""Check if group is active."""
|
72
|
+
return self._data.get("active", True)
|
73
|
+
|
74
|
+
@property
|
75
|
+
def members_count(self) -> int:
|
76
|
+
"""Get group members count."""
|
77
|
+
return self._data.get("members_count", 0)
|
78
|
+
|
79
|
+
async def load(self) -> Group:
|
80
|
+
"""Load full group data from API.
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
Self with updated data
|
84
|
+
"""
|
85
|
+
groups = await self.client.get_groups_by_ids([self.id])
|
86
|
+
if not groups:
|
87
|
+
raise PararamNotFound(f"Group {self.id} not found")
|
88
|
+
|
89
|
+
# Update our data with loaded data
|
90
|
+
self._data.update(groups[0]._data)
|
91
|
+
return self
|
92
|
+
|
93
|
+
async def get_members(self) -> list[int]:
|
94
|
+
"""Get group member user IDs.
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
List of user IDs
|
98
|
+
"""
|
99
|
+
url = f"/group/{self.id}/members"
|
100
|
+
response = await self.client.api_get(url)
|
101
|
+
return response.get("members", [])
|
102
|
+
|
103
|
+
async def add_members(self, user_ids: list[int]) -> bool:
|
104
|
+
"""Add members to group.
|
105
|
+
|
106
|
+
Args:
|
107
|
+
user_ids: List of user IDs to add
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
True if successful
|
111
|
+
"""
|
112
|
+
url = f"/group/{self.id}/members"
|
113
|
+
data = {"user_ids": user_ids}
|
114
|
+
response = await self.client.api_post(url, data)
|
115
|
+
return response.get("success", False)
|
116
|
+
|
117
|
+
async def add_member(self, user_id: int, reload: bool = True) -> None:
|
118
|
+
"""Add a single member to group.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
user_id: User ID to add
|
122
|
+
reload: Whether to reload group data after operation
|
123
|
+
|
124
|
+
Raises:
|
125
|
+
PararamioRequestException: If operation fails
|
126
|
+
"""
|
127
|
+
url = f"/core/group/{self.id}/users/{user_id}"
|
128
|
+
response = await self.client.api_post(url)
|
129
|
+
|
130
|
+
if response.get("result") == "OK":
|
131
|
+
# Update local cache if we have users data
|
132
|
+
if "users" in self._data:
|
133
|
+
if user_id not in self._data["users"]:
|
134
|
+
self._data["users"].append(user_id)
|
135
|
+
|
136
|
+
if reload:
|
137
|
+
await self.load()
|
138
|
+
else:
|
139
|
+
raise PararamioRequestException(f"Failed to add user {user_id} to group {self.id}")
|
140
|
+
|
141
|
+
async def remove_members(self, user_ids: list[int]) -> bool:
|
142
|
+
"""Remove members from group.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
user_ids: List of user IDs to remove
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
True if successful
|
149
|
+
"""
|
150
|
+
# Use DELETE with query parameters instead of request body
|
151
|
+
url = f"/group/{self.id}/members?user_ids={join_ids(user_ids)}"
|
152
|
+
response = await self.client.api_delete(url)
|
153
|
+
return response.get("success", False)
|
154
|
+
|
155
|
+
async def remove_member(self, user_id: int, reload: bool = True) -> None:
|
156
|
+
"""Remove a single member from group.
|
157
|
+
|
158
|
+
Args:
|
159
|
+
user_id: User ID to remove
|
160
|
+
reload: Whether to reload group data after operation
|
161
|
+
|
162
|
+
Raises:
|
163
|
+
PararamioRequestException: If operation fails
|
164
|
+
"""
|
165
|
+
url = f"/core/group/{self.id}/users/{user_id}"
|
166
|
+
response = await self.client.api_delete(url)
|
167
|
+
|
168
|
+
if response.get("result") == "OK":
|
169
|
+
# Update local cache if we have users data
|
170
|
+
if "users" in self._data and user_id in self._data["users"]:
|
171
|
+
self._data["users"].remove(user_id)
|
172
|
+
|
173
|
+
# Also remove from admins if present
|
174
|
+
if "admins" in self._data and user_id in self._data["admins"]:
|
175
|
+
self._data["admins"].remove(user_id)
|
176
|
+
|
177
|
+
if reload:
|
178
|
+
await self.load()
|
179
|
+
else:
|
180
|
+
raise PararamioRequestException(f"Failed to remove user {user_id} from group {self.id}")
|
181
|
+
|
182
|
+
async def add_admins(self, admin_ids: list[int]) -> bool:
|
183
|
+
"""Add admin users to the group.
|
184
|
+
|
185
|
+
Args:
|
186
|
+
admin_ids: List of user IDs to make admins
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
True if successful
|
190
|
+
"""
|
191
|
+
url = f"/core/group/{self.id}/admins/{join_ids(admin_ids)}"
|
192
|
+
response = await self.client.api_post(url)
|
193
|
+
return response.get("result") == "OK"
|
194
|
+
|
195
|
+
async def update_settings(self, **kwargs) -> bool:
|
196
|
+
"""Update group settings.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
**kwargs: Settings to update (name, description, etc.)
|
200
|
+
|
201
|
+
Returns:
|
202
|
+
True if successful
|
203
|
+
"""
|
204
|
+
# Filter allowed fields
|
205
|
+
allowed_fields = {"unique_name", "name", "description", "email_domain"}
|
206
|
+
data = {k: v for k, v in kwargs.items() if k in allowed_fields}
|
207
|
+
|
208
|
+
if not data:
|
209
|
+
return False
|
210
|
+
|
211
|
+
url = f"/group/{self.id}"
|
212
|
+
response = await self.client.api_put(url, data)
|
213
|
+
|
214
|
+
# Update local data
|
215
|
+
if response.get("success"):
|
216
|
+
self._data.update(data)
|
217
|
+
return True
|
218
|
+
|
219
|
+
return False
|
220
|
+
|
221
|
+
async def edit(self, changes: dict[str, str | None], reload: bool = True) -> None:
|
222
|
+
"""Edit group settings.
|
223
|
+
|
224
|
+
Args:
|
225
|
+
changes: Dictionary of fields to change
|
226
|
+
reload: Whether to reload group data after operation
|
227
|
+
|
228
|
+
Raises:
|
229
|
+
PararamioValidationException: If invalid fields provided
|
230
|
+
"""
|
231
|
+
# Define editable fields
|
232
|
+
editable_fields = ["unique_name", "name", "description", "email_domain"]
|
233
|
+
|
234
|
+
# Validate fields
|
235
|
+
invalid_fields = set(changes.keys()) - set(editable_fields)
|
236
|
+
if invalid_fields:
|
237
|
+
raise PararamioValidationException(
|
238
|
+
f"Invalid fields: {invalid_fields}. Valid fields are: {editable_fields}"
|
239
|
+
)
|
240
|
+
|
241
|
+
# Ensure we have current data
|
242
|
+
if not self._data.get("name"):
|
243
|
+
await self.load()
|
244
|
+
|
245
|
+
url = f"/core/group/{self.id}"
|
246
|
+
response = await self.client.api_put(url, changes)
|
247
|
+
|
248
|
+
if response.get("result") == "OK":
|
249
|
+
# Update local data
|
250
|
+
self._data.update(changes)
|
251
|
+
|
252
|
+
if reload:
|
253
|
+
await self.load()
|
254
|
+
|
255
|
+
async def delete(self) -> bool:
|
256
|
+
"""Delete this group.
|
257
|
+
|
258
|
+
Returns:
|
259
|
+
True if successful
|
260
|
+
"""
|
261
|
+
url = f"/group/{self.id}"
|
262
|
+
response = await self.client.api_delete(url)
|
263
|
+
return response.get("success", False)
|
264
|
+
|
265
|
+
# @classmethod
|
266
|
+
# async def search(cls, client: AsyncPararamio, query: str) -> list[Group]:
|
267
|
+
# """Search for groups.
|
268
|
+
#
|
269
|
+
# DEPRECATED: Group search is not available in the API.
|
270
|
+
# Groups might be returned as part of user search results.
|
271
|
+
#
|
272
|
+
# Args:
|
273
|
+
# client: AsyncPararamio client
|
274
|
+
# query: Search query
|
275
|
+
#
|
276
|
+
# Returns:
|
277
|
+
# List of found groups
|
278
|
+
# """
|
279
|
+
# from urllib.parse import quote
|
280
|
+
# # Use the same endpoint as user search (they seem to be combined)
|
281
|
+
# url = f"/user/search?flt={quote(query)}"
|
282
|
+
# response = await client.api_get(url)
|
283
|
+
#
|
284
|
+
# groups = []
|
285
|
+
# for group_data in response.get("groups", []):
|
286
|
+
# group = cls.from_dict(client, group_data)
|
287
|
+
# groups.append(group)
|
288
|
+
#
|
289
|
+
# return groups
|
290
|
+
|
291
|
+
@classmethod
|
292
|
+
async def create(
|
293
|
+
cls,
|
294
|
+
client: AsyncPararamio,
|
295
|
+
name: str,
|
296
|
+
unique_name: str,
|
297
|
+
description: str = "",
|
298
|
+
email_domain: str | None = None,
|
299
|
+
) -> Group:
|
300
|
+
"""Create a new group.
|
301
|
+
|
302
|
+
Args:
|
303
|
+
client: AsyncPararamio client
|
304
|
+
name: Group display name
|
305
|
+
unique_name: Group unique identifier
|
306
|
+
description: Group description
|
307
|
+
email_domain: Optional email domain
|
308
|
+
|
309
|
+
Returns:
|
310
|
+
Created group object
|
311
|
+
"""
|
312
|
+
data = {
|
313
|
+
"name": name,
|
314
|
+
"unique_name": unique_name,
|
315
|
+
"description": description,
|
316
|
+
}
|
317
|
+
|
318
|
+
if email_domain:
|
319
|
+
data["email_domain"] = email_domain
|
320
|
+
|
321
|
+
response = await client.api_post("/group", data)
|
322
|
+
group_id = response["group_id"]
|
323
|
+
|
324
|
+
group = await client.get_group_by_id(group_id)
|
325
|
+
if group is None:
|
326
|
+
raise ValueError(f"Failed to retrieve created group with id {group_id}")
|
327
|
+
return group
|
328
|
+
|
329
|
+
def __eq__(self, other) -> bool:
|
330
|
+
"""Check equality with another group."""
|
331
|
+
if not isinstance(other, Group):
|
332
|
+
return False
|
333
|
+
return self.id == other.id
|
334
|
+
|
335
|
+
def __str__(self) -> str:
|
336
|
+
"""String representation."""
|
337
|
+
return self.name or f"Group({self.id})"
|
338
|
+
|
339
|
+
@classmethod
|
340
|
+
async def search(cls, client: AsyncPararamio, search_string: str) -> list[Group]:
|
341
|
+
"""Search for groups.
|
342
|
+
|
343
|
+
Note: This uses the user search endpoint which also returns groups.
|
344
|
+
|
345
|
+
Args:
|
346
|
+
client: AsyncPararamio client
|
347
|
+
search_string: Search query
|
348
|
+
|
349
|
+
Returns:
|
350
|
+
List of matching groups
|
351
|
+
"""
|
352
|
+
from urllib.parse import quote # pylint: disable=import-outside-toplevel
|
353
|
+
|
354
|
+
# Use the same endpoint as user search (they seem to be combined)
|
355
|
+
url = f"/user/search?flt={quote(search_string)}&self=false"
|
356
|
+
response = await client.api_get(url)
|
357
|
+
groups = []
|
358
|
+
for group_data in response.get("groups", []):
|
359
|
+
group = cls.from_dict(client, group_data)
|
360
|
+
groups.append(group)
|
361
|
+
return groups
|