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.
Files changed (43) hide show
  1. marqetive/__init__.py +58 -59
  2. marqetive/core/__init__.py +1 -1
  3. marqetive/factory.py +380 -0
  4. marqetive/platforms/__init__.py +6 -6
  5. marqetive/platforms/base.py +36 -3
  6. marqetive/platforms/instagram/__init__.py +2 -4
  7. marqetive/platforms/instagram/client.py +8 -4
  8. marqetive/platforms/instagram/exceptions.py +1 -1
  9. marqetive/platforms/instagram/media.py +2 -2
  10. marqetive/platforms/linkedin/__init__.py +2 -4
  11. marqetive/platforms/linkedin/client.py +8 -4
  12. marqetive/platforms/linkedin/exceptions.py +1 -1
  13. marqetive/platforms/linkedin/media.py +4 -4
  14. marqetive/platforms/tiktok/__init__.py +2 -4
  15. marqetive/platforms/tiktok/client.py +324 -104
  16. marqetive/platforms/tiktok/exceptions.py +170 -66
  17. marqetive/platforms/tiktok/media.py +545 -159
  18. marqetive/platforms/twitter/__init__.py +2 -4
  19. marqetive/platforms/twitter/client.py +11 -53
  20. marqetive/platforms/twitter/exceptions.py +1 -1
  21. marqetive/platforms/twitter/media.py +4 -4
  22. marqetive/utils/__init__.py +3 -3
  23. marqetive/utils/file_handlers.py +1 -1
  24. marqetive/utils/oauth.py +2 -2
  25. marqetive/utils/token_validator.py +1 -1
  26. {marqetive_lib-0.1.2.dist-info → marqetive_lib-0.1.4.dist-info}/METADATA +1 -1
  27. marqetive_lib-0.1.4.dist-info/RECORD +35 -0
  28. marqetive/core/account_factory.py +0 -212
  29. marqetive/core/base_manager.py +0 -303
  30. marqetive/core/progress.py +0 -291
  31. marqetive/core/registry.py +0 -257
  32. marqetive/platforms/instagram/factory.py +0 -106
  33. marqetive/platforms/instagram/manager.py +0 -112
  34. marqetive/platforms/linkedin/factory.py +0 -130
  35. marqetive/platforms/linkedin/manager.py +0 -119
  36. marqetive/platforms/tiktok/factory.py +0 -188
  37. marqetive/platforms/tiktok/manager.py +0 -115
  38. marqetive/platforms/twitter/factory.py +0 -151
  39. marqetive/platforms/twitter/manager.py +0 -121
  40. marqetive/platforms/twitter/threads.py +0 -442
  41. marqetive/registry_init.py +0 -66
  42. marqetive_lib-0.1.2.dist-info/RECORD +0 -48
  43. {marqetive_lib-0.1.2.dist-info → marqetive_lib-0.1.4.dist-info}/WHEEL +0 -0
@@ -1,119 +0,0 @@
1
- """LinkedIn 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.linkedin.client import LinkedInClient
8
- from src.marqetive.platforms.linkedin.factory import LinkedInAccountFactory
9
- from src.marqetive.platforms.models import AuthCredentials, Post, PostCreateRequest
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- class LinkedInPostManager(BasePostManager):
15
- """Manager for LinkedIn post operations.
16
-
17
- Coordinates post creation, media uploads, and progress tracking for LinkedIn.
18
-
19
- Example:
20
- >>> manager = LinkedInPostManager()
21
- >>> credentials = AuthCredentials(
22
- ... platform="linkedin",
23
- ... access_token="token",
24
- ... refresh_token="refresh"
25
- ... )
26
- >>> request = PostCreateRequest(content="Hello LinkedIn!")
27
- >>> post = await manager.execute_post(credentials, request)
28
- >>> print(f"Post URN: {post.post_id}")
29
- """
30
-
31
- def __init__(
32
- self,
33
- account_factory: LinkedInAccountFactory | None = None,
34
- client_id: str | None = None,
35
- client_secret: str | None = None,
36
- ) -> None:
37
- """Initialize LinkedIn post manager.
38
-
39
- Args:
40
- account_factory: LinkedIn account factory (creates default if None).
41
- client_id: LinkedIn OAuth client ID (for default factory).
42
- client_secret: LinkedIn OAuth client secret (for default factory).
43
- """
44
- if account_factory is None:
45
- account_factory = LinkedInAccountFactory(
46
- client_id=client_id,
47
- client_secret=client_secret,
48
- )
49
-
50
- super().__init__(account_factory=account_factory)
51
-
52
- @property
53
- def platform_name(self) -> str:
54
- """Get platform name."""
55
- return "linkedin"
56
-
57
- async def _execute_post_impl(
58
- self,
59
- client: Any,
60
- request: PostCreateRequest,
61
- credentials: AuthCredentials, # noqa: ARG002
62
- ) -> Post:
63
- """Execute LinkedIn post creation.
64
-
65
- Args:
66
- client: LinkedInClient instance.
67
- request: Post creation request.
68
- credentials: LinkedIn credentials.
69
-
70
- Returns:
71
- Created Post object.
72
- """
73
- if not isinstance(client, LinkedInClient):
74
- raise TypeError(f"Expected LinkedInClient, got {type(client)}")
75
-
76
- # Handle media uploads with progress tracking
77
- media_ids: list[str] = []
78
- if request.media_urls:
79
- self._progress_tracker.emit_start(
80
- "upload_media",
81
- total=len(request.media_urls),
82
- message=f"Uploading {len(request.media_urls)} media files...",
83
- )
84
-
85
- for idx, media_url in enumerate(request.media_urls):
86
- if self.is_cancelled():
87
- raise InterruptedError("Post creation was cancelled")
88
-
89
- self._progress_tracker.emit_progress(
90
- "upload_media",
91
- progress=idx,
92
- total=len(request.media_urls),
93
- message=f"Uploading media {idx + 1}/{len(request.media_urls)}...",
94
- )
95
-
96
- media_attachment = await client.upload_media(
97
- media_url=media_url,
98
- media_type="image", # Default to image
99
- alt_text=None,
100
- )
101
- media_ids.append(media_attachment.media_id)
102
-
103
- self._progress_tracker.emit_complete(
104
- "upload_media",
105
- message="All media uploaded successfully",
106
- )
107
-
108
- # Create post with progress tracking
109
- self._progress_tracker.emit_progress(
110
- "execute_post",
111
- progress=50,
112
- total=100,
113
- message="Creating LinkedIn post...",
114
- )
115
-
116
- # Use the client to create the post
117
- post = await client.create_post(request)
118
-
119
- return post
@@ -1,188 +0,0 @@
1
- """TikTok account factory for managing credentials and client creation."""
2
-
3
- import logging
4
- import os
5
- from collections.abc import Callable
6
- from datetime import datetime, timedelta
7
-
8
- from src.marqetive.core.account_factory import BaseAccountFactory
9
- from src.marqetive.platforms.exceptions import PlatformAuthError
10
- from src.marqetive.platforms.models import AccountStatus, AuthCredentials
11
- from src.marqetive.platforms.tiktok.client import TikTokClient
12
- from src.marqetive.utils.oauth import fetch_tiktok_token, refresh_tiktok_token
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- class TikTokAccountFactory(BaseAccountFactory):
18
- """Factory for creating and managing TikTok accounts and clients.
19
-
20
- This factory handles the instantiation of TikTokClients, manages OAuth
21
- credentials, and provides a mechanism for token refreshing.
22
- """
23
-
24
- def __init__(
25
- self,
26
- client_id: str | None = None,
27
- client_secret: str | None = None,
28
- on_status_update: Callable[[str, AccountStatus], None] | None = None,
29
- ) -> None:
30
- """Initialize TikTok account factory.
31
-
32
- Args:
33
- client_id: TikTok App client ID (uses TIKTOK_CLIENT_ID env if None).
34
- client_secret: TikTok App client secret (uses TIKTOK_CLIENT_SECRET env if None).
35
- on_status_update: Optional callback when account status changes.
36
- """
37
- super().__init__(on_status_update=on_status_update)
38
- self.client_id = client_id or os.getenv("TIKTOK_CLIENT_ID")
39
- self.client_secret = client_secret or os.getenv("TIKTOK_CLIENT_SECRET")
40
-
41
- if not self.client_id or not self.client_secret:
42
- logger.warning(
43
- "TikTok client_id/client_secret not provided. "
44
- "Token refresh may not work."
45
- )
46
-
47
- @property
48
- def platform_name(self) -> str:
49
- """Get the platform name."""
50
- return "tiktok"
51
-
52
- async def get_credentials_from_auth_code(
53
- self,
54
- auth_code: str,
55
- redirect_uri: str,
56
- code_verifier: str | None = None,
57
- ) -> AuthCredentials:
58
- """Get credentials from an authorization code.
59
-
60
- This method exchanges an authorization code for an access token and
61
- formats it into the standard AuthCredentials object.
62
-
63
- Args:
64
- auth_code: The authorization code received from TikTok.
65
- redirect_uri: The redirect URI used in the initial auth request.
66
- code_verifier: The PKCE code verifier, if used.
67
-
68
- Returns:
69
- An AuthCredentials object for the user.
70
-
71
- Raises:
72
- PlatformAuthError: If the token exchange fails.
73
- """
74
- if not self.client_id or not self.client_secret:
75
- raise PlatformAuthError(
76
- "TikTok client_id and client_secret are required.",
77
- platform=self.platform_name,
78
- )
79
-
80
- token_data = await fetch_tiktok_token(
81
- code=auth_code,
82
- client_id=self.client_id,
83
- client_secret=self.client_secret,
84
- redirect_uri=redirect_uri,
85
- code_verifier=code_verifier,
86
- )
87
-
88
- expires_at = None
89
- if "expires_in" in token_data:
90
- expires_at = datetime.now() + timedelta(seconds=token_data["expires_in"])
91
-
92
- refresh_expires_at = None
93
- if "refresh_expires_in" in token_data:
94
- refresh_expires_at = datetime.now() + timedelta(
95
- seconds=token_data["refresh_expires_in"]
96
- )
97
-
98
- credentials = AuthCredentials(
99
- platform=self.platform_name,
100
- access_token=token_data["access_token"],
101
- refresh_token=token_data.get("refresh_token"),
102
- expires_at=expires_at,
103
- scope=token_data.get("scope", []),
104
- additional_data={
105
- "open_id": token_data["open_id"],
106
- "token_type": token_data.get("token_type"),
107
- "refresh_expires_at": refresh_expires_at,
108
- },
109
- )
110
- return credentials
111
-
112
- async def refresh_token(self, credentials: AuthCredentials) -> AuthCredentials:
113
- """Refresh a TikTok OAuth2 access token.
114
-
115
- Args:
116
- credentials: The current credentials containing the refresh token.
117
-
118
- Returns:
119
- Updated credentials with a new access token.
120
-
121
- Raises:
122
- PlatformAuthError: If refresh fails or credentials are missing.
123
- """
124
- if not self.client_id or not self.client_secret:
125
- raise PlatformAuthError(
126
- "TikTok client_id and client_secret are required for token refresh.",
127
- platform=self.platform_name,
128
- )
129
-
130
- if not credentials.refresh_token:
131
- raise PlatformAuthError(
132
- "No refresh token available for TikTok.", platform=self.platform_name
133
- )
134
-
135
- logger.info("Refreshing TikTok access token...")
136
- # This function would call the actual TikTok token refresh endpoint
137
- return await refresh_tiktok_token(
138
- credentials, self.client_id, self.client_secret
139
- )
140
-
141
- async def create_client(self, credentials: AuthCredentials) -> TikTokClient:
142
- """Create a TikTok API client.
143
-
144
- Args:
145
- credentials: Valid TikTok authentication credentials.
146
-
147
- Returns:
148
- An instance of TikTokClient.
149
-
150
- Raises:
151
- PlatformAuthError: If credentials are incomplete or invalid.
152
- """
153
- if not credentials.access_token:
154
- raise PlatformAuthError(
155
- "Access token is required for TikTok.", platform=self.platform_name
156
- )
157
- if (
158
- not credentials.additional_data
159
- or "open_id" not in credentials.additional_data
160
- ):
161
- raise PlatformAuthError(
162
- "'open_id' must be provided in additional_data for TikTok.",
163
- platform=self.platform_name,
164
- )
165
-
166
- return TikTokClient(credentials=credentials)
167
-
168
- async def validate_credentials(self, credentials: AuthCredentials) -> bool:
169
- """Validate TikTok credentials by making a test API call.
170
-
171
- Args:
172
- credentials: The credentials to validate.
173
-
174
- Returns:
175
- True if the credentials are valid, False otherwise.
176
- """
177
- try:
178
- client = await self.create_client(credentials)
179
- async with client:
180
- return await client.is_authenticated()
181
- except PlatformAuthError as e:
182
- logger.warning(f"TikTok credential validation failed: {e}")
183
- return False
184
- except Exception as e:
185
- logger.error(
186
- f"An unexpected error occurred during TikTok credential validation: {e}"
187
- )
188
- return False
@@ -1,115 +0,0 @@
1
- """TikTok post manager for handling video 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.models import AuthCredentials, Post, PostCreateRequest
8
- from src.marqetive.platforms.tiktok.client import TikTokClient
9
- from src.marqetive.platforms.tiktok.factory import TikTokAccountFactory
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- class TikTokPostManager(BasePostManager):
15
- """Manager for TikTok video post operations.
16
-
17
- This manager coordinates the multi-step process of posting a video to
18
- TikTok, including media upload, processing, and the final publishing step.
19
- It provides progress tracking throughout the operation.
20
- """
21
-
22
- def __init__(
23
- self,
24
- account_factory: TikTokAccountFactory | None = None,
25
- client_id: str | None = None,
26
- client_secret: str | None = None,
27
- ) -> None:
28
- """Initialize the TikTok post manager.
29
-
30
- Args:
31
- account_factory: An instance of TikTokAccountFactory. If not provided,
32
- a default one will be created.
33
- client_id: TikTok App client ID (for the default factory).
34
- client_secret: TikTok App client secret (for the default factory).
35
- """
36
- if account_factory is None:
37
- account_factory = TikTokAccountFactory(
38
- client_id=client_id,
39
- client_secret=client_secret,
40
- )
41
- super().__init__(account_factory=account_factory)
42
-
43
- @property
44
- def platform_name(self) -> str:
45
- """Get the platform name."""
46
- return "tiktok"
47
-
48
- async def _execute_post_impl(
49
- self,
50
- client: Any,
51
- request: PostCreateRequest,
52
- credentials: AuthCredentials,
53
- ) -> Post:
54
- """Execute the TikTok video post creation process.
55
-
56
- Args:
57
- client: An authenticated TikTokClient instance.
58
- request: The post creation request.
59
- credentials: The credentials used for authentication.
60
-
61
- Returns:
62
- The created Post object representing the TikTok video.
63
-
64
- Raises:
65
- TypeError: If the provided client is not a TikTokClient.
66
- InterruptedError: If the operation is cancelled.
67
- """
68
- if not isinstance(client, TikTokClient):
69
- raise TypeError(f"Expected TikTokClient, got {type(client)}")
70
-
71
- # The TikTok posting process is primarily about the video upload.
72
- # The client's create_post method handles the upload and publish steps.
73
- # We can wrap it here to provide progress updates.
74
-
75
- self._progress_tracker.emit_start(
76
- "execute_post", total=100, message="Starting TikTok post..."
77
- )
78
-
79
- if self.is_cancelled():
80
- raise InterruptedError("Post creation was cancelled before start.")
81
-
82
- # Media upload progress can be tracked inside the client, but for simplicity,
83
- # we'll emit high-level progress here.
84
- self._progress_tracker.emit_progress(
85
- "execute_post",
86
- progress=10,
87
- total=100,
88
- message="Uploading video to TikTok...",
89
- )
90
-
91
- # The `create_post` method in the client will handle the full flow:
92
- # 1. Upload media
93
- # 2. Publish post
94
- # 3. Fetch final post data
95
- # A more advanced implementation might involve callbacks from the client
96
- # to the manager for more granular progress updates.
97
- post = await client.create_post(request)
98
-
99
- if self.is_cancelled():
100
- # If cancellation happened during the client call, we might need cleanup.
101
- # For now, we just raise.
102
- raise InterruptedError("Post creation was cancelled during execution.")
103
-
104
- self._progress_tracker.emit_progress(
105
- "execute_post",
106
- progress=90,
107
- total=100,
108
- message="Finalizing post...",
109
- )
110
-
111
- self._progress_tracker.emit_complete(
112
- "execute_post", message="TikTok post published successfully!"
113
- )
114
-
115
- return post
@@ -1,151 +0,0 @@
1
- """Twitter 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.models import AccountStatus, AuthCredentials
10
- from src.marqetive.platforms.twitter.client import TwitterClient
11
- from src.marqetive.utils.oauth import refresh_twitter_token
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- class TwitterAccountFactory(BaseAccountFactory):
17
- """Factory for creating and managing Twitter/X accounts and clients.
18
-
19
- Example:
20
- >>> factory = TwitterAccountFactory(
21
- ... client_id="your_client_id",
22
- ... client_secret="your_client_secret"
23
- ... )
24
- >>> credentials = AuthCredentials(
25
- ... platform="twitter",
26
- ... access_token="token",
27
- ... refresh_token="refresh"
28
- ... )
29
- >>> client = await factory.create_authenticated_client(credentials)
30
- >>> async with client:
31
- ... tweet = 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 Twitter account factory.
41
-
42
- Args:
43
- client_id: Twitter OAuth client ID (uses TWITTER_CLIENT_ID env if None).
44
- client_secret: Twitter OAuth client secret (uses TWITTER_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("TWITTER_CLIENT_ID")
49
- self.client_secret = client_secret or os.getenv("TWITTER_CLIENT_SECRET")
50
-
51
- if not self.client_id or not self.client_secret:
52
- logger.warning(
53
- "Twitter 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 "twitter"
61
-
62
- async def refresh_token(self, credentials: AuthCredentials) -> AuthCredentials:
63
- """Refresh Twitter 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
- "Twitter 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 Twitter access token...")
87
- return await refresh_twitter_token(
88
- credentials,
89
- self.client_id,
90
- self.client_secret,
91
- )
92
-
93
- async def create_client(self, credentials: AuthCredentials) -> TwitterClient:
94
- """Create Twitter API client.
95
-
96
- Args:
97
- credentials: Valid Twitter credentials.
98
-
99
- Returns:
100
- TwitterClient 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
- # Get additional data for Twitter
112
- additional_data = credentials.additional_data or {}
113
-
114
- # Twitter needs API key/secret for some operations
115
- api_key = additional_data.get("api_key") or os.getenv("TWITTER_API_KEY")
116
- api_secret = additional_data.get("api_secret") or os.getenv(
117
- "TWITTER_API_SECRET"
118
- )
119
- additional_data.get("access_secret")
120
-
121
- # Update additional_data if we have env values
122
- if api_key:
123
- additional_data["api_key"] = api_key
124
- if api_secret:
125
- additional_data["api_secret"] = api_secret
126
-
127
- credentials.additional_data = additional_data
128
-
129
- return TwitterClient(credentials=credentials)
130
-
131
- async def validate_credentials(self, credentials: AuthCredentials) -> bool:
132
- """Validate Twitter credentials by making a test API call.
133
-
134
- Args:
135
- credentials: Credentials to validate.
136
-
137
- Returns:
138
- True if credentials are valid, False otherwise.
139
- """
140
- try:
141
- client = await self.create_client(credentials)
142
- async with client:
143
- # Try to verify credentials by getting current user
144
- # This is a lightweight call to test authentication
145
- if client._tweepy_client:
146
- me = client._tweepy_client.get_me()
147
- return me is not None
148
- return False
149
- except Exception as e:
150
- logger.error(f"Error validating Twitter credentials: {e}")
151
- return False
@@ -1,121 +0,0 @@
1
- """Twitter 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.models import AuthCredentials, Post, PostCreateRequest
8
- from src.marqetive.platforms.twitter.client import TwitterClient
9
- from src.marqetive.platforms.twitter.factory import TwitterAccountFactory
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- class TwitterPostManager(BasePostManager):
15
- """Manager for Twitter/X post operations.
16
-
17
- Coordinates post creation, media uploads, and progress tracking for Twitter.
18
-
19
- Example:
20
- >>> manager = TwitterPostManager()
21
- >>> credentials = AuthCredentials(
22
- ... platform="twitter",
23
- ... access_token="token",
24
- ... refresh_token="refresh"
25
- ... )
26
- >>> request = PostCreateRequest(content="Hello Twitter!")
27
- >>> post = await manager.execute_post(credentials, request)
28
- >>> print(f"Tweet ID: {post.post_id}")
29
- """
30
-
31
- def __init__(
32
- self,
33
- account_factory: TwitterAccountFactory | None = None,
34
- client_id: str | None = None,
35
- client_secret: str | None = None,
36
- ) -> None:
37
- """Initialize Twitter post manager.
38
-
39
- Args:
40
- account_factory: Twitter account factory (creates default if None).
41
- client_id: Twitter OAuth client ID (for default factory).
42
- client_secret: Twitter OAuth client secret (for default factory).
43
- """
44
- if account_factory is None:
45
- account_factory = TwitterAccountFactory(
46
- client_id=client_id,
47
- client_secret=client_secret,
48
- )
49
-
50
- super().__init__(account_factory=account_factory)
51
-
52
- @property
53
- def platform_name(self) -> str:
54
- """Get platform name."""
55
- return "twitter"
56
-
57
- async def _execute_post_impl(
58
- self,
59
- client: Any,
60
- request: PostCreateRequest,
61
- credentials: AuthCredentials, # noqa: ARG002
62
- ) -> Post:
63
- """Execute Twitter post creation.
64
-
65
- Args:
66
- client: TwitterClient instance.
67
- request: Post creation request.
68
- credentials: Twitter credentials.
69
-
70
- Returns:
71
- Created Post object.
72
- """
73
- if not isinstance(client, TwitterClient):
74
- raise TypeError(f"Expected TwitterClient, got {type(client)}")
75
-
76
- # Handle media uploads with progress tracking
77
- media_ids: list[str] = []
78
- if request.media_urls:
79
- self._progress_tracker.emit_start(
80
- "upload_media",
81
- total=len(request.media_urls),
82
- message=f"Uploading {len(request.media_urls)} media files...",
83
- )
84
-
85
- for idx, media_url in enumerate(request.media_urls):
86
- if self.is_cancelled():
87
- raise InterruptedError("Post creation was cancelled")
88
-
89
- self._progress_tracker.emit_progress(
90
- "upload_media",
91
- progress=idx,
92
- total=len(request.media_urls),
93
- message=f"Uploading media {idx + 1}/{len(request.media_urls)}...",
94
- )
95
-
96
- # Twitter media upload is simplified in the client
97
- # In production, this would use tweepy's media upload
98
- media_attachment = await client.upload_media(
99
- media_url=media_url,
100
- media_type="image", # Default to image
101
- alt_text=None,
102
- )
103
- media_ids.append(media_attachment.media_id)
104
-
105
- self._progress_tracker.emit_complete(
106
- "upload_media",
107
- message="All media uploaded successfully",
108
- )
109
-
110
- # Create post with progress tracking
111
- self._progress_tracker.emit_progress(
112
- "execute_post",
113
- progress=50,
114
- total=100,
115
- message="Creating tweet...",
116
- )
117
-
118
- # Use the client to create the post
119
- post = await client.create_post(request)
120
-
121
- return post