posthubify 0.1.0__tar.gz
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.
- posthubify-0.1.0/PKG-INFO +100 -0
- posthubify-0.1.0/README.md +86 -0
- posthubify-0.1.0/posthubify/__init__.py +13 -0
- posthubify-0.1.0/posthubify/_http.py +154 -0
- posthubify-0.1.0/posthubify/client.py +258 -0
- posthubify-0.1.0/posthubify/errors.py +32 -0
- posthubify-0.1.0/posthubify/resources/__init__.py +1 -0
- posthubify-0.1.0/posthubify/resources/accounts.py +233 -0
- posthubify-0.1.0/posthubify/resources/ads.py +242 -0
- posthubify-0.1.0/posthubify/resources/analytics.py +343 -0
- posthubify-0.1.0/posthubify/resources/messaging.py +966 -0
- posthubify-0.1.0/posthubify/resources/platform.py +329 -0
- posthubify-0.1.0/posthubify/resources/posts.py +103 -0
- posthubify-0.1.0/posthubify/resources/telecom.py +137 -0
- posthubify-0.1.0/posthubify.egg-info/PKG-INFO +100 -0
- posthubify-0.1.0/posthubify.egg-info/SOURCES.txt +22 -0
- posthubify-0.1.0/posthubify.egg-info/dependency_links.txt +1 -0
- posthubify-0.1.0/posthubify.egg-info/requires.txt +3 -0
- posthubify-0.1.0/posthubify.egg-info/top_level.txt +1 -0
- posthubify-0.1.0/pyproject.toml +24 -0
- posthubify-0.1.0/setup.cfg +4 -0
- posthubify-0.1.0/tests/test_contract.py +166 -0
- posthubify-0.1.0/tests/test_guides.py +72 -0
- posthubify-0.1.0/tests/test_http.py +123 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: posthubify
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: PostHubify resmî Python SDK'sı — sosyal medya yayını, gelen kutusu, kişiler, reklam ve telekom (SMS/OTP) API'si.
|
|
5
|
+
Author: PostHubify
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://posthubify.com
|
|
8
|
+
Project-URL: Documentation, https://docs.posthubify.com
|
|
9
|
+
Keywords: posthubify,social-media,api,sdk,ads,sms,otp
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# PostHubify Python SDK
|
|
16
|
+
|
|
17
|
+
PostHubify `/v1` API'sinin resmî Python istemcisi — sosyal medya yayını, gelen kutusu,
|
|
18
|
+
kişiler, analitik, reklam (ads) ve telekom (SMS/OTP) için tek paket.
|
|
19
|
+
|
|
20
|
+
- **Sıfır runtime bağımlılığı** — yalnızca Python standart kütüphanesi (`urllib`).
|
|
21
|
+
- **Tam yüzey** — `/v1` spec'inin 190+ operasyonu birebir kapsanır (kontrat testiyle doğrulanır).
|
|
22
|
+
- Python 3.8+
|
|
23
|
+
|
|
24
|
+
## Kurulum
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install posthubify
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Hızlı başlangıç
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from posthubify import Posthubify, PosthubifyError
|
|
34
|
+
|
|
35
|
+
ph = Posthubify(api_key="sk_...", base_url="https://api.posthubify.com/v1")
|
|
36
|
+
|
|
37
|
+
# Bağlantı testi
|
|
38
|
+
print(ph.ping()) # {"pong": True, "version": "v1", ...}
|
|
39
|
+
|
|
40
|
+
# Gönderi yayınla
|
|
41
|
+
draft = ph.posts.create({
|
|
42
|
+
"content": "Merhaba dünya!",
|
|
43
|
+
"accountIds": ["acc_123"],
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
# Gelen kutusu analitiği
|
|
47
|
+
vol = ph.inbox_analytics.volume(from_date="2026-06-01", to_date="2026-06-14")
|
|
48
|
+
print(vol["summary"]) # {"received": ..., "sent": ..., ...}
|
|
49
|
+
|
|
50
|
+
# Reklam kampanyası
|
|
51
|
+
camps = ph.ads.campaigns("adacc_1", status="enabled")
|
|
52
|
+
|
|
53
|
+
# Hata yönetimi
|
|
54
|
+
try:
|
|
55
|
+
ph.posts.get("yok")
|
|
56
|
+
except PosthubifyError as e:
|
|
57
|
+
print(e.status, e.message, e.code) # 404 "..." "..."
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Resource grupları
|
|
61
|
+
|
|
62
|
+
İstemci, Node SDK'sıyla birebir aynı yüzeyi snake_case niteliklerle sunar:
|
|
63
|
+
|
|
64
|
+
`profiles` · `accounts` · `posts` · `media` · `tools` · `ads` · `insights` ·
|
|
65
|
+
`platform_analytics` · `inbox_analytics` · `inbox` · `comments` · `reviews` ·
|
|
66
|
+
`contacts` · `automations` · `broadcasts` · `sequences` · `webhooks` · `api_keys` ·
|
|
67
|
+
`queue` (`.schedules`) · `account_groups` · `engagement` (`.x`) · `users` ·
|
|
68
|
+
`invite_tokens` · `numbers` · `senders` · `sms` · `otp`
|
|
69
|
+
|
|
70
|
+
Üst seviye: `ph.openapi()`, `ph.ping()`, `ph.me()`, `ph.analytics()`,
|
|
71
|
+
`ph.analytics_posts()`, `ph.analytics_timeseries()`, `ph.logs()`, `ph.usage()`.
|
|
72
|
+
|
|
73
|
+
## Medya yükleme
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
with open("logo.png", "rb") as f:
|
|
77
|
+
asset = ph.media.upload(f.read(), filename="logo.png")
|
|
78
|
+
print(asset["url"])
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Geliştirme
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cd packages/python-sdk
|
|
85
|
+
python3 -m unittest discover -s tests # kontrat + transport testleri
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Kontrat testi (`tests/test_contract.py`) her SDK metodunun ürettiği uç yolunun
|
|
89
|
+
OpenAPI spec'inde (`tests/openapi.json`) tanımlı olduğunu doğrular. Spec fixture'ı
|
|
90
|
+
yeni uç eklendiğinde güncellemek için repo kökünden:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx tsx -e "import {buildOpenApiSpec} from './apps/server/src/openapi.ts'; \
|
|
94
|
+
import {writeFileSync} from 'node:fs'; \
|
|
95
|
+
writeFileSync('packages/python-sdk/tests/openapi.json', JSON.stringify(buildOpenApiSpec(),null,2))"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Lisans
|
|
99
|
+
|
|
100
|
+
MIT
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# PostHubify Python SDK
|
|
2
|
+
|
|
3
|
+
PostHubify `/v1` API'sinin resmî Python istemcisi — sosyal medya yayını, gelen kutusu,
|
|
4
|
+
kişiler, analitik, reklam (ads) ve telekom (SMS/OTP) için tek paket.
|
|
5
|
+
|
|
6
|
+
- **Sıfır runtime bağımlılığı** — yalnızca Python standart kütüphanesi (`urllib`).
|
|
7
|
+
- **Tam yüzey** — `/v1` spec'inin 190+ operasyonu birebir kapsanır (kontrat testiyle doğrulanır).
|
|
8
|
+
- Python 3.8+
|
|
9
|
+
|
|
10
|
+
## Kurulum
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install posthubify
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Hızlı başlangıç
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from posthubify import Posthubify, PosthubifyError
|
|
20
|
+
|
|
21
|
+
ph = Posthubify(api_key="sk_...", base_url="https://api.posthubify.com/v1")
|
|
22
|
+
|
|
23
|
+
# Bağlantı testi
|
|
24
|
+
print(ph.ping()) # {"pong": True, "version": "v1", ...}
|
|
25
|
+
|
|
26
|
+
# Gönderi yayınla
|
|
27
|
+
draft = ph.posts.create({
|
|
28
|
+
"content": "Merhaba dünya!",
|
|
29
|
+
"accountIds": ["acc_123"],
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
# Gelen kutusu analitiği
|
|
33
|
+
vol = ph.inbox_analytics.volume(from_date="2026-06-01", to_date="2026-06-14")
|
|
34
|
+
print(vol["summary"]) # {"received": ..., "sent": ..., ...}
|
|
35
|
+
|
|
36
|
+
# Reklam kampanyası
|
|
37
|
+
camps = ph.ads.campaigns("adacc_1", status="enabled")
|
|
38
|
+
|
|
39
|
+
# Hata yönetimi
|
|
40
|
+
try:
|
|
41
|
+
ph.posts.get("yok")
|
|
42
|
+
except PosthubifyError as e:
|
|
43
|
+
print(e.status, e.message, e.code) # 404 "..." "..."
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Resource grupları
|
|
47
|
+
|
|
48
|
+
İstemci, Node SDK'sıyla birebir aynı yüzeyi snake_case niteliklerle sunar:
|
|
49
|
+
|
|
50
|
+
`profiles` · `accounts` · `posts` · `media` · `tools` · `ads` · `insights` ·
|
|
51
|
+
`platform_analytics` · `inbox_analytics` · `inbox` · `comments` · `reviews` ·
|
|
52
|
+
`contacts` · `automations` · `broadcasts` · `sequences` · `webhooks` · `api_keys` ·
|
|
53
|
+
`queue` (`.schedules`) · `account_groups` · `engagement` (`.x`) · `users` ·
|
|
54
|
+
`invite_tokens` · `numbers` · `senders` · `sms` · `otp`
|
|
55
|
+
|
|
56
|
+
Üst seviye: `ph.openapi()`, `ph.ping()`, `ph.me()`, `ph.analytics()`,
|
|
57
|
+
`ph.analytics_posts()`, `ph.analytics_timeseries()`, `ph.logs()`, `ph.usage()`.
|
|
58
|
+
|
|
59
|
+
## Medya yükleme
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
with open("logo.png", "rb") as f:
|
|
63
|
+
asset = ph.media.upload(f.read(), filename="logo.png")
|
|
64
|
+
print(asset["url"])
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Geliştirme
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cd packages/python-sdk
|
|
71
|
+
python3 -m unittest discover -s tests # kontrat + transport testleri
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Kontrat testi (`tests/test_contract.py`) her SDK metodunun ürettiği uç yolunun
|
|
75
|
+
OpenAPI spec'inde (`tests/openapi.json`) tanımlı olduğunu doğrular. Spec fixture'ı
|
|
76
|
+
yeni uç eklendiğinde güncellemek için repo kökünden:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx tsx -e "import {buildOpenApiSpec} from './apps/server/src/openapi.ts'; \
|
|
80
|
+
import {writeFileSync} from 'node:fs'; \
|
|
81
|
+
writeFileSync('packages/python-sdk/tests/openapi.json', JSON.stringify(buildOpenApiSpec(),null,2))"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Lisans
|
|
85
|
+
|
|
86
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""PostHubify resmî Python SDK'sı.
|
|
2
|
+
|
|
3
|
+
from posthubify import Posthubify, PosthubifyError
|
|
4
|
+
|
|
5
|
+
ph = Posthubify(api_key="sk_...", base_url="https://api.posthubify.com/v1")
|
|
6
|
+
print(ph.ping())
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .client import Posthubify
|
|
10
|
+
from .errors import PosthubifyError
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
__all__ = ["Posthubify", "PosthubifyError", "__version__"]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""HTTP taşıma katmanı — stdlib urllib tabanlı (sıfır runtime bağımlılık).
|
|
2
|
+
|
|
3
|
+
Node @posthubify/node transport deseninin Python aynası: Bearer auth, JSON gövde,
|
|
4
|
+
multipart yükleme, ``{ data }`` zarfı açma, 2xx-dışı → PosthubifyError.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
import uuid
|
|
14
|
+
from typing import Any, Dict, Mapping, Optional, Tuple
|
|
15
|
+
|
|
16
|
+
from .errors import PosthubifyError
|
|
17
|
+
|
|
18
|
+
Query = Optional[Mapping[str, Any]]
|
|
19
|
+
|
|
20
|
+
_DEFAULT_BASE = "http://localhost:8787/v1"
|
|
21
|
+
_USER_AGENT = "posthubify-python/0.1.0"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _qval(v: object) -> str:
|
|
25
|
+
# bool → JSON-küçük harf ('true'/'false'); Python str(True)='True' backend `=== 'true'` ile eşleşmez.
|
|
26
|
+
if isinstance(v, bool):
|
|
27
|
+
return "true" if v else "false"
|
|
28
|
+
return str(v)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _encode_query(query: Query) -> str:
|
|
32
|
+
if not query:
|
|
33
|
+
return ""
|
|
34
|
+
pairs = [
|
|
35
|
+
(str(k), _qval(v))
|
|
36
|
+
for k, v in query.items()
|
|
37
|
+
if v is not None
|
|
38
|
+
]
|
|
39
|
+
return urllib.parse.urlencode(pairs) if pairs else ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Transport:
|
|
43
|
+
"""Düşük seviye istek motoru. ``req`` ham gövdeyi, ``data`` ``{data}`` zarfını açar."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, api_key: str, base_url: str = _DEFAULT_BASE, timeout: float = 30.0) -> None:
|
|
46
|
+
if not api_key:
|
|
47
|
+
raise ValueError("api_key gerekli (sk_…)")
|
|
48
|
+
self._key = api_key
|
|
49
|
+
self._base = base_url.rstrip("/")
|
|
50
|
+
self._timeout = timeout
|
|
51
|
+
|
|
52
|
+
# -- genel istek --
|
|
53
|
+
def req(
|
|
54
|
+
self,
|
|
55
|
+
method: str,
|
|
56
|
+
path: str,
|
|
57
|
+
*,
|
|
58
|
+
query: Query = None,
|
|
59
|
+
body: Any = None,
|
|
60
|
+
files: Optional[Tuple[str, bytes, str]] = None,
|
|
61
|
+
idempotency_key: Optional[str] = None,
|
|
62
|
+
) -> Any:
|
|
63
|
+
"""İsteği gönderir; 2xx → ayrıştırılmış gövde, değilse PosthubifyError.
|
|
64
|
+
|
|
65
|
+
files: (alan_adı, bytes, dosya_adı) — çok parçalı yükleme (media.upload).
|
|
66
|
+
"""
|
|
67
|
+
qs = _encode_query(query)
|
|
68
|
+
url = f"{self._base}{path}" + (f"?{qs}" if qs else "")
|
|
69
|
+
headers: Dict[str, str] = {
|
|
70
|
+
"Authorization": f"Bearer {self._key}",
|
|
71
|
+
"User-Agent": _USER_AGENT,
|
|
72
|
+
"Accept": "application/json",
|
|
73
|
+
}
|
|
74
|
+
if idempotency_key:
|
|
75
|
+
headers["Idempotency-Key"] = idempotency_key
|
|
76
|
+
|
|
77
|
+
data_bytes: Optional[bytes] = None
|
|
78
|
+
if files is not None:
|
|
79
|
+
field, content, filename = files
|
|
80
|
+
boundary = uuid.uuid4().hex
|
|
81
|
+
data_bytes, content_type = _build_multipart(field, content, filename, boundary)
|
|
82
|
+
headers["Content-Type"] = content_type
|
|
83
|
+
elif body is not None:
|
|
84
|
+
data_bytes = json.dumps(body).encode("utf-8")
|
|
85
|
+
headers["Content-Type"] = "application/json"
|
|
86
|
+
|
|
87
|
+
request = urllib.request.Request(url, data=data_bytes, headers=headers, method=method)
|
|
88
|
+
try:
|
|
89
|
+
with urllib.request.urlopen(request, timeout=self._timeout) as resp:
|
|
90
|
+
raw = resp.read()
|
|
91
|
+
return _parse(raw)
|
|
92
|
+
except urllib.error.HTTPError as exc: # 4xx/5xx
|
|
93
|
+
raw = exc.read()
|
|
94
|
+
parsed = _parse(raw)
|
|
95
|
+
message = None
|
|
96
|
+
code = None
|
|
97
|
+
if isinstance(parsed, dict):
|
|
98
|
+
message = parsed.get("error")
|
|
99
|
+
code = parsed.get("code")
|
|
100
|
+
raise PosthubifyError(
|
|
101
|
+
exc.code,
|
|
102
|
+
message or f"HTTP {exc.code}",
|
|
103
|
+
code,
|
|
104
|
+
parsed,
|
|
105
|
+
) from None
|
|
106
|
+
except urllib.error.URLError as exc: # ağ/bağlantı
|
|
107
|
+
raise PosthubifyError(0, f"Ağ hatası: {exc.reason}") from None
|
|
108
|
+
|
|
109
|
+
def data(
|
|
110
|
+
self,
|
|
111
|
+
method: str,
|
|
112
|
+
path: str,
|
|
113
|
+
*,
|
|
114
|
+
query: Query = None,
|
|
115
|
+
body: Any = None,
|
|
116
|
+
files: Optional[Tuple[str, bytes, str]] = None,
|
|
117
|
+
idempotency_key: Optional[str] = None,
|
|
118
|
+
) -> Any:
|
|
119
|
+
"""``{ data: ... }`` zarfını açar."""
|
|
120
|
+
result = self.req(
|
|
121
|
+
method, path, query=query, body=body, files=files, idempotency_key=idempotency_key
|
|
122
|
+
)
|
|
123
|
+
if isinstance(result, dict) and "data" in result:
|
|
124
|
+
return result["data"]
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _parse(raw: bytes) -> Any:
|
|
129
|
+
if not raw:
|
|
130
|
+
return None
|
|
131
|
+
try:
|
|
132
|
+
return json.loads(raw.decode("utf-8"))
|
|
133
|
+
except (ValueError, UnicodeDecodeError):
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
_MIME_BY_EXT = {"png": "image/png", "jpg": "image/jpeg", "jpeg": "image/jpeg", "mp4": "video/mp4"}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _mime_for(filename: str) -> str:
|
|
141
|
+
# Sistem-bağımsız sabit eşleme (diğer SDK'larla parite + backend image/*|video/* kontrolü garantisi).
|
|
142
|
+
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
|
|
143
|
+
return _MIME_BY_EXT.get(ext, "application/octet-stream")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _build_multipart(field: str, content: bytes, filename: str, boundary: str) -> Tuple[bytes, str]:
|
|
147
|
+
mime = _mime_for(filename)
|
|
148
|
+
pre = (
|
|
149
|
+
f"--{boundary}\r\n"
|
|
150
|
+
f'Content-Disposition: form-data; name="{field}"; filename="{filename}"\r\n'
|
|
151
|
+
f"Content-Type: {mime}\r\n\r\n"
|
|
152
|
+
).encode("utf-8")
|
|
153
|
+
post = f"\r\n--{boundary}--\r\n".encode("utf-8")
|
|
154
|
+
return pre + content + post, f"multipart/form-data; boundary={boundary}"
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Posthubify istemcisi — Node @posthubify/node ``Posthubify`` sınıfının Python aynası.
|
|
2
|
+
|
|
3
|
+
Tüm resource grupları snake_case nitelik olarak bağlanır (Node camelCase → Python):
|
|
4
|
+
profiles, accounts, posts, media, tools, ads, insights, platform_analytics,
|
|
5
|
+
inbox_analytics, inbox, comments, reviews, contacts, automations, broadcasts,
|
|
6
|
+
sequences, webhooks, api_keys, queue, account_groups, engagement, users,
|
|
7
|
+
invite_tokens, numbers, senders, sms, otp.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from ._http import Transport, _DEFAULT_BASE
|
|
15
|
+
from .resources.accounts import AccountsResource, ProfilesResource
|
|
16
|
+
from .resources.ads import AdsResource
|
|
17
|
+
from .resources.analytics import (
|
|
18
|
+
InboxAnalyticsResource,
|
|
19
|
+
InsightsResource,
|
|
20
|
+
PlatformAnalyticsResource,
|
|
21
|
+
)
|
|
22
|
+
from .resources.messaging import (
|
|
23
|
+
AutomationsResource,
|
|
24
|
+
CommentAutomationsResource,
|
|
25
|
+
WorkflowsResource,
|
|
26
|
+
BroadcastsResource,
|
|
27
|
+
CommentsResource,
|
|
28
|
+
ContactsResource,
|
|
29
|
+
InboxResource,
|
|
30
|
+
ReviewsResource,
|
|
31
|
+
SequencesResource,
|
|
32
|
+
CustomFieldsResource,
|
|
33
|
+
WhatsAppResource,
|
|
34
|
+
GmbResource,
|
|
35
|
+
DiscordResource,
|
|
36
|
+
DiscoveryResource,
|
|
37
|
+
)
|
|
38
|
+
from .resources.platform import (
|
|
39
|
+
AccountGroupsResource,
|
|
40
|
+
ApiKeysResource,
|
|
41
|
+
EngagementResource,
|
|
42
|
+
InviteTokensResource,
|
|
43
|
+
QueueResource,
|
|
44
|
+
UsersResource,
|
|
45
|
+
WebhooksResource,
|
|
46
|
+
)
|
|
47
|
+
from .resources.posts import MediaResource, PostsResource, ToolsResource
|
|
48
|
+
from .resources.telecom import (
|
|
49
|
+
NumbersResource,
|
|
50
|
+
OtpResource,
|
|
51
|
+
SendersResource,
|
|
52
|
+
SmsResource,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Posthubify:
|
|
57
|
+
"""PostHubify /v1 API istemcisi.
|
|
58
|
+
|
|
59
|
+
Örnek:
|
|
60
|
+
>>> from posthubify import Posthubify
|
|
61
|
+
>>> ph = Posthubify(api_key="sk_...", base_url="https://api.posthubify.com/v1")
|
|
62
|
+
>>> ph.ping()
|
|
63
|
+
{"pong": True, "version": "v1", ...}
|
|
64
|
+
>>> ph.posts.list(platform="x", limit=10)
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
api_key: str,
|
|
70
|
+
*,
|
|
71
|
+
base_url: str = _DEFAULT_BASE,
|
|
72
|
+
timeout: float = 30.0,
|
|
73
|
+
transport: Optional[Transport] = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
# transport enjeksiyonu testler içindir (Node'daki opts.fetch karşılığı).
|
|
76
|
+
self._http = transport or Transport(api_key, base_url=base_url, timeout=timeout)
|
|
77
|
+
|
|
78
|
+
# --- resource grupları (Node attribute adlarının snake_case aynası) ---
|
|
79
|
+
self.profiles = ProfilesResource(self._http)
|
|
80
|
+
self.accounts = AccountsResource(self._http)
|
|
81
|
+
self.posts = PostsResource(self._http)
|
|
82
|
+
self.media = MediaResource(self._http)
|
|
83
|
+
self.tools = ToolsResource(self._http)
|
|
84
|
+
self.ads = AdsResource(self._http)
|
|
85
|
+
self.insights = InsightsResource(self._http)
|
|
86
|
+
self.platform_analytics = PlatformAnalyticsResource(self._http)
|
|
87
|
+
self.inbox_analytics = InboxAnalyticsResource(self._http)
|
|
88
|
+
self.inbox = InboxResource(self._http)
|
|
89
|
+
self.comments = CommentsResource(self._http)
|
|
90
|
+
self.reviews = ReviewsResource(self._http)
|
|
91
|
+
self.contacts = ContactsResource(self._http)
|
|
92
|
+
self.automations = AutomationsResource(self._http)
|
|
93
|
+
self.comment_automations = CommentAutomationsResource(self._http)
|
|
94
|
+
self.workflows = WorkflowsResource(self._http)
|
|
95
|
+
self.broadcasts = BroadcastsResource(self._http)
|
|
96
|
+
self.sequences = SequencesResource(self._http)
|
|
97
|
+
self.custom_fields = CustomFieldsResource(self._http)
|
|
98
|
+
self.whatsapp = WhatsAppResource(self._http)
|
|
99
|
+
self.gmb = GmbResource(self._http)
|
|
100
|
+
self.discord = DiscordResource(self._http)
|
|
101
|
+
self.discovery = DiscoveryResource(self._http)
|
|
102
|
+
self.webhooks = WebhooksResource(self._http)
|
|
103
|
+
self.api_keys = ApiKeysResource(self._http)
|
|
104
|
+
self.queue = QueueResource(self._http)
|
|
105
|
+
self.account_groups = AccountGroupsResource(self._http)
|
|
106
|
+
self.engagement = EngagementResource(self._http)
|
|
107
|
+
self.users = UsersResource(self._http)
|
|
108
|
+
self.invite_tokens = InviteTokensResource(self._http)
|
|
109
|
+
self.numbers = NumbersResource(self._http)
|
|
110
|
+
self.senders = SendersResource(self._http)
|
|
111
|
+
self.sms = SmsResource(self._http)
|
|
112
|
+
self.otp = OtpResource(self._http)
|
|
113
|
+
|
|
114
|
+
# --- üst seviye uçlar (Node sınıf metodları) ---
|
|
115
|
+
def openapi(self) -> Dict[str, Any]:
|
|
116
|
+
"""OpenAPI 3.1 spec dokümanı (auth gerekmez ama anahtarla da çalışır)."""
|
|
117
|
+
return self._http.req("GET", "/openapi.json")
|
|
118
|
+
|
|
119
|
+
def ping(self) -> Dict[str, Any]:
|
|
120
|
+
"""Entegrasyon sağlık testi: bağlantı + anahtar doğrulaması (geçersiz anahtar → 401)."""
|
|
121
|
+
return self._http.data("GET", "/ping")
|
|
122
|
+
|
|
123
|
+
def me(self) -> Dict[str, Any]:
|
|
124
|
+
"""API anahtarı kimliği."""
|
|
125
|
+
return self._http.data("GET", "/me")
|
|
126
|
+
|
|
127
|
+
def generate(
|
|
128
|
+
self,
|
|
129
|
+
command: str,
|
|
130
|
+
platforms: List[str],
|
|
131
|
+
*,
|
|
132
|
+
variants: Optional[int] = None,
|
|
133
|
+
context: Optional[str] = None,
|
|
134
|
+
max_chars: Optional[int] = None,
|
|
135
|
+
) -> Dict[str, Any]:
|
|
136
|
+
"""AI içerik üretimi (F1) — komut → platform-başına taslak + varyant (çok-dil)."""
|
|
137
|
+
return self._http.data("POST", "/generate", body={
|
|
138
|
+
"command": command, "platforms": platforms, "variants": variants,
|
|
139
|
+
"context": context, "maxChars": max_chars,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
def subtitles(
|
|
143
|
+
self,
|
|
144
|
+
text: str,
|
|
145
|
+
*,
|
|
146
|
+
format: str = "srt",
|
|
147
|
+
duration_sec: Optional[float] = None,
|
|
148
|
+
max_chars_per_line: Optional[int] = None,
|
|
149
|
+
max_lines_per_cue: Optional[int] = None,
|
|
150
|
+
chars_per_sec: Optional[float] = None,
|
|
151
|
+
gap_ms: Optional[int] = None,
|
|
152
|
+
) -> Dict[str, Any]:
|
|
153
|
+
"""Altyazı üretimi (F4) — metin → zamanlanmış SRT/VTT (saf dönüşüm, AI/ücret yok).
|
|
154
|
+
|
|
155
|
+
duration_sec verilirse küeler bu süreye orantılı dağıtılır (sesle/videoyla senkron; ≤86400).
|
|
156
|
+
chars_per_sec okuma hızıdır (kesirli olabilir, örn. 15.5)."""
|
|
157
|
+
return self._http.data("POST", "/subtitles", body={
|
|
158
|
+
"text": text, "format": format, "durationSec": duration_sec,
|
|
159
|
+
"maxCharsPerLine": max_chars_per_line, "maxLinesPerCue": max_lines_per_cue,
|
|
160
|
+
"charsPerSec": chars_per_sec, "gapMs": gap_ms,
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
def videos(
|
|
164
|
+
self,
|
|
165
|
+
image_url: str,
|
|
166
|
+
format: str,
|
|
167
|
+
*,
|
|
168
|
+
audio_url: Optional[str] = None,
|
|
169
|
+
subtitle_text: Optional[str] = None,
|
|
170
|
+
subtitle_rtl: Optional[bool] = None,
|
|
171
|
+
bumper_duration_sec: Optional[float] = None,
|
|
172
|
+
main_duration_sec: Optional[float] = None,
|
|
173
|
+
) -> Dict[str, Any]:
|
|
174
|
+
"""Promo video render (F4) — uzak görsel (+ops. ses/altyazı) → markalı video (ffmpeg) → R2 URL.
|
|
175
|
+
|
|
176
|
+
format: '9:16' | '1:1' | '16:9'. AĞIR işlem (yazma izni + düşük hız sınırı).
|
|
177
|
+
Medya SSRF-korumalı indirilir (https + içerik-tipi + ≤25MB)."""
|
|
178
|
+
return self._http.data("POST", "/videos", body={
|
|
179
|
+
"imageUrl": image_url, "format": format, "audioUrl": audio_url,
|
|
180
|
+
"subtitleText": subtitle_text, "subtitleRtl": subtitle_rtl,
|
|
181
|
+
"bumperDurationSec": bumper_duration_sec, "mainDurationSec": main_duration_sec,
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
def analytics(self) -> Dict[str, Any]:
|
|
185
|
+
"""Bağlı hesap + gönderi analitiği (platform API'lerinden canlı)."""
|
|
186
|
+
return self._http.data("GET", "/analytics")
|
|
187
|
+
|
|
188
|
+
def analytics_posts(
|
|
189
|
+
self,
|
|
190
|
+
*,
|
|
191
|
+
platform: Optional[str] = None,
|
|
192
|
+
account_id: Optional[str] = None,
|
|
193
|
+
source: Optional[str] = None,
|
|
194
|
+
limit: Optional[int] = None,
|
|
195
|
+
cursor: Optional[str] = None,
|
|
196
|
+
) -> Dict[str, Any]:
|
|
197
|
+
"""Gönderi analitiği listesi — source='external': platformdan senkronlanan, PostHubify-dışı gönderiler (B5)."""
|
|
198
|
+
return self._http.req(
|
|
199
|
+
"GET",
|
|
200
|
+
"/analytics/posts",
|
|
201
|
+
query={
|
|
202
|
+
"platform": platform,
|
|
203
|
+
"accountId": account_id,
|
|
204
|
+
"source": source,
|
|
205
|
+
"limit": limit,
|
|
206
|
+
"cursor": cursor,
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def analytics_timeseries(
|
|
211
|
+
self,
|
|
212
|
+
*,
|
|
213
|
+
days: Optional[int] = None,
|
|
214
|
+
platform: Optional[str] = None,
|
|
215
|
+
account_id: Optional[str] = None,
|
|
216
|
+
from_date: Optional[str] = None,
|
|
217
|
+
to_date: Optional[str] = None,
|
|
218
|
+
source: Optional[str] = None,
|
|
219
|
+
) -> Dict[str, Any]:
|
|
220
|
+
"""Depolanan günlük metrik zaman serisi (hesap/post/inbox toplamları)."""
|
|
221
|
+
return self._http.data(
|
|
222
|
+
"GET",
|
|
223
|
+
"/analytics/timeseries",
|
|
224
|
+
query={
|
|
225
|
+
"days": days,
|
|
226
|
+
"platform": platform,
|
|
227
|
+
"accountId": account_id,
|
|
228
|
+
"fromDate": from_date,
|
|
229
|
+
"toDate": to_date,
|
|
230
|
+
"source": source,
|
|
231
|
+
},
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
def logs(
|
|
235
|
+
self,
|
|
236
|
+
*,
|
|
237
|
+
limit: Optional[int] = None,
|
|
238
|
+
category: Optional[str] = None,
|
|
239
|
+
status: Optional[str] = None,
|
|
240
|
+
platform: Optional[str] = None,
|
|
241
|
+
days: Optional[int] = None,
|
|
242
|
+
) -> Any:
|
|
243
|
+
"""Etkinlik/denetim günlüğü kayıtları."""
|
|
244
|
+
return self._http.data(
|
|
245
|
+
"GET",
|
|
246
|
+
"/logs",
|
|
247
|
+
query={
|
|
248
|
+
"limit": limit,
|
|
249
|
+
"category": category,
|
|
250
|
+
"status": status,
|
|
251
|
+
"platform": platform,
|
|
252
|
+
"days": days,
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def usage(self) -> Dict[str, Any]:
|
|
257
|
+
"""Plan + cüzdan + 30 günlük kullanım sayaçları."""
|
|
258
|
+
return self._http.data("GET", "/usage")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""PostHubify API hata türü (Node @posthubify/node PosthubifyError aynası)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PosthubifyError(Exception):
|
|
9
|
+
"""Bir API isteği 2xx olmayan durum döndürdüğünde fırlatılır.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
status: HTTP durum kodu (ör. 400, 401, 404, 429, 502).
|
|
13
|
+
message: Sunucunun ``error`` alanı ya da ``HTTP <status>``.
|
|
14
|
+
code: Sunucunun makine-okunur ``code`` alanı (varsa).
|
|
15
|
+
body: Ayrıştırılmış yanıt gövdesi (varsa).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
status: int,
|
|
21
|
+
message: str,
|
|
22
|
+
code: Optional[str] = None,
|
|
23
|
+
body: Any = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.status = status
|
|
27
|
+
self.message = message
|
|
28
|
+
self.code = code
|
|
29
|
+
self.body = body
|
|
30
|
+
|
|
31
|
+
def __repr__(self) -> str: # pragma: no cover - sadece hata ayıklama
|
|
32
|
+
return f"PosthubifyError(status={self.status!r}, message={self.message!r}, code={self.code!r})"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Resource sınıfları — Posthubify istemcisi bunları bağlar."""
|