Communitygithub.com

Habilidade de Postar no X (Twitter)

Publica textos e imagens no X (Twitter) via Modo de Uso de Computador do Chrome ou API oficial.

O que é Habilidade de Postar no X (Twitter)?

Esta habilidade permite que agentes de IA publiquem textos e imagens no X (Twitter) de forma automatizada, seguindo um modelo de execução híbrida. Primeiro, tenta usar a API oficial do X para postagens de texto, que é rápida e estável. Se a API falhar (por excesso de cota, limitação de taxa ou falta de credenciais), a habilidade cai automaticamente no Modo de Uso de Computador do Chrome, simulando ações do navegador. Ela também suporta a exclusão de tweets e a exportação de tweets do perfil do usuário. Para ambientes sem ferramentas de teclado/mouse do Computer Use, oferece um modo 'CLI Bridge' que usa AppleScript (macOS), scripts do PowerShell (Windows) ou CDP (Linux) para controlar o Chrome e injetar JavaScript. A habilidade inclui diretrizes detalhadas de automação DOM para lidar com o editor Draft.js do React do X, garantindo que a estrutura do editor não seja corrompida. A segurança é priorizada: nunca publica um tweet sem confirmação explícita do usuário.

Funciona com✓Claude Code~Codex CLI~Cursor✓Antigravity
npx skills add hankunpeng/hkp-skills

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Habilidade para Twitter

Publica textos e imagens no X (Twitter) via Modo de Uso de Computador do Chrome.

Diretório de Scripts

Importante: Todos os scripts estão localizados no subdiretório scripts/ desta habilidade.

Instruções de Execução para o Agente:

  1. Determine o caminho do diretório deste arquivo SKILL.md como {baseDir}
  2. Caminhos dos scripts:
    • Auxiliar de área de transferência: {baseDir}/scripts/copy-to-clipboard.ts
    • Exportador/raspador de tweets: {baseDir}/scripts/export-tweets.ts
    • Auxiliar da API oficial do X: {baseDir}/scripts/x-api.ts
  3. Resolva o runtime ${BUN_X}: se bun estiver instalado → bun; se npx estiver disponível → npx -y bun; caso contrário, sugira instalar o bun
  4. Substitua todas as ocorrências de {baseDir} e ${BUN_X} neste documento pelos valores reais

Modo de Execução

Esta habilidade segue um Modelo de Execução Híbrida:

  1. API Primeiro (Recomendado para postagens de texto): Tente publicar o tweet usando o script da API oficial do X (x-api.ts). Isso é rápido, estável e não usa recursos do navegador.
  2. Fallback para Navegador: Se a chamada de API falhar (por exemplo, cota excedida do nível Gratuito de 1500 tweets/mês, limitação de taxa) ou as credenciais da API não estiverem configuradas em subscription.yaml, faça o fallback automaticamente para o Modo de Uso de Computador do Chrome / CLI Bridge para simular ações do navegador.

Pré-requisitos

  • Para Modo API: Configure suas credenciais da API do X (Tokens de Contexto de Usuário OAuth 1.0a) no arquivo local subscription.yaml (que é ignorado pelo git):
    x_api:
      api_key: "YOUR_API_KEY"
      api_key_secret: "YOUR_API_KEY_SECRET"
      access_token: "YOUR_ACCESS_TOKEN"
      access_token_secret: "YOUR_ACCESS_TOKEN_SECRET"
    
  • Para Fallback do Navegador: Google Chrome instalado, logado no X (Twitter) no Chrome e permissões de acessibilidade do macOS concedidas, se necessário.

Fluxo de Trabalho para Postagens Regulares (Texto e Imagens)

Ao executar uma postagem:

  1. Inicie a rodada do agente chamando get_app_state (ou ferramenta equivalente) para Google Chrome.
  2. Abra ou navegue no Google Chrome para https://x.com/compose/post.
  3. Localize a caixa de entrada do compositor de tweet.
  4. Digite o texto da postagem no compositor usando entradas de teclado do Computer Use.
  5. Se houver imagens para anexar (máx. 4): Para cada imagem: a. Execute o script auxiliar de área de transferência para copiar a imagem para a área de transferência:
    ${BUN_X} {baseDir}/scripts/copy-to-clipboard.ts image /absolute/path/to/image.png
    
    b. Cole a imagem no compositor usando o atalho de colar (super+v no macOS, control+v no Windows/Linux). c. Aguarde 2-3 segundos até o X terminar o upload da mídia.
  6. Segurança de Publicação: Nunca clique em Publicar, Postar ou qualquer botão equivalente para publicar o tweet sem obter confirmação final explícita do usuário na conversa atual.
  7. Assim que o usuário confirmar, clique no botão Postar para publicar.
  8. Após publicar, feche o modal do compositor para que a interface não fique presa na caixa de diálogo de composição. Use o botão de fechar ou Escape:
    • Seletor DOM: [data-testid="app-bar-close"] ou [aria-label="Close"]
    • Fallback: dispare um evento de tecla Escape
  9. Recarregar Feed Automaticamente (Opcional): Se o usuário tiver outras abas abertas para seu perfil (por exemplo, x.com/[username]) ou feed inicial (x.com/home), recarregue-as para que o novo tweet fique visível imediatamente.
var closeBtn = document.querySelector('[data-testid="app-bar-close"], [aria-label="Close"]');
if (closeBtn) { closeBtn.click(); }
else { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true })); }

CLI Bridge (Sem Ferramentas de Computer Use)

Quando o ambiente não possui ferramentas de teclado/mouse do Computer Use, use métodos específicos da plataforma para abrir o Chrome e injetar JavaScript na página.

macOS (AppleScript)

Abrir página de composição:

open -a "Google Chrome" "https://x.com/compose/post"

Executar JavaScript no Chrome — escreva o JS em um arquivo temporário primeiro (evita problemas de escape de shell) e depois execute via AppleScript:

cat > /tmp/tweet.js << 'EOF'
(function() { /* seu código */ })();
EOF

osascript -e '
tell application "Google Chrome"
    activate
    set js to read "/tmp/tweet.js"
    set result to execute front window'"'"'s active tab javascript js
    return result
end tell'

Padrão: Sempre escreva o payload JS em um arquivo temporário primeiro. NÃO tente osascript -e embutido com JS inline — conflitos de citação/escape causarão erros de análise.

Fechar compositor após publicação (senão o modal permanece aberto):

var closeBtn = document.querySelector('[data-testid="app-bar-close"], [aria-label="Close"]');
if (closeBtn) { closeBtn.click(); }
else { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true })); }

Recarregar abas de perfil/início para refletir a nova postagem imediatamente (Opcional):

osascript -e '
tell application "Google Chrome"
    tell window 1
        set tabList to every tab whose URL contains "x.com/hankunpeng" or URL contains "x.com/home"
        repeat with t in tabList
            reload t
        end repeat
    end tell
end tell'

Linux

google-chrome "https://x.com/compose/post"
# Use chrome-remote-interface ou cliente CDP similar para executar JS

Windows

Start-Process "chrome" "https://x.com/compose/post"
# Use CDP via --remote-debugging-port ou automação PowerShell

Diretrizes de Automação JavaScript / DOM (Fallback)

Se eventos diretos de teclado/mouse do Computer Use não estiverem disponíveis, ou se você estiver executando em modos de script de navegador/CDP, siga estas diretrizes para interagir com o editor Draft.js/React do X:

  1. Detecção de Múltiplos Editores: As páginas do X geralmente contêm várias áreas de texto de composição (por exemplo, compositor inline no feed inicial e modal ativo). Sempre consulte todos os editores:

    var els = document.querySelectorAll('[data-testid="tweetTextarea_0"]');
    var el = els.length > 1 ? els[els.length - 1] : els[0];
    

    Sempre segmente o compositor modal ativo (geralmente o último elemento na lista).

  2. Vinculação de Seleção e Foco: Antes de inserir texto, você DEVE clicar no elemento para acionar a vinculação de seleção do Draft.js e, em seguida, focar:

    el.click();
    el.focus();
    
  3. Preservação da Estrutura do Editor:

    • NÃO use el.innerHTML = '' ou document.execCommand('delete') em um compositor vazio. Limpar os nós DOM destrói a estrutura interna do wrapper do Draft.js (por exemplo, o span public-DraftStyleDefault-block), o que quebra o componente React e deixa o botão Publicar permanentemente desabilitado.
    • Simplesmente use document.execCommand('insertText', false, text) diretamente no editor vazio focado.
  4. Acionando Atualizações de Estado do React: Após a inserção de texto, dispare um evento input com propagação para notificar o React:

    el.dispatchEvent(new Event('input', { bubbles: true }));
    
  5. Localizando o Botão de Postagem Correto: Os testids dos botões (tweetButtonInline e tweetButton) podem ser trocados dependendo do contexto. Sempre procure pelo botão visível e habilitado:

    var btns = document.querySelectorAll('[data-testid="tweetButtonInline"], [data-testid="tweetButton"]');
    var activeBtn = Array.from(btns).find(function(btn) {
        var isVisible = btn.offsetWidth > 0 && btn.offsetHeight > 0;
        var isDisabled = btn.disabled || btn.getAttribute('aria-disabled') === 'true';
        return isVisible && !isDisabled;
    });
     if (activeBtn) activeBtn.click();
    

Fluxo de Exclusão de Tweet

Ao executar uma exclusão:

  1. Abra ou navegue no Google Chrome para a página de perfil do usuário (https://x.com/[username]) ou o URL direto do tweet (https://x.com/[username]/status/[tweetId]).
  2. Procure pelo container <article> do tweet alvo contendo o texto a ser excluído.
  3. Clique no botão de menu de opções no tweet:
    • Seletor DOM: [data-testid="caret"]
  4. Aguarde 1-2 segundos, então clique no item de menu "Excluir":
    • Seletor DOM: Um elemento [role="menuitem"] cujo texto contenha "Delete" ou "删除".
  5. Aguarde 1-2 segundos, então clique no botão de confirmação de exclusão na folha de diálogo:
    • Seletor DOM: [data-testid="confirmationSheetConfirm"] (ou fallback para qualquer botão de diálogo com texto "Delete" ou "删除").

Exemplo de CLI Bridge (macOS)

Use o mesmo padrão de arquivo temporário + AppleScript da postagem. Substitua TWEET_TEXT_HERE pelo conteúdo do tweet alvo.

Etapa 1 — Encontrar tweet e clicar no caret:

cat > /tmp/del-1.js << 'EOF'
(function() {
  var articles = document.querySelectorAll('article');
  var target = null;
  var needle = 'TWEET_TEXT_HERE';
  for (var i = 0; i < articles.length; i++) {
    var textEl = articles[i].querySelector('[data-testid="tweetText"]');
    if (textEl && textEl.textContent.trim().indexOf(needle) !== -1) {
      target = articles[i];
      break;
    }
  }
  if (!target) return 'ERROR: tweet not found';
  var caret = target.querySelector('[data-testid="caret"]');
  if (!caret) return 'ERROR: caret not found';
  caret.click();
  return 'OK: caret clicked';
})();
EOF

osascript -e '
tell application "Google Chrome"
    set js to read "/tmp/del-1.js"
    set result to execute front window'"'"'s active tab javascript js
    return result
end tell'

Etapa 2 — Clicar no item de menu Excluir (aguardar 1-2s após etapa 1):

cat > /tmp/del-2.js << 'EOF'
(function() {
  var items = document.querySelectorAll('[role="menuitem"]');
  for (var i = 0; i < items.length; i++) {
    var txt = items[i].textContent.trim();
    if (txt === 'Delete' || txt === '删除') {
      // Clica no próprio menuitem, ou se contém um elemento clicável aninhado
      var clickTarget = items[i].querySelector('[role="menuitem"]') || items[i];
      clickTarget.click();
      return 'OK: delete clicked (index ' + i + ')';
    }
  }
  // Fallback: clica no primeiro menuitem (sempre "Delete" no dropdown)
  var first = document.querySelectorAll('[role="menuitem"]')[0];
  if (first) { first.click(); return 'OK: first menuitem clicked'; }
  return 'ERROR: no menuitems';
})();
EOF

osascript -e '
tell application "Google Chrome"
    set js to read "/tmp/del-2.js"
    set result to execute front window'"'"'s active tab javascript js
    return result
end tell'

Etapa 3 — Confirmar exclusão (aguardar 1-2s após etapa 2):

cat > /tmp/del-3.js << 'EOF'
(function() {
  var confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
  if (!confirmBtn) {
    var buttons = document.querySelectorAll('[role="button"], button');
    for (var i = 0; i < buttons.length; i++) {
      var txt = buttons[i].textContent.trim();
      if (txt === 'Delete' || txt === '删除') {
        confirmBtn = buttons[i];
        break;
      }
    }
  }
  if (!confirmBtn) return 'ERROR: confirm button not found';
  confirmBtn.click();
  return 'OK: confirm clicked';
})();
EOF

osascript -e '
tell application "Google Chrome"
    set js to read "/tmp/del-3.js"
    set result to execute front window'"'"'s active tab javascript js
    return result
end tell'

Fluxo de Exportação de Tweets

Para exportar/salvar todos os tweets ou tweets filtrados da sua página de perfil:

  1. Execute o script exportador:
    ${BUN_X} {baseDir}/scripts/export-tweets.ts [dataInicio] [dataFim]
    
    • Filtros de Data Opcionais: Você pode passar dataInicio (ex.: 2026-06-01) e dataFim (ex.: 2026-06-30) para filtrar a saída por intervalo de datas. Se omitidos, todos os tweets raspados são exportados.
    • Arquivo de Saída: Os tweets serão salvos em /Users/alex/twitter/twitter.yaml onde o URL do tweet é a chave e o conteúdo do texto do tweet é o valor.

Habilidades Relacionadas