marqetive-lib 0.1.2__py3-none-any.whl → 0.1.4__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.
- marqetive/__init__.py +58 -59
- marqetive/core/__init__.py +1 -1
- marqetive/factory.py +380 -0
- marqetive/platforms/__init__.py +6 -6
- marqetive/platforms/base.py +36 -3
- marqetive/platforms/instagram/__init__.py +2 -4
- marqetive/platforms/instagram/client.py +8 -4
- marqetive/platforms/instagram/exceptions.py +1 -1
- marqetive/platforms/instagram/media.py +2 -2
- marqetive/platforms/linkedin/__init__.py +2 -4
- marqetive/platforms/linkedin/client.py +8 -4
- marqetive/platforms/linkedin/exceptions.py +1 -1
- marqetive/platforms/linkedin/media.py +4 -4
- marqetive/platforms/tiktok/__init__.py +2 -4
- marqetive/platforms/tiktok/client.py +324 -104
- marqetive/platforms/tiktok/exceptions.py +170 -66
- marqetive/platforms/tiktok/media.py +545 -159
- marqetive/platforms/twitter/__init__.py +2 -4
- marqetive/platforms/twitter/client.py +11 -53
- marqetive/platforms/twitter/exceptions.py +1 -1
- marqetive/platforms/twitter/media.py +4 -4
- marqetive/utils/__init__.py +3 -3
- marqetive/utils/file_handlers.py +1 -1
- marqetive/utils/oauth.py +2 -2
- marqetive/utils/token_validator.py +1 -1
- {marqetive_lib-0.1.2.dist-info → marqetive_lib-0.1.4.dist-info}/METADATA +1 -1
- marqetive_lib-0.1.4.dist-info/RECORD +35 -0
- marqetive/core/account_factory.py +0 -212
- marqetive/core/base_manager.py +0 -303
- marqetive/core/progress.py +0 -291
- marqetive/core/registry.py +0 -257
- marqetive/platforms/instagram/factory.py +0 -106
- marqetive/platforms/instagram/manager.py +0 -112
- marqetive/platforms/linkedin/factory.py +0 -130
- marqetive/platforms/linkedin/manager.py +0 -119
- marqetive/platforms/tiktok/factory.py +0 -188
- marqetive/platforms/tiktok/manager.py +0 -115
- marqetive/platforms/twitter/factory.py +0 -151
- marqetive/platforms/twitter/manager.py +0 -121
- marqetive/platforms/twitter/threads.py +0 -442
- marqetive/registry_init.py +0 -66
- marqetive_lib-0.1.2.dist-info/RECORD +0 -48
- {marqetive_lib-0.1.2.dist-info → marqetive_lib-0.1.4.dist-info}/WHEEL +0 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Twitter/X platform integration."""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from src.marqetive.platforms.twitter.factory import TwitterAccountFactory
|
|
5
|
-
from src.marqetive.platforms.twitter.manager import TwitterPostManager
|
|
3
|
+
from marqetive.platforms.twitter.client import TwitterClient
|
|
6
4
|
|
|
7
|
-
__all__ = ["TwitterClient"
|
|
5
|
+
__all__ = ["TwitterClient"]
|
|
@@ -13,8 +13,8 @@ import httpx
|
|
|
13
13
|
import tweepy
|
|
14
14
|
from pydantic import HttpUrl
|
|
15
15
|
|
|
16
|
-
from
|
|
17
|
-
from
|
|
16
|
+
from marqetive.platforms.base import ProgressCallback, SocialMediaPlatform
|
|
17
|
+
from marqetive.platforms.exceptions import (
|
|
18
18
|
MediaUploadError,
|
|
19
19
|
PlatformAuthError,
|
|
20
20
|
PlatformError,
|
|
@@ -22,7 +22,7 @@ from src.marqetive.platforms.exceptions import (
|
|
|
22
22
|
RateLimitError,
|
|
23
23
|
ValidationError,
|
|
24
24
|
)
|
|
25
|
-
from
|
|
25
|
+
from marqetive.platforms.models import (
|
|
26
26
|
AuthCredentials,
|
|
27
27
|
Comment,
|
|
28
28
|
CommentStatus,
|
|
@@ -33,12 +33,7 @@ from src.marqetive.platforms.models import (
|
|
|
33
33
|
PostStatus,
|
|
34
34
|
PostUpdateRequest,
|
|
35
35
|
)
|
|
36
|
-
from
|
|
37
|
-
from src.marqetive.platforms.twitter.threads import (
|
|
38
|
-
ThreadResult,
|
|
39
|
-
TweetData,
|
|
40
|
-
TwitterThreadManager,
|
|
41
|
-
)
|
|
36
|
+
from marqetive.platforms.twitter.media import TwitterMediaManager
|
|
42
37
|
|
|
43
38
|
|
|
44
39
|
class TwitterClient(SocialMediaPlatform):
|
|
@@ -72,12 +67,15 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
72
67
|
self,
|
|
73
68
|
credentials: AuthCredentials,
|
|
74
69
|
timeout: float = 30.0,
|
|
70
|
+
progress_callback: ProgressCallback | None = None,
|
|
75
71
|
) -> None:
|
|
76
72
|
"""Initialize Twitter client.
|
|
77
73
|
|
|
78
74
|
Args:
|
|
79
75
|
credentials: Twitter authentication credentials
|
|
80
76
|
timeout: Request timeout in seconds
|
|
77
|
+
progress_callback: Optional callback for progress updates during
|
|
78
|
+
long-running operations like media uploads.
|
|
81
79
|
|
|
82
80
|
Raises:
|
|
83
81
|
PlatformAuthError: If credentials are invalid
|
|
@@ -88,15 +86,15 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
88
86
|
credentials=credentials,
|
|
89
87
|
base_url=base_url,
|
|
90
88
|
timeout=timeout,
|
|
89
|
+
progress_callback=progress_callback,
|
|
91
90
|
)
|
|
92
91
|
|
|
93
92
|
# Initialize tweepy client
|
|
94
93
|
self._tweepy_client: tweepy.Client | None = None
|
|
95
94
|
self._setup_tweepy_client()
|
|
96
95
|
|
|
97
|
-
# Initialize media
|
|
96
|
+
# Initialize media manager
|
|
98
97
|
self._media_manager: TwitterMediaManager | None = None
|
|
99
|
-
self._thread_manager: TwitterThreadManager | None = None
|
|
100
98
|
|
|
101
99
|
def _setup_tweepy_client(self) -> None:
|
|
102
100
|
"""Setup tweepy Client with credentials."""
|
|
@@ -107,7 +105,7 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
107
105
|
)
|
|
108
106
|
|
|
109
107
|
async def _setup_managers(self) -> None:
|
|
110
|
-
"""Setup media
|
|
108
|
+
"""Setup media manager."""
|
|
111
109
|
if not self._tweepy_client:
|
|
112
110
|
return
|
|
113
111
|
|
|
@@ -117,15 +115,11 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
117
115
|
timeout=self.timeout,
|
|
118
116
|
)
|
|
119
117
|
|
|
120
|
-
# Initialize thread manager
|
|
121
|
-
self._thread_manager = TwitterThreadManager(self._tweepy_client)
|
|
122
|
-
|
|
123
118
|
async def _cleanup_managers(self) -> None:
|
|
124
|
-
"""Cleanup media
|
|
119
|
+
"""Cleanup media manager."""
|
|
125
120
|
if self._media_manager:
|
|
126
121
|
await self._media_manager.__aexit__(None, None, None)
|
|
127
122
|
self._media_manager = None
|
|
128
|
-
self._thread_manager = None
|
|
129
123
|
|
|
130
124
|
async def __aenter__(self) -> "TwitterClient":
|
|
131
125
|
"""Async context manager entry."""
|
|
@@ -138,42 +132,6 @@ class TwitterClient(SocialMediaPlatform):
|
|
|
138
132
|
await self._cleanup_managers()
|
|
139
133
|
await super().__aexit__(exc_type, exc_val, exc_tb)
|
|
140
134
|
|
|
141
|
-
# ==================== Thread Methods ====================
|
|
142
|
-
|
|
143
|
-
async def create_thread(
|
|
144
|
-
self,
|
|
145
|
-
tweets: list[TweetData],
|
|
146
|
-
*,
|
|
147
|
-
auto_number: bool = False,
|
|
148
|
-
number_format: str = "{index}/{total}",
|
|
149
|
-
) -> ThreadResult:
|
|
150
|
-
"""Create a Twitter thread from multiple tweets."""
|
|
151
|
-
if not self._thread_manager:
|
|
152
|
-
raise RuntimeError("Client must be used as async context manager")
|
|
153
|
-
|
|
154
|
-
return await self._thread_manager.create_thread(
|
|
155
|
-
tweets,
|
|
156
|
-
auto_number=auto_number,
|
|
157
|
-
number_format=number_format,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
async def delete_thread(
|
|
161
|
-
self,
|
|
162
|
-
thread_id: str,
|
|
163
|
-
tweet_ids: list[str],
|
|
164
|
-
*,
|
|
165
|
-
continue_on_error: bool = True,
|
|
166
|
-
) -> dict[str, bool]:
|
|
167
|
-
"""Delete all tweets in a thread."""
|
|
168
|
-
if not self._thread_manager:
|
|
169
|
-
raise RuntimeError("Client must be used as async context manager")
|
|
170
|
-
|
|
171
|
-
return await self._thread_manager.delete_thread(
|
|
172
|
-
thread_id,
|
|
173
|
-
tweet_ids,
|
|
174
|
-
continue_on_error=continue_on_error,
|
|
175
|
-
)
|
|
176
|
-
|
|
177
135
|
# ==================== Authentication Methods ====================
|
|
178
136
|
|
|
179
137
|
async def authenticate(self) -> AuthCredentials:
|
|
@@ -9,7 +9,7 @@ This module provides comprehensive error handling for Twitter API errors includi
|
|
|
9
9
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from
|
|
12
|
+
from marqetive.platforms.exceptions import (
|
|
13
13
|
MediaUploadError,
|
|
14
14
|
PlatformAuthError,
|
|
15
15
|
PlatformError,
|
|
@@ -18,17 +18,17 @@ from typing import Any, Literal
|
|
|
18
18
|
|
|
19
19
|
import httpx
|
|
20
20
|
|
|
21
|
-
from
|
|
21
|
+
from marqetive.platforms.exceptions import (
|
|
22
22
|
InvalidFileTypeError,
|
|
23
23
|
MediaUploadError,
|
|
24
24
|
)
|
|
25
|
-
from
|
|
26
|
-
from
|
|
25
|
+
from marqetive.utils.file_handlers import download_file
|
|
26
|
+
from marqetive.utils.media import (
|
|
27
27
|
detect_mime_type,
|
|
28
28
|
format_file_size,
|
|
29
29
|
get_chunk_count,
|
|
30
30
|
)
|
|
31
|
-
from
|
|
31
|
+
from marqetive.utils.retry import STANDARD_BACKOFF, retry_async
|
|
32
32
|
|
|
33
33
|
logger = logging.getLogger(__name__)
|
|
34
34
|
|
marqetive/utils/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Utility functions for MarqetiveLib."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from marqetive.utils.file_handlers import (
|
|
4
4
|
TempFileManager,
|
|
5
5
|
download_file,
|
|
6
6
|
download_to_memory,
|
|
@@ -9,8 +9,8 @@ from src.marqetive.utils.file_handlers import (
|
|
|
9
9
|
stream_file_upload,
|
|
10
10
|
write_file_bytes,
|
|
11
11
|
)
|
|
12
|
-
from
|
|
13
|
-
from
|
|
12
|
+
from marqetive.utils.helpers import format_response, parse_query_params
|
|
13
|
+
from marqetive.utils.media import (
|
|
14
14
|
MediaValidator,
|
|
15
15
|
chunk_file,
|
|
16
16
|
detect_mime_type,
|
marqetive/utils/file_handlers.py
CHANGED
marqetive/utils/oauth.py
CHANGED
|
@@ -10,8 +10,8 @@ from typing import Any
|
|
|
10
10
|
|
|
11
11
|
import httpx
|
|
12
12
|
|
|
13
|
-
from
|
|
14
|
-
from
|
|
13
|
+
from marqetive.platforms.exceptions import PlatformAuthError
|
|
14
|
+
from marqetive.platforms.models import AuthCredentials
|
|
15
15
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
marqetive/__init__.py,sha256=LaGrC2HSkbpJBj9mKWuVOflTSN1upTzHowZm6Aw0nOY,2923
|
|
2
|
+
marqetive/core/__init__.py,sha256=qOC6JhGjWOpj9_7umtc6VGH16ZJZE6iNDhA6iDZm8kg,113
|
|
3
|
+
marqetive/core/client.py,sha256=2_FoNpqaRglsWg10i5RTbyDg_kRQKhgWjYs6iDdFxLg,3210
|
|
4
|
+
marqetive/factory.py,sha256=W-8O7eowx_M5ykmL9ZEnjZVoMqNKq8rsDWpyU6X7mxg,14107
|
|
5
|
+
marqetive/platforms/__init__.py,sha256=jMzSg8fI5nCO20aTNaoF6cQ6RIZoxzVhEDvq3ZpAbXc,1327
|
|
6
|
+
marqetive/platforms/base.py,sha256=o0bY9tcV350uGYOHpo3SbSYI61gvx6Ll9KyHaSYFXko,13258
|
|
7
|
+
marqetive/platforms/exceptions.py,sha256=Xyj0bzNiZm5VTErmzXgVW8T6IQnOpF92-HJiKPKjIio,7076
|
|
8
|
+
marqetive/platforms/instagram/__init__.py,sha256=7ClfTovAcCHac2DzKS7z1MFuZpy9lcwet7YP5d6MPeY,135
|
|
9
|
+
marqetive/platforms/instagram/client.py,sha256=Y7jnIDoIRnzwP3QtubwKd8aRi89zrxr5ZLgCqS43xwI,25202
|
|
10
|
+
marqetive/platforms/instagram/exceptions.py,sha256=zwTc90VbG8uz_6O5bKw68Pp53SoevLQUqPiXj4ZgThU,8983
|
|
11
|
+
marqetive/platforms/instagram/media.py,sha256=JDRFB66UlhSjAf0PFR3raGqYxtNTSd5yIZ_2xTGmZRk,21166
|
|
12
|
+
marqetive/platforms/linkedin/__init__.py,sha256=zCnokoPYs56iA1sBSYIlaZW2J50L3CbnQpJSaOLrzP8,131
|
|
13
|
+
marqetive/platforms/linkedin/client.py,sha256=GSWUq66KkerS4H9Vh8j3F6KrMzrlz1DYokbERNyrrCc,23821
|
|
14
|
+
marqetive/platforms/linkedin/exceptions.py,sha256=WHjMZ-XJnKgT6uzzi7LXqFt4lD7nSCjzMzaqtOQA3hM,9767
|
|
15
|
+
marqetive/platforms/linkedin/media.py,sha256=360OT1sdBuJRjuL3wJ-T1irAX5gHmcf4AwAU__cvwwA,16901
|
|
16
|
+
marqetive/platforms/models.py,sha256=W_yOuRWItWSn82n8vXRNN_ScdNkzY1De2qqXaVN2RGU,10974
|
|
17
|
+
marqetive/platforms/tiktok/__init__.py,sha256=BQtxdECd2bW9_vV9W-MY4A1rdXi_xurGWWmzTjTUpMM,123
|
|
18
|
+
marqetive/platforms/tiktok/client.py,sha256=UkljwzrSybIgm4opT5ItT_EXdGx3OGbV4ig8yPoaCbM,17041
|
|
19
|
+
marqetive/platforms/tiktok/exceptions.py,sha256=yOjkZgHyo_t9Chrhws_2n-2sbiDDkCUBv5fdW2nfiHI,10129
|
|
20
|
+
marqetive/platforms/tiktok/media.py,sha256=pAT9AVZX7ZnAnhgoameFLszKDyX16nY43we_CHc5z9s,23771
|
|
21
|
+
marqetive/platforms/twitter/__init__.py,sha256=AA5BELRvZyl2WE_7-puSEWArxZjaXcTJ_i8NGOWrv6k,129
|
|
22
|
+
marqetive/platforms/twitter/client.py,sha256=vZ9wvFPaTOuBD-L48RD1UH6MCPZoF2EHRvFWe0m_cfc,19642
|
|
23
|
+
marqetive/platforms/twitter/exceptions.py,sha256=aKrjUZL07KWQ8hatj5-U7UfnSTl-n8DnTgGGigYCrIY,8931
|
|
24
|
+
marqetive/platforms/twitter/media.py,sha256=16atvKwSukvK_YC_gIm2qsl3pmidWEBAwIfJBPG9HQo,24968
|
|
25
|
+
marqetive/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
marqetive/utils/__init__.py,sha256=bSrNajbxYBSKQayrPviLz8JeGjplnyK8y_NGDtgb7yQ,977
|
|
27
|
+
marqetive/utils/file_handlers.py,sha256=4TP5kmWofNTSZmlS683CM1UYP83WvRd_NubMbqtXv-g,12568
|
|
28
|
+
marqetive/utils/helpers.py,sha256=8-ljhL47SremKcQO2GF8DIHOPODEv1rSioVNuSPCbec,2634
|
|
29
|
+
marqetive/utils/media.py,sha256=Rvxw9XKU65n-z4G1bEihG3wXZBmjSDZUqClfjGFrg6k,12013
|
|
30
|
+
marqetive/utils/oauth.py,sha256=NjHh3o5iKlXMJmzSFJHq-pn5yn82DvmpB7DMTJT0ht8,12105
|
|
31
|
+
marqetive/utils/retry.py,sha256=lAniJLMNWp9XsHrvU0XBNifpNEjfde4MGfd5hlFTPfA,7636
|
|
32
|
+
marqetive/utils/token_validator.py,sha256=pNimFr_FNJLcYGV5oADhoBjflKfEbLQ3Epwnwg_nTbg,6703
|
|
33
|
+
marqetive_lib-0.1.4.dist-info/METADATA,sha256=KawBEFdT6w-WUGCF5ulti5nfi6cWer0oOqP-DFXDDaM,7798
|
|
34
|
+
marqetive_lib-0.1.4.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
35
|
+
marqetive_lib-0.1.4.dist-info/RECORD,,
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
"""Base account factory for managing platform credentials and client creation.
|
|
2
|
-
|
|
3
|
-
This module provides an abstract base class for platform-specific account
|
|
4
|
-
factories that handle credential management, token refresh, and client creation.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import logging
|
|
8
|
-
from abc import ABC, abstractmethod
|
|
9
|
-
from collections.abc import Callable
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from src.marqetive.platforms.exceptions import PlatformAuthError
|
|
13
|
-
from src.marqetive.platforms.models import AccountStatus, AuthCredentials
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class BaseAccountFactory(ABC):
|
|
19
|
-
"""Abstract base class for platform account factories.
|
|
20
|
-
|
|
21
|
-
Account factories manage the lifecycle of platform credentials including:
|
|
22
|
-
- Token expiry checking and automatic refresh
|
|
23
|
-
- Account status management
|
|
24
|
-
- Platform-specific client creation
|
|
25
|
-
|
|
26
|
-
Subclasses must implement:
|
|
27
|
-
- refresh_token(): Platform-specific token refresh logic
|
|
28
|
-
- create_client(): Create platform-specific API client
|
|
29
|
-
- validate_credentials(): Check if credentials are valid
|
|
30
|
-
|
|
31
|
-
Example:
|
|
32
|
-
>>> class TwitterAccountFactory(BaseAccountFactory):
|
|
33
|
-
... async def refresh_token(self, credentials):
|
|
34
|
-
... # Implement Twitter OAuth token refresh
|
|
35
|
-
... pass
|
|
36
|
-
...
|
|
37
|
-
... async def create_client(self, credentials):
|
|
38
|
-
... return TwitterClient(credentials=credentials)
|
|
39
|
-
...
|
|
40
|
-
... async def validate_credentials(self, credentials):
|
|
41
|
-
... # Check if credentials work
|
|
42
|
-
... pass
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
def __init__(
|
|
46
|
-
self,
|
|
47
|
-
on_status_update: Callable[[str, AccountStatus], None] | None = None,
|
|
48
|
-
) -> None:
|
|
49
|
-
"""Initialize the account factory.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
on_status_update: Optional callback when account status changes.
|
|
53
|
-
Called with (user_id, new_status).
|
|
54
|
-
"""
|
|
55
|
-
self.on_status_update = on_status_update
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
@abstractmethod
|
|
59
|
-
def platform_name(self) -> str:
|
|
60
|
-
"""Get the name of the platform this factory manages.
|
|
61
|
-
|
|
62
|
-
Returns:
|
|
63
|
-
Platform name (e.g., "twitter", "linkedin").
|
|
64
|
-
"""
|
|
65
|
-
pass
|
|
66
|
-
|
|
67
|
-
@abstractmethod
|
|
68
|
-
async def refresh_token(self, credentials: AuthCredentials) -> AuthCredentials:
|
|
69
|
-
"""Refresh the OAuth access token.
|
|
70
|
-
|
|
71
|
-
Args:
|
|
72
|
-
credentials: Current credentials with expired token.
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
Updated credentials with new token.
|
|
76
|
-
|
|
77
|
-
Raises:
|
|
78
|
-
PlatformAuthError: If token refresh fails.
|
|
79
|
-
"""
|
|
80
|
-
pass
|
|
81
|
-
|
|
82
|
-
@abstractmethod
|
|
83
|
-
async def create_client(self, credentials: AuthCredentials) -> Any:
|
|
84
|
-
"""Create a platform-specific API client.
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
credentials: Valid credentials for the platform.
|
|
88
|
-
|
|
89
|
-
Returns:
|
|
90
|
-
Platform-specific API client instance.
|
|
91
|
-
|
|
92
|
-
Raises:
|
|
93
|
-
PlatformAuthError: If credentials are invalid.
|
|
94
|
-
"""
|
|
95
|
-
pass
|
|
96
|
-
|
|
97
|
-
@abstractmethod
|
|
98
|
-
async def validate_credentials(self, credentials: AuthCredentials) -> bool:
|
|
99
|
-
"""Validate that credentials are working.
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
credentials: Credentials to validate.
|
|
103
|
-
|
|
104
|
-
Returns:
|
|
105
|
-
True if credentials are valid, False otherwise.
|
|
106
|
-
"""
|
|
107
|
-
pass
|
|
108
|
-
|
|
109
|
-
async def get_credentials(
|
|
110
|
-
self,
|
|
111
|
-
credentials: AuthCredentials,
|
|
112
|
-
auto_refresh: bool = True,
|
|
113
|
-
) -> AuthCredentials:
|
|
114
|
-
"""Get credentials, refreshing if necessary.
|
|
115
|
-
|
|
116
|
-
This method checks if credentials are expired and automatically
|
|
117
|
-
refreshes them if auto_refresh is True.
|
|
118
|
-
|
|
119
|
-
Args:
|
|
120
|
-
credentials: Current credentials.
|
|
121
|
-
auto_refresh: Whether to automatically refresh expired tokens.
|
|
122
|
-
|
|
123
|
-
Returns:
|
|
124
|
-
Valid credentials (refreshed if necessary).
|
|
125
|
-
|
|
126
|
-
Raises:
|
|
127
|
-
PlatformAuthError: If refresh fails or credentials are invalid.
|
|
128
|
-
"""
|
|
129
|
-
# Check if token needs refresh
|
|
130
|
-
if credentials.needs_refresh():
|
|
131
|
-
if not auto_refresh:
|
|
132
|
-
logger.warning(
|
|
133
|
-
f"Credentials for {credentials.platform} are expired "
|
|
134
|
-
"but auto_refresh is disabled"
|
|
135
|
-
)
|
|
136
|
-
return credentials
|
|
137
|
-
|
|
138
|
-
logger.info(
|
|
139
|
-
f"Token expired for {credentials.platform}, attempting refresh..."
|
|
140
|
-
)
|
|
141
|
-
try:
|
|
142
|
-
refreshed_creds = await self.refresh_token(credentials)
|
|
143
|
-
refreshed_creds.mark_valid()
|
|
144
|
-
|
|
145
|
-
# Notify status update
|
|
146
|
-
if self.on_status_update and refreshed_creds.user_id:
|
|
147
|
-
self.on_status_update(refreshed_creds.user_id, AccountStatus.VALID)
|
|
148
|
-
|
|
149
|
-
logger.info(f"Successfully refreshed token for {credentials.platform}")
|
|
150
|
-
return refreshed_creds
|
|
151
|
-
|
|
152
|
-
except PlatformAuthError as e:
|
|
153
|
-
# Determine if this is an OAuth error requiring reconnection
|
|
154
|
-
if "oauth" in str(e).lower() or "authorization" in str(e).lower():
|
|
155
|
-
credentials.mark_reconnection_required()
|
|
156
|
-
if self.on_status_update and credentials.user_id:
|
|
157
|
-
self.on_status_update(
|
|
158
|
-
credentials.user_id, AccountStatus.RECONNECTION_REQUIRED
|
|
159
|
-
)
|
|
160
|
-
logger.error(
|
|
161
|
-
f"OAuth error refreshing token for {credentials.platform}: {e}"
|
|
162
|
-
)
|
|
163
|
-
else:
|
|
164
|
-
credentials.mark_error()
|
|
165
|
-
if self.on_status_update and credentials.user_id:
|
|
166
|
-
self.on_status_update(credentials.user_id, AccountStatus.ERROR)
|
|
167
|
-
logger.error(
|
|
168
|
-
f"Error refreshing token for {credentials.platform}: {e}"
|
|
169
|
-
)
|
|
170
|
-
raise
|
|
171
|
-
|
|
172
|
-
return credentials
|
|
173
|
-
|
|
174
|
-
async def create_authenticated_client(
|
|
175
|
-
self,
|
|
176
|
-
credentials: AuthCredentials,
|
|
177
|
-
auto_refresh: bool = True,
|
|
178
|
-
) -> Any:
|
|
179
|
-
"""Create an authenticated client, refreshing credentials if needed.
|
|
180
|
-
|
|
181
|
-
This is the main method to use for getting a ready-to-use client.
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
credentials: Platform credentials.
|
|
185
|
-
auto_refresh: Whether to automatically refresh expired tokens.
|
|
186
|
-
|
|
187
|
-
Returns:
|
|
188
|
-
Authenticated platform client.
|
|
189
|
-
|
|
190
|
-
Raises:
|
|
191
|
-
PlatformAuthError: If authentication fails.
|
|
192
|
-
"""
|
|
193
|
-
# Get valid credentials (refresh if needed)
|
|
194
|
-
valid_creds = await self.get_credentials(credentials, auto_refresh=auto_refresh)
|
|
195
|
-
|
|
196
|
-
# Create client
|
|
197
|
-
client = await self.create_client(valid_creds)
|
|
198
|
-
|
|
199
|
-
return client
|
|
200
|
-
|
|
201
|
-
def _update_status(self, user_id: str | None, status: AccountStatus) -> None:
|
|
202
|
-
"""Internal method to update account status.
|
|
203
|
-
|
|
204
|
-
Args:
|
|
205
|
-
user_id: User ID for the account.
|
|
206
|
-
status: New status.
|
|
207
|
-
"""
|
|
208
|
-
if self.on_status_update and user_id:
|
|
209
|
-
try:
|
|
210
|
-
self.on_status_update(user_id, status)
|
|
211
|
-
except Exception as e:
|
|
212
|
-
logger.error(f"Error calling status update callback: {e}")
|