Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Exportador MindMeister → Obsidian

Aplicação em Python que exporta seus mapas mentais do MindMeister para arquivos Markdown, prontos para carregar em um vault do Obsidian, preservando:

  1. a estrutura de pastas do MindMeister no disco local;
  2. as imagens de cada mapa em uma subpasta attachments/ no mesmo nível do .md;
  3. a hierarquia de tópicos do mapa como cabeçalhos (#, ##, ###, ...) e itens de lista (- texto);
  4. os links "anexados" a um nó (cards de link com preview) e vídeos anexados, como uma linha aninhada sob o item — em vez de se perderem;
  5. links que apontam para outro mapa da mesma conta, como wikilink do Obsidian ([[Título]]) em vez de link externo.

Como funciona: uma única credencial

Toda a exportação usa a API interna da própria aplicação web do MindMeister (https://www.mindmeister.com/app/api/...), autenticada com o cookie de sessão do navegador (_mind_session) — é a única credencial necessária (ver "Configuração" abaixo).

Isso não é uma API pública/documentada, mas foi confirmada testando exaustivamente contra a conta real, como a única fonte que cobre tudo que a exportação precisa:

  • Listagem de mapas e pastas — via GraphQL (POST /app/api/graphql):
    query Maps { maps { id title folderId } }
    query Folders { folders { id name parentId } }
    Cada mapa já vem com seu folderId, sem precisar de filtro por pasta.
  • Conteúdo de um mapa — via GET /app/api/maps/content.json?idea_id={id}: retorna a árvore completa de "ideas" (id, hierarquia por parentId/rank, título, nota, imagem, vídeo e os links anexados a cada nó).
  • Download de imagem — a imageURL de cada nó, autenticada com o mesmo cookie.

Por que não a API oficial (v2, Personal Access Token) ou a API v1?

Isso foi descoberto e simplificado ao longo do desenvolvimento — registrando aqui porque explica algumas decisões de arquitetura:

  • A API v2 (OAuth2/Personal Access Token) não tem endpoint de listagem de mapas/pastas (testado exaustivamente: GET /maps, GET /folders e variações retornam 404 com qualquer escopo de token). O endpoint de exportação (GET /maps/{id}.docx + Pandoc) também perde os links anexados/vídeos de cada nó e pode distorcer a proporção de imagens.
  • A API v1 (REST clássica, "deprecated") tem os métodos de listagem (mm.maps.getList/mm.folders.getList), mas exige um fluxo de autenticação totalmente separado (api_key + shared secret + auth_token via autorização de 3 pernas) — e ainda assim não dá acesso aos links anexados/vídeos.
  • A API interna da aplicação web (a que este projeto usa) cobre listagem e conteúdo completo com uma única credencial simples de capturar, então as outras duas deixaram de ser necessárias.

Estratégia de conversão (render.py)

render.py::render_map() percorre a árvore de "ideas" do content.json (por parentId/rank) e gera o Markdown diretamente — sem Pandoc, sem .docx:

  • nó com filhos → heading (#.."######", nível = profundidade na árvore);
  • nó sem filhos (folha) → item de lista (- texto);
  • nó cujo título tem quebra de linha real (não o wrap automático de texto longo, que vem como \r) → bloco de código dentro de um item de lista (- ``` ... ```), preservando indentação exata — o usuário digitou ali um trecho de várias linhas (YAML, config, notas formatadas) e colapsar isso destruiria a formatação;
  • nota, link anexado, vídeo e imagem de um nó (chamados de "extras") aparecem aninhados (indentados um nível) sob o item/bloco de código do próprio nó — nunca soltos no mesmo nível, senão fica ambíguo a quem pertencem. Quando o nó é um heading, os extras ficam soltos (não há bullet do heading para aninhar embaixo);
  • link anexado que aponta para outro mapa da mesma conta (mindmeister.com/app/map/{id}) é resolvido para um wikilink do Obsidian ([[Título]]) via resolve_map_title (callback passado a render_map(), que faz um lookup no dicionário de títulos já carregado — sem chamada extra). Sobrevive a mudanças de pasta, já que o Obsidian resolve [[...]] pelo nome do arquivo, não pelo caminho. Se o mapa de destino não puder ser resolvido, o link externo original é mantido;
  • imagem do nó → baixada (autenticada com o mesmo cookie) e embutida como ![[nome.png]], sem distorção (tamanho original), com nome no padrão idea_image_{id}_{largura}x{altura}.{ext};
  • sem linha em branco entre itens de lista consecutivos da mesma profundidade (lista compacta); uma linha em branco aparece quando a profundidade diminui entre dois itens consecutivos — ou seja, terminamos os filhos de uma seção e voltamos ao nível de um irmão dela. Sem essa regra, um item irmão de um heading (não filho dele) fica visualmente colado aos filhos desse heading, parecendo pertencer a ele.

Instalação

pip install -r requirements.txt

Sem dependências externas ao Python — não precisa de Pandoc nem de nenhuma outra ferramenta instalada no sistema.

Configuração

Copie .env.example para .env e preencha:

MINDMEISTER_SESSION_COOKIE=_mind_session=...
OBSIDIAN_VAULT_DIR=/caminho/para/seu/vault

Como capturar o cookie de sessão

  1. Abra https://www.mindmeister.com/app/ logado no navegador.
  2. Abra o DevTools (F12) → aba Network → filtre por Fetch/XHR → abra qualquer mapa (ou recarregue a página de um mapa já aberto).
  3. Ache uma requisição para content.json na lista → aba HeadersRequest Headers → copie o valor do cookie _mind_session (dentro do header Cookie:, é o trecho _mind_session=... — não precisa dos outros cookies de analytics que aparecem junto).
  4. Cole no .env.

Atenção: esse cookie funciona como uma senha temporária da sua conta e expira periodicamente (login de novo no navegador invalida o anterior). Quando main.py/discover.py começarem a falhar, repita os passos acima para capturar um cookie novo. É um endpoint interno da aplicação web, não uma API pública/documentada — pode mudar sem aviso.

Uso

# 1. Checar se o cookie está OK e ver a listagem de pastas/mapas
python discover.py

# 2. Exportar
python main.py --map-id 123  # exporta só um mapa específico (para testar)
python main.py                # exporta todos os mapas da conta

# Opções úteis
python main.py --dry-run              # mostra o que seria criado, sem baixar nada
python main.py --exclude 123,456      # pula esses IDs de mapa (ex: mapas grandes que dão timeout)
python main.py --heading-offset 1     # desloca os níveis de heading (## -> ###)

Tanto com --map-id quanto sem, o script sempre lista todos os mapas/pastas da conta primeiro (via GraphQL) — isso é o que permite colocar cada mapa na pasta certa do vault e resolver os wikilinks entre mapas, mesmo exportando um único mapa por vez.

Mapas muito grandes podem dar timeout (erro de rede ou 524 do Cloudflare) — o servidor do MindMeister demora demais para responder. Não há como aumentar esse limite do lado do cliente indefinidamente (o 524 é do próprio servidor). Nesses casos, use --exclude para pular esses mapas na exportação em lote e trate-os manualmente depois com --map-id.

Estrutura do projeto

mm_api_web.py     # cliente da API interna da app web (cookie de sessão) — GraphQL + content.json + download de imagem
render.py         # gera o Markdown direto do JSON de mm_api_web.py
organizer.py      # dataclasses MMFolder/MMMap + caminho local de cada mapa (espelha as pastas)
discover.py       # utilitário de diagnóstico (rode antes do main.py)
main.py           # orquestração / CLI

About

Aplicação em Python que exporta seus mapas mentais do MindMeister para arquivos Markdown, prontos para carregar em um vault do Obsidian.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages