Publicador de Artigos X
Publique conteúdo Markdown no editor de Artigos do X (Twitter), preservando a formatação com conversão para rich text.
Pré-requisitos
- Playwright MCP para automação de navegador
- Usuário logado no X com assinatura Premium Plus
- Python 3.9+ com dependências:
- macOS:
pip install Pillow pyobjc-framework-Cocoa - Windows:
pip install Pillow pywin32 clip-util
- macOS:
- Para diagramas Mermaid:
npm install -g @mermaid-js/mermaid-cli
Scripts
Localizados em ~/.claude/skills/x-article-publisher/scripts/:
parse_markdown.py
Analisar Markdown e extrair dados estruturados:
python parse_markdown.py <arquivo_markdown> [--output json|html] [--html-only]
Retorna JSON com: title, cover_image, content_images, dividers (com block_index para posicionamento), html, total_blocks
copy_to_clipboard.py
Copiar imagem ou HTML para a área de transferência do sistema (multiplataforma):
# Copiar imagem (com compressão opcional)
python copy_to_clipboard.py image /caminho/para/imagem.jpg [--quality 80]
# Copiar HTML para colar rich text
python copy_to_clipboard.py html --file /caminho/para/conteudo.html
table_to_image.py
Converter tabela Markdown para imagem PNG:
python table_to_image.py <entrada.md> <saida.png> [--scale 2]
Use quando o editor de Artigos do X não suportar renderização nativa de tabelas ou para estilização consistente.
Pré-Processamento (Opcional)
Antes de publicar, verifique o Markdown em busca de elementos que precisam de conversão:
Tabelas → PNG
# Extrair tabela para arquivo temporário e depois converter
python ~/.claude/skills/x-article-publisher/scripts/table_to_image.py /tmp/tabela.md /tmp/tabela.png
# Substituir tabela no markdown por: 
Diagramas Mermaid → PNG
# Extrair bloco mermaid para arquivo .mmd, depois converter
mmdc -i /tmp/diagrama.mmd -o /tmp/diagrama.png -b white -s 2
# Substituir bloco mermaid por: 
Divisores (---)
Os divisores são detectados automaticamente pelo parse_markdown.py e exibidos no array dividers. Eles devem ser inseridos através do menu Inserir > Divisor do editor de Artigos do X (as tags HTML <hr> são ignoradas pelo X).
Fluxo de Trabalho
Estratégia: "Texto Primeiro, Imagens Depois, Divisores por Último"
Para artigos com imagens e divisores, cole TODO o conteúdo de texto primeiro, depois insira imagens e divisores nas posições corretas usando o índice de blocos.
- (Opcional) Pré-processar: Converter tabelas/mermaid para imagens
- Analisar Markdown com script Python → obter título, imagens, divisores com block_index, HTML
- Navegar para o editor de Artigos do X
- Fazer upload da imagem de capa (primeira imagem)
- Preencher título
- Copiar HTML para a área de transferência (Python) → Colar com Cmd+V
- Inserir imagens de conteúdo nas posições especificadas por block_index
- Inserir divisores nas posições especificadas por block_index (via menu Inserir > Divisor)
- Salvar como rascunho (NUNCA publicar automaticamente)
Princípios de Execução Eficiente
Objetivo: Minimizar o tempo de espera entre operações para uma experiência de automação fluida.
1. Evitar browser_snapshot desnecessário
A maioria das operações do navegador (click, type, press_key, etc.) já inclui o estado da página no resultado. Não chame browser_snapshot separadamente após cada operação; use diretamente o estado da página retornado pela operação.
❌ Incorreto:
browser_click → browser_snapshot → análise → browser_click → browser_snapshot → ...
✅ Correto:
browser_click → obter estado da página do resultado → browser_click → ...
2. Evitar browser_wait_for desnecessário
Use browser_wait_for apenas nestes casos:
- Aguardar upload de imagem ser concluído (
textGone="Fazendo upload da mídia") - Aguardar carregamento inicial da página (raro)
Não use browser_wait_for para aguardar o aparecimento de botões ou campos de entrada – eles estão disponíveis imediatamente após o carregamento da página.
3. Executar operações independentes em paralelo
Quando duas operações não têm dependência, você pode chamar várias ferramentas em paralelo na mesma mensagem:
✅ Pode ser paralelo:
- Preencher título (browser_type) + Copiar HTML para a área de transferência (Bash)
- Analisar Markdown para gerar JSON + Gerar arquivo HTML
❌ Não pode ser paralelo (tem dependência):
- É preciso clicar em create primeiro para fazer upload da imagem de capa
- É preciso colar o conteúdo primeiro para inserir imagens
4. Executar operações do navegador em sequência
Cada operação do navegador retorna um estado da página com todas as referências de elementos necessárias. Use essas referências diretamente para a próxima operação:
# Fluxo ideal (cada etapa executa diretamente, sem esperas extras):
browser_navigate → a partir do estado retornado, encontrar botão create → browser_click(create)
→ a partir do estado retornado, encontrar botão de upload → browser_click(upload) → browser_file_upload
→ a partir do estado retornado, encontrar botão de aplicar → browser_click(aplicar)
→ a partir do estado retornado, encontrar campo de título → browser_type(título)
→ clicar no editor → browser_press_key(Meta+v)
→ ...
5. Preparação antecipada
Antes de iniciar as operações do navegador, conclua toda a preparação:
- Analisar Markdown para obter dados JSON
- Gerar arquivo HTML em /tmp/
- Registrar informações de title, cover_image, content_images, etc.
Dessa forma, a fase de operações do navegador pode ser executada continuamente, sem precisar parar para processar dados.
Etapa 1: Analisar Markdown (Python)
Use parse_markdown.py para extrair todos os dados estruturados:
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /caminho/para/artigo.md
Saída JSON:
{
"title": "Título do Artigo",
"cover_image": "/caminho/para/primeira-imagem.jpg",
"cover_exists": true,
"content_images": [
{"path": "/caminho/para/img2.jpg", "original_path": "/md/dir/assets/img2.jpg", "exists": true, "block_index": 5, "after_text": "contexto..."},
{"path": "/caminho/para/img3.jpg", "original_path": "/md/dir/assets/img3.jpg", "exists": true, "block_index": 12, "after_text": "outro..."}
],
"html": "<p>Conteúdo...</p><h2>Seção</h2>...",
"total_blocks": 45,
"missing_images": 0
}
Campos principais:
block_index: A imagem deve ser inserida APÓS o elemento de bloco neste índice (baseado em 0)total_blocks: Número total de elementos de bloco no HTMLafter_text: Mantido para referência/depuração apenas, NÃO para posicionamentoexists: Se o arquivo de imagem foi encontrado (se false, o upload falhará)original_path: O caminho resolvido do Markdown (antes da busca automática)path: O caminho real a ser usado (pode diferir de original_path se a busca automática foi usada)missing_images: Contagem de imagens não encontradas em lugar nenhum
Salve o HTML em arquivo temporário para a área de transferência:
python parse_markdown.py artigo.md --html-only > /tmp/artigo_html.html
Etapa 2: Abrir o Editor de Artigos do X
Tratamento de erros do navegador
Se encontrar o erro Error: Browser is already in use:
# Opção 1: Fechar o navegador e reabrir
browser_close
browser_navigate: https://x.com/compose/articles
# Opção 2: Se browser_close não funcionar (travado), solicitar que o usuário feche o Chrome manualmente
# Opção 3: Usar uma aba existente e navegar diretamente
browser_tabs action=list # Ver abas existentes
browser_navigate: https://x.com/compose/articles # Navegar na aba atual
Melhor prática: Antes de começar, use browser_tabs action=list para verificar o estado, evitando criar abas em branco desnecessárias.
Navegar para o editor
browser_navigate: https://x.com/compose/articles
Importante: Após o carregamento da página, a lista de rascunhos será exibida, não o editor. É necessário:
- Aguardar o carregamento da página: Use
browser_snapshotpara verificar o estado - Clicar imediatamente no botão "create": Não espere por elementos do editor como "Adicionar título", eles só aparecem após clicar em create
- Aguardar o carregamento do editor: Após clicar em create, aguarde o aparecimento dos elementos do editor
# 1. Navegar para a página
browser_navigate: https://x.com/compose/articles
# 2. Obter snapshot da página, encontrar o botão create
browser_snapshot
# 3. Clicar no botão create (geralmente ref é algo como "create" ou com a tag create)
browser_click: element="botão create", ref=<ref_botao_create>
# 4. Agora o editor deve estar aberto, pode continuar com upload da imagem de capa, etc.
Nota: Não use browser_wait_for text="Adicionar título" para aguardar o carregamento da página, pois este texto só aparece após clicar em create, o que causará timeout.
Se for necessário login, solicite que o usuário faça login manualmente.
Etapa 3: Fazer Upload da Imagem de Capa
- Clique no botão "Adicionar foto ou vídeo"
- Use browser_file_upload com o caminho da imagem de capa (da saída JSON)
- Verifique se a imagem foi carregada
Etapa 4: Preencher Título
- Encontre a caixa de texto com o placeholder "Adicionar título"
- Use browser_type para inserir o título (da saída JSON)
Etapa 5: Colar Conteúdo de Texto (Área de Transferência Python)
Copie o HTML para a área de transferência do sistema usando Python e depois cole:
# Copiar HTML para a área de transferência
python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/artigo_html.html
Depois, no navegador:
browser_click na caixa de texto do editor
browser_press_key: Meta+v
Isso preserva toda a formatação rich text (H2, negrito, links, listas).
Etapa 6: Inserir Imagens de Conteúdo (Posicionamento por Busca de Texto)
Método recomendado: Use a busca pelo texto after_text para posicionamento, mais intuitivo e confiável que block_index.
Princípio de Posicionamento
O campo after_text de cada imagem registra o texto final do parágrafo anterior (máximo 80 caracteres). No editor, procure o parágrafo que contém esse texto e clique para inserir a imagem.
Passos
Para cada imagem de conteúdo (do array content_images), na ordem decrescente de block_index:
# 1. Copiar imagem para a área de transferência (com compressão)
python ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py image /caminho/para/img.jpg --quality 85
# 2. No browser_snapshot, procure o parágrafo que contém after_text
# Encontre a ref desse parágrafo
# 3. Clique no parágrafo que contém after_text
browser_click: element="parágrafo com texto alvo", ref=<ref_paragrafo>
# 4. **Passo crucial**: Pressione End para mover o cursor para o final da linha
# Muito importante! Evita clicar em links dentro do parágrafo causando deslocamento de posição
browser_press_key: End
# 5. Colar imagem
browser_press_key: Meta+v
# 6. Aguardar upload (use apenas textGone, sem parâmetro de tempo)
browser_wait_for textGone="Fazendo upload da mídia"
Por que pressionar End?
Problema: Quando o parágrafo contém um link (como [texto do link](https://raw.githubusercontent.com/wshuyi/x-article-publisher-skill/main/url)), clicar no parágrafo pode:
- Abrir um popup de edição de link
- Posicionar o cursor dentro do link, em vez de no final do parágrafo
Solução: Após clicar no parágrafo, pressione imediatamente End:
- Garante que o cursor vá para o final do parágrafo
- Evita interferência de links
- A imagem será inserida corretamente após o parágrafo
Estratégia de Posicionamento
Na estrutura retornada pelo browser_snapshot, pesquise pela palavra-chave de after_text:
textbox [ref=editor]:
generic [ref=p1]:
- StaticText: "Nas férias de Ano Novo eu estava no celular vendo fotos..." # Se after_text contém este texto, clique em p1
heading [ref=h1]:
- StaticText: "Demonstração"
generic [ref=p2]:
- StaticText: "Quão fácil é isso?"
- link [ref=link1]: "Claude Code" # Nota: o parágrafo pode conter links
...
Exemplo de Inserção Reversa
Se houver 3 imagens, com block_index 5, 12, 27:
- Primeiro insira a imagem com block_index=27 (busca por after_text + End + colar)
- Depois insira a imagem com block_index=12
- Por último, insira a imagem com block_index=5
Inserir do maior para o menor evita problemas de deslocamento de posição.
Etapa 6.5: Inserir Divisores (Via Menu)
Importante: Os divisores --- do Markdown não podem ser colados via tag HTML <hr> (o X Articles ignora). Eles devem ser inseridos através do menu Inserir do X Articles.
Passos
Para cada divisor (do array dividers), na ordem inversa de block_index:
# 1. Clique no elemento de bloco na posição block_index
browser_click no elemento na posição block_index no editor
# 2. Abrir o menu Inserir (botão Adicionar Mídia)
browser_click no botão "Inserir" ou "Adicionar mídia"
# 3. Clicar no item de menu Divisor
browser_click no item de menu "Divisor" ou "Divider"
# O divisor é inserido na posição do cursor
Ordem de inserção com imagens
Recomenda-se inserir todas as imagens primeiro e depois todos os divisores. Ambos na ordem decrescente de block_index:
- Inserir todas as imagens (do maior block_index para o menor)
- Inserir todos os divisores (do maior block_index para o menor)
Etapa 7: Salvar Rascunho
- Verifique se o conteúdo foi colado (confira o indicador de contagem de palavras)
- O rascunho salva automaticamente, ou clique no botão Salvar se necessário
- Clique em "Visualizar" para verificar a formatação
- Relate: "Rascunho salvo. Revise e publique manualmente."
Regras Críticas
- NUNCA publique - Apenas salve como rascunho
- Primeira imagem = capa - Faça upload da primeira imagem como imagem de capa
- Conversão para rich text - Sempre converta Markdown para HTML antes de colar
- Use a API de área de transferência - Cole via área de transferência para formatação adequada
- Posicionamento por índice de bloco - Use block_index para posicionamento preciso de imagens e divisores
- Inserção em ordem reversa - Insira imagens e divisores do maior para o menor block_index
- Tratamento de H1 - H1 é usado apenas como título, não incluído no corpo
- Divisores via menu -
---do Markdown deve ser inserido via menu Inserir > Divisor (HTML<hr>é ignorado)
Formatação Suportada
| Elemento | Suporte | Notas |
|---|---|---|
H2 (##) | Nativo | Cabeçalhos de seção |
Negrito (**) | Nativo | Ênfase forte |
Itálico (*) | Nativo | Ênfase |
Links ([](https://raw.githubusercontent.com/wshuyi/x-article-publisher-skill/main/url)) | Nativo | Hiperlinks |
| Listas ordenadas | Nativo | 1. 2. 3. |
| Listas não ordenadas | Nativo | - marcadores |
Blockquotes (>) | Nativo | Texto citado |
| Blocos de código | Convertido | → Blockquotes |
| Tabelas | Convertido | → Imagens PNG (use table_to_image.py) |
| Mermaid | Convertido | → Imagens PNG (use mmdc) |
Divisores (---) | Inserção via menu | → Inserir > Divisor |
Fluxo de Exemplo
Usuário: "Publique /caminho/para/artigo.md no X"
# Etapa 1: Analisar Markdown
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /caminho/para/artigo.md > /tmp/artigo.json
python ~/.claude/skills/x-article-publisher/scripts/parse_markdown.py /caminho/para/artigo.md --html-only > /tmp/artigo_html.html
- Navegar para https://x.com/compose/articles
- Fazer upload da imagem de capa (browser_file_upload apenas para a capa)
- Preencher título (do JSON:
title) - Copiar e colar HTML:
Depois: browser_press_key Meta+vpython ~/.claude/skills/x-article-publisher/scripts/copy_to_clipboard.py html --file /tmp/artigo_html.html - Para cada imagem de conteúdo, na ordem inversa de block_index:
python copy_to_clipboard.py image /caminho/para/img.jpg --quality 85- Clique no elemento de bloco na posição
block_index - browser_press_key Meta+v
- Aguarde até o upload ser concluído
- Clique no elemento de bloco na posição
- Verificar na visualização
- "Rascunho salvo. Revise e publique manualmente."
Melhores Práticas
Por que usar block_index em vez de correspondência de texto?
- Posicionamento preciso: Não depende do conteúdo do texto, mesmo que textos semelhantes apareçam em vários lugares, a posição é correta
- Confiabilidade: O índice é determinístico, não há confusão devido a textos semelhantes
- Depuração facilitada:
after_textainda é mantido para verificação manual
Por que usar Python em vez de JavaScript no navegador?
- Processamento local mais confiável: O Python opera diretamente na área de transferência do sistema, sem as restrições de sandbox do navegador
- Compressão de imagem: Comprimir imagens antes do upload (--quality 85), reduzindo o tempo de upload
- Reutilização de código: Os scripts são fixos, não sendo necessário reescrever a lógica de conversão a cada vez
- Depuração facilitada: Os scripts podem ser testados separadamente, problemas são fáceis de localizar
Estratégia de Espera
Descoberta importante: O comportamento real do browser_wait_for do Playwright MCP é primeiro aguardar time segundos, depois verificar a condição, não fazer polling!
// Código real executado:
await new Promise(f => setTimeout(f, time * 1000)); // Espera fixa primeiro
await page.getByText("xxx").waitFor({ state: 'hidden' }); // Depois verifica
Uso correto:
- ✅ Apenas
textGone, semtime: Deixa o Playwright fazer polling sozinho - ✅ Apenas
time: Espera fixa pelos segundos especificados - ❌
textGone+timejuntos: Espera time segundos primeiro e depois verifica, perdendo tempo
# Recomendado: apenas textGone, deixa esperar automaticamente a condição ser atendida
browser_wait_for textGone="Fazendo upload da mídia"
# Ou: use browser_snapshot para verificar o estado via polling
# Após cada operação, verifique o estado da página retornado, sem espera extra
Eficiência na Inserção de Imagens
As operações no navegador para cada imagem foram reduzidas de 5 para 2 etapas:
- Antigo: Clicar → Adicionar mídia → Mídia → Adicionar foto → file_upload
- Novo: Clicar no parágrafo → Meta+v
Imagem de Capa vs. Imagens de Conteúdo
- Imagem de capa: Use browser_file_upload (porque há um botão de upload específico)
- Imagens de conteúdo: Use a área de transferência Python + colar (mais eficiente)
Solução de Problemas
Problemas de Conexão MCP
Se a ferramenta Playwright MCP não estiver disponível (erro No such tool available ou Not connected):
Opção 1: Reconectar o MCP (recomendado)
Execute o comando /mcp, selecione playwright e escolha Restart
Opção 2: Limpar processos residuais e reconectar
# Mate todos os processos playwright residuais
pkill -f "mcp-server-playwright"
pkill -f "@playwright/mcp"
# Depois execute /mcp para reconectar
Localização do arquivo de configuração: ~/.claude/mcp_servers.json
Tratamento de Erros do Navegador
Se encontrar o erro Error: Browser is already in use:
# Opção 1: Fechar o navegador e reabrir
browser_close
browser_navigate: https://x.com/compose/articles
# Opção 2: Matar o processo Chrome
pkill -f "Chrome.*--remote-debugging"
# Depois navegar novamente
Deslocamento de Posição de Imagem
Se a imagem for inserida na posição errada (especialmente ao clicar em parágrafos com links):
Causa: Clicar no parágrafo pode ativar o link acidentalmente, fazendo o cursor ficar na posição errada
Solução: Após clicar, é obrigatório pressionar End para mover o cursor para o final da linha
# Fluxo correto
1. browser_click no parágrafo alvo
2. browser_press_key: End # Passo crucial!
3. browser_press_key: Meta+v # Colar imagem
4. browser_wait_for textGone="Fazendo upload da mídia"
Caminho de Imagem Não Encontrado
Se as imagens com caminho relativo no Markdown não forem encontradas (por exemplo, ./assets/image.png está em outro local):
Busca automática: O script parse_markdown.py pesquisa em diretórios comuns. Se ainda assim não encontrar, você pode:
- Verificar manualmente o local do arquivo e fornecer o caminho absoluto
- Mover a imagem para o diretório do artigo
- Usar uma imagem placeholder ou remover a referência
Notas Finais
- Sempre teste com um artigo curto primeiro antes de publicar conteúdo longo
- Mantenha os scripts Python atualizados conforme as mudanças na interface do X
- Para artigos com muitas imagens, considere dividir a publicação em sessões para evitar timeouts

