pararamio-aio 3.0.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.
Files changed (56) hide show
  1. pararamio_aio/__init__.py +26 -0
  2. pararamio_aio/_core/__init__.py +125 -0
  3. pararamio_aio/_core/_types.py +120 -0
  4. pararamio_aio/_core/base.py +143 -0
  5. pararamio_aio/_core/client_protocol.py +90 -0
  6. pararamio_aio/_core/constants/__init__.py +7 -0
  7. pararamio_aio/_core/constants/base.py +9 -0
  8. pararamio_aio/_core/constants/endpoints.py +84 -0
  9. pararamio_aio/_core/cookie_decorator.py +208 -0
  10. pararamio_aio/_core/cookie_manager.py +1222 -0
  11. pararamio_aio/_core/endpoints.py +67 -0
  12. pararamio_aio/_core/exceptions/__init__.py +6 -0
  13. pararamio_aio/_core/exceptions/auth.py +91 -0
  14. pararamio_aio/_core/exceptions/base.py +124 -0
  15. pararamio_aio/_core/models/__init__.py +17 -0
  16. pararamio_aio/_core/models/base.py +66 -0
  17. pararamio_aio/_core/models/chat.py +92 -0
  18. pararamio_aio/_core/models/post.py +65 -0
  19. pararamio_aio/_core/models/user.py +54 -0
  20. pararamio_aio/_core/py.typed +2 -0
  21. pararamio_aio/_core/utils/__init__.py +73 -0
  22. pararamio_aio/_core/utils/async_requests.py +417 -0
  23. pararamio_aio/_core/utils/auth_flow.py +202 -0
  24. pararamio_aio/_core/utils/authentication.py +235 -0
  25. pararamio_aio/_core/utils/captcha.py +92 -0
  26. pararamio_aio/_core/utils/helpers.py +336 -0
  27. pararamio_aio/_core/utils/http_client.py +199 -0
  28. pararamio_aio/_core/utils/requests.py +424 -0
  29. pararamio_aio/_core/validators.py +78 -0
  30. pararamio_aio/_types.py +29 -0
  31. pararamio_aio/client.py +989 -0
  32. pararamio_aio/constants/__init__.py +16 -0
  33. pararamio_aio/exceptions/__init__.py +31 -0
  34. pararamio_aio/exceptions/base.py +1 -0
  35. pararamio_aio/file_operations.py +232 -0
  36. pararamio_aio/models/__init__.py +31 -0
  37. pararamio_aio/models/activity.py +127 -0
  38. pararamio_aio/models/attachment.py +141 -0
  39. pararamio_aio/models/base.py +83 -0
  40. pararamio_aio/models/bot.py +274 -0
  41. pararamio_aio/models/chat.py +722 -0
  42. pararamio_aio/models/deferred_post.py +174 -0
  43. pararamio_aio/models/file.py +103 -0
  44. pararamio_aio/models/group.py +361 -0
  45. pararamio_aio/models/poll.py +275 -0
  46. pararamio_aio/models/post.py +643 -0
  47. pararamio_aio/models/team.py +403 -0
  48. pararamio_aio/models/user.py +239 -0
  49. pararamio_aio/py.typed +2 -0
  50. pararamio_aio/utils/__init__.py +18 -0
  51. pararamio_aio/utils/authentication.py +383 -0
  52. pararamio_aio/utils/requests.py +75 -0
  53. pararamio_aio-3.0.0.dist-info/METADATA +269 -0
  54. pararamio_aio-3.0.0.dist-info/RECORD +56 -0
  55. pararamio_aio-3.0.0.dist-info/WHEEL +5 -0
  56. pararamio_aio-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,403 @@
1
+ """Async Team 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 PararamioRequestException
10
+
11
+ from .base import BaseModel
12
+ from .group import Group
13
+ from .user import User
14
+
15
+ if TYPE_CHECKING:
16
+ from ..client import AsyncPararamio
17
+
18
+ __all__ = ("TeamMemberStatus", "TeamMember", "Team")
19
+
20
+
21
+ class TeamCommonMixin:
22
+ """Mixin for common team/member properties."""
23
+
24
+ _data: dict[str, Any] # This will be provided by BaseModel
25
+
26
+ @property
27
+ def state(self) -> str:
28
+ """Get state."""
29
+ return self._data.get("state", "")
30
+
31
+ @property
32
+ def time_created(self) -> datetime | None:
33
+ """Get creation time."""
34
+ return self._data.get("time_created")
35
+
36
+ @property
37
+ def time_updated(self) -> datetime | None:
38
+ """Get last update time."""
39
+ return self._data.get("time_updated")
40
+
41
+
42
+ class TeamMemberStatus(BaseModel):
43
+ """Team member status model."""
44
+
45
+ def __init__(self, client: AsyncPararamio, id: int, **kwargs):
46
+ """Initialize team member status.
47
+
48
+ Args:
49
+ client: AsyncPararamio client
50
+ id: Status ID
51
+ **kwargs: Additional status data
52
+ """
53
+ super().__init__(client, id=id, **kwargs)
54
+ self.id = id
55
+
56
+ @property
57
+ def user_id(self) -> int:
58
+ """Get user ID."""
59
+ return self._data.get("user_id", 0)
60
+
61
+ @property
62
+ def setter_id(self) -> int:
63
+ """Get setter user ID."""
64
+ return self._data.get("setter_id", 0)
65
+
66
+ @property
67
+ def org_id(self) -> int:
68
+ """Get organization ID."""
69
+ return self._data.get("org_id", 0)
70
+
71
+ @property
72
+ def time_created(self) -> datetime | None:
73
+ """Get creation time."""
74
+ return self._data.get("time_created")
75
+
76
+ @property
77
+ def status(self) -> str:
78
+ """Get status text."""
79
+ return self._data.get("status", "")
80
+
81
+
82
+ class TeamMember(TeamCommonMixin, BaseModel):
83
+ """Team member model."""
84
+
85
+ def __init__(self, client: AsyncPararamio, id: int, org_id: int, **kwargs):
86
+ """Initialize team member.
87
+
88
+ Args:
89
+ client: AsyncPararamio client
90
+ id: Member ID
91
+ org_id: Organization ID
92
+ **kwargs: Additional member data
93
+ """
94
+ super().__init__(client, id=id, org_id=org_id, **kwargs)
95
+ self.id = id
96
+ self.org_id = org_id
97
+
98
+ @property
99
+ def email(self) -> str:
100
+ """Get member email."""
101
+ return self._data.get("email", "")
102
+
103
+ @property
104
+ def chats(self) -> list[int]:
105
+ """Get member chat IDs."""
106
+ return self._data.get("chats", [])
107
+
108
+ @property
109
+ def groups(self) -> list[int]:
110
+ """Get member group IDs."""
111
+ return self._data.get("groups", [])
112
+
113
+ @property
114
+ def is_admin(self) -> bool:
115
+ """Check if member is admin."""
116
+ return self._data.get("is_admin", False)
117
+
118
+ @property
119
+ def is_member(self) -> bool:
120
+ """Check if user is active member."""
121
+ return self._data.get("is_member", True)
122
+
123
+ @property
124
+ def last_activity(self) -> datetime | None:
125
+ """Get last activity time."""
126
+ return self._data.get("last_activity")
127
+
128
+ @property
129
+ def phonenumber(self) -> str | None:
130
+ """Get phone number."""
131
+ return self._data.get("phonenumber")
132
+
133
+ @property
134
+ def two_step_enabled(self) -> bool:
135
+ """Check if two-step verification is enabled."""
136
+ return self._data.get("two_step_enabled", False)
137
+
138
+ @property
139
+ def inviter_id(self) -> int | None:
140
+ """Get inviter user ID."""
141
+ return self._data.get("inviter_id")
142
+
143
+ async def get_user(self) -> User | None:
144
+ """Get associated User object.
145
+
146
+ Returns:
147
+ User object or None
148
+ """
149
+ return await self.client.get_user_by_id(self.id)
150
+
151
+ async def get_last_status(self) -> TeamMemberStatus | None:
152
+ """Get last status for this member.
153
+
154
+ Returns:
155
+ TeamMemberStatus or None if no status
156
+ """
157
+ url = f"/core/org/status?user_ids={self.id}"
158
+ response = await self.client.api_get(url)
159
+ data = response.get("data", [])
160
+
161
+ if not data:
162
+ return None
163
+
164
+ return TeamMemberStatus(self.client, **data[0])
165
+
166
+ async def add_status(self, status: str) -> bool:
167
+ """Add status for this member.
168
+
169
+ Args:
170
+ status: Status text
171
+
172
+ Returns:
173
+ True if successful
174
+ """
175
+ url = "/core/org/status"
176
+ data = {
177
+ "org_id": self.org_id,
178
+ "status": status,
179
+ "user_id": self.id,
180
+ }
181
+ response = await self.client.api_post(url, data=data)
182
+ return bool(response) and response.get("result") == "OK"
183
+
184
+ def __str__(self) -> str:
185
+ """String representation."""
186
+ return self.email or str(self.id)
187
+
188
+ def __eq__(self, other) -> bool:
189
+ """Check equality."""
190
+ if not isinstance(other, (TeamMember, User)):
191
+ return False
192
+ return self.id == other.id
193
+
194
+
195
+ class Team(TeamCommonMixin, BaseModel):
196
+ """Async Team model with explicit loading."""
197
+
198
+ def __init__(self, client: AsyncPararamio, id: int, **kwargs):
199
+ """Initialize async team.
200
+
201
+ Args:
202
+ client: AsyncPararamio client
203
+ id: Team ID
204
+ **kwargs: Additional team data
205
+ """
206
+ super().__init__(client, id=id, **kwargs)
207
+ self.id = id
208
+
209
+ @property
210
+ def title(self) -> str:
211
+ """Get team title."""
212
+ return self._data.get("title", "")
213
+
214
+ @property
215
+ def slug(self) -> str:
216
+ """Get team slug."""
217
+ return self._data.get("slug", "")
218
+
219
+ @property
220
+ def description(self) -> str | None:
221
+ """Get team description."""
222
+ return self._data.get("description")
223
+
224
+ @property
225
+ def email_domain(self) -> str | None:
226
+ """Get email domain."""
227
+ return self._data.get("email_domain")
228
+
229
+ @property
230
+ def is_active(self) -> bool:
231
+ """Check if team is active."""
232
+ return self._data.get("is_active", True)
233
+
234
+ @property
235
+ def two_step_required(self) -> bool:
236
+ """Check if two-step verification is required."""
237
+ return self._data.get("two_step_required", False)
238
+
239
+ @property
240
+ def default_thread_id(self) -> int:
241
+ """Get default thread ID."""
242
+ return self._data.get("default_thread_id", 0)
243
+
244
+ @property
245
+ def guest_thread_id(self) -> int | None:
246
+ """Get guest thread ID."""
247
+ return self._data.get("guest_thread_id")
248
+
249
+ @property
250
+ def inviter_id(self) -> int | None:
251
+ """Get inviter user ID."""
252
+ return self._data.get("inviter_id")
253
+
254
+ @property
255
+ def users(self) -> list[int]:
256
+ """Get team user IDs."""
257
+ return self._data.get("users", [])
258
+
259
+ @property
260
+ def admins(self) -> list[int]:
261
+ """Get team admin IDs."""
262
+ return self._data.get("admins", [])
263
+
264
+ @property
265
+ def groups(self) -> list[int]:
266
+ """Get team group IDs."""
267
+ return self._data.get("groups", [])
268
+
269
+ @property
270
+ def guests(self) -> list[int]:
271
+ """Get team guest IDs."""
272
+ return self._data.get("guests", [])
273
+
274
+ async def load(self) -> Team:
275
+ """Load full team data from API.
276
+
277
+ Returns:
278
+ Self with updated data
279
+ """
280
+ url = f"/core/org?ids={self.id}"
281
+ response = await self.client.api_get(url)
282
+
283
+ if "orgs" in response and response["orgs"]:
284
+ self._data.update(response["orgs"][0])
285
+ else:
286
+ self._data.update(response)
287
+
288
+ return self
289
+
290
+ async def create_role(self, name: str, description: str | None = None) -> Group:
291
+ """Create a new role (group) in this team.
292
+
293
+ Args:
294
+ name: Role name
295
+ description: Role description
296
+
297
+ Returns:
298
+ Created Group object
299
+ """
300
+ return await Group.create(
301
+ self.client,
302
+ name=name,
303
+ unique_name=name, # Async version requires unique_name
304
+ description=description or "",
305
+ # Note: organization_id parameter not supported in async Group.create
306
+ )
307
+
308
+ async def get_member_info(self, user_id: int) -> TeamMember:
309
+ """Get information about a specific member.
310
+
311
+ Args:
312
+ user_id: User ID
313
+
314
+ Returns:
315
+ TeamMember object
316
+
317
+ Raises:
318
+ PararamioRequestException: If member not found
319
+ """
320
+ url = f"/core/org/{self.id}/member_info/{user_id}"
321
+ response = await self.client.api_get(url)
322
+
323
+ if not response:
324
+ raise PararamioRequestException(f"empty response for user {user_id}")
325
+
326
+ return TeamMember(self.client, org_id=self.id, **response)
327
+
328
+ async def get_members_info(self) -> list[TeamMember]:
329
+ """Get information about all team members.
330
+
331
+ Returns:
332
+ List of TeamMember objects
333
+ """
334
+ url = f"/core/org/{self.id}/member_info"
335
+ response = await self.client.api_get(url)
336
+
337
+ if response:
338
+ return [
339
+ TeamMember(self.client, org_id=self.id, **member_data)
340
+ for member_data in response.get("data", [])
341
+ ]
342
+ return []
343
+
344
+ async def mark_all_messages_as_read(self) -> bool:
345
+ """Mark all messages in this team as read.
346
+
347
+ Returns:
348
+ True if successful
349
+ """
350
+ return await self.client.mark_all_messages_as_read(self.id)
351
+
352
+ @classmethod
353
+ async def get_my_team_ids(cls, client: AsyncPararamio) -> list[int]:
354
+ """Get IDs of teams the current user belongs to.
355
+
356
+ Args:
357
+ client: AsyncPararamio client
358
+
359
+ Returns:
360
+ List of team IDs
361
+ """
362
+ url = "/core/org/sync"
363
+ response = await client.api_get(url)
364
+ return response.get("ids", [])
365
+
366
+ @classmethod
367
+ async def load_teams(cls, client: AsyncPararamio) -> list[Team]:
368
+ """Load all teams for the current user.
369
+
370
+ Args:
371
+ client: AsyncPararamio client
372
+
373
+ Returns:
374
+ List of Team objects
375
+ """
376
+ ids = await cls.get_my_team_ids(client)
377
+
378
+ if not ids:
379
+ return []
380
+
381
+ url = "/core/org?ids=" + ",".join(map(str, ids))
382
+ response = await client.api_get(url)
383
+
384
+ if response:
385
+ return [cls(client, **team_data) for team_data in response.get("orgs", [])]
386
+
387
+ return []
388
+
389
+ def __contains__(self, item) -> bool:
390
+ """Check if user is in team."""
391
+ if not isinstance(item, (TeamMember, User)):
392
+ return False
393
+ return item.id in self.users
394
+
395
+ def __str__(self) -> str:
396
+ """String representation."""
397
+ return self.title or f"Team({self.id})"
398
+
399
+ def __eq__(self, other) -> bool:
400
+ """Check equality."""
401
+ if not isinstance(other, Team):
402
+ return False
403
+ return self.id == other.id
@@ -0,0 +1,239 @@
1
+ """Async User model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import datetime
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ # Imports from core
10
+ from pararamio_aio._core import PararamNotFound
11
+
12
+ from .activity import Activity, ActivityAction
13
+ from .base import BaseModel
14
+
15
+ if TYPE_CHECKING:
16
+ from ..client import AsyncPararamio
17
+ from .chat import Chat
18
+ from .post import Post
19
+
20
+ __all__ = ("User", "UserSearchResult")
21
+
22
+
23
+ @dataclass
24
+ class UserSearchResult:
25
+ """User search result with explicit data."""
26
+
27
+ id: int
28
+ avatar: str | None
29
+ name: str
30
+ unique_name: str
31
+ custom_name: str | None
32
+ time_created: str
33
+ time_updated: str
34
+ other_blocked: bool
35
+ pm_thread_id: int | None
36
+ is_bot: bool
37
+ client: AsyncPararamio
38
+
39
+ @property
40
+ def has_pm(self) -> bool:
41
+ """Check if user has PM thread."""
42
+ return self.pm_thread_id is not None
43
+
44
+ async def get_pm_thread(self) -> Chat:
45
+ """Get PM thread for this user.
46
+
47
+ Returns:
48
+ Chat object for PM
49
+ """
50
+ if self.pm_thread_id:
51
+ chat = await self.client.get_chat_by_id(self.pm_thread_id)
52
+ if chat is None:
53
+ raise ValueError(f"Chat with id {self.pm_thread_id} not found")
54
+ return chat
55
+ # Create new PM thread
56
+ return await self.create_pm_thread()
57
+
58
+ async def create_pm_thread(self) -> Chat:
59
+ """Create a new PM thread with this user.
60
+
61
+ Returns:
62
+ New chat object
63
+ """
64
+
65
+ url = f"/core/chat/pm/{self.id}"
66
+ response = await self.client.api_post(url)
67
+ chat_id = response["chat_id"]
68
+ chat = await self.client.get_chat_by_id(chat_id)
69
+ if chat is None:
70
+ raise ValueError(f"Failed to create or retrieve chat with id {chat_id}")
71
+ return chat
72
+
73
+ async def send_message(self, text: str) -> Post:
74
+ """Send a private message to this user.
75
+
76
+ Args:
77
+ text: Message text
78
+
79
+ Returns:
80
+ Created post
81
+ """
82
+ chat = await self.get_pm_thread()
83
+ return await chat.send_message(text)
84
+
85
+
86
+ class User(BaseModel):
87
+ """Async User model with explicit loading."""
88
+
89
+ def __init__(self, client: AsyncPararamio, id: int, name: str | None = None, **kwargs):
90
+ """Initialize async user.
91
+
92
+ Args:
93
+ client: AsyncPararamio client
94
+ id: User ID
95
+ name: Optional user name
96
+ **kwargs: Additional user data
97
+ """
98
+ super().__init__(client, id=id, name=name, **kwargs)
99
+ self.id = id
100
+
101
+ @property
102
+ def name(self) -> str | None:
103
+ """Get user name."""
104
+ return self._data.get("name")
105
+
106
+ @property
107
+ def unique_name(self) -> str | None:
108
+ """Get user unique name."""
109
+ return self._data.get("unique_name")
110
+
111
+ @property
112
+ def is_bot(self) -> bool:
113
+ """Check if user is a bot."""
114
+ return self._data.get("is_bot", False)
115
+
116
+ @property
117
+ def time_created(self) -> datetime | None:
118
+ """Get user creation time."""
119
+ return self._data.get("time_created")
120
+
121
+ @property
122
+ def time_updated(self) -> datetime | None:
123
+ """Get user last update time."""
124
+ return self._data.get("time_updated")
125
+
126
+ @property
127
+ def info(self) -> str | None:
128
+ """Get user info."""
129
+ return self._data.get("info")
130
+
131
+ @property
132
+ def organizations(self) -> list[int]:
133
+ """Get user organization IDs."""
134
+ return self._data.get("organizations", [])
135
+
136
+ async def load(self) -> User:
137
+ """Load full user data from API.
138
+
139
+ Returns:
140
+ Self with updated data
141
+ """
142
+ users = await self.client.get_users_by_ids([self.id])
143
+ if not users:
144
+ raise PararamNotFound(f"User {self.id} not found")
145
+
146
+ # Update our data with loaded data
147
+ self._data.update(users[0]._data)
148
+ return self
149
+
150
+ async def send_private_message(self, text: str) -> Post:
151
+ """Send a private message to this user.
152
+
153
+ Args:
154
+ text: Message text
155
+
156
+ Returns:
157
+ Created post
158
+ """
159
+ url = "/msg/post/private"
160
+ response = await self.client.api_post(url, {"text": text, "user_id": self.id})
161
+
162
+ # Load the created post
163
+ post = await self.client.get_post(response["chat_id"], response["post_no"])
164
+ if post is None:
165
+ raise ValueError(
166
+ f"Failed to retrieve post {response['post_no']} from chat {response['chat_id']}"
167
+ )
168
+ return post
169
+
170
+ async def _activity_page_loader(
171
+ self, action: ActivityAction | None = None, page: int = 1
172
+ ) -> dict[str, Any]:
173
+ """Load activity page from API.
174
+
175
+ Args:
176
+ action: Optional action type to filter
177
+ page: Page number
178
+
179
+ Returns:
180
+ API response dict
181
+ """
182
+ url = f"/activity?user_id={self.id}&page={page}"
183
+ if action:
184
+ url += f"&action={action.value}"
185
+
186
+ return await self.client.api_get(url)
187
+
188
+ async def get_activity(
189
+ self, start: datetime, end: datetime, actions: list[ActivityAction] | None = None
190
+ ) -> list[Activity]:
191
+ """Get user activity within date range.
192
+
193
+ Args:
194
+ start: Start datetime
195
+ end: End datetime
196
+ actions: Optional list of ActivityAction types to filter
197
+
198
+ Returns:
199
+ List of Activity objects sorted by time
200
+ """
201
+
202
+ # Create async page loader
203
+ async def page_loader(
204
+ action: ActivityAction | None = None, page: int = 1
205
+ ) -> dict[str, Any]:
206
+ return await self._activity_page_loader(action, page)
207
+
208
+ return await Activity.get_activity(page_loader, start, end, actions)
209
+
210
+ @classmethod
211
+ async def search(cls, client: AsyncPararamio, query: str) -> list[UserSearchResult]:
212
+ """Search for users.
213
+
214
+ Args:
215
+ client: AsyncPararamio client
216
+ query: Search query
217
+
218
+ Returns:
219
+ List of search results
220
+ """
221
+ url = f"/users?flt={query}"
222
+ response = await client.api_get(url)
223
+
224
+ results = []
225
+ for data in response.get("users", []):
226
+ result = UserSearchResult(client=client, **data)
227
+ results.append(result)
228
+
229
+ return results
230
+
231
+ def __eq__(self, other) -> bool:
232
+ """Check equality with another user."""
233
+ if not isinstance(other, User):
234
+ return False
235
+ return self.id == other.id
236
+
237
+ def __str__(self) -> str:
238
+ """String representation."""
239
+ return self.name or f"User({self.id})"
pararamio_aio/py.typed ADDED
@@ -0,0 +1,2 @@
1
+ # This file indicates that this package supports type hints
2
+ # See PEP 561 for more details
@@ -0,0 +1,18 @@
1
+ """Async utilities for pararamio_aio package."""
2
+
3
+ from .authentication import (
4
+ async_authenticate,
5
+ async_do_second_step,
6
+ async_do_second_step_with_code,
7
+ get_async_xsrf_token,
8
+ )
9
+ from .requests import async_api_request, async_bot_request
10
+
11
+ __all__ = [
12
+ "async_authenticate",
13
+ "async_do_second_step",
14
+ "async_do_second_step_with_code",
15
+ "get_async_xsrf_token",
16
+ "async_api_request",
17
+ "async_bot_request",
18
+ ]