marqetive-lib 0.1.0__py3-none-any.whl → 0.1.2__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 +9 -9
- marqetive/core/__init__.py +1 -1
- marqetive/core/account_factory.py +2 -2
- marqetive/core/base_manager.py +4 -4
- marqetive/core/client.py +1 -1
- marqetive/core/registry.py +1 -1
- marqetive/platforms/__init__.py +3 -3
- marqetive/platforms/base.py +3 -3
- marqetive/platforms/exceptions.py +2 -1
- marqetive/platforms/instagram/__init__.py +3 -3
- marqetive/platforms/instagram/client.py +4 -4
- marqetive/platforms/instagram/exceptions.py +1 -1
- marqetive/platforms/instagram/factory.py +5 -5
- marqetive/platforms/instagram/manager.py +4 -4
- marqetive/platforms/instagram/media.py +2 -2
- marqetive/platforms/linkedin/__init__.py +3 -3
- marqetive/platforms/linkedin/client.py +4 -4
- marqetive/platforms/linkedin/exceptions.py +1 -1
- marqetive/platforms/linkedin/factory.py +5 -5
- marqetive/platforms/linkedin/manager.py +4 -4
- marqetive/platforms/linkedin/media.py +4 -4
- marqetive/platforms/models.py +2 -0
- marqetive/platforms/tiktok/__init__.py +7 -0
- marqetive/platforms/tiktok/client.py +277 -0
- marqetive/platforms/tiktok/exceptions.py +180 -0
- marqetive/platforms/tiktok/factory.py +188 -0
- marqetive/platforms/tiktok/manager.py +115 -0
- marqetive/platforms/tiktok/media.py +305 -0
- marqetive/platforms/twitter/__init__.py +3 -3
- marqetive/platforms/twitter/client.py +6 -6
- marqetive/platforms/twitter/exceptions.py +1 -1
- marqetive/platforms/twitter/factory.py +5 -5
- marqetive/platforms/twitter/manager.py +4 -4
- marqetive/platforms/twitter/media.py +4 -4
- marqetive/platforms/twitter/threads.py +2 -2
- marqetive/registry_init.py +4 -4
- marqetive/utils/__init__.py +3 -3
- marqetive/utils/file_handlers.py +1 -1
- marqetive/utils/oauth.py +137 -2
- marqetive/utils/token_validator.py +1 -1
- {marqetive_lib-0.1.0.dist-info → marqetive_lib-0.1.2.dist-info}/METADATA +1 -2
- marqetive_lib-0.1.2.dist-info/RECORD +48 -0
- marqetive_lib-0.1.0.dist-info/RECORD +0 -43
- {marqetive_lib-0.1.0.dist-info → marqetive_lib-0.1.2.dist-info}/WHEEL +0 -0
marqetive/__init__.py
CHANGED
|
@@ -28,20 +28,20 @@ Usage:
|
|
|
28
28
|
|
|
29
29
|
# Core API client
|
|
30
30
|
# Account management
|
|
31
|
-
from marqetive.core.account_factory import BaseAccountFactory
|
|
31
|
+
from src.marqetive.core.account_factory import BaseAccountFactory
|
|
32
32
|
|
|
33
33
|
# Registry and managers
|
|
34
|
-
from marqetive.core.base_manager import BasePostManager
|
|
35
|
-
from marqetive.core.client import APIClient
|
|
34
|
+
from src.marqetive.core.base_manager import BasePostManager
|
|
35
|
+
from src.marqetive.core.client import APIClient
|
|
36
36
|
|
|
37
37
|
# Progress tracking
|
|
38
|
-
from marqetive.core.progress import (
|
|
38
|
+
from src.marqetive.core.progress import (
|
|
39
39
|
ProgressCallback,
|
|
40
40
|
ProgressEvent,
|
|
41
41
|
ProgressStatus,
|
|
42
42
|
ProgressTracker,
|
|
43
43
|
)
|
|
44
|
-
from marqetive.core.registry import (
|
|
44
|
+
from src.marqetive.core.registry import (
|
|
45
45
|
PlatformRegistry,
|
|
46
46
|
get_available_platforms,
|
|
47
47
|
get_manager_for_platform,
|
|
@@ -50,7 +50,7 @@ from marqetive.core.registry import (
|
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
# Models
|
|
53
|
-
from marqetive.platforms.models import (
|
|
53
|
+
from src.marqetive.platforms.models import (
|
|
54
54
|
AccountStatus,
|
|
55
55
|
AuthCredentials,
|
|
56
56
|
Comment,
|
|
@@ -62,16 +62,16 @@ from marqetive.platforms.models import (
|
|
|
62
62
|
PostStatus,
|
|
63
63
|
PostUpdateRequest,
|
|
64
64
|
)
|
|
65
|
-
from marqetive.registry_init import (
|
|
65
|
+
from src.marqetive.registry_init import (
|
|
66
66
|
initialize_platform_registry,
|
|
67
67
|
is_registry_initialized,
|
|
68
68
|
)
|
|
69
69
|
|
|
70
70
|
# Utilities
|
|
71
|
-
from marqetive.utils.helpers import format_response
|
|
71
|
+
from src.marqetive.utils.helpers import format_response
|
|
72
72
|
|
|
73
73
|
# Retry utilities
|
|
74
|
-
from marqetive.utils.retry import STANDARD_BACKOFF, BackoffConfig, retry_async
|
|
74
|
+
from src.marqetive.utils.retry import STANDARD_BACKOFF, BackoffConfig, retry_async
|
|
75
75
|
|
|
76
76
|
__version__ = "0.1.0"
|
|
77
77
|
|
marqetive/core/__init__.py
CHANGED
|
@@ -9,8 +9,8 @@ from abc import ABC, abstractmethod
|
|
|
9
9
|
from collections.abc import Callable
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from marqetive.platforms.exceptions import PlatformAuthError
|
|
13
|
-
from marqetive.platforms.models import AccountStatus, AuthCredentials
|
|
12
|
+
from src.marqetive.platforms.exceptions import PlatformAuthError
|
|
13
|
+
from src.marqetive.platforms.models import AccountStatus, AuthCredentials
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
marqetive/core/base_manager.py
CHANGED
|
@@ -10,10 +10,10 @@ import logging
|
|
|
10
10
|
from abc import ABC, abstractmethod
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
from marqetive.core.account_factory import BaseAccountFactory
|
|
14
|
-
from marqetive.core.progress import ProgressCallback, ProgressTracker
|
|
15
|
-
from marqetive.platforms.exceptions import PlatformError
|
|
16
|
-
from marqetive.platforms.models import AuthCredentials, Post, PostCreateRequest
|
|
13
|
+
from src.marqetive.core.account_factory import BaseAccountFactory
|
|
14
|
+
from src.marqetive.core.progress import ProgressCallback, ProgressTracker
|
|
15
|
+
from src.marqetive.platforms.exceptions import PlatformError
|
|
16
|
+
from src.marqetive.platforms.models import AuthCredentials, Post, PostCreateRequest
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
marqetive/core/client.py
CHANGED
|
@@ -73,7 +73,7 @@ class APIClient:
|
|
|
73
73
|
if not self._client:
|
|
74
74
|
raise RuntimeError("Client not initialized. Use async context manager.")
|
|
75
75
|
|
|
76
|
-
response
|
|
76
|
+
response = await self._client.get(path, params=params)
|
|
77
77
|
response.raise_for_status()
|
|
78
78
|
|
|
79
79
|
return APIResponse(
|
marqetive/core/registry.py
CHANGED
|
@@ -7,7 +7,7 @@ platform managers across the application.
|
|
|
7
7
|
import threading
|
|
8
8
|
from typing import Any, TypeVar
|
|
9
9
|
|
|
10
|
-
from marqetive.platforms.models import AuthCredentials
|
|
10
|
+
from src.marqetive.platforms.models import AuthCredentials
|
|
11
11
|
|
|
12
12
|
# Type variable for manager classes
|
|
13
13
|
ManagerType = TypeVar("ManagerType")
|
marqetive/platforms/__init__.py
CHANGED
|
@@ -9,8 +9,8 @@ Platform clients are available via their respective subpackages:
|
|
|
9
9
|
- from marqetive_lib.platforms.instagram import InstagramClient
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
from marqetive.platforms.base import SocialMediaPlatform
|
|
13
|
-
from marqetive.platforms.exceptions import (
|
|
12
|
+
from src.marqetive.platforms.base import SocialMediaPlatform
|
|
13
|
+
from src.marqetive.platforms.exceptions import (
|
|
14
14
|
MediaUploadError,
|
|
15
15
|
PlatformAuthError,
|
|
16
16
|
PlatformError,
|
|
@@ -18,7 +18,7 @@ from marqetive.platforms.exceptions import (
|
|
|
18
18
|
RateLimitError,
|
|
19
19
|
ValidationError,
|
|
20
20
|
)
|
|
21
|
-
from marqetive.platforms.models import (
|
|
21
|
+
from src.marqetive.platforms.models import (
|
|
22
22
|
AuthCredentials,
|
|
23
23
|
Comment,
|
|
24
24
|
CommentStatus,
|
marqetive/platforms/base.py
CHANGED
|
@@ -10,12 +10,12 @@ from datetime import datetime
|
|
|
10
10
|
from traceback import TracebackException
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
from marqetive.core.client import APIClient
|
|
14
|
-
from marqetive.platforms.exceptions import (
|
|
13
|
+
from src.marqetive.core.client import APIClient
|
|
14
|
+
from src.marqetive.platforms.exceptions import (
|
|
15
15
|
PlatformAuthError,
|
|
16
16
|
RateLimitError,
|
|
17
17
|
)
|
|
18
|
-
from marqetive.platforms.models import (
|
|
18
|
+
from src.marqetive.platforms.models import (
|
|
19
19
|
AuthCredentials,
|
|
20
20
|
Comment,
|
|
21
21
|
MediaAttachment,
|
|
@@ -115,9 +115,10 @@ class PostNotFoundError(PlatformError):
|
|
|
115
115
|
post_id: str,
|
|
116
116
|
platform: str | None = None,
|
|
117
117
|
status_code: int | None = None,
|
|
118
|
+
message: str | None = None,
|
|
118
119
|
) -> None:
|
|
119
120
|
self.post_id = post_id
|
|
120
|
-
message = f"Post not found: {post_id}"
|
|
121
|
+
message = message or f"Post not found: {post_id}"
|
|
121
122
|
super().__init__(message, platform, status_code)
|
|
122
123
|
|
|
123
124
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Instagram platform integration."""
|
|
2
2
|
|
|
3
|
-
from marqetive.platforms.instagram.client import InstagramClient
|
|
4
|
-
from marqetive.platforms.instagram.factory import InstagramAccountFactory
|
|
5
|
-
from marqetive.platforms.instagram.manager import InstagramPostManager
|
|
3
|
+
from src.marqetive.platforms.instagram.client import InstagramClient
|
|
4
|
+
from src.marqetive.platforms.instagram.factory import InstagramAccountFactory
|
|
5
|
+
from src.marqetive.platforms.instagram.manager import InstagramPostManager
|
|
6
6
|
|
|
7
7
|
__all__ = ["InstagramClient", "InstagramAccountFactory", "InstagramPostManager"]
|
|
@@ -12,19 +12,19 @@ from typing import Any, Literal, cast
|
|
|
12
12
|
import httpx
|
|
13
13
|
from pydantic import HttpUrl
|
|
14
14
|
|
|
15
|
-
from marqetive.platforms.base import SocialMediaPlatform
|
|
16
|
-
from marqetive.platforms.exceptions import (
|
|
15
|
+
from src.marqetive.platforms.base import SocialMediaPlatform
|
|
16
|
+
from src.marqetive.platforms.exceptions import (
|
|
17
17
|
MediaUploadError,
|
|
18
18
|
PlatformAuthError,
|
|
19
19
|
PlatformError,
|
|
20
20
|
PostNotFoundError,
|
|
21
21
|
ValidationError,
|
|
22
22
|
)
|
|
23
|
-
from marqetive.platforms.instagram.media import (
|
|
23
|
+
from src.marqetive.platforms.instagram.media import (
|
|
24
24
|
InstagramMediaManager,
|
|
25
25
|
MediaItem,
|
|
26
26
|
)
|
|
27
|
-
from marqetive.platforms.models import (
|
|
27
|
+
from src.marqetive.platforms.models import (
|
|
28
28
|
AuthCredentials,
|
|
29
29
|
Comment,
|
|
30
30
|
CommentStatus,
|
|
@@ -5,7 +5,7 @@ This module provides comprehensive error handling for Instagram Graph API errors
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from marqetive.platforms.exceptions import (
|
|
8
|
+
from src.marqetive.platforms.exceptions import (
|
|
9
9
|
MediaUploadError,
|
|
10
10
|
PlatformAuthError,
|
|
11
11
|
PlatformError,
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from collections.abc import Callable
|
|
5
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
|
|
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
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from typing import Any
|
|
5
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
|
|
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
10
|
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
@@ -19,11 +19,11 @@ from typing import Any, Literal
|
|
|
19
19
|
|
|
20
20
|
import httpx
|
|
21
21
|
|
|
22
|
-
from marqetive.platforms.exceptions import (
|
|
22
|
+
from src.marqetive.platforms.exceptions import (
|
|
23
23
|
MediaUploadError,
|
|
24
24
|
ValidationError,
|
|
25
25
|
)
|
|
26
|
-
from marqetive.utils.retry import STANDARD_BACKOFF, retry_async
|
|
26
|
+
from src.marqetive.utils.retry import STANDARD_BACKOFF, retry_async
|
|
27
27
|
|
|
28
28
|
logger = logging.getLogger(__name__)
|
|
29
29
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""LinkedIn platform integration."""
|
|
2
2
|
|
|
3
|
-
from marqetive.platforms.linkedin.client import LinkedInClient
|
|
4
|
-
from marqetive.platforms.linkedin.factory import LinkedInAccountFactory
|
|
5
|
-
from marqetive.platforms.linkedin.manager import LinkedInPostManager
|
|
3
|
+
from src.marqetive.platforms.linkedin.client import LinkedInClient
|
|
4
|
+
from src.marqetive.platforms.linkedin.factory import LinkedInAccountFactory
|
|
5
|
+
from src.marqetive.platforms.linkedin.manager import LinkedInPostManager
|
|
6
6
|
|
|
7
7
|
__all__ = ["LinkedInClient", "LinkedInAccountFactory", "LinkedInPostManager"]
|
|
@@ -12,16 +12,16 @@ from typing import Any, cast
|
|
|
12
12
|
import httpx
|
|
13
13
|
from pydantic import HttpUrl
|
|
14
14
|
|
|
15
|
-
from marqetive.platforms.base import SocialMediaPlatform
|
|
16
|
-
from marqetive.platforms.exceptions import (
|
|
15
|
+
from src.marqetive.platforms.base import SocialMediaPlatform
|
|
16
|
+
from src.marqetive.platforms.exceptions import (
|
|
17
17
|
MediaUploadError,
|
|
18
18
|
PlatformAuthError,
|
|
19
19
|
PlatformError,
|
|
20
20
|
PostNotFoundError,
|
|
21
21
|
ValidationError,
|
|
22
22
|
)
|
|
23
|
-
from marqetive.platforms.linkedin.media import LinkedInMediaManager, MediaAsset
|
|
24
|
-
from marqetive.platforms.models import (
|
|
23
|
+
from src.marqetive.platforms.linkedin.media import LinkedInMediaManager, MediaAsset
|
|
24
|
+
from src.marqetive.platforms.models import (
|
|
25
25
|
AuthCredentials,
|
|
26
26
|
Comment,
|
|
27
27
|
CommentStatus,
|
|
@@ -9,7 +9,7 @@ This module provides comprehensive error handling for LinkedIn API errors includ
|
|
|
9
9
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from marqetive.platforms.exceptions import (
|
|
12
|
+
from src.marqetive.platforms.exceptions import (
|
|
13
13
|
MediaUploadError,
|
|
14
14
|
PlatformAuthError,
|
|
15
15
|
PlatformError,
|
|
@@ -4,11 +4,11 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
from collections.abc import Callable
|
|
6
6
|
|
|
7
|
-
from marqetive.core.account_factory import BaseAccountFactory
|
|
8
|
-
from marqetive.platforms.exceptions import PlatformAuthError
|
|
9
|
-
from marqetive.platforms.linkedin.client import LinkedInClient
|
|
10
|
-
from marqetive.platforms.models import AccountStatus, AuthCredentials
|
|
11
|
-
from marqetive.utils.oauth import refresh_linkedin_token
|
|
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
12
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from marqetive.core.base_manager import BasePostManager
|
|
7
|
-
from marqetive.platforms.linkedin.client import LinkedInClient
|
|
8
|
-
from marqetive.platforms.linkedin.factory import LinkedInAccountFactory
|
|
9
|
-
from marqetive.platforms.models import AuthCredentials, Post, PostCreateRequest
|
|
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
10
|
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
@@ -20,13 +20,13 @@ from typing import Any, Literal
|
|
|
20
20
|
|
|
21
21
|
import httpx
|
|
22
22
|
|
|
23
|
-
from marqetive.platforms.exceptions import (
|
|
23
|
+
from src.marqetive.platforms.exceptions import (
|
|
24
24
|
MediaUploadError,
|
|
25
25
|
ValidationError,
|
|
26
26
|
)
|
|
27
|
-
from marqetive.utils.file_handlers import download_file, read_file_bytes
|
|
28
|
-
from marqetive.utils.media import detect_mime_type, format_file_size
|
|
29
|
-
from marqetive.utils.retry import STANDARD_BACKOFF, retry_async
|
|
27
|
+
from src.marqetive.utils.file_handlers import download_file, read_file_bytes
|
|
28
|
+
from src.marqetive.utils.media import detect_mime_type, format_file_size
|
|
29
|
+
from src.marqetive.utils.retry import STANDARD_BACKOFF, retry_async
|
|
30
30
|
|
|
31
31
|
logger = logging.getLogger(__name__)
|
|
32
32
|
|
marqetive/platforms/models.py
CHANGED
|
@@ -305,6 +305,7 @@ class PostCreateRequest(BaseModel):
|
|
|
305
305
|
link: URL to include in the post
|
|
306
306
|
tags: List of hashtags or user tags
|
|
307
307
|
location: Location/place tag for the post
|
|
308
|
+
additional_data: Platform-specific data
|
|
308
309
|
|
|
309
310
|
Example:
|
|
310
311
|
>>> request = PostCreateRequest(
|
|
@@ -321,6 +322,7 @@ class PostCreateRequest(BaseModel):
|
|
|
321
322
|
link: str | None = None
|
|
322
323
|
tags: list[str] = Field(default_factory=list)
|
|
323
324
|
location: str | None = None
|
|
325
|
+
additional_data: dict[str, Any] = Field(default_factory=dict)
|
|
324
326
|
|
|
325
327
|
|
|
326
328
|
class PostUpdateRequest(BaseModel):
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""TikTok platform integration."""
|
|
2
|
+
|
|
3
|
+
from src.marqetive.platforms.tiktok.client import TikTokClient
|
|
4
|
+
from src.marqetive.platforms.tiktok.factory import TikTokAccountFactory
|
|
5
|
+
from src.marqetive.platforms.tiktok.manager import TikTokPostManager
|
|
6
|
+
|
|
7
|
+
__all__ = ["TikTokClient", "TikTokAccountFactory", "TikTokPostManager"]
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""TikTok API client implementation.
|
|
2
|
+
|
|
3
|
+
This module provides a concrete implementation of the SocialMediaPlatform
|
|
4
|
+
ABC for TikTok, using the TikTok for Business API (hypothetical).
|
|
5
|
+
|
|
6
|
+
API Documentation: https://developers.tiktok.com/doc/overview
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pydantic import HttpUrl
|
|
13
|
+
|
|
14
|
+
from src.marqetive.platforms.base import SocialMediaPlatform
|
|
15
|
+
from src.marqetive.platforms.exceptions import (
|
|
16
|
+
PlatformAuthError,
|
|
17
|
+
PlatformError,
|
|
18
|
+
PostNotFoundError,
|
|
19
|
+
ValidationError,
|
|
20
|
+
)
|
|
21
|
+
from src.marqetive.platforms.models import (
|
|
22
|
+
AuthCredentials,
|
|
23
|
+
Comment,
|
|
24
|
+
CommentStatus,
|
|
25
|
+
MediaAttachment,
|
|
26
|
+
MediaType,
|
|
27
|
+
Post,
|
|
28
|
+
PostCreateRequest,
|
|
29
|
+
PostStatus,
|
|
30
|
+
PostUpdateRequest,
|
|
31
|
+
)
|
|
32
|
+
from src.marqetive.platforms.tiktok.media import TikTokMediaManager
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TikTokClient(SocialMediaPlatform):
|
|
36
|
+
"""TikTok API client.
|
|
37
|
+
|
|
38
|
+
This client implements the SocialMediaPlatform interface for TikTok,
|
|
39
|
+
focusing on video uploads and management.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
credentials: AuthCredentials,
|
|
45
|
+
timeout: float = 300.0,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Initialize TikTok client."""
|
|
48
|
+
base_url = "https://open.tiktokapis.com/v2/"
|
|
49
|
+
super().__init__(
|
|
50
|
+
platform_name="tiktok",
|
|
51
|
+
credentials=credentials,
|
|
52
|
+
base_url=base_url,
|
|
53
|
+
timeout=timeout,
|
|
54
|
+
)
|
|
55
|
+
self._media_manager: TikTokMediaManager | None = None
|
|
56
|
+
|
|
57
|
+
async def _setup_managers(self) -> None:
|
|
58
|
+
"""Setup media manager."""
|
|
59
|
+
if not self.credentials.access_token or not self.credentials.additional_data:
|
|
60
|
+
raise PlatformAuthError("Access token and open_id are required", "tiktok")
|
|
61
|
+
|
|
62
|
+
self._media_manager = TikTokMediaManager(
|
|
63
|
+
access_token=self.credentials.access_token,
|
|
64
|
+
open_id=self.credentials.additional_data["open_id"],
|
|
65
|
+
timeout=self.timeout,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
async def _cleanup_managers(self) -> None:
|
|
69
|
+
"""Cleanup media manager."""
|
|
70
|
+
if self._media_manager:
|
|
71
|
+
await self._media_manager.__aexit__(None, None, None)
|
|
72
|
+
self._media_manager = None
|
|
73
|
+
|
|
74
|
+
async def __aenter__(self) -> "TikTokClient":
|
|
75
|
+
await super().__aenter__()
|
|
76
|
+
await self._setup_managers()
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
80
|
+
await self._cleanup_managers()
|
|
81
|
+
await super().__aexit__(exc_type, exc_val, exc_tb)
|
|
82
|
+
|
|
83
|
+
async def authenticate(self) -> AuthCredentials:
|
|
84
|
+
"""Perform TikTok authentication."""
|
|
85
|
+
if await self.is_authenticated():
|
|
86
|
+
return self.credentials
|
|
87
|
+
raise PlatformAuthError(
|
|
88
|
+
"Invalid or expired credentials. Please re-authenticate via TikTok OAuth.",
|
|
89
|
+
platform=self.platform_name,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def refresh_token(self) -> AuthCredentials:
|
|
93
|
+
"""Refresh TikTok access token."""
|
|
94
|
+
# The refresh logic should be handled by the AccountFactory
|
|
95
|
+
# For now, we assume it's done and we just return the creds
|
|
96
|
+
return self.credentials
|
|
97
|
+
|
|
98
|
+
async def is_authenticated(self) -> bool:
|
|
99
|
+
"""Check if TikTok credentials are valid."""
|
|
100
|
+
if not self.api_client or not self.credentials.additional_data:
|
|
101
|
+
return False
|
|
102
|
+
try:
|
|
103
|
+
# Verify credentials by fetching authenticated user info
|
|
104
|
+
params = {
|
|
105
|
+
"fields": "open_id,union_id,avatar_url,display_name",
|
|
106
|
+
"open_id": self.credentials.additional_data["open_id"],
|
|
107
|
+
}
|
|
108
|
+
response = await self.api_client.get("user/info/", params=params)
|
|
109
|
+
return response.data.get("data", {}).get("user") is not None
|
|
110
|
+
except PlatformError:
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
async def create_post(self, request: PostCreateRequest) -> Post:
|
|
114
|
+
"""Create and publish a TikTok video.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
request: The post creation request, must contain a video URL.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
The created Post object.
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
ValidationError: If the request is invalid (e.g., no media).
|
|
124
|
+
MediaUploadError: If the video upload fails.
|
|
125
|
+
"""
|
|
126
|
+
if not self._media_manager or not self.api_client:
|
|
127
|
+
raise RuntimeError("Client must be used as async context manager")
|
|
128
|
+
|
|
129
|
+
if not request.media_urls:
|
|
130
|
+
raise ValidationError(
|
|
131
|
+
"A video URL is required to create a TikTok post.",
|
|
132
|
+
platform=self.platform_name,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# 1. Upload the video
|
|
136
|
+
video_url = request.media_urls[0]
|
|
137
|
+
upload_result = await self._media_manager.upload_media(video_url)
|
|
138
|
+
|
|
139
|
+
if not upload_result.media_id:
|
|
140
|
+
raise PlatformError("Media upload succeeded but did not yield a media ID.")
|
|
141
|
+
|
|
142
|
+
# 2. Create the post with the uploaded video
|
|
143
|
+
payload = {
|
|
144
|
+
"post_info": {
|
|
145
|
+
"title": request.content or "",
|
|
146
|
+
"description": request.additional_data.get("description", ""),
|
|
147
|
+
"privacy_level": request.additional_data.get(
|
|
148
|
+
"privacy_level", "PUBLIC_TO_EVERYONE"
|
|
149
|
+
),
|
|
150
|
+
},
|
|
151
|
+
"source_info": {
|
|
152
|
+
"source": "FILE_UPLOAD",
|
|
153
|
+
"video_id": upload_result.media_id,
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
response = await self.api_client.post("video/publish/", data=payload)
|
|
157
|
+
post_id = response.data.get("data", {}).get("publish_id")
|
|
158
|
+
|
|
159
|
+
if not post_id:
|
|
160
|
+
raise PlatformError("Post creation succeeded but no publish_id returned.")
|
|
161
|
+
|
|
162
|
+
# 3. Fetch the created post to return a full Post object
|
|
163
|
+
return await self.get_post(post_id)
|
|
164
|
+
|
|
165
|
+
async def get_post(self, post_id: str) -> Post:
|
|
166
|
+
"""Retrieve a TikTok video by its ID."""
|
|
167
|
+
if not self.api_client or not self.credentials.additional_data:
|
|
168
|
+
raise RuntimeError("Client must be used as async context manager")
|
|
169
|
+
try:
|
|
170
|
+
params = {
|
|
171
|
+
"open_id": self.credentials.additional_data["open_id"],
|
|
172
|
+
"video_ids": [post_id],
|
|
173
|
+
}
|
|
174
|
+
response = await self.api_client.post("video/query/", data=params)
|
|
175
|
+
video_data = response.data.get("data", {}).get("videos", [])
|
|
176
|
+
if not video_data:
|
|
177
|
+
raise PostNotFoundError(post_id, self.platform_name)
|
|
178
|
+
return self._parse_video_post(video_data[0])
|
|
179
|
+
except PlatformError as e:
|
|
180
|
+
raise PostNotFoundError(
|
|
181
|
+
post_id, self.platform_name, status_code=e.status_code
|
|
182
|
+
) from e
|
|
183
|
+
|
|
184
|
+
async def update_post(self, post_id: str, request: PostUpdateRequest) -> Post:
|
|
185
|
+
"""Update a TikTok video. This is not supported by TikTok API."""
|
|
186
|
+
raise PlatformError(
|
|
187
|
+
"TikTok API does not support updating videos.", self.platform_name
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def delete_post(self, post_id: str) -> bool:
|
|
191
|
+
"""Delete a TikTok video. This is not supported by TikTok API."""
|
|
192
|
+
raise PlatformError(
|
|
193
|
+
"TikTok API does not support deleting videos.", self.platform_name
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
async def get_comments(
|
|
197
|
+
self, post_id: str, limit: int = 20, offset: int = 0
|
|
198
|
+
) -> list[Comment]:
|
|
199
|
+
"""Retrieve comments for a TikTok video."""
|
|
200
|
+
if not self.api_client or not self.credentials.additional_data:
|
|
201
|
+
raise RuntimeError("Client must be used as async context manager")
|
|
202
|
+
# This is a hypothetical endpoint
|
|
203
|
+
params = {
|
|
204
|
+
"open_id": self.credentials.additional_data["open_id"],
|
|
205
|
+
"video_id": post_id,
|
|
206
|
+
"count": limit,
|
|
207
|
+
"cursor": offset,
|
|
208
|
+
}
|
|
209
|
+
response = await self.api_client.get("video/comment/list/", params=params)
|
|
210
|
+
comments_data = response.data.get("data", {}).get("comments", [])
|
|
211
|
+
return [self._parse_comment(c, post_id) for c in comments_data]
|
|
212
|
+
|
|
213
|
+
async def create_comment(self, post_id: str, content: str) -> Comment:
|
|
214
|
+
"""Create a comment on a TikTok video."""
|
|
215
|
+
raise PlatformError("Commenting via API is not supported.", self.platform_name)
|
|
216
|
+
|
|
217
|
+
async def delete_comment(self, comment_id: str) -> bool:
|
|
218
|
+
"""Delete a comment on a TikTok video."""
|
|
219
|
+
raise PlatformError(
|
|
220
|
+
"Deleting comments via API is not supported.", self.platform_name
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
async def upload_media(
|
|
224
|
+
self, media_url: str, media_type: str, alt_text: str | None = None
|
|
225
|
+
) -> MediaAttachment:
|
|
226
|
+
"""Upload a video to TikTok."""
|
|
227
|
+
if not self._media_manager:
|
|
228
|
+
raise RuntimeError("Client not initialized. Use as async context manager.")
|
|
229
|
+
if media_type != "video":
|
|
230
|
+
raise ValidationError("Only video media type is supported for TikTok.")
|
|
231
|
+
|
|
232
|
+
result = await self._media_manager.upload_media(media_url)
|
|
233
|
+
return MediaAttachment(
|
|
234
|
+
media_id=result.media_id or result.upload_id,
|
|
235
|
+
media_type=MediaType.VIDEO,
|
|
236
|
+
url=HttpUrl(media_url),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def _parse_video_post(self, video_data: dict[str, Any]) -> Post:
|
|
240
|
+
"""Parse a TikTok API video object into a Post model."""
|
|
241
|
+
return Post(
|
|
242
|
+
post_id=video_data["video_id"],
|
|
243
|
+
platform=self.platform_name,
|
|
244
|
+
content=video_data.get("title", ""),
|
|
245
|
+
media=[
|
|
246
|
+
MediaAttachment(
|
|
247
|
+
media_id=video_data["video_id"],
|
|
248
|
+
media_type=MediaType.VIDEO,
|
|
249
|
+
url=HttpUrl(video_data.get("share_url", "")),
|
|
250
|
+
width=video_data.get("width"),
|
|
251
|
+
height=video_data.get("height"),
|
|
252
|
+
)
|
|
253
|
+
],
|
|
254
|
+
status=PostStatus.PUBLISHED,
|
|
255
|
+
created_at=datetime.fromtimestamp(video_data.get("create_time", 0)),
|
|
256
|
+
author_id=str(video_data.get("open_id")),
|
|
257
|
+
likes_count=video_data.get("like_count", 0),
|
|
258
|
+
comments_count=video_data.get("comment_count", 0),
|
|
259
|
+
shares_count=video_data.get("share_count", 0),
|
|
260
|
+
views_count=video_data.get("view_count", 0),
|
|
261
|
+
raw_data=video_data,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def _parse_comment(self, comment_data: dict[str, Any], post_id: str) -> Comment:
|
|
265
|
+
"""Parse a TikTok API comment object into a Comment model."""
|
|
266
|
+
return Comment(
|
|
267
|
+
comment_id=comment_data["comment_id"],
|
|
268
|
+
post_id=post_id,
|
|
269
|
+
platform=self.platform_name,
|
|
270
|
+
content=comment_data.get("text", ""),
|
|
271
|
+
author_id=comment_data.get("open_id", ""),
|
|
272
|
+
created_at=datetime.fromtimestamp(comment_data.get("create_time", 0)),
|
|
273
|
+
likes_count=comment_data.get("like_count", 0),
|
|
274
|
+
replies_count=0,
|
|
275
|
+
status=CommentStatus.VISIBLE,
|
|
276
|
+
raw_data=comment_data,
|
|
277
|
+
)
|