planvortex 0.4.0__tar.gz → 0.5.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.
- {planvortex-0.4.0 → planvortex-0.5.0}/CHANGELOG.md +20 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/PKG-INFO +1 -1
- {planvortex-0.4.0 → planvortex-0.5.0}/openapi/planvortex.openapi.json +15 -11
- {planvortex-0.4.0 → planvortex-0.5.0}/pyproject.toml +1 -1
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_generated/models.py +7 -2
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_shapes.py +4 -3
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_version.py +1 -1
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/accounts.py +1 -1
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/accounts.py +1 -1
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/types.py +1 -1
- {planvortex-0.4.0 → planvortex-0.5.0}/.gitignore +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/LICENSE +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/README.md +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/scripts/build_docs.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/scripts/check_packaging.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/scripts/generate_models.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/scripts/generate_sync.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/scripts/route_coverage.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/__init__.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_client.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_client_sync.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_core/__init__.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_core/auth.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_core/auth_sync.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_core/errors.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_core/files.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_core/http.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_core/http_sync.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_core/pagination.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_core/query.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_core/transport.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/_generated/__init__.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/py.typed +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/__init__.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/ai_plans.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/apps.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/base.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/catalog.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/clients.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/comments.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/contacts.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/dashboard.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/integrations.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/messages.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/organizations.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/products.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/publications.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources/uploads.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/__init__.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/ai_plans.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/apps.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/base.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/catalog.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/clients.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/comments.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/contacts.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/dashboard.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/integrations.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/messages.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/organizations.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/products.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/publications.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/resources_sync/uploads.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/src/planvortex/webhooks.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/__init__.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/conftest.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/contrato.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/live/__init__.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/live/conftest.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/live/test_auth.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/live/test_catalog.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/live/test_connect_flow.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/live/test_errors.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/live/test_publish.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/live/test_read.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_accounts.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_ai_plans.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_apps.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_catalog.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_client.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_clients.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_comments.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_contacts.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_core.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_dashboard.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_example_comments.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_example_connect.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_example_publish.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_example_schedule.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_example_webhooks.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_files.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_generate_models.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_generate_sync.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_integrations.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_messages.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_models.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_openapi_freshness.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_organizations.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_packaging.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_pagination.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_products.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_publications.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_query_parity.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_readme.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_route_coverage.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_shapes_parity.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_types.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_uploads.py +0 -0
- {planvortex-0.4.0 → planvortex-0.5.0}/tests/test_webhooks.py +0 -0
|
@@ -4,6 +4,26 @@ All notable changes to this package are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.5.0] - 2026-09-02
|
|
8
|
+
|
|
9
|
+
**Threads is the twelfth network**, and the types now know it.
|
|
10
|
+
|
|
11
|
+
Nothing here removes anything: `SocialNetwork` gains a value, `PublishableNetwork` gains another,
|
|
12
|
+
and everything that type-checked before type-checks now. What was wrong until today was quieter than
|
|
13
|
+
an error — the committed copy of the spec predated Threads, so an account coming back from a real
|
|
14
|
+
API arrived with a `social_network` no `Literal` had ever heard of, and the prose still counted
|
|
15
|
+
eleven networks where the API had twelve.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- The OpenAPI copy and the generated models are rebuilt from the published document, so `threads`
|
|
20
|
+
is a `SocialNetwork`, a `CommentNetwork` and a publishable network.
|
|
21
|
+
- `PublishableNetwork` — hand-maintained, because generating it is not possible — includes
|
|
22
|
+
`threads`: **eleven of the twelve** networks have a feed, and `google_business` is still the one
|
|
23
|
+
that does not.
|
|
24
|
+
- The prose that counts networks: a connection link is a `redirect` on **ten of the twelve**, not
|
|
25
|
+
nine of eleven. WhatsApp and Telegram are still the two that are not.
|
|
26
|
+
|
|
7
27
|
## [0.4.0] - 2026-08-31
|
|
8
28
|
|
|
9
29
|
An AI plan can now be **archived**, and deleting one finally means what people thought it meant.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: planvortex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Official Python client for the PlanVortex API
|
|
5
5
|
Project-URL: Homepage, https://planvortex.com/developers
|
|
6
6
|
Project-URL: Documentation, https://taliasoftworks.github.io/PlanVortexPython/
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"accounts"
|
|
108
108
|
],
|
|
109
109
|
"summary": "Get the connection links of every connectable network",
|
|
110
|
-
"description": "The authorization URL of each network, so the user can connect an account to this organization.\n\n**A network that cannot produce a link simply does not appear.** That is a legitimate answer, not a failure: it is what happens with `discord` in an organization that has not saved its own bot credentials yet (see `PUT /organizations/{id_organization}/social_credentials/{social_network}`).\n\n**An app cannot call this.** Connecting a social account is an OAuth flow with a person in front of it, so this endpoint only accepts a user token or a temporal connect token; with app credentials it answers error 519. The way an integration does it is to issue a temporal connect token with `GET /organizations/{id_organization}/temporal_connect_token` and hand it to its end user.\n\n**Read `authorization`, not `link`.**
|
|
110
|
+
"description": "The authorization URL of each network, so the user can connect an account to this organization.\n\n**A network that cannot produce a link simply does not appear.** That is a legitimate answer, not a failure: it is what happens with `discord` in an organization that has not saved its own bot credentials yet (see `PUT /organizations/{id_organization}/social_credentials/{social_network}`).\n\n**An app cannot call this.** Connecting a social account is an OAuth flow with a person in front of it, so this endpoint only accepts a user token or a temporal connect token; with app credentials it answers error 519. The way an integration does it is to issue a temporal connect token with `GET /organizations/{id_organization}/temporal_connect_token` and hand it to its end user.\n\n**Read `authorization`, not `link`.** Ten of the twelve networks are `redirect` and you send the person to `link`. Two are not, and neither of them fails visibly if you treat it as one:\n\n• **WhatsApp is not a URL at all.** Its sign-up is Meta's Embedded Signup, a popup you raise with the Facebook JavaScript SDK, so its `link` is an empty string and everything you need to open that popup travels in `authorization`. A client that loops over the list and redirects to `link` sends its user to its own page.\n• **Telegram has a link and still is not a redirect.** It opens a chat with the PlanVortex bot, and nobody comes back from it: the account is born minutes later, from the bot being added to a channel, and it is announced over the WebSocket. Open it in another tab and keep listening; redirect to it and there is nobody left to tell.",
|
|
111
111
|
"operationId": "getConnectLinks",
|
|
112
112
|
"x-planvortex-identity": [
|
|
113
113
|
"current_user",
|
|
@@ -1872,7 +1872,8 @@
|
|
|
1872
1872
|
"google_business",
|
|
1873
1873
|
"bluesky",
|
|
1874
1874
|
"discord",
|
|
1875
|
-
"telegram"
|
|
1875
|
+
"telegram",
|
|
1876
|
+
"threads"
|
|
1876
1877
|
]
|
|
1877
1878
|
}
|
|
1878
1879
|
}
|
|
@@ -3597,7 +3598,7 @@
|
|
|
3597
3598
|
"comments"
|
|
3598
3599
|
],
|
|
3599
3600
|
"summary": "The inbox: first-level comments across the whole organization",
|
|
3600
|
-
"description": "Served from PlanVortex's database, so it costs nothing and calls no social network. It is a **snapshot**: `collected_date` says when each row was last read. Open a thread to see what the network says right now.\n\nOrdered by `creation_date` descending — the date on the network, not the date we collected it — so rows from
|
|
3601
|
+
"description": "Served from PlanVortex's database, so it costs nothing and calls no social network. It is a **snapshot**: `collected_date` says when each row was last read. Open a thread to see what the network says right now.\n\nOrdered by `creation_date` descending — the date on the network, not the date we collected it — so rows from ten networks interleave correctly.\n\n**On `telegram` the inbox starts the day the channel was connected.** The Bot API has no way to read the past: a bot only learns what happens while it is inside, so nothing written before the connection exists here and never will. Say so in your UI — an inbox that opens empty on a busy channel reads like a failure.\n\n**Your own replies are not in here.** Anything with `author.is_own: true` is filtered out: what you wrote is not incoming mail. They are still stored, and they do show up in the thread.\n\nRequires the `comments:read` permission (`client_organization_comments:read` for apps) and a paid plan.",
|
|
3601
3602
|
"operationId": "getComments",
|
|
3602
3603
|
"x-planvortex-identity": [
|
|
3603
3604
|
"current_user",
|
|
@@ -4086,7 +4087,7 @@
|
|
|
4086
4087
|
"catalog"
|
|
4087
4088
|
],
|
|
4088
4089
|
"summary": "What every network supports",
|
|
4089
|
-
"description": "The full matrix network → capabilities. It is the **single source of truth** about what a network can do: the catalogue publishes it, the backend checks it before acting, and your integration should filter its account pickers with it rather than keeping a list of its own.\n\n`comments` is the coarse gate — whether the network has comments at all. Which *actions* it allows on one is a finer question and lives in `GET /social_comment_actions`, because the shape here is `{[capability]: boolean}` and nesting an object inside would break it.\n\nToday
|
|
4090
|
+
"description": "The full matrix network → capabilities. It is the **single source of truth** about what a network can do: the catalogue publishes it, the backend checks it before acting, and your integration should filter its account pickers with it rather than keeping a list of its own.\n\n`comments` is the coarse gate — whether the network has comments at all. Which *actions* it allows on one is a finer question and lives in `GET /social_comment_actions`, because the shape here is `{[capability]: boolean}` and nesting an object inside would break it.\n\nToday ten networks answer `comments: true`: Facebook, Instagram, Threads, LinkedIn, X, YouTube, Google Business, Bluesky, Discord and Telegram. TikTok and WhatsApp answer `false`, for reasons of theirs and not ours.\n\nA `true` here is about the **network**, not about one account of it: a Telegram channel with no linked discussion group answers 965 on its comments even though the network has them.\n\nOnly needs authentication.",
|
|
4090
4091
|
"operationId": "getSocialCapabilities",
|
|
4091
4092
|
"x-planvortex-identity": [
|
|
4092
4093
|
"current_user",
|
|
@@ -4144,7 +4145,7 @@
|
|
|
4144
4145
|
"catalog"
|
|
4145
4146
|
],
|
|
4146
4147
|
"summary": "What each network lets you do to a comment",
|
|
4147
|
-
"description": "The fine-grained matrix: reply, hide, delete your own, delete somebody else's — network by network. Read it before painting controls. Networks without comments answer `false` to all four.\n\nThe differences are real and each has a reason in the network's own API:\n\n| Network | reply | hide | delete_own | delete_others |\n| --- | --- | --- | --- | --- |\n| `facebook` | yes | yes | yes | yes |\n| `instagram` | yes | yes | yes | **no** — Instagram only lets you hide someone else's |\n| `youtube` | yes | yes | yes | yes — the channel owner really does moderate |\n| `linkedin` | yes | **no** — there is no hide endpoint | yes | yes |\n| `twitter` | yes | yes | yes | **no** — you cannot delete another account's post |\n| `google_business` | yes | **no** | yes — **your reply**, never the review | **no** |\n| `bluesky` | yes | yes — through the post's `threadgate` | yes | **no** — the reply lives in somebody else's repository |\n| `discord` | yes | **no** — Discord has no hide, only delete | yes | yes |\n| `telegram` | yes | **no** — Telegram has no hide either | yes | yes — with the bot as an admin of the discussion group, otherwise error 969 |\n| `tiktok`, `whatsapp` | no | no | no | no |\n\nOnly needs authentication.",
|
|
4148
|
+
"description": "The fine-grained matrix: reply, hide, delete your own, delete somebody else's — network by network. Read it before painting controls. Networks without comments answer `false` to all four.\n\nThe differences are real and each has a reason in the network's own API:\n\n| Network | reply | hide | delete_own | delete_others |\n| --- | --- | --- | --- | --- |\n| `facebook` | yes | yes | yes | yes |\n| `instagram` | yes | yes | yes | **no** — Instagram only lets you hide someone else's |\n| `youtube` | yes | yes | yes | yes — the channel owner really does moderate |\n| `linkedin` | yes | **no** — there is no hide endpoint | yes | yes |\n| `twitter` | yes | yes | yes | **no** — you cannot delete another account's post |\n| `google_business` | yes | **no** | yes — **your reply**, never the review | **no** |\n| `bluesky` | yes | yes — through the post's `threadgate` | yes | **no** — the reply lives in somebody else's repository |\n| `discord` | yes | **no** — Discord has no hide, only delete | yes | yes |\n| `telegram` | yes | **no** — Telegram has no hide either | yes | yes — with the bot as an admin of the discussion group, otherwise error 969 |\n| `threads` | yes | yes | yes | **no** — deleting somebody else's reply is not a permission you lack, it is a call that does not exist in the API |\n| `tiktok`, `whatsapp` | no | no | no | no |\n\nOnly needs authentication.",
|
|
4148
4149
|
"operationId": "getSocialCommentActions",
|
|
4149
4150
|
"x-planvortex-identity": [
|
|
4150
4151
|
"current_user",
|
|
@@ -9510,7 +9511,7 @@
|
|
|
9510
9511
|
"required": [
|
|
9511
9512
|
"type"
|
|
9512
9513
|
],
|
|
9513
|
-
"description": "**How** an account of this network is authorized, which is not always \"send the user to this URL\".\n\
|
|
9514
|
+
"description": "**How** an account of this network is authorized, which is not always \"send the user to this URL\".\n\nTen of the twelve networks are `redirect`: open `link` and the network sends the person back to PlanVortex with a code. Two are not:\n\n• **WhatsApp.** Its sign-up is Meta's *Embedded Signup*: a popup raised by the Facebook JavaScript SDK from your own page, which returns — over `postMessage` — session data (`waba_id`, `phone_number_id`) that no query string carries. Its `link` is therefore an empty string.\n• **Telegram.** There is no OAuth here: no consent screen, no `code`, no account token. `link` opens a private chat with the PlanVortex bot, the person then adds that bot to their channel, and **the account is created from that event**, not from any request of yours. Which means the connection cannot be finished by calling `GET /organizations/{id_organization}/account-connect/telegram` — see that endpoint.\n\nBranch on `authorization.type`, never on whether `link` is empty.",
|
|
9514
9515
|
"properties": {
|
|
9515
9516
|
"type": {
|
|
9516
9517
|
"type": "string",
|
|
@@ -10514,7 +10515,7 @@
|
|
|
10514
10515
|
"$ref": "#/components/schemas/CatalogSocialLimitsMap"
|
|
10515
10516
|
}
|
|
10516
10517
|
],
|
|
10517
|
-
"description": "Maximum length of a publication's text. Bluesky
|
|
10518
|
+
"description": "Maximum length of a publication's text. **Bluesky and Threads count GRAPHEMES**, everyone else counts characters — Telegram included, where `String.length` is exactly the right unit. The difference is not academic: an emoji is one grapheme and two `String.length` units, so counting a Threads post with `.length` rejects at 250 emojis what the network publishes happily at 500.\n\nOn Telegram there are **two numbers for the same field**: `telegram` (4.096) while the publication is text only, and `telegram_media` (1.024) the moment it carries an image or a video, because then the text is a media caption and not a message. Switch the counter when the file is attached, not when publish is pressed."
|
|
10518
10519
|
},
|
|
10519
10520
|
"max_post_bytes": {
|
|
10520
10521
|
"allOf": [
|
|
@@ -11257,7 +11258,8 @@
|
|
|
11257
11258
|
"google_business",
|
|
11258
11259
|
"bluesky",
|
|
11259
11260
|
"discord",
|
|
11260
|
-
"telegram"
|
|
11261
|
+
"telegram",
|
|
11262
|
+
"threads"
|
|
11261
11263
|
]
|
|
11262
11264
|
},
|
|
11263
11265
|
"CommentsCommentThread": {
|
|
@@ -13725,7 +13727,7 @@
|
|
|
13725
13727
|
},
|
|
13726
13728
|
"PublicationStats": {
|
|
13727
13729
|
"type": "object",
|
|
13728
|
-
"description": "Raw, per-network metrics for a publication. Only the fields that belong to the publication's own social network are returned.\n\n**An absent field is not a zero.** A field is present only when the network actually reported it; `0` means the network measured zero. This matters most on X (Twitter), where metrics are split into groups with different access levels: `public_metrics` (likes, replys, retwets, quotes, bookmarks, impressions) is always available, while clicks, the pre-computed `engagement` and the video playback quartiles come from X's non-public metrics — only for your own posts, within 30 days of publishing, and only if the app is entitled to them. When they are unavailable they are omitted rather than returned as `0`. Do not default missing fields to zero when displaying them.\n\nOn `discord` there are only two: `likes` (the reactions on the message) and `comments` (the messages in its thread). There is no impressions figure anywhere in Discord's API, so engagement is computed over the server's member count.\n\nOn `bluesky` there are no impressions and no reach either — only the public counters — so engagement is computed over followers.\n\nOn `telegram` there are two as well, and **neither of them is asked for**: the Bot API has no method that returns a message's metrics, so `reactions` arrives on its own through the bot and `comments` is counted in PlanVortex's own inbox. There are no impressions, no reach, no views and no forwards to be had anywhere in it, so engagement is computed over followers.",
|
|
13730
|
+
"description": "Raw, per-network metrics for a publication. Only the fields that belong to the publication's own social network are returned.\n\n**An absent field is not a zero.** A field is present only when the network actually reported it; `0` means the network measured zero. This matters most on X (Twitter), where metrics are split into groups with different access levels: `public_metrics` (likes, replys, retwets, quotes, bookmarks, impressions) is always available, while clicks, the pre-computed `engagement` and the video playback quartiles come from X's non-public metrics — only for your own posts, within 30 days of publishing, and only if the app is entitled to them. When they are unavailable they are omitted rather than returned as `0`. Do not default missing fields to zero when displaying them.\n\nOn `discord` there are only two: `likes` (the reactions on the message) and `comments` (the messages in its thread). There is no impressions figure anywhere in Discord's API, so engagement is computed over the server's member count.\n\nOn `bluesky` there are no impressions and no reach either — only the public counters — so engagement is computed over followers.\n\nOn `threads` there are six, and the one that matters is `views`: it is the only one of the four newest networks with something like impressions, so its engagement rate is computed over a real base and not over followers. Sharing arrives split in three — `reposts`, `quotes` and `shares` (outside Threads) — and the three are added into one figure, the same criterion as on X; the breakdown stays in `raw`. A reply is what every other network calls a comment.\n\nOn `telegram` there are two as well, and **neither of them is asked for**: the Bot API has no method that returns a message's metrics, so `reactions` arrives on its own through the bot and `comments` is counted in PlanVortex's own inbox. There are no impressions, no reach, no views and no forwards to be had anywhere in it, so engagement is computed over followers.",
|
|
13729
13731
|
"properties": {
|
|
13730
13732
|
"likes": {
|
|
13731
13733
|
"type": "integer"
|
|
@@ -13864,7 +13866,8 @@
|
|
|
13864
13866
|
"youtube",
|
|
13865
13867
|
"bluesky",
|
|
13866
13868
|
"discord",
|
|
13867
|
-
"telegram"
|
|
13869
|
+
"telegram",
|
|
13870
|
+
"threads"
|
|
13868
13871
|
],
|
|
13869
13872
|
"description": "Network the publication targets. **Required when creating**: the request fails with error 702 if it is missing or not one of these values. It must match the network of the account in the path.\n\nNot every connectable network publishes — a local business listing receives reviews, not posts — so this list is shorter than the one in `GET /social_networks`. Ask `GET /allowed_social_publications` rather than hardcoding it, because it grows."
|
|
13870
13873
|
},
|
|
@@ -14207,7 +14210,8 @@
|
|
|
14207
14210
|
"google_business",
|
|
14208
14211
|
"bluesky",
|
|
14209
14212
|
"discord",
|
|
14210
|
-
"telegram"
|
|
14213
|
+
"telegram",
|
|
14214
|
+
"threads"
|
|
14211
14215
|
],
|
|
14212
14216
|
"type": "string",
|
|
14213
14217
|
"description": "A social network supported by PlanVortex.\n\n**This list grows.** Treat it as an open enumeration: a client that rejects an unknown value breaks the day a network is added, which happens several times a year. Not every network does everything — ask `GET /social_capabilities`."
|
|
@@ -9,7 +9,7 @@ name = "planvortex"
|
|
|
9
9
|
# entonces `project.version` desapareceria del pyproject y el paso del workflow de release que
|
|
10
10
|
# compara el tag con la version —que lo lee con `tomllib`— dejaria de encontrarla. Se quedan las dos
|
|
11
11
|
# y las ata `tests/test_packaging.py`, que falla si se separan.
|
|
12
|
-
version = "0.
|
|
12
|
+
version = "0.5.0"
|
|
13
13
|
description = "Official Python client for the PlanVortex API"
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
requires-python = ">=3.10"
|
|
@@ -137,7 +137,7 @@ class AccountsSocialAuthorizationMethod(TypedDict):
|
|
|
137
137
|
"""
|
|
138
138
|
**How** an account of this network is authorized, which is not always "send the user to this URL".
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
Ten of the twelve networks are `redirect`: open `link` and the network sends the person back to PlanVortex with a code. Two are not:
|
|
141
141
|
|
|
142
142
|
• **WhatsApp.** Its sign-up is Meta's *Embedded Signup*: a popup raised by the Facebook JavaScript SDK from your own page, which returns — over `postMessage` — session data (`waba_id`, `phone_number_id`) that no query string carries. Its `link` is therefore an empty string.
|
|
143
143
|
• **Telegram.** There is no OAuth here: no consent screen, no `code`, no account token. `link` opens a private chat with the PlanVortex bot, the person then adds that bot to their channel, and **the account is created from that event**, not from any request of yours. Which means the connection cannot be finished by calling `GET /organizations/{id_organization}/account-connect/telegram` — see that endpoint.
|
|
@@ -855,6 +855,7 @@ CommentsCommentNetworkName: TypeAlias = Literal[
|
|
|
855
855
|
"bluesky",
|
|
856
856
|
"discord",
|
|
857
857
|
"telegram",
|
|
858
|
+
"threads",
|
|
858
859
|
]
|
|
859
860
|
"""
|
|
860
861
|
A network that has comments. Treat it as an open list: a new one is added before your integration hears about it.
|
|
@@ -1668,6 +1669,8 @@ class PublicationStats(TypedDict):
|
|
|
1668
1669
|
|
|
1669
1670
|
On `bluesky` there are no impressions and no reach either — only the public counters — so engagement is computed over followers.
|
|
1670
1671
|
|
|
1672
|
+
On `threads` there are six, and the one that matters is `views`: it is the only one of the four newest networks with something like impressions, so its engagement rate is computed over a real base and not over followers. Sharing arrives split in three — `reposts`, `quotes` and `shares` (outside Threads) — and the three are added into one figure, the same criterion as on X; the breakdown stays in `raw`. A reply is what every other network calls a comment.
|
|
1673
|
+
|
|
1671
1674
|
On `telegram` there are two as well, and **neither of them is asked for**: the Bot API has no method that returns a message's metrics, so `reactions` arrives on its own through the bot and `comments` is counted in PlanVortex's own inbox. There are no impressions, no reach, no views and no forwards to be had anywhere in it, so engagement is computed over followers.
|
|
1672
1675
|
"""
|
|
1673
1676
|
|
|
@@ -1758,6 +1761,7 @@ class PublicationsPublicationInput(TypedDict):
|
|
|
1758
1761
|
"bluesky",
|
|
1759
1762
|
"discord",
|
|
1760
1763
|
"telegram",
|
|
1764
|
+
"threads",
|
|
1761
1765
|
]
|
|
1762
1766
|
]
|
|
1763
1767
|
"""
|
|
@@ -1907,6 +1911,7 @@ SocialNetwork: TypeAlias = Literal[
|
|
|
1907
1911
|
"bluesky",
|
|
1908
1912
|
"discord",
|
|
1909
1913
|
"telegram",
|
|
1914
|
+
"threads",
|
|
1910
1915
|
]
|
|
1911
1916
|
"""
|
|
1912
1917
|
A social network supported by PlanVortex.
|
|
@@ -2175,7 +2180,7 @@ class CatalogPlannerTemplate(TypedDict):
|
|
|
2175
2180
|
class CatalogSocialLimits(TypedDict):
|
|
2176
2181
|
characters: CatalogSocialLimitsMap
|
|
2177
2182
|
"""
|
|
2178
|
-
Maximum length of a publication's text. Bluesky
|
|
2183
|
+
Maximum length of a publication's text. **Bluesky and Threads count GRAPHEMES**, everyone else counts characters — Telegram included, where `String.length` is exactly the right unit. The difference is not academic: an emoji is one grapheme and two `String.length` units, so counting a Threads post with `.length` rejects at 250 emojis what the network publishes happily at 500.
|
|
2179
2184
|
|
|
2180
2185
|
On Telegram there are **two numbers for the same field**: `telegram` (4.096) while the publication is text only, and `telegram_media` (1.024) the moment it carries an image or a video, because then the text is a media caption and not a message. Switch the counter when the file is attached, not when publish is pressed.
|
|
2181
2186
|
"""
|
|
@@ -137,7 +137,7 @@ class AccountUpdate(TypedDict):
|
|
|
137
137
|
|
|
138
138
|
|
|
139
139
|
class RedirectAuthorization(TypedDict):
|
|
140
|
-
"""Send the person to the entry's ``link``.
|
|
140
|
+
"""Send the person to the entry's ``link``. Ten of the twelve networks.
|
|
141
141
|
|
|
142
142
|
One of the THREE halves of the ``authorization`` block of a connection link. The generated type
|
|
143
143
|
is a single flat ``TypedDict`` with every field ``NotRequired``, because that is the only way
|
|
@@ -255,6 +255,7 @@ class EnableResult(TypedDict):
|
|
|
255
255
|
PublishableNetwork: TypeAlias = Literal[
|
|
256
256
|
"facebook",
|
|
257
257
|
"instagram",
|
|
258
|
+
"threads",
|
|
258
259
|
"twitter",
|
|
259
260
|
"linkedin",
|
|
260
261
|
"tiktok",
|
|
@@ -264,12 +265,12 @@ PublishableNetwork: TypeAlias = Literal[
|
|
|
264
265
|
"discord",
|
|
265
266
|
"telegram",
|
|
266
267
|
]
|
|
267
|
-
"""A network that accepts publications: the
|
|
268
|
+
"""A network that accepts publications: the eleven of the twelve that have a feed.
|
|
268
269
|
|
|
269
270
|
``google_business`` is the one missing, and it is not an oversight: a local listing receives
|
|
270
271
|
reviews, not posts. Sending it raises error 702. It is a **narrower** type than
|
|
271
272
|
``types.SocialNetwork`` and that is the whole point — an account's ``social_network`` is one of
|
|
272
|
-
|
|
273
|
+
twelve and this is one of eleven, so handing one straight to the other is a type error even when a
|
|
273
274
|
``capability="publications"`` filter has already made it impossible at runtime.
|
|
274
275
|
``types.is_publishable_network`` is what bridges it.
|
|
275
276
|
|
|
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import platform
|
|
14
14
|
|
|
15
|
-
VERSION = "0.
|
|
15
|
+
VERSION = "0.5.0"
|
|
16
16
|
|
|
17
17
|
# Sin barra final, y con el `/v1.0.0` dentro: la version de la API es parte de la ruta, no una
|
|
18
18
|
# cabecera, asi que una `base_url` sin ella da 404 en todo.
|
|
@@ -56,7 +56,7 @@ class AsyncAccountsResource(AsyncResource):
|
|
|
56
56
|
rather than a failure: it is what happens to Discord in an organization that has not saved
|
|
57
57
|
its own bot credentials yet.
|
|
58
58
|
|
|
59
|
-
**LOOK AT ``authorization``, NOT AT WHETHER ``link`` IS EMPTY.**
|
|
59
|
+
**LOOK AT ``authorization``, NOT AT WHETHER ``link`` IS EMPTY.** Ten of the twelve networks
|
|
60
60
|
are ``redirect`` and the person is sent to ``link``. The other two are not, and neither of them
|
|
61
61
|
fails visibly if you walk the list redirecting to ``link``:
|
|
62
62
|
|
|
@@ -49,7 +49,7 @@ class AccountsResource(Resource):
|
|
|
49
49
|
rather than a failure: it is what happens to Discord in an organization that has not saved
|
|
50
50
|
its own bot credentials yet.
|
|
51
51
|
|
|
52
|
-
**LOOK AT ``authorization``, NOT AT WHETHER ``link`` IS EMPTY.**
|
|
52
|
+
**LOOK AT ``authorization``, NOT AT WHETHER ``link`` IS EMPTY.** Ten of the twelve networks
|
|
53
53
|
are ``redirect`` and the person is sent to ``link``. The other two are not, and neither of them
|
|
54
54
|
fails visibly if you walk the list redirecting to ``link``:
|
|
55
55
|
|
|
@@ -502,7 +502,7 @@ for whoever is in Docker.
|
|
|
502
502
|
|
|
503
503
|
That ``datetime`` is why this is the one shape written by hand on top of the generated type: OpenAPI
|
|
504
504
|
has a ``string`` with ``format: date-time`` and no way to say "or the language's own date". Its
|
|
505
|
-
``social_network`` is :data:`PublishableNetwork`, which is **
|
|
505
|
+
``social_network`` is :data:`PublishableNetwork`, which is **eleven** networks and not twelve.
|
|
506
506
|
"""
|
|
507
507
|
|
|
508
508
|
PublicationErrorDetail: TypeAlias = _models.PublicationError
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|