feat(wordpress): nova skill mcp-bit-social - MCP dedicado BitSocial/BitSocial Pro
Cobre: contas sociais ligadas (+ activar/desactivar), schedules de auto-post e Share Now, logs de publicacao, resumo de falhas por plataforma, analytics, estado da licenca Pro. 12 tools, testado ponta-a-ponta em producao 19-08-2026. references/data-model.md com o schema completo wpah_bit_social_* e references/diagnostics.md com o playbook de investigacao de falhas (inclui o caso real recorrente do Pinterest sem imagem).
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
# BitSocial — modelo de dados (confirmado em produção, 2026-08-19)
|
||||
|
||||
Prefixo real de tabela em descomplicar.pt: `wpah_` (WP table prefix) + `bit_social_` (`Config::VAR_PREFIX`).
|
||||
Todas as tabelas usam `Schema::withPrefix(...)`, `timestamps()` standard (`created_at`/`updated_at`).
|
||||
|
||||
## Tabelas
|
||||
|
||||
### `wpah_bit_social_accounts` (4 linhas)
|
||||
|
||||
| Coluna | Tipo | Notas |
|
||||
|---|---|---|
|
||||
| `id` | bigint PK | |
|
||||
| `custom_app_id` | bigint, nullable, FK → `custom_apps.id` | null quando usa a app proxy da BitApps |
|
||||
| `profile_id` | string | ID do perfil/utilizador na plataforma |
|
||||
| `account_id` | string | ID da conta/página na plataforma (ex.: Facebook page id) |
|
||||
| `account_name` | string | |
|
||||
| `details` | longtext (JSON) | **NUNCA seleccionar** — contém `access_token` OAuth em claro, `icon`, `category`, etc. |
|
||||
| `platform` | string | `facebook`, `linkedin`, `pinterest`, `tumblr`, `instagram`, `twitter`, `discord`, `threads`, `tiktok`, `bluesky`, `telegram`, `googleBusinessProfile`, … |
|
||||
| `account_type` | int | `Account::accountType`: `DEFAULT=1` (app proxy BitApps), `CUSTOM=2` (app própria), `AI_PLATFORM=3` |
|
||||
| `status` | int (bool) | `1` activo, `0` inactivo |
|
||||
|
||||
### `wpah_bit_social_custom_apps` (1 linha)
|
||||
|
||||
| Coluna | Tipo | Notas |
|
||||
|---|---|---|
|
||||
| `id` | bigint PK | |
|
||||
| `name` | string | |
|
||||
| `platform` | string | |
|
||||
| `credential` | longtext | **NUNCA seleccionar** — segredo de app cifrado (base64 de payload AES) |
|
||||
| `status` | int (bool) | |
|
||||
|
||||
### `wpah_bit_social_schedules` (175 linhas)
|
||||
|
||||
Cobre **dois fluxos diferentes na mesma tabela**, distinguidos por `schedule_type`:
|
||||
|
||||
| Coluna | Tipo | Notas |
|
||||
|---|---|---|
|
||||
| `id` | bigint PK | |
|
||||
| `name` | string | Ex.: `"Auto Post - Post ID: 60261"` (auto-post) |
|
||||
| `config` | longtext (JSON) | Ver forma abaixo. Coluna aninhada — precisa de segundo `JSON.parse` no lado do cliente. |
|
||||
| `published_post_ids` | longtext (JSON, array de ints) | Posts já publicados por este schedule |
|
||||
| `repeat_schedule` | bool | |
|
||||
| `schedule_type` | int | `Schedule::scheduleType`: `SCHEDULE_SHARE=1` (auto-post recorrente/agendado), `DIRECT_SHARE=2` ("Share Now") |
|
||||
| `status` | int | `Schedule::status`: `INACTIVE=0`, `ACTIVE=1`, `COMPLETED=2`, `DRAFT=3`, `MISSED=4` |
|
||||
| `cron_status` | int | `INACTIVE=0`, `ACTIVE=1` |
|
||||
| `started_at`, `ended_at`, `last_published_at`, `next_published_at` | timestamp, nullable | |
|
||||
|
||||
Forma real de `config` (exemplo de um schedule de auto-post):
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": { "started_at": "2026-08-11 11:50:34" },
|
||||
"post_filters": { "post_type": "post", "specific_postIds": [60261] },
|
||||
"accounts": { "accountIds": [1, 4, 5, 6], "groupIds": [] },
|
||||
"templates": {
|
||||
"facebook": { "postingType": "isFeaturedImage", "content": "{post_title}", "trimMessage": false },
|
||||
"pinterest": { "postingType": "isLinkCard", "content": "{post_title}", "trimMessage": true, "isLinkCard": false },
|
||||
"...": "uma entrada por plataforma, mesma forma que bit_social_templates_settings"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `wpah_bit_social_logs` (1825 linhas)
|
||||
|
||||
Uma linha por tentativa de publicação (schedule × plataforma).
|
||||
|
||||
| Coluna | Tipo | Notas |
|
||||
|---|---|---|
|
||||
| `id` | bigint PK | |
|
||||
| `schedule_id` | bigint, nullable, FK → `schedules.id` | |
|
||||
| `details` | longtext (JSON) | Ver forma abaixo |
|
||||
| `platform` | string | |
|
||||
| `status` | int (bool) | `1` sucesso, `0` falha — confirmado em `AnalyticsController::index` (`Log::where('status', true)`) |
|
||||
|
||||
Forma de `details` em sucesso:
|
||||
|
||||
```json
|
||||
{
|
||||
"account_id": "164743196715367",
|
||||
"account_name": "Descomplicar - Agência de Aceleração Digital",
|
||||
"response": { "id": "164743196715367_122245726064096762", "post_supports_client_mutation_id": true },
|
||||
"post_id": 60261,
|
||||
"post_url": "https://fb.com/164743196715367_122245726064096762"
|
||||
}
|
||||
```
|
||||
|
||||
Forma de `details` em falha (o caso real mais comum, Pinterest):
|
||||
|
||||
```json
|
||||
{
|
||||
"account_id": "1051238806710440771",
|
||||
"account_name": "Blog Descomplicar",
|
||||
"post_id": 60261,
|
||||
"response": { "status": "error", "error_msg": "Must have image for create pin in Pinterest." },
|
||||
"post_url": null
|
||||
}
|
||||
```
|
||||
|
||||
O erro está sempre em `details.response.error_msg` nos casos observados; `extractErrorMessage()`
|
||||
no MCP também tenta `response.message`, `error`, `message` como fallback para plataformas com
|
||||
forma diferente.
|
||||
|
||||
### `wpah_bit_social_groups` / `wpah_bit_social_groups_accounts` (Pro, 0 linhas em produção)
|
||||
|
||||
`groups`: `id`, `name`, `status`. `groups_accounts`: pivot `group_id` × `account_id`, cascade on
|
||||
delete. Um grupo pode substituir uma lista explícita de `accountIds` num `config.accounts.groupIds`.
|
||||
|
||||
## `wp_options` relevantes
|
||||
|
||||
| Chave | Conteúdo |
|
||||
|---|---|
|
||||
| `bit_social_version` / `bit_social_pro_version` | string, ex. `"1.16.0"` |
|
||||
| `bit_social_db_version` / `bit_social_pro_db_version` | string |
|
||||
| `bit_social_installed` / `bit_social_pro_installed` | bool |
|
||||
| `bit_social_auto_post_settings` | `{isEnabled, keepLogs, taxonomies[], accounts:{accountIds[],groupIds[]}, postType[], postDelay:{every,unit}}` |
|
||||
| `bit_social_templates_settings` | um objecto por plataforma, mesma forma que `config.templates` num schedule |
|
||||
| `bit_social_pro_settings` | `{cron:{isExternalCronEnabled}}` |
|
||||
| `bit_social_pro_license_data` | `{key, status, expireIn}` — `key` **nunca** deve ser devolvida sem máscara |
|
||||
| `bit_social_secret_key` | segredo interno do plugin — nunca ler/expor |
|
||||
|
||||
## Análogo AnalyticsController (referência, não reimplementação 1:1)
|
||||
|
||||
`AnalyticsController::index` só calcula `active_account_count`, `published_post_count` (logs com
|
||||
`status=true`) e `active_schedule_count`. `bs_get_analytics` estende com `failed_post_count`
|
||||
(logs com `status=0`) porque o controller original omite essa contagem apesar de ser a métrica
|
||||
mais accionável.
|
||||
@@ -0,0 +1,54 @@
|
||||
# BitSocial — playbook de diagnóstico
|
||||
|
||||
## Investigar porque uma publicação falhou
|
||||
|
||||
1. Encontrar o schedule pelo nome do post (`bs_list_schedules({search: "Post ID: 60261"})`) ou
|
||||
pelo id se já conhecido.
|
||||
2. `bs_get_schedule({schedule_id})` — confirmar `status` (`completed`/`missed`/…), `config.accounts`
|
||||
(que contas estavam alvo) e `config.templates` (que tipo de conteúdo cada plataforma ia usar,
|
||||
ex. `postingType: "isLinkCard"` para Pinterest).
|
||||
3. `bs_list_logs({schedule_id})` — uma linha por plataforma tentada; `status: 0` é falha,
|
||||
`details.response.error_msg` tem a razão devolvida pela API da plataforma.
|
||||
4. Cruzar com `bs_list_accounts({platform})` para confirmar que a conta ainda está `active` — uma
|
||||
conta desligada não é a causa de uma falha já registada em log (o log só existe se a tentativa
|
||||
chegou a correr), mas explica ausência de tentativa nenhuma.
|
||||
|
||||
## Falha recorrente conhecida: Pinterest sem imagem
|
||||
|
||||
`"Must have image for create pin in Pinterest."` é o erro mais comum em produção (39 ocorrências
|
||||
em 60 dias, confirmado com `bs_get_failure_summary({days: 60})`). Causa: o `postingType` do
|
||||
template Pinterest (`bs_get_social_templates`) está como `isLinkCard`
|
||||
(`"isLinkCard": false` no exemplo real — nome do campo enganador, o efectivo é o `postingType`)
|
||||
mas o post de origem não tem imagem destacada elegível para a Pinterest API. Não é um bug do
|
||||
MCP nem do plugin — é um requisito da API do Pinterest (todo pin precisa de imagem). Diagnóstico
|
||||
correcto: confirmar se o post WordPress em causa tem featured image antes de o auto-post disparar;
|
||||
o MCP não cobre a correcção (isso é conteúdo/publicação, fora do âmbito de `mcp-bit-social`).
|
||||
|
||||
## Ver o que está configurado para disparar auto-post
|
||||
|
||||
`bs_get_auto_post_settings` — confirma `isEnabled`, que `postType`s (ex.: `["post", "podcast"]`) e
|
||||
taxonomias disparam publicação automática, atraso (`postDelay`) e que contas/grupos são alvo por
|
||||
omissão. Cruzar com `bs_get_social_templates` para ver o formato de conteúdo por plataforma.
|
||||
|
||||
## Pausar uma conta sem a desligar (perder OAuth)
|
||||
|
||||
`bs_set_account_status({account_id, status: "inactive"})` — sai da rotação de auto-post e "Share
|
||||
Now" imediatamente (mirror de `AccountController::updateStatus`), sem apagar a ligação OAuth
|
||||
(`accounts.details` continua intacto). Reactivar com o mesmo tool e `status: "active"`.
|
||||
Não confundir com apagar a conta (não coberto por este MCP — fazer via wp-admin se necessário,
|
||||
apagar tem cascade sobre `groups_accounts` e pode invalidar schedules já criados que a referenciam).
|
||||
|
||||
## Saúde geral rápida
|
||||
|
||||
`bs_get_plugin_status` (versões/licença) + `bs_get_analytics` (contas activas, schedules activos,
|
||||
publicações OK/falhadas) — dois tools, sem argumentos, para um snapshot inicial antes de investigar
|
||||
mais fundo.
|
||||
|
||||
## Fora do âmbito deste MCP (fazer via wp-admin)
|
||||
|
||||
- Repetir uma publicação falhada (`RetryController` faz um pedido HTTP real à plataforma social —
|
||||
lógica de negócio, não um `UPDATE` SQL).
|
||||
- Mudar o estado de um schedule directamente (`ScheduleController::updateStatus` tem checks
|
||||
condicionais de `COMPLETED`/`repeat` que não são um simples `UPDATE status=…`).
|
||||
- Ligar uma conta social nova (fluxo OAuth) ou criar uma custom app.
|
||||
- Editar o conteúdo/template de publicação por plataforma.
|
||||
Reference in New Issue
Block a user