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
marqetive/core/registry.py
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
"""Platform registry for managing platform manager instances.
|
|
2
|
-
|
|
3
|
-
This module provides a singleton registry pattern for registering and accessing
|
|
4
|
-
platform managers across the application.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import threading
|
|
8
|
-
from typing import Any, TypeVar
|
|
9
|
-
|
|
10
|
-
from src.marqetive.platforms.models import AuthCredentials
|
|
11
|
-
|
|
12
|
-
# Type variable for manager classes
|
|
13
|
-
ManagerType = TypeVar("ManagerType")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class PlatformRegistry:
|
|
17
|
-
"""Singleton registry for platform managers.
|
|
18
|
-
|
|
19
|
-
The registry maintains a mapping of platform names to their manager classes
|
|
20
|
-
and provides caching of manager instances to avoid unnecessary recreation.
|
|
21
|
-
|
|
22
|
-
Thread-safe implementation using threading.Lock.
|
|
23
|
-
|
|
24
|
-
Example:
|
|
25
|
-
>>> from marqetive_lib.platforms.twitter.manager import TwitterPostManager
|
|
26
|
-
>>> registry = PlatformRegistry()
|
|
27
|
-
>>> registry.register_platform("twitter", TwitterPostManager)
|
|
28
|
-
>>> manager = registry.get_manager("twitter", credentials=creds)
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
_instance: "PlatformRegistry | None" = None
|
|
32
|
-
_lock: threading.Lock = threading.Lock()
|
|
33
|
-
|
|
34
|
-
def __new__(cls) -> "PlatformRegistry":
|
|
35
|
-
"""Ensure only one instance exists (singleton pattern)."""
|
|
36
|
-
if cls._instance is None:
|
|
37
|
-
with cls._lock:
|
|
38
|
-
# Double-check locking pattern
|
|
39
|
-
if cls._instance is None:
|
|
40
|
-
cls._instance = super().__new__(cls)
|
|
41
|
-
cls._instance._initialized = False
|
|
42
|
-
return cls._instance
|
|
43
|
-
|
|
44
|
-
def __init__(self) -> None:
|
|
45
|
-
"""Initialize the registry."""
|
|
46
|
-
# Only initialize once
|
|
47
|
-
if self._initialized:
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
self._platforms: dict[str, type] = {}
|
|
51
|
-
self._manager_cache: dict[str, Any] = {}
|
|
52
|
-
self._cache_lock = threading.Lock()
|
|
53
|
-
self._initialized = True
|
|
54
|
-
|
|
55
|
-
def register_platform(self, platform_name: str, manager_class: type) -> None:
|
|
56
|
-
"""Register a platform manager class.
|
|
57
|
-
|
|
58
|
-
Args:
|
|
59
|
-
platform_name: Name of the platform (e.g., "twitter", "linkedin").
|
|
60
|
-
manager_class: The manager class to register.
|
|
61
|
-
|
|
62
|
-
Raises:
|
|
63
|
-
ValueError: If platform is already registered.
|
|
64
|
-
|
|
65
|
-
Example:
|
|
66
|
-
>>> registry.register_platform("twitter", TwitterPostManager)
|
|
67
|
-
"""
|
|
68
|
-
if platform_name in self._platforms:
|
|
69
|
-
raise ValueError(f"Platform '{platform_name}' is already registered")
|
|
70
|
-
|
|
71
|
-
self._platforms[platform_name] = manager_class
|
|
72
|
-
|
|
73
|
-
def unregister_platform(self, platform_name: str) -> None:
|
|
74
|
-
"""Unregister a platform and clear its cached instances.
|
|
75
|
-
|
|
76
|
-
Args:
|
|
77
|
-
platform_name: Name of the platform to unregister.
|
|
78
|
-
|
|
79
|
-
Example:
|
|
80
|
-
>>> registry.unregister_platform("twitter")
|
|
81
|
-
"""
|
|
82
|
-
with self._cache_lock:
|
|
83
|
-
self._platforms.pop(platform_name, None)
|
|
84
|
-
# Clear cached instances for this platform
|
|
85
|
-
keys_to_remove = [
|
|
86
|
-
k for k in self._manager_cache if k.startswith(f"{platform_name}:")
|
|
87
|
-
]
|
|
88
|
-
for key in keys_to_remove:
|
|
89
|
-
self._manager_cache.pop(key)
|
|
90
|
-
|
|
91
|
-
def get_manager(
|
|
92
|
-
self,
|
|
93
|
-
platform_name: str,
|
|
94
|
-
use_cache: bool = True,
|
|
95
|
-
**kwargs: Any,
|
|
96
|
-
) -> Any:
|
|
97
|
-
"""Get or create a manager instance for the specified platform.
|
|
98
|
-
|
|
99
|
-
Args:
|
|
100
|
-
platform_name: Name of the platform (e.g., "twitter", "linkedin").
|
|
101
|
-
use_cache: Whether to use cached instance (default: True).
|
|
102
|
-
**kwargs: Arguments to pass to the manager constructor.
|
|
103
|
-
|
|
104
|
-
Returns:
|
|
105
|
-
Manager instance for the platform.
|
|
106
|
-
|
|
107
|
-
Raises:
|
|
108
|
-
ValueError: If platform is not registered.
|
|
109
|
-
|
|
110
|
-
Example:
|
|
111
|
-
>>> manager = registry.get_manager("twitter", credentials=creds)
|
|
112
|
-
>>> post = await manager.execute_post(...)
|
|
113
|
-
"""
|
|
114
|
-
if platform_name not in self._platforms:
|
|
115
|
-
available = ", ".join(self.get_available_platforms())
|
|
116
|
-
raise ValueError(
|
|
117
|
-
f"Platform '{platform_name}' is not registered. "
|
|
118
|
-
f"Available platforms: {available}"
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
# Generate cache key from platform name and kwargs
|
|
122
|
-
cache_key = self._generate_cache_key(platform_name, **kwargs)
|
|
123
|
-
|
|
124
|
-
if use_cache:
|
|
125
|
-
with self._cache_lock:
|
|
126
|
-
if cache_key in self._manager_cache:
|
|
127
|
-
return self._manager_cache[cache_key]
|
|
128
|
-
|
|
129
|
-
# Create new manager instance
|
|
130
|
-
manager_class = self._platforms[platform_name]
|
|
131
|
-
manager = manager_class(**kwargs)
|
|
132
|
-
|
|
133
|
-
if use_cache:
|
|
134
|
-
with self._cache_lock:
|
|
135
|
-
self._manager_cache[cache_key] = manager
|
|
136
|
-
|
|
137
|
-
return manager
|
|
138
|
-
|
|
139
|
-
def get_available_platforms(self) -> list[str]:
|
|
140
|
-
"""Get list of all registered platform names.
|
|
141
|
-
|
|
142
|
-
Returns:
|
|
143
|
-
List of platform names.
|
|
144
|
-
|
|
145
|
-
Example:
|
|
146
|
-
>>> platforms = registry.get_available_platforms()
|
|
147
|
-
>>> print(platforms)
|
|
148
|
-
['twitter', 'linkedin', 'instagram', 'tiktok']
|
|
149
|
-
"""
|
|
150
|
-
return list(self._platforms.keys())
|
|
151
|
-
|
|
152
|
-
def clear_cache(self) -> None:
|
|
153
|
-
"""Clear all cached manager instances.
|
|
154
|
-
|
|
155
|
-
Example:
|
|
156
|
-
>>> registry.clear_cache()
|
|
157
|
-
"""
|
|
158
|
-
with self._cache_lock:
|
|
159
|
-
self._manager_cache.clear()
|
|
160
|
-
|
|
161
|
-
def _generate_cache_key(self, platform_name: str, **kwargs: Any) -> str:
|
|
162
|
-
"""Generate a cache key for manager instance.
|
|
163
|
-
|
|
164
|
-
Args:
|
|
165
|
-
platform_name: Name of the platform.
|
|
166
|
-
**kwargs: Manager constructor arguments.
|
|
167
|
-
|
|
168
|
-
Returns:
|
|
169
|
-
Cache key string.
|
|
170
|
-
"""
|
|
171
|
-
# For credentials, use account_id if available
|
|
172
|
-
if "credentials" in kwargs:
|
|
173
|
-
creds = kwargs["credentials"]
|
|
174
|
-
if isinstance(creds, AuthCredentials) and creds.user_id:
|
|
175
|
-
return f"{platform_name}:user_id:{creds.user_id}"
|
|
176
|
-
|
|
177
|
-
# Default to platform name only
|
|
178
|
-
return platform_name
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
# Global registry instance
|
|
182
|
-
_global_registry: PlatformRegistry | None = None
|
|
183
|
-
_global_lock = threading.Lock()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def get_registry() -> PlatformRegistry:
|
|
187
|
-
"""Get the global platform registry instance.
|
|
188
|
-
|
|
189
|
-
Returns:
|
|
190
|
-
Global PlatformRegistry instance.
|
|
191
|
-
|
|
192
|
-
Example:
|
|
193
|
-
>>> registry = get_registry()
|
|
194
|
-
>>> manager = registry.get_manager("twitter", credentials=creds)
|
|
195
|
-
"""
|
|
196
|
-
global _global_registry
|
|
197
|
-
|
|
198
|
-
if _global_registry is None:
|
|
199
|
-
with _global_lock:
|
|
200
|
-
if _global_registry is None:
|
|
201
|
-
_global_registry = PlatformRegistry()
|
|
202
|
-
|
|
203
|
-
return _global_registry
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def register_platform(platform_name: str, manager_class: type) -> None:
|
|
207
|
-
"""Register a platform in the global registry.
|
|
208
|
-
|
|
209
|
-
Convenience function that uses the global registry.
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
platform_name: Name of the platform.
|
|
213
|
-
manager_class: The manager class to register.
|
|
214
|
-
|
|
215
|
-
Example:
|
|
216
|
-
>>> from marqetive_lib.platforms.twitter.manager import TwitterPostManager
|
|
217
|
-
>>> register_platform("twitter", TwitterPostManager)
|
|
218
|
-
"""
|
|
219
|
-
registry = get_registry()
|
|
220
|
-
registry.register_platform(platform_name, manager_class)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def get_manager_for_platform(platform_name: str, **kwargs: Any) -> Any:
|
|
224
|
-
"""Get a manager instance for the specified platform.
|
|
225
|
-
|
|
226
|
-
Convenience function that uses the global registry.
|
|
227
|
-
|
|
228
|
-
Args:
|
|
229
|
-
platform_name: Name of the platform.
|
|
230
|
-
**kwargs: Arguments to pass to the manager constructor.
|
|
231
|
-
|
|
232
|
-
Returns:
|
|
233
|
-
Manager instance for the platform.
|
|
234
|
-
|
|
235
|
-
Example:
|
|
236
|
-
>>> manager = get_manager_for_platform("twitter", credentials=creds)
|
|
237
|
-
>>> post = await manager.execute_post(...)
|
|
238
|
-
"""
|
|
239
|
-
registry = get_registry()
|
|
240
|
-
return registry.get_manager(platform_name, **kwargs)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
def get_available_platforms() -> list[str]:
|
|
244
|
-
"""Get list of all registered platforms.
|
|
245
|
-
|
|
246
|
-
Convenience function that uses the global registry.
|
|
247
|
-
|
|
248
|
-
Returns:
|
|
249
|
-
List of platform names.
|
|
250
|
-
|
|
251
|
-
Example:
|
|
252
|
-
>>> platforms = get_available_platforms()
|
|
253
|
-
>>> print(platforms)
|
|
254
|
-
['twitter', 'linkedin', 'instagram']
|
|
255
|
-
"""
|
|
256
|
-
registry = get_registry()
|
|
257
|
-
return registry.get_available_platforms()
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
"""Instagram account factory for managing credentials and client creation."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from collections.abc import Callable
|
|
5
|
-
|
|
6
|
-
from src.marqetive.core.account_factory import BaseAccountFactory
|
|
7
|
-
from src.marqetive.platforms.exceptions import PlatformAuthError
|
|
8
|
-
from src.marqetive.platforms.instagram.client import InstagramClient
|
|
9
|
-
from src.marqetive.platforms.models import AccountStatus, AuthCredentials
|
|
10
|
-
from src.marqetive.utils.oauth import refresh_instagram_token
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger(__name__)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class InstagramAccountFactory(BaseAccountFactory):
|
|
16
|
-
"""Factory for creating and managing Instagram accounts and clients.
|
|
17
|
-
|
|
18
|
-
Example:
|
|
19
|
-
>>> factory = InstagramAccountFactory()
|
|
20
|
-
>>> credentials = AuthCredentials(
|
|
21
|
-
... platform="instagram",
|
|
22
|
-
... access_token="token"
|
|
23
|
-
... )
|
|
24
|
-
>>> client = await factory.create_authenticated_client(credentials)
|
|
25
|
-
>>> async with client:
|
|
26
|
-
... post = await client.create_post(request)
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
on_status_update: Callable[[str, AccountStatus], None] | None = None,
|
|
32
|
-
) -> None:
|
|
33
|
-
"""Initialize Instagram account factory.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
on_status_update: Optional callback when account status changes.
|
|
37
|
-
"""
|
|
38
|
-
super().__init__(on_status_update=on_status_update)
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def platform_name(self) -> str:
|
|
42
|
-
"""Get platform name."""
|
|
43
|
-
return "instagram"
|
|
44
|
-
|
|
45
|
-
async def refresh_token(self, credentials: AuthCredentials) -> AuthCredentials:
|
|
46
|
-
"""Refresh Instagram long-lived access token.
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
credentials: Current credentials.
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
Updated credentials with refreshed token.
|
|
53
|
-
|
|
54
|
-
Raises:
|
|
55
|
-
PlatformAuthError: If refresh fails.
|
|
56
|
-
"""
|
|
57
|
-
logger.info("Refreshing Instagram access token...")
|
|
58
|
-
return await refresh_instagram_token(credentials)
|
|
59
|
-
|
|
60
|
-
async def create_client(self, credentials: AuthCredentials) -> InstagramClient:
|
|
61
|
-
"""Create Instagram API client.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
credentials: Valid Instagram credentials.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
InstagramClient instance.
|
|
68
|
-
|
|
69
|
-
Raises:
|
|
70
|
-
PlatformAuthError: If credentials are invalid.
|
|
71
|
-
"""
|
|
72
|
-
if not credentials.access_token:
|
|
73
|
-
raise PlatformAuthError(
|
|
74
|
-
"Access token is required",
|
|
75
|
-
platform=self.platform_name,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
# Instagram needs instagram_business_account_id in additional_data
|
|
79
|
-
instagram_business_account_id = credentials.additional_data.get(
|
|
80
|
-
"instagram_business_account_id"
|
|
81
|
-
)
|
|
82
|
-
if not instagram_business_account_id:
|
|
83
|
-
raise PlatformAuthError(
|
|
84
|
-
"instagram_business_account_id is required in additional_data",
|
|
85
|
-
platform=self.platform_name,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
return InstagramClient(credentials=credentials)
|
|
89
|
-
|
|
90
|
-
async def validate_credentials(self, credentials: AuthCredentials) -> bool:
|
|
91
|
-
"""Validate Instagram credentials by making a test API call.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
credentials: Credentials to validate.
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
True if credentials are valid, False otherwise.
|
|
98
|
-
"""
|
|
99
|
-
try:
|
|
100
|
-
client = await self.create_client(credentials)
|
|
101
|
-
async with client:
|
|
102
|
-
# Try to verify credentials
|
|
103
|
-
return await client.is_authenticated()
|
|
104
|
-
except Exception as e:
|
|
105
|
-
logger.error(f"Error validating Instagram credentials: {e}")
|
|
106
|
-
return False
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
"""Instagram post manager for handling post operations."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
from src.marqetive.core.base_manager import BasePostManager
|
|
7
|
-
from src.marqetive.platforms.instagram.client import InstagramClient
|
|
8
|
-
from src.marqetive.platforms.instagram.factory import InstagramAccountFactory
|
|
9
|
-
from src.marqetive.platforms.models import AuthCredentials, Post, PostCreateRequest
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class InstagramPostManager(BasePostManager):
|
|
15
|
-
"""Manager for Instagram post operations.
|
|
16
|
-
|
|
17
|
-
Coordinates post creation, media uploads, and progress tracking for Instagram.
|
|
18
|
-
|
|
19
|
-
Example:
|
|
20
|
-
>>> manager = InstagramPostManager()
|
|
21
|
-
>>> credentials = AuthCredentials(
|
|
22
|
-
... platform="instagram",
|
|
23
|
-
... access_token="token",
|
|
24
|
-
... additional_data={"instagram_business_account_id": "123"}
|
|
25
|
-
... )
|
|
26
|
-
>>> request = PostCreateRequest(content="Hello Instagram!")
|
|
27
|
-
>>> post = await manager.execute_post(credentials, request)
|
|
28
|
-
>>> print(f"Media ID: {post.post_id}")
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
def __init__(
|
|
32
|
-
self,
|
|
33
|
-
account_factory: InstagramAccountFactory | None = None,
|
|
34
|
-
) -> None:
|
|
35
|
-
"""Initialize Instagram post manager.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
account_factory: Instagram account factory (creates default if None).
|
|
39
|
-
"""
|
|
40
|
-
if account_factory is None:
|
|
41
|
-
account_factory = InstagramAccountFactory()
|
|
42
|
-
|
|
43
|
-
super().__init__(account_factory=account_factory)
|
|
44
|
-
|
|
45
|
-
@property
|
|
46
|
-
def platform_name(self) -> str:
|
|
47
|
-
"""Get platform name."""
|
|
48
|
-
return "instagram"
|
|
49
|
-
|
|
50
|
-
async def _execute_post_impl(
|
|
51
|
-
self,
|
|
52
|
-
client: Any,
|
|
53
|
-
request: PostCreateRequest,
|
|
54
|
-
credentials: AuthCredentials, # noqa: ARG002
|
|
55
|
-
) -> Post:
|
|
56
|
-
"""Execute Instagram post creation.
|
|
57
|
-
|
|
58
|
-
Args:
|
|
59
|
-
client: InstagramClient instance.
|
|
60
|
-
request: Post creation request.
|
|
61
|
-
credentials: Instagram credentials.
|
|
62
|
-
|
|
63
|
-
Returns:
|
|
64
|
-
Created Post object.
|
|
65
|
-
"""
|
|
66
|
-
if not isinstance(client, InstagramClient):
|
|
67
|
-
raise TypeError(f"Expected InstagramClient, got {type(client)}")
|
|
68
|
-
|
|
69
|
-
# Handle media uploads with progress tracking
|
|
70
|
-
media_ids: list[str] = []
|
|
71
|
-
if request.media_urls:
|
|
72
|
-
self._progress_tracker.emit_start(
|
|
73
|
-
"upload_media",
|
|
74
|
-
total=len(request.media_urls),
|
|
75
|
-
message=f"Uploading {len(request.media_urls)} media files...",
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
for idx, media_url in enumerate(request.media_urls):
|
|
79
|
-
if self.is_cancelled():
|
|
80
|
-
raise InterruptedError("Post creation was cancelled")
|
|
81
|
-
|
|
82
|
-
self._progress_tracker.emit_progress(
|
|
83
|
-
"upload_media",
|
|
84
|
-
progress=idx,
|
|
85
|
-
total=len(request.media_urls),
|
|
86
|
-
message=f"Uploading media {idx + 1}/{len(request.media_urls)}...",
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
media_attachment = await client.upload_media(
|
|
90
|
-
media_url=media_url,
|
|
91
|
-
media_type="image", # Default to image
|
|
92
|
-
alt_text=None,
|
|
93
|
-
)
|
|
94
|
-
media_ids.append(media_attachment.media_id)
|
|
95
|
-
|
|
96
|
-
self._progress_tracker.emit_complete(
|
|
97
|
-
"upload_media",
|
|
98
|
-
message="All media uploaded successfully",
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
# Create post with progress tracking
|
|
102
|
-
self._progress_tracker.emit_progress(
|
|
103
|
-
"execute_post",
|
|
104
|
-
progress=50,
|
|
105
|
-
total=100,
|
|
106
|
-
message="Creating Instagram post...",
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
# Use the client to create the post
|
|
110
|
-
post = await client.create_post(request)
|
|
111
|
-
|
|
112
|
-
return post
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"""LinkedIn account factory for managing credentials and client creation."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import os
|
|
5
|
-
from collections.abc import Callable
|
|
6
|
-
|
|
7
|
-
from src.marqetive.core.account_factory import BaseAccountFactory
|
|
8
|
-
from src.marqetive.platforms.exceptions import PlatformAuthError
|
|
9
|
-
from src.marqetive.platforms.linkedin.client import LinkedInClient
|
|
10
|
-
from src.marqetive.platforms.models import AccountStatus, AuthCredentials
|
|
11
|
-
from src.marqetive.utils.oauth import refresh_linkedin_token
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class LinkedInAccountFactory(BaseAccountFactory):
|
|
17
|
-
"""Factory for creating and managing LinkedIn accounts and clients.
|
|
18
|
-
|
|
19
|
-
Example:
|
|
20
|
-
>>> factory = LinkedInAccountFactory(
|
|
21
|
-
... client_id="your_client_id",
|
|
22
|
-
... client_secret="your_client_secret"
|
|
23
|
-
... )
|
|
24
|
-
>>> credentials = AuthCredentials(
|
|
25
|
-
... platform="linkedin",
|
|
26
|
-
... access_token="token",
|
|
27
|
-
... refresh_token="refresh"
|
|
28
|
-
... )
|
|
29
|
-
>>> client = await factory.create_authenticated_client(credentials)
|
|
30
|
-
>>> async with client:
|
|
31
|
-
... post = await client.create_post(request)
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
def __init__(
|
|
35
|
-
self,
|
|
36
|
-
client_id: str | None = None,
|
|
37
|
-
client_secret: str | None = None,
|
|
38
|
-
on_status_update: Callable[[str, AccountStatus], None] | None = None,
|
|
39
|
-
) -> None:
|
|
40
|
-
"""Initialize LinkedIn account factory.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
client_id: LinkedIn OAuth client ID (uses LINKEDIN_CLIENT_ID env if None).
|
|
44
|
-
client_secret: LinkedIn OAuth client secret (uses LINKEDIN_CLIENT_SECRET env if None).
|
|
45
|
-
on_status_update: Optional callback when account status changes.
|
|
46
|
-
"""
|
|
47
|
-
super().__init__(on_status_update=on_status_update)
|
|
48
|
-
self.client_id = client_id or os.getenv("LINKEDIN_CLIENT_ID")
|
|
49
|
-
self.client_secret = client_secret or os.getenv("LINKEDIN_CLIENT_SECRET")
|
|
50
|
-
|
|
51
|
-
if not self.client_id or not self.client_secret:
|
|
52
|
-
logger.warning(
|
|
53
|
-
"LinkedIn client_id/client_secret not provided. "
|
|
54
|
-
"Token refresh will not work."
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def platform_name(self) -> str:
|
|
59
|
-
"""Get platform name."""
|
|
60
|
-
return "linkedin"
|
|
61
|
-
|
|
62
|
-
async def refresh_token(self, credentials: AuthCredentials) -> AuthCredentials:
|
|
63
|
-
"""Refresh LinkedIn OAuth2 access token.
|
|
64
|
-
|
|
65
|
-
Args:
|
|
66
|
-
credentials: Current credentials with refresh token.
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
Updated credentials with new access token.
|
|
70
|
-
|
|
71
|
-
Raises:
|
|
72
|
-
PlatformAuthError: If refresh fails or credentials missing.
|
|
73
|
-
"""
|
|
74
|
-
if not self.client_id or not self.client_secret:
|
|
75
|
-
raise PlatformAuthError(
|
|
76
|
-
"LinkedIn client_id and client_secret are required for token refresh",
|
|
77
|
-
platform=self.platform_name,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
if not credentials.refresh_token:
|
|
81
|
-
raise PlatformAuthError(
|
|
82
|
-
"No refresh token available",
|
|
83
|
-
platform=self.platform_name,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
logger.info("Refreshing LinkedIn access token...")
|
|
87
|
-
return await refresh_linkedin_token(
|
|
88
|
-
credentials,
|
|
89
|
-
self.client_id,
|
|
90
|
-
self.client_secret,
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
async def create_client(self, credentials: AuthCredentials) -> LinkedInClient:
|
|
94
|
-
"""Create LinkedIn API client.
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
credentials: Valid LinkedIn credentials.
|
|
98
|
-
|
|
99
|
-
Returns:
|
|
100
|
-
LinkedInClient instance.
|
|
101
|
-
|
|
102
|
-
Raises:
|
|
103
|
-
PlatformAuthError: If credentials are invalid.
|
|
104
|
-
"""
|
|
105
|
-
if not credentials.access_token:
|
|
106
|
-
raise PlatformAuthError(
|
|
107
|
-
"Access token is required",
|
|
108
|
-
platform=self.platform_name,
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
return LinkedInClient(credentials=credentials)
|
|
112
|
-
|
|
113
|
-
async def validate_credentials(self, credentials: AuthCredentials) -> bool:
|
|
114
|
-
"""Validate LinkedIn credentials by making a test API call.
|
|
115
|
-
|
|
116
|
-
Args:
|
|
117
|
-
credentials: Credentials to validate.
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
True if credentials are valid, False otherwise.
|
|
121
|
-
"""
|
|
122
|
-
try:
|
|
123
|
-
client = await self.create_client(credentials)
|
|
124
|
-
async with client:
|
|
125
|
-
# Try to verify credentials by getting current user
|
|
126
|
-
# This would need to be implemented in the client
|
|
127
|
-
return await client.is_authenticated()
|
|
128
|
-
except Exception as e:
|
|
129
|
-
logger.error(f"Error validating LinkedIn credentials: {e}")
|
|
130
|
-
return False
|