marqetive-lib 0.1.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.
- marqetive/__init__.py +113 -0
- marqetive/core/__init__.py +5 -0
- marqetive/core/account_factory.py +212 -0
- marqetive/core/base_manager.py +303 -0
- marqetive/core/client.py +108 -0
- marqetive/core/progress.py +291 -0
- marqetive/core/registry.py +257 -0
- marqetive/platforms/__init__.py +55 -0
- marqetive/platforms/base.py +390 -0
- marqetive/platforms/exceptions.py +238 -0
- marqetive/platforms/instagram/__init__.py +7 -0
- marqetive/platforms/instagram/client.py +786 -0
- marqetive/platforms/instagram/exceptions.py +311 -0
- marqetive/platforms/instagram/factory.py +106 -0
- marqetive/platforms/instagram/manager.py +112 -0
- marqetive/platforms/instagram/media.py +669 -0
- marqetive/platforms/linkedin/__init__.py +7 -0
- marqetive/platforms/linkedin/client.py +733 -0
- marqetive/platforms/linkedin/exceptions.py +335 -0
- marqetive/platforms/linkedin/factory.py +130 -0
- marqetive/platforms/linkedin/manager.py +119 -0
- marqetive/platforms/linkedin/media.py +549 -0
- marqetive/platforms/models.py +345 -0
- marqetive/platforms/tiktok/__init__.py +0 -0
- marqetive/platforms/twitter/__init__.py +7 -0
- marqetive/platforms/twitter/client.py +647 -0
- marqetive/platforms/twitter/exceptions.py +311 -0
- marqetive/platforms/twitter/factory.py +151 -0
- marqetive/platforms/twitter/manager.py +121 -0
- marqetive/platforms/twitter/media.py +779 -0
- marqetive/platforms/twitter/threads.py +442 -0
- marqetive/py.typed +0 -0
- marqetive/registry_init.py +66 -0
- marqetive/utils/__init__.py +45 -0
- marqetive/utils/file_handlers.py +438 -0
- marqetive/utils/helpers.py +99 -0
- marqetive/utils/media.py +399 -0
- marqetive/utils/oauth.py +265 -0
- marqetive/utils/retry.py +239 -0
- marqetive/utils/token_validator.py +240 -0
- marqetive_lib-0.1.0.dist-info/METADATA +261 -0
- marqetive_lib-0.1.0.dist-info/RECORD +43 -0
- marqetive_lib-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Instagram-specific exception handling and error code mapping.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive error handling for Instagram Graph API errors.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from marqetive.platforms.exceptions import (
|
|
9
|
+
MediaUploadError,
|
|
10
|
+
PlatformAuthError,
|
|
11
|
+
PlatformError,
|
|
12
|
+
RateLimitError,
|
|
13
|
+
ValidationError,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Instagram Graph API error codes
|
|
18
|
+
# Source: https://developers.facebook.com/docs/graph-api/guides/error-handling
|
|
19
|
+
class InstagramErrorCode:
|
|
20
|
+
"""Instagram Graph API error codes."""
|
|
21
|
+
|
|
22
|
+
# Authentication & Authorization (1-99)
|
|
23
|
+
API_UNKNOWN = 1
|
|
24
|
+
API_SERVICE = 2
|
|
25
|
+
API_TOO_MANY_CALLS = 4
|
|
26
|
+
API_USER_TOO_MANY_CALLS = 17
|
|
27
|
+
API_PERMISSION_DENIED = 10
|
|
28
|
+
OAuthException = 190
|
|
29
|
+
ACCESS_TOKEN_EXPIRED = 190
|
|
30
|
+
|
|
31
|
+
# Resource errors (100-199)
|
|
32
|
+
UNSUPPORTED_GET_REQUEST = 100
|
|
33
|
+
INVALID_PARAMETER = 100
|
|
34
|
+
USER_NOT_VISIBLE = 190
|
|
35
|
+
|
|
36
|
+
# Rate limiting (200-299)
|
|
37
|
+
APPLICATION_LIMIT = 4
|
|
38
|
+
USER_REQUEST_LIMIT = 17
|
|
39
|
+
|
|
40
|
+
# Media errors (300-399)
|
|
41
|
+
MEDIA_UPLOAD_ERROR = 324
|
|
42
|
+
INVALID_MEDIA_TYPE = 352
|
|
43
|
+
MEDIA_PROCESSING_ERROR = 368
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Error type constants
|
|
47
|
+
ERROR_TYPE_OAUTH = "OAuthException"
|
|
48
|
+
ERROR_TYPE_API = "FacebookApiException"
|
|
49
|
+
ERROR_TYPE_GRAPH_METHOD = "GraphMethodException"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Mapping of error codes to user-friendly messages
|
|
53
|
+
ERROR_MESSAGES: dict[int, str] = {
|
|
54
|
+
1: "An unknown error occurred.",
|
|
55
|
+
2: "A service error occurred. Please try again later.",
|
|
56
|
+
4: "Application request limit reached. Please retry later.",
|
|
57
|
+
17: "User request limit reached. Please retry later.",
|
|
58
|
+
10: "Permission denied. Check your app permissions.",
|
|
59
|
+
100: "Invalid or unsupported parameter in request.",
|
|
60
|
+
190: "Access token is invalid or expired. Please re-authenticate.",
|
|
61
|
+
324: "Media upload failed. Please check file format and size.",
|
|
62
|
+
352: "Invalid media type. Instagram only supports specific formats.",
|
|
63
|
+
368: "Media processing failed. Please try a different file.",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Retryable error codes
|
|
68
|
+
RETRYABLE_ERROR_CODES = {
|
|
69
|
+
1, # Unknown error
|
|
70
|
+
2, # Service error
|
|
71
|
+
4, # Too many calls
|
|
72
|
+
17, # User too many calls
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Non-retryable error codes
|
|
77
|
+
NON_RETRYABLE_ERROR_CODES = {
|
|
78
|
+
10, # Permission denied
|
|
79
|
+
100, # Invalid parameter
|
|
80
|
+
190, # OAuth exception
|
|
81
|
+
352, # Invalid media type
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def map_instagram_error(
|
|
86
|
+
status_code: int | None,
|
|
87
|
+
error_code: int | None = None,
|
|
88
|
+
error_type: str | None = None,
|
|
89
|
+
error_message: str | None = None,
|
|
90
|
+
response_data: dict[str, Any] | None = None,
|
|
91
|
+
) -> PlatformError:
|
|
92
|
+
"""Map Instagram Graph API error to appropriate exception.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
status_code: HTTP status code.
|
|
96
|
+
error_code: Instagram-specific error code.
|
|
97
|
+
error_type: Instagram error type.
|
|
98
|
+
error_message: Error message from API.
|
|
99
|
+
response_data: Full response data from API.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Appropriate PlatformError subclass.
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
>>> error = map_instagram_error(401, 190, "OAuthException")
|
|
106
|
+
>>> print(type(error).__name__)
|
|
107
|
+
PlatformAuthError
|
|
108
|
+
"""
|
|
109
|
+
# Extract error details from response if provided
|
|
110
|
+
if response_data and "error" in response_data:
|
|
111
|
+
error_obj = response_data["error"]
|
|
112
|
+
if not error_code:
|
|
113
|
+
error_code = error_obj.get("code")
|
|
114
|
+
if not error_type:
|
|
115
|
+
error_type = error_obj.get("type")
|
|
116
|
+
if not error_message:
|
|
117
|
+
error_message = error_obj.get("message")
|
|
118
|
+
|
|
119
|
+
# Get user-friendly message
|
|
120
|
+
friendly_message = ERROR_MESSAGES.get(
|
|
121
|
+
error_code or 0, error_message or "Unknown error"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Determine retry-after for rate limits
|
|
125
|
+
retry_after = None
|
|
126
|
+
if error_code in (4, 17):
|
|
127
|
+
# Instagram uses 1-hour windows typically
|
|
128
|
+
retry_after = 3600 # 1 hour in seconds
|
|
129
|
+
|
|
130
|
+
# Map to appropriate exception type
|
|
131
|
+
# OAuth / Authentication errors
|
|
132
|
+
if error_type == ERROR_TYPE_OAUTH or error_code == 190:
|
|
133
|
+
return PlatformAuthError(
|
|
134
|
+
friendly_message,
|
|
135
|
+
platform="instagram",
|
|
136
|
+
status_code=status_code or 401,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Permission errors
|
|
140
|
+
if error_code == 10 or status_code == 403:
|
|
141
|
+
return PlatformAuthError(
|
|
142
|
+
friendly_message,
|
|
143
|
+
platform="instagram",
|
|
144
|
+
status_code=status_code or 403,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Rate limit errors
|
|
148
|
+
if error_code in (4, 17) or status_code == 429:
|
|
149
|
+
return RateLimitError(
|
|
150
|
+
friendly_message,
|
|
151
|
+
platform="instagram",
|
|
152
|
+
status_code=status_code or 429,
|
|
153
|
+
retry_after=retry_after,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Validation errors
|
|
157
|
+
if error_code == 100:
|
|
158
|
+
return ValidationError(
|
|
159
|
+
friendly_message,
|
|
160
|
+
platform="instagram",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Media errors
|
|
164
|
+
if error_code in (324, 352, 368):
|
|
165
|
+
return MediaUploadError(
|
|
166
|
+
friendly_message,
|
|
167
|
+
platform="instagram",
|
|
168
|
+
status_code=status_code,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Generic platform error
|
|
172
|
+
return PlatformError(
|
|
173
|
+
friendly_message,
|
|
174
|
+
platform="instagram",
|
|
175
|
+
status_code=status_code,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def is_retryable_instagram_error(
|
|
180
|
+
status_code: int | None,
|
|
181
|
+
error_code: int | None = None,
|
|
182
|
+
error_type: str | None = None,
|
|
183
|
+
) -> bool:
|
|
184
|
+
"""Determine if an Instagram error is retryable.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
status_code: HTTP status code.
|
|
188
|
+
error_code: Instagram-specific error code.
|
|
189
|
+
error_type: Instagram error type.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
True if error is retryable, False otherwise.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
>>> is_retryable_instagram_error(500)
|
|
196
|
+
True
|
|
197
|
+
>>> is_retryable_instagram_error(400, 190, "OAuthException")
|
|
198
|
+
False
|
|
199
|
+
"""
|
|
200
|
+
# Check explicit non-retryable codes first
|
|
201
|
+
if error_code in NON_RETRYABLE_ERROR_CODES:
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
# OAuth errors are not retryable
|
|
205
|
+
if error_type == ERROR_TYPE_OAUTH:
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
# Check retryable codes
|
|
209
|
+
if error_code in RETRYABLE_ERROR_CODES:
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
# Check HTTP status codes
|
|
213
|
+
if status_code:
|
|
214
|
+
# 5xx errors are generally retryable
|
|
215
|
+
if 500 <= status_code < 600:
|
|
216
|
+
return True
|
|
217
|
+
# 429 is retryable
|
|
218
|
+
if status_code == 429:
|
|
219
|
+
return True
|
|
220
|
+
# 4xx errors (except 429) are generally not retryable
|
|
221
|
+
if 400 <= status_code < 500:
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
# Default to not retryable for safety
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def get_retry_delay(
|
|
229
|
+
status_code: int | None,
|
|
230
|
+
error_code: int | None = None,
|
|
231
|
+
attempt: int = 1,
|
|
232
|
+
) -> float:
|
|
233
|
+
"""Get recommended retry delay for Instagram error.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
status_code: HTTP status code.
|
|
237
|
+
error_code: Instagram-specific error code.
|
|
238
|
+
attempt: Current retry attempt number.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Recommended delay in seconds.
|
|
242
|
+
|
|
243
|
+
Example:
|
|
244
|
+
>>> get_retry_delay(503, attempt=1)
|
|
245
|
+
5.0
|
|
246
|
+
>>> get_retry_delay(None, 4)
|
|
247
|
+
3600.0
|
|
248
|
+
"""
|
|
249
|
+
# Rate limit errors - wait full window
|
|
250
|
+
if error_code in (4, 17):
|
|
251
|
+
return 3600.0 # 1 hour
|
|
252
|
+
|
|
253
|
+
# Server errors - exponential backoff
|
|
254
|
+
if status_code and 500 <= status_code < 600:
|
|
255
|
+
base_delay = 5.0
|
|
256
|
+
return min(base_delay * (2 ** (attempt - 1)), 120.0)
|
|
257
|
+
|
|
258
|
+
# Default delay
|
|
259
|
+
return 10.0
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class InstagramAPIError(PlatformError):
|
|
263
|
+
"""Instagram Graph API specific error with detailed information.
|
|
264
|
+
|
|
265
|
+
Attributes:
|
|
266
|
+
error_code: Instagram-specific error code.
|
|
267
|
+
error_type: Instagram error type.
|
|
268
|
+
error_subcode: Instagram error subcode (if available).
|
|
269
|
+
is_retryable: Whether the error is retryable.
|
|
270
|
+
retry_delay: Recommended retry delay in seconds.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
def __init__(
|
|
274
|
+
self,
|
|
275
|
+
message: str,
|
|
276
|
+
*,
|
|
277
|
+
status_code: int | None = None,
|
|
278
|
+
error_code: int | None = None,
|
|
279
|
+
error_type: str | None = None,
|
|
280
|
+
error_subcode: int | None = None,
|
|
281
|
+
response_data: dict[str, Any] | None = None,
|
|
282
|
+
) -> None:
|
|
283
|
+
"""Initialize Instagram API error.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
message: Error message.
|
|
287
|
+
status_code: HTTP status code.
|
|
288
|
+
error_code: Instagram-specific error code.
|
|
289
|
+
error_type: Instagram error type.
|
|
290
|
+
error_subcode: Instagram error subcode.
|
|
291
|
+
response_data: Full response data from API.
|
|
292
|
+
"""
|
|
293
|
+
super().__init__(message, platform="instagram", status_code=status_code)
|
|
294
|
+
self.error_code = error_code
|
|
295
|
+
self.error_type = error_type
|
|
296
|
+
self.error_subcode = error_subcode
|
|
297
|
+
self.response_data = response_data
|
|
298
|
+
self.is_retryable = is_retryable_instagram_error(
|
|
299
|
+
status_code, error_code, error_type
|
|
300
|
+
)
|
|
301
|
+
self.retry_delay = get_retry_delay(status_code, error_code)
|
|
302
|
+
|
|
303
|
+
def __repr__(self) -> str:
|
|
304
|
+
"""String representation of error."""
|
|
305
|
+
return (
|
|
306
|
+
f"InstagramAPIError(message={self.message!r}, "
|
|
307
|
+
f"status_code={self.status_code}, "
|
|
308
|
+
f"error_code={self.error_code}, "
|
|
309
|
+
f"error_type={self.error_type!r}, "
|
|
310
|
+
f"retryable={self.is_retryable})"
|
|
311
|
+
)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Instagram account factory for managing credentials and client creation."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
from marqetive.core.account_factory import BaseAccountFactory
|
|
7
|
+
from marqetive.platforms.exceptions import PlatformAuthError
|
|
8
|
+
from marqetive.platforms.instagram.client import InstagramClient
|
|
9
|
+
from marqetive.platforms.models import AccountStatus, AuthCredentials
|
|
10
|
+
from 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
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Instagram post manager for handling post operations."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from marqetive.core.base_manager import BasePostManager
|
|
7
|
+
from marqetive.platforms.instagram.client import InstagramClient
|
|
8
|
+
from marqetive.platforms.instagram.factory import InstagramAccountFactory
|
|
9
|
+
from 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
|