Skip to content

Instantly share code, notes, and snippets.

@shuantsu
Last active April 15, 2026 19:21
Show Gist options
  • Select an option

  • Save shuantsu/50b756984fff097f69c9e46275ee832b to your computer and use it in GitHub Desktop.

Select an option

Save shuantsu/50b756984fff097f69c9e46275ee832b to your computer and use it in GitHub Desktop.
BOOKMARKLET WOL JW ORG REUNIÕES MEIO DE SEMANA JSON

📖 WOL Meeting Bookmarklet — Documentação Completa

Extrai e estrutura em JSON os dados da reunião semanal da congregação diretamente da página do Watchtower Online Library (WOL).


Índice

  1. O que é e o que faz
  2. Como usar
  3. Estrutura do JSON gerado
  4. Lógica interna e cálculos
  5. Potencial dos dados estruturados
  6. Tarefas comuns com Python

1. O que é e o que faz

O bookmarklet é um trecho de JavaScript executado diretamente no navegador, sem depender de nenhuma extensão, servidor ou instalação. Quando executado na página de uma reunião semanal do WOL (wol.jw.org), ele:

  1. Lê o DOM da página — percorre os elementos h2 e h3 dentro do #article para identificar seções e atividades da reunião.
  2. Classifica cada atividade — distingue itens da apostila (numerados como 1., 2....) de elementos de transição (cânticos, orações, comentários finais).
  3. Busca a duração de cada parte — extrai o tempo em minutos indicado pelo WOL (ex.: (10 min)) e converte para segundos.
  4. Resolve a duração dos cânticos — consulta um banco de dados interno com as durações reais (em segundos) dos 163 cânticos, baseadas nos arquivos MP3 oficiais.
  5. Calcula o tempo das orações — usa o saldo de tempo disponível na reunião (105 minutos = 6300 segundos) após descontar todos os tempos conhecidos, distribuindo esse saldo igualmente entre as orações/transições identificadas.
  6. Coleta referências e links — extrai todos os hyperlinks presentes no conteúdo de cada atividade (versículos bíblicos, publicações, vídeos).
  7. Abre uma nova aba com o JSON resultante exibido em um <textarea> estilizado, pronto para copiar.

2. Como usar

Pré-requisito

Você precisa estar em uma página de reunião semanal do WOL, por exemplo:

https://wol.jw.org/pt/wol/d/r5/lp-t/202026087

Passo a passo

Para usar como bookmarklet

  1. Use esta página para poder criar o bookmarklet a primeira vez: https://caiorss.github.io/bookmarklet-maker/
  2. Use o bookmarklet que você criou.
  3. Uma nova aba será aberta automaticamente com o JSON completo da reunião exibido em um textarea de fundo escuro. Selecione tudo (Ctrl+A / Cmd+A) e copie.

Método alternativo

Passo 1 — Abra o Console do Desenvolvedor do navegador:

Navegador Atalho Windows/Linux Atalho macOS
Chrome / Edge F12 ou Ctrl+Shift+J Cmd+Option+J
Firefox F12 ou Ctrl+Shift+K Cmd+Option+K
Safari Habilite o menu Desenvolvedor em Preferências → Avançado, depois Cmd+Option+C

Passo 2 — Clique na aba Console.

Passo 3 — Cole todo o código JavaScript do bookmarklet no campo de entrada do console.

Passo 4 — Pressione Enter.

Passo 5 — Uma nova aba será aberta automaticamente com o JSON completo da reunião exibido em um textarea de fundo escuro. Selecione tudo (Ctrl+A / Cmd+A) e copie.

⚠️ Atenção com bloqueadores de popup: Se a nova aba não abrir, verifique se o navegador bloqueou o popup e autorize-o clicando no ícone que aparece na barra de endereços.


3. Estrutura do JSON gerado

O JSON possui dois blocos principais: metadata e atividades.

3.1 metadata

"metadata": {
    "uid_semana": "2026-04-13",
    "semana_label": "13-19 DE ABRIL",
    "total_segundos": 6300,
    "logs": [
        "ℹ️ Saldo distribuído: 351s por oração (Total: 701.54s)."
    ]
}
Campo Tipo Descrição
uid_semana string Data da segunda-feira da semana no formato YYYY-MM-DD. Serve como chave única para identificar a semana.
semana_label string Título da semana conforme exibido no WOL, ex.: "13-19 DE ABRIL".
total_segundos number Duração total da reunião em segundos (105 min = 6300 s). Constante.
logs array Mensagens geradas durante o processamento, como avisos sobre cânticos não encontrados e o cálculo do saldo de orações.

3.2 atividades[]

Cada elemento do array representa uma parte da reunião.

{
    "id": "2026-04-13_03",
    "is_apostila": true,
    "secao": "TESOUROS DA PALAVRA DE DEUS",
    "numero": 1,
    "titulo_limpo": "Jesus mostrou um grande amor!",
    "recursos": [ ... ],
    "conteudo_texto": [ ... ],
    "ponto_a_considerar": null,
    "referencias": [ ... ]
}
Campo Tipo Descrição
id string Identificador único no formato YYYY-MM-DD_NN, onde NN é o índice do header na página.
is_apostila boolean true se o item é uma parte numerada da apostila; false para cânticos, orações e transições.
secao string Seção da reunião à qual o item pertence (ex.: "TESOUROS DA PALAVRA DE DEUS").
numero number Número do item na apostila (1, 2, 3...) ou -1 para itens não numerados.
titulo_limpo string Título da atividade sem o número, sem a indicação de tempo e sem metadados extras.
recursos array Lista de recursos de tempo associados à atividade. Ver tabela abaixo.
conteudo_texto array<string> Linhas de texto do corpo da atividade (pontos a desenvolver, instruções, perguntas).
ponto_a_considerar number|null Número da lição do livro Pregue e Ensine (th) referenciada, quando aplicável.
referencias array Links extraídos do conteúdo: versículos, publicações, vídeos. Ver tabela abaixo.

3.3 recursos[]

Cada atividade pode ter um ou mais recursos, representando diferentes "fatias" de tempo.

{
    "tipo": "AUDIO",
    "label": "Cântico 18",
    "segundos": 157.32,
    "origem_tempo": "MP3"
}
Campo Tipo Descrição
tipo string Categoria do recurso. Valores possíveis: AUDIO, ORACAO, PARTE.
label string Descrição legível do recurso.
segundos number Duração em segundos (pode ter casas decimais para áudios).
origem_tempo string Origem do valor de duração. Ver tabela de origens abaixo.

Origens de tempo possíveis:

Valor Significado
MP3 Duração obtida do banco de dados interno de cânticos (medição do arquivo MP3).
AVG Cântico não encontrado no banco; aplicada a média dos 163 cânticos.
WOL Duração extraída diretamente do texto da página do WOL (ex.: (10 min)).
CAL Duração calculada pelo algoritmo de saldo (usado para orações/transições).

3.4 referencias[]

{
    "texto": "Isa. 53:3;",
    "url": "https://wol.jw.org/pt/wol/bc/r5/lp-t/202026087/1/0"
}
Campo Tipo Descrição
texto string Texto âncora do link na página.
url string URL completa do link.

3.5 Json Schema completo

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://gist.github.com/shuantsu/wol-reuniao-schema.json",
  "title": "WOL Reunião Meio de Semana",
  "description": "Schema para o JSON gerado pelo bookmarklet de extração de reuniões do Watchtower Online Library (wol.jw.org).",
  "type": "object",
  "required": ["metadata", "atividades"],
  "additionalProperties": false,

  "properties": {

    "metadata": {
      "type": "object",
      "title": "Metadados da Reunião",
      "description": "Informações gerais sobre a semana e o processamento realizado pelo bookmarklet.",
      "required": ["uid_semana", "semana_label", "total_segundos", "logs"],
      "additionalProperties": false,
      "properties": {

        "uid_semana": {
          "type": "string",
          "title": "UID da Semana",
          "description": "Data da segunda-feira da semana no formato ISO 8601 (YYYY-MM-DD). Serve como chave única para identificar a semana.",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "examples": ["2026-04-13"]
        },

        "semana_label": {
          "type": "string",
          "title": "Rótulo da Semana",
          "description": "Título da semana conforme exibido no WOL, extraído do h1 da página.",
          "minLength": 1,
          "examples": ["13-19 DE ABRIL"]
        },

        "total_segundos": {
          "type": "integer",
          "title": "Total de Segundos da Reunião",
          "description": "Duração total da reunião em segundos. Valor fixo de 6300 (105 minutos).",
          "const": 6300,
          "examples": [6300]
        },

        "logs": {
          "type": "array",
          "title": "Logs de Processamento",
          "description": "Mensagens geradas durante a extração. Podem indicar avisos (cântico não encontrado no banco) ou informações sobre o cálculo de saldo das orações.",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "examples": [
            ["ℹ️ Saldo distribuído: 351s por oração (Total: 701.54s)."],
            ["⚠️ Cântico 999 não encontrado. Média de 158.42s aplicada."]
          ]
        }

      }
    },

    "atividades": {
      "type": "array",
      "title": "Atividades da Reunião",
      "description": "Lista ordenada de todas as partes da reunião identificadas na página do WOL, incluindo cânticos, orações, partes da apostila e transições.",
      "minItems": 1,
      "items": {
        "$ref": "#/definitions/Atividade"
      }
    }

  },

  "definitions": {

    "Atividade": {
      "type": "object",
      "title": "Atividade",
      "description": "Representa uma parte individual da reunião: pode ser uma parte numerada da apostila, um cântico, uma oração ou comentários finais.",
      "required": [
        "id",
        "is_apostila",
        "secao",
        "numero",
        "titulo_limpo",
        "recursos",
        "conteudo_texto",
        "ponto_a_considerar",
        "referencias"
      ],
      "additionalProperties": false,
      "properties": {

        "id": {
          "type": "string",
          "title": "ID da Atividade",
          "description": "Identificador único da atividade no formato YYYY-MM-DD_NN, onde NN é o índice com dois dígitos do header correspondente na página.",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}_\\d{2}$",
          "examples": ["2026-04-13_01", "2026-04-13_03"]
        },

        "is_apostila": {
          "type": "boolean",
          "title": "É Item da Apostila",
          "description": "true quando o item é uma parte numerada da apostila semanal (ex.: '1. Título'). false para cânticos, orações e transições sem número.",
          "examples": [true, false]
        },

        "secao": {
          "type": "string",
          "title": "Seção da Reunião",
          "description": "Nome da seção da reunião à qual a atividade pertence, extraído dos elementos h2 da página.",
          "minLength": 1,
          "examples": [
            "ABERTURA",
            "TESOUROS DA PALAVRA DE DEUS",
            "FAÇA SEU MELHOR NO MINISTÉRIO",
            "NOSSA VIDA CRISTÃ"
          ]
        },

        "numero": {
          "type": "integer",
          "title": "Número do Item",
          "description": "Número sequencial do item na apostila (1, 2, 3...). Vale -1 para itens não numerados como cânticos, orações e comentários finais.",
          "minimum": -1,
          "examples": [1, 2, 3, -1]
        },

        "titulo_limpo": {
          "type": "string",
          "title": "Título Limpo",
          "description": "Título da atividade com o número de item, a indicação de tempo e metadados extras removidos.",
          "minLength": 1,
          "examples": [
            "Jesus mostrou um grande amor!",
            "Cântico 18 e oração",
            "Comentários finais"
          ]
        },

        "recursos": {
          "type": "array",
          "title": "Recursos de Tempo",
          "description": "Lista de fatias de tempo associadas à atividade. Uma atividade pode ter múltiplos recursos (ex.: cântico + oração + parte WOL).",
          "minItems": 0,
          "items": {
            "$ref": "#/definitions/Recurso"
          }
        },

        "conteudo_texto": {
          "type": "array",
          "title": "Conteúdo Textual",
          "description": "Linhas de texto extraídas do corpo da atividade (parágrafos e itens de lista). Pode incluir pontos a desenvolver, instruções e perguntas.",
          "items": {
            "type": "string",
            "minLength": 1
          },
          "examples": [
            [
              "Jesus seria desprezado. (Isa. 53:3; Mat. 26:67, 68)",
              "Por amor, Jesus estava disposto a sofrer."
            ],
            []
          ]
        },

        "ponto_a_considerar": {
          "title": "Ponto a Considerar (th)",
          "description": "Número da lição do livro 'Pregue e Ensine' (th) referenciada na atividade, quando aplicável. null se não houver referência a uma lição th.",
          "oneOf": [
            {
              "type": "integer",
              "minimum": 1,
              "examples": [10, 4, 7]
            },
            {
              "type": "null"
            }
          ]
        },

        "referencias": {
          "type": "array",
          "title": "Referências e Links",
          "description": "Links extraídos do conteúdo da atividade: versículos bíblicos, publicações da organização e vídeos.",
          "items": {
            "$ref": "#/definitions/Referencia"
          }
        }

      }
    },

    "Recurso": {
      "type": "object",
      "title": "Recurso de Tempo",
      "description": "Representa uma fatia de tempo dentro de uma atividade, com sua categoria, duração e origem do valor.",
      "required": ["tipo", "label", "segundos", "origem_tempo"],
      "additionalProperties": false,
      "properties": {

        "tipo": {
          "type": "string",
          "title": "Tipo do Recurso",
          "description": "Categoria do recurso de tempo.",
          "enum": ["AUDIO", "ORACAO", "PARTE"],
          "x-enumDescriptions": {
            "AUDIO": "Cântico do cancioneiro. Duração medida a partir dos arquivos MP3 oficiais.",
            "ORACAO": "Oração de abertura ou encerramento. Duração calculada por saldo de tempo.",
            "PARTE": "Parte regular da apostila ou transição com tempo definido pelo WOL."
          }
        },

        "label": {
          "type": "string",
          "title": "Rótulo do Recurso",
          "description": "Descrição legível do recurso, usada para identificação humana.",
          "minLength": 1,
          "examples": ["Cântico 18", "Oração", "Jesus mostrou um grande amor!", "Comentários finais"]
        },

        "segundos": {
          "type": "number",
          "title": "Duração em Segundos",
          "description": "Duração do recurso em segundos. Pode conter casas decimais para recursos do tipo AUDIO (medições de MP3).",
          "minimum": 0,
          "examples": [157.32, 351, 600, 1800]
        },

        "origem_tempo": {
          "type": "string",
          "title": "Origem do Valor de Tempo",
          "description": "Indica como o valor de 'segundos' foi obtido.",
          "enum": ["MP3", "AVG", "WOL", "CAL"],
          "x-enumDescriptions": {
            "MP3": "Duração obtida do banco de dados interno, baseada na medição do arquivo MP3 oficial do cântico.",
            "AVG": "Cântico não encontrado no banco de dados; aplicada a média aritmética dos 163 cânticos cadastrados.",
            "WOL": "Duração extraída diretamente do texto da página do WOL (ex.: indicação '(10 min)' convertida para 600 segundos).",
            "CAL": "Duração calculada pelo algoritmo de saldo: tempo restante após descontar WOL e MP3, distribuído igualmente entre as orações."
          }
        }

      }
    },

    "Referencia": {
      "type": "object",
      "title": "Referência / Link",
      "description": "Um hyperlink extraído do conteúdo da atividade, com o texto âncora e a URL de destino.",
      "required": ["texto", "url"],
      "additionalProperties": false,
      "properties": {

        "texto": {
          "type": "string",
          "title": "Texto Âncora",
          "description": "Texto visível do link na página do WOL.",
          "minLength": 1,
          "examples": ["Isa. 53:3;", "th lição 10", "lmd lição 4 ponto 4", "Mostre o VÍDEO"]
        },

        "url": {
          "type": "string",
          "title": "URL do Link",
          "description": "URL completa de destino do link. Pode apontar para wol.jw.org (versículos e publicações) ou jw.org (vídeos e outros recursos).",
          "format": "uri",
          "examples": [
            "https://wol.jw.org/pt/wol/bc/r5/lp-t/202026087/1/0",
            "https://wol.jw.org/pt/wol/pc/r5/lp-t/202026087/4/0",
            "https://www.jw.org/finder?wtlocale=T&lank=pub-pk_55_VIDEO"
          ]
        }

      }
    }

  }
}

4. Lógica interna e cálculos

4.1 Identificação do início da semana

Segunda-feira = data atual - (dia_da_semana - 1), exceto domingo → volta 6 dias

O resultado vira o uid_semana e prefixo de todos os ids, garantindo unicidade por semana.

4.2 Banco de dados de cânticos

O objeto dbCanticos mapeia os números de 1 a 163 para suas durações em segundos (ex.: "18": 157.32). Esses valores foram medidos a partir dos arquivos MP3 oficiais do cancioneiro. Quando um cântico não está no banco, aplica-se a média aritmética de todos os 163 valores:

$$\bar{t} = \frac{\sum_{i=1}^{163} t_i}{163}$$

4.3 Cálculo do saldo para orações

O algoritmo segue:

$$t_{saldo} = t_{total} - \sum t_{WOL} - \sum t_{MP3}$$

$$t_{por_oracao} = \left\lfloor \frac{t_{saldo}}{n_{oracoes}} \right\rceil$$

Onde:

  • $t_{total} = 6300$ segundos (105 minutos)
  • $\sum t_{WOL}$ = soma de todos os tempos extraídos do texto da página
  • $\sum t_{MP3}$ = soma das durações reais de todos os cânticos identificados
  • $n_{oracoes}$ = número de orações/transições identificadas sem tempo definido

Esse cálculo distribui o tempo "sobrante" igualmente entre as orações de abertura e encerramento.


5. Potencial dos dados estruturados

5.1 Para uso humano

O JSON gerado é limpo, legível e previsível, permitindo:

  • Preparação pessoal: visualizar rapidamente todas as referências bíblicas e publicações de uma reunião sem precisar navegar pelo WOL.
  • Criação de roteiros: gerar apresentações, slides ou scripts para condutores de reunião a partir dos conteudo_texto e referencias.
  • Controle de tempo: simular a cronometragem da reunião somando os segundos de cada recurso, identificando partes que possam estourar o tempo.
  • Histórico semanal: ao armazenar os JSONs por uid_semana, cria-se um arquivo completo de todas as reuniões ao longo do ano.
  • Dashboards: alimentar ferramentas como Google Sheets, Notion ou Airtable para acompanhar progresso de estudos e publicações usadas.

5.2 Para uso com LLMs (IA)

O formato estruturado é ideal para ser passado como contexto para modelos de linguagem. Exemplos de prompts que se tornam possíveis:

Objetivo Como o JSON ajuda
Resumo da reunião Passe o JSON completo e peça um resumo em linguagem natural por seção.
Geração de perguntas de estudo Use conteudo_texto de cada atividade para gerar perguntas de revisão.
Explicação de referências Use referencias[].texto para pedir ao modelo que explique cada versículo citado.
Plano de preparação Peça ao modelo para criar um plano de estudo diário baseado nas atividades da semana.
Comparação entre semanas Passe JSONs de semanas diferentes e peça análise de tendências de temas.
Tradução e adaptação Adapte o conteúdo para diferentes níveis de linguagem (crianças, novos estudantes).

O JSON é especialmente valioso para LLMs porque:

  • É determinístico — sempre tem os mesmos campos, sem ambiguidade.
  • É relacionalsecao, numero e id permitem filtrar e cruzar dados facilmente.
  • É temporal — os segundos em cada recurso permitem raciocinar sobre cronograma.
  • É linkado — as URLs em referencias permitem ao modelo (ou ao código) buscar conteúdo adicional.

6. Tarefas comuns com Python

Todos os exemplos abaixo assumem que o JSON foi salvo em um arquivo chamado reuniao.json.

import json

with open("reuniao.json", "r", encoding="utf-8") as f:
    data = json.load(f)

6.1 Listar todas as atividades com seus tempos

for ativ in data["atividades"]:
    total_seg = sum(r["segundos"] for r in ativ["recursos"])
    minutos = total_seg / 60
    print(f"[{ativ['secao']}] {ativ['titulo_limpo']}{minutos:.1f} min ({total_seg:.0f}s)")

Saída esperada:

[ISAÍAS 52-53] Cântico 18 e oração — 9.5 min (568s)
[TESOUROS DA PALAVRA DE DEUS] Jesus mostrou um grande amor! — 10.0 min (600s)
[TESOUROS DA PALAVRA DE DEUS] Joias espirituais — 10.0 min (600s)
...

6.2 Filtrar apenas atividades da apostila (is_apostila = true)

apostila = [a for a in data["atividades"] if a["is_apostila"]]

for item in apostila:
    print(f"  {item['numero']}. {item['titulo_limpo']} [{item['secao']}]")

6.3 Listar todos os cânticos da reunião

for ativ in data["atividades"]:
    for recurso in ativ["recursos"]:
        if recurso["tipo"] == "AUDIO":
            print(f"{recurso['label']}{recurso['segundos']}s (origem: {recurso['origem_tempo']})")

6.4 Extrair todas as referências bíblicas e links únicos

todas_refs = []
for ativ in data["atividades"]:
    for ref in ativ["referencias"]:
        todas_refs.append({
            "atividade": ativ["titulo_limpo"],
            "texto": ref["texto"],
            "url": ref["url"]
        })

# Remover duplicatas pelo texto
vistos = set()
unicas = []
for r in todas_refs:
    if r["texto"] not in vistos:
        vistos.add(r["texto"])
        unicas.append(r)

for r in unicas:
    print(f"  {r['texto']:30s}{r['url']}")

6.5 Calcular o tempo total por seção

from collections import defaultdict

tempos_por_secao = defaultdict(float)

for ativ in data["atividades"]:
    for recurso in ativ["recursos"]:
        tempos_por_secao[ativ["secao"]] += recurso["segundos"]

for secao, total in tempos_por_secao.items():
    print(f"{secao}: {total/60:.1f} min")

Saída esperada:

ISAÍAS 52-53: 9.5 min
TESOUROS DA PALAVRA DE DEUS: 24.0 min
FAÇA SEU MELHOR NO MINISTÉRIO: 12.0 min
NOSSA VIDA CRISTÃ: 59.5 min

6.6 Verificar se a reunião ultrapassa o tempo total

total_calculado = sum(
    r["segundos"]
    for ativ in data["atividades"]
    for r in ativ["recursos"]
)

limite = data["metadata"]["total_segundos"]
diferenca = total_calculado - limite

if diferenca > 0:
    print(f"⚠️  Reunião EXCEDE o tempo em {diferenca:.0f}s ({diferenca/60:.1f} min)")
elif diferenca < 0:
    print(f"✅  Reunião termina {abs(diferenca):.0f}s ({abs(diferenca)/60:.1f} min) antes do limite")
else:
    print("✅  Reunião está exatamente no tempo!")

6.7 Encontrar atividades que referenciam um livro específico (ex.: "th")

livro_alvo = "th"

for ativ in data["atividades"]:
    refs_do_livro = [r for r in ativ["referencias"] if livro_alvo in r["texto"]]
    if refs_do_livro:
        print(f"\n📌 {ativ['titulo_limpo']}")
        for r in refs_do_livro:
            print(f"   → {r['texto']}: {r['url']}")
    
    if ativ["ponto_a_considerar"] is not None:
        print(f"   ⭐ Ponto th a considerar: lição {ativ['ponto_a_considerar']}")

6.8 Exportar um resumo para CSV

import csv

with open("resumo_reuniao.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["semana", "secao", "numero", "titulo", "tempo_minutos", "is_apostila"])
    
    semana = data["metadata"]["uid_semana"]
    
    for ativ in data["atividades"]:
        total_seg = sum(r["segundos"] for r in ativ["recursos"])
        writer.writerow([
            semana,
            ativ["secao"],
            ativ["numero"] if ativ["numero"] != -1 else "",
            ativ["titulo_limpo"],
            round(total_seg / 60, 2),
            ativ["is_apostila"]
        ])

print("✅ CSV gerado: resumo_reuniao.csv")

6.9 Montar um prompt pronto para enviar a uma LLM

def montar_prompt_llm(data: dict) -> str:
    semana = data["metadata"]["semana_label"]
    linhas = [f"# Reunião da semana de {semana}\n"]
    
    for ativ in data["atividades"]:
        if not ativ["is_apostila"]:
            continue
        
        total_seg = sum(r["segundos"] for r in ativ["recursos"])
        linhas.append(f"## {ativ['numero']}. {ativ['titulo_limpo']} ({total_seg/60:.0f} min)")
        linhas.append(f"**Seção:** {ativ['secao']}")
        
        if ativ["conteudo_texto"]:
            linhas.append("**Conteúdo:**")
            for linha in ativ["conteudo_texto"]:
                linhas.append(f"- {linha}")
        
        if ativ["referencias"]:
            refs_texto = ", ".join(r["texto"] for r in ativ["referencias"])
            linhas.append(f"**Referências:** {refs_texto}")
        
        linhas.append("")
    
    return "\n".join(linhas)


prompt = montar_prompt_llm(data)
print(prompt)
# Cole o resultado diretamente no chat de qualquer LLM

6.10 Processar múltiplas semanas de uma vez

import os
import glob

semanas = []

for arquivo in glob.glob("reunioes/*.json"):
    with open(arquivo, "r", encoding="utf-8") as f:
        semanas.append(json.load(f))

# Ordenar cronologicamente
semanas.sort(key=lambda x: x["metadata"]["uid_semana"])

# Exemplo: listar todas as semanas processadas
for s in semanas:
    total_ativ = len([a for a in s["atividades"] if a["is_apostila"]])
    print(f"{s['metadata']['uid_semana']}{s['metadata']['semana_label']} ({total_ativ} partes da apostila)")

Apêndice — Campos de referência rápida

Tipos de recurso (recursos[].tipo)

Valor Descrição
AUDIO Cântico
ORACAO Oração (tempo calculado por saldo)
PARTE Parte regular com tempo definido no WOL

Origens de tempo (recursos[].origem_tempo)

Valor Descrição
MP3 Medição real do arquivo de áudio do cântico
AVG Média dos 163 cânticos (fallback)
WOL Extraído do texto da página ((X min))
CAL Calculado por saldo (orações e transições)

Seções típicas de uma reunião

Seção Descrição
ABERTURA Cântico e oração de abertura
TESOUROS DA PALAVRA DE DEUS Partes de estudo bíblico (itens 1–3)
FAÇA SEU MELHOR NO MINISTÉRIO Partes de treinamento ministerial (4–6)
NOSSA VIDA CRISTÃ Partes de aplicação cristã (7–8) + encerramento
(function() {
const dbCanticos = {"1":137.88,"2":176.208,"3":197.136,"4":225.528,"5":152.712,"6":139.176,"7":200.352,"8":143.544,"9":133.992,"10":109.248,"11":160.92,"12":130.776,"13":106.992,"14":120.24,"15":141.96,"16":138.696,"17":135.0,"18":157.32,"19":200.544,"20":176.928,"21":134.616,"22":159.96,"23":211.68,"24":164.712,"25":159.768,"26":260.112,"27":138.384,"28":140.784,"29":150.816,"30":158.328,"31":165.864,"32":214.488,"33":179.472,"34":176.664,"35":159.792,"36":138.696,"37":125.352,"38":169.584,"39":130.68,"40":134.736,"41":117.768,"42":164.928,"43":140.448,"44":152.976,"45":133.272,"46":166.656,"47":123.096,"48":157.128,"49":110.328,"50":118.992,"51":144.576,"52":157.224,"53":159.36,"54":154.2,"55":180.816,"56":185.472,"57":164.208,"58":178.944,"59":119.736,"60":151.512,"61":139.656,"62":160.128,"63":182.712,"64":99.168,"65":127.92,"66":159.888,"67":125.88,"68":114.12,"69":126.72,"70":120.624,"71":178.896,"72":118.272,"73":139.824,"74":149.328,"75":238.272,"76":182.448,"77":159.768,"78":161.352,"79":176.328,"80":115.56,"81":165.816,"82":126.408,"83":139.752,"84":173.592,"85":130.368,"86":120.624,"87":123.816,"88":133.296,"89":157.656,"90":175.776,"91":173.568,"92":161.568,"93":95.664,"94":128.76,"95":134.472,"96":170.76,"97":188.88,"98":114.432,"99":135.192,"100":129.936,"101":116.64,"102":158.4,"103":169.776,"104":124.008,"105":174.864,"106":124.752,"107":176.76,"108":216.504,"109":110.016,"110":133.128,"111":141.048,"112":137.136,"113":151.464,"114":137.208,"115":136.8,"116":104.208,"117":130.176,"118":135.48,"119":134.88,"120":126.144,"121":136.488,"122":181.608,"123":130.44,"124":139.488,"125":167.928,"126":128.208,"127":125.976,"128":133.8,"129":212.088,"130":156.192,"131":103.92,"132":127.8,"133":92.352,"134":150.744,"135":143.952,"136":170.592,"137":162.576,"138":171.984,"139":204.096,"140":157.2,"141":142.752,"142":107.448,"143":147.84,"144":129.432,"145":185.208,"146":222.672,"147":139.704,"148":232.176,"149":141.504,"150":140.88,"151":230.592,"152":191.016,"153":257.976,"154":235.392,"155":254.52,"156":265.992,"157":214.704,"158":249.744,"159":301.44,"160":275.76,"161":262.464,"162":251.496,"163":232.2};
const getMonday = () => {
const d = new Date();
const day = d.getDay(), diff = d.getDate() - day + (day == 0 ? -6 : 1);
return new Date(d.setDate(diff)).toISOString().split('T')[0];
};
const article = document.querySelector('#article');
const mondayId = getMonday();
const result = {
metadata: {
uid_semana: mondayId,
semana_label: article.querySelector('h1')?.innerText.trim(),
total_segundos: 6300,
logs: []
},
atividades: []
};
const headers = Array.from(article.querySelectorAll('h2, h3')).filter(h => {
const t = h.innerText.trim();
return h.tagName === 'H2' || (h.tagName === 'H3' && (/^\d+\./.test(t) || /Cântico/i.test(t) || /Comentários/i.test(t)));
});
let currentSecao = "ABERTURA", tempoWOL = 0, tempoMP3 = 0, oracoes = [];
headers.forEach((header, index) => {
const text = header.innerText.trim();
if (header.tagName === 'H2') { currentSecao = text; return; }
const numMatch = text.match(/^(\d+)\./);
const atv = {
id: `${mondayId}_${index.toString().padStart(2, '0')}`,
is_apostila: numMatch !== null,
secao: currentSecao,
numero: numMatch ? parseInt(numMatch[1]) : -1,
titulo_limpo: text.replace(/^\d+\.\s*/, '').replace(/\(\d+\s*min\)/, '').split('|')[0].trim(),
recursos: [],
conteudo_texto: [],
referencias: []
};
const range = document.createRange();
range.setStartAfter(header);
const next = headers[index + 1];
if (next) range.setEndBefore(next);
else range.setEndBefore(article.querySelector('.pswp, #questionContainer') || article.lastElementChild);
const tempDiv = document.createElement('div');
tempDiv.appendChild(range.cloneContents());
const textSet = new Set();
tempDiv.querySelectorAll('p, li').forEach(node => {
let txt = node.innerText.replace(/Sua resposta/g, "").trim();
if (txt && txt !== atv.titulo_limpo && !/^\(\d+\s*min\)$/.test(txt)) {
textSet.add(txt);
}
});
atv.conteudo_texto = Array.from(textSet);
// LÓGICA DO PONTO A CONSIDERAR (Seção específica ou Leitura da Bíblia)
if (currentSecao.includes("MINISTÉRIO") || atv.titulo_limpo.includes("Leitura da Bíblia")) {
atv.conteudo_texto.some(txt => {
const match = txt.match(/\(([^)]+)\)$/);
if (match) {
atv.ponto_a_considerar = match[1];
return true;
}
return false;
});
}
const timeMatch = text.match(/\((\d+)\s*min\)/) || tempDiv.textContent.match(/(\d+)\s*min/);
const segWOL = timeMatch ? parseInt(timeMatch[1]) * 60 : 0;
const cMatch = text.match(/Cântico\s+(\d+)/i);
if (cMatch) {
const cid = cMatch[1];
const dur = dbCanticos[cid] || 161;
tempoMP3 += dur;
atv.recursos.push({ tipo: "AUDIO", label: `Cântico ${cid}`, segundos: parseFloat(dur.toFixed(2)), origem_tempo: dbCanticos[cid] ? "MP3" : "AVG" });
}
if (/oração/i.test(text)) {
const resO = { tipo: "ORACAO", label: "Oração", segundos: 0, origem_tempo: "CAL" };
atv.recursos.push(resO);
oracoes.push(resO);
}
if (segWOL > 0) {
tempoWOL += segWOL;
atv.recursos.push({ tipo: "PARTE", label: atv.titulo_limpo, segundos: segWOL, origem_tempo: "WOL" });
}
tempDiv.querySelectorAll('a').forEach(a => {
const tA = a.textContent.trim();
if (tA && !a.href.includes('#page')) {
atv.referencias.push({ texto: tA, url: a.href });
}
});
result.atividades.push(atv);
});
const saldo = 6300 - tempoWOL - tempoMP3;
if (oracoes.length > 0) {
const cada = Math.round(saldo / oracoes.length);
result.metadata.logs.push(`ℹ️ Saldo distribuído: ${cada}s por oração.`);
oracoes.forEach(r => r.segundos = cada);
}
const win = window.open("", "_blank");
win.document.write(`<html><body style="margin:0;background:#1e1e1e;padding:20px;"><textarea style="width:100%;height:95vh;background:#1a1b26;color:#9ece6a;border:1px solid #414868;padding:15px;font-family:monospace;font-size:13px;outline:none;border-radius:4px;line-height:1.5;">${JSON.stringify(result, null, 4)}</textarea></body></html>`);
win.document.close();
})();
{
"metadata": {
"uid_semana": "2026-04-13",
"semana_label": "13-19 DE ABRIL",
"total_segundos": 6300,
"logs": [
"ℹ️ Saldo distribuído: 351s por oração."
]
},
"atividades": [
{
"id": "2026-04-13_01",
"is_apostila": false,
"secao": "ISAÍAS 52-53",
"numero": -1,
"titulo_limpo": "Cântico 18 e oração",
"recursos": [
{
"tipo": "AUDIO",
"label": "Cântico 18",
"segundos": 157.32,
"origem_tempo": "MP3"
},
{
"tipo": "ORACAO",
"label": "Oração",
"segundos": 351,
"origem_tempo": "CAL"
},
{
"tipo": "PARTE",
"label": "Cântico 18 e oração",
"segundos": 60,
"origem_tempo": "WOL"
}
],
"conteudo_texto": [],
"referencias": []
},
{
"id": "2026-04-13_03",
"is_apostila": true,
"secao": "TESOUROS DA PALAVRA DE DEUS",
"numero": 1,
"titulo_limpo": "Jesus mostrou um grande amor!",
"recursos": [
{
"tipo": "PARTE",
"label": "Jesus mostrou um grande amor!",
"segundos": 600,
"origem_tempo": "WOL"
}
],
"conteudo_texto": [
"Jesus seria desprezado. (Isa. 53:3; Mat. 26:67, 68; w10 15/11 7 § 2)",
"Jesus aceitaria passar por sofrimento. (Isa. 53:7; ip-2 205 § 25)",
"Por amor, Jesus estava disposto a sofrer para fazer a vontade de Jeová e levar embora os nossos pecados. (Isa. 53:10-12; João 14:31; 15:13)"
],
"referencias": [
{
"texto": "Isa. 53:3;",
"url": "https://wol.jw.org/pt/wol/bc/r5/lp-t/202026087/1/0"
},
{
"texto": "Mat. 26:67, 68",
"url": "https://wol.jw.org/pt/wol/bc/r5/lp-t/202026087/1/1"
},
{
"texto": "w10 15/11 7 § 2",
"url": "https://wol.jw.org/pt/wol/pc/r5/lp-t/202026087/1/0"
},
{
"texto": "Isa. 53:7",
"url": "https://wol.jw.org/pt/wol/bc/r5/lp-t/202026087/2/0"
},
{
"texto": "ip-2 205 § 25",
"url": "https://wol.jw.org/pt/wol/pc/r5/lp-t/202026087/2/0"
},
{
"texto": "Isa. 53:10-12;",
"url": "https://wol.jw.org/pt/wol/bc/r5/lp-t/202026087/3/0"
},
{
"texto": "João 14:31;",
"url": "https://wol.jw.org/pt/wol/bc/r5/lp-t/202026087/3/1"
},
{
"texto": "15:13",
"url": "https://wol.jw.org/pt/wol/bc/r5/lp-t/202026087/3/2"
}
]
},
{
"id": "2026-04-13_04",
"is_apostila": true,
"secao": "TESOUROS DA PALAVRA DE DEUS",
"numero": 2,
"titulo_limpo": "Joias espirituais",
"recursos": [
{
"tipo": "PARTE",
"label": "Joias espirituais",
"segundos": 600,
"origem_tempo": "WOL"
}
],
"conteudo_texto": [
"Isa. 52:11 — O que podemos aprender com essa ordem profética? (it “Utensílios” § 2)",
"Que joias espirituais você encontrou na leitura da Bíblia desta semana?"
],
"referencias": [
{
"texto": "Isa. 52:11",
"url": "https://wol.jw.org/pt/wol/bc/r5/lp-t/202026087/4/0"
},
{
"texto": "it “Utensílios” § 2",
"url": "https://wol.jw.org/pt/wol/pc/r5/lp-t/202026087/3/0"
}
]
},
{
"id": "2026-04-13_05",
"is_apostila": true,
"secao": "TESOUROS DA PALAVRA DE DEUS",
"numero": 3,
"titulo_limpo": "Leitura da Bíblia",
"recursos": [
{
"tipo": "PARTE",
"label": "Leitura da Bíblia",
"segundos": 240,
"origem_tempo": "WOL"
}
],
"conteudo_texto": [
"(4 min) Isa. 53:3-12 (th lição 10)"
],
"referencias": [
{
"texto": "Isa. 53:3-12",
"url": "https://wol.jw.org/pt/wol/bc/r5/lp-t/202026087/5/0"
},
{
"texto": "th lição 10",
"url": "https://wol.jw.org/pt/wol/pc/r5/lp-t/202026087/4/0"
}
],
"ponto_a_considerar": "th lição 10"
},
{
"id": "2026-04-13_07",
"is_apostila": true,
"secao": "FAÇA SEU MELHOR NO MINISTÉRIO",
"numero": 4,
"titulo_limpo": "Iniciando conversas",
"recursos": [
{
"tipo": "PARTE",
"label": "Iniciando conversas",
"segundos": 240,
"origem_tempo": "WOL"
}
],
"conteudo_texto": [
"(4 min) DE CASA EM CASA. Ofereça um estudo bíblico. (lmd lição 4 ponto 4)"
],
"referencias": [
{
"texto": "lmd lição 4 ponto 4",
"url": "https://wol.jw.org/pt/wol/pc/r5/lp-t/202026087/5/0"
}
],
"ponto_a_considerar": "lmd lição 4 ponto 4"
},
{
"id": "2026-04-13_08",
"is_apostila": true,
"secao": "FAÇA SEU MELHOR NO MINISTÉRIO",
"numero": 5,
"titulo_limpo": "Iniciando conversas",
"recursos": [
{
"tipo": "PARTE",
"label": "Iniciando conversas",
"segundos": 180,
"origem_tempo": "WOL"
}
],
"conteudo_texto": [
"(3 min) DE CASA EM CASA. Fale sobre uma das verdades do apêndice A da brochura Ame as Pessoas. (lmd lição 3 ponto 3)"
],
"referencias": [
{
"texto": "apêndice A",
"url": "https://wol.jw.org/pt/wol/pc/r5/lp-t/202026087/6/0"
},
{
"texto": "lmd lição 3 ponto 3",
"url": "https://wol.jw.org/pt/wol/pc/r5/lp-t/202026087/7/0"
}
],
"ponto_a_considerar": "lmd lição 3 ponto 3"
},
{
"id": "2026-04-13_09",
"is_apostila": true,
"secao": "FAÇA SEU MELHOR NO MINISTÉRIO",
"numero": 6,
"titulo_limpo": "Cultivando o interesse",
"recursos": [
{
"tipo": "PARTE",
"label": "Cultivando o interesse",
"segundos": 300,
"origem_tempo": "WOL"
}
],
"conteudo_texto": [
"(5 min) DE CASA EM CASA. Ofereça um estudo bíblico para uma pessoa que assistiu à Celebração. (lmd lição 8 ponto 3)"
],
"referencias": [
{
"texto": "lmd lição 8 ponto 3",
"url": "https://wol.jw.org/pt/wol/pc/r5/lp-t/202026087/8/0"
}
],
"ponto_a_considerar": "lmd lição 8 ponto 3"
},
{
"id": "2026-04-13_11",
"is_apostila": false,
"secao": "NOSSA VIDA CRISTÃ",
"numero": -1,
"titulo_limpo": "Cântico 20",
"recursos": [
{
"tipo": "AUDIO",
"label": "Cântico 20",
"segundos": 176.93,
"origem_tempo": "MP3"
}
],
"conteudo_texto": [],
"referencias": []
},
{
"id": "2026-04-13_12",
"is_apostila": true,
"secao": "NOSSA VIDA CRISTÃ",
"numero": 7,
"titulo_limpo": "Torne-se Amigo de Jeová — O Melhor Presente de Todos",
"recursos": [
{
"tipo": "PARTE",
"label": "Torne-se Amigo de Jeová — O Melhor Presente de Todos",
"segundos": 900,
"origem_tempo": "WOL"
}
],
"conteudo_texto": [
"(15 min) Consideração.",
"Mostre o VÍDEO. Depois, pergunte:",
"Por que a morte de Jesus é um presente que Jeová nos deu?",
"Como podemos mostrar gratidão pelo que Jesus fez por nós?"
],
"referencias": [
{
"texto": "Mostre o VÍDEO",
"url": "https://www.jw.org/finder?wtlocale=T&lank=pub-pk_55_VIDEO"
}
]
},
{
"id": "2026-04-13_13",
"is_apostila": true,
"secao": "NOSSA VIDA CRISTÃ",
"numero": 8,
"titulo_limpo": "Estudo bíblico de congregação",
"recursos": [
{
"tipo": "PARTE",
"label": "Estudo bíblico de congregação",
"segundos": 1800,
"origem_tempo": "WOL"
}
],
"conteudo_texto": [
"(30 min) lfb histórias 76-77"
],
"referencias": [
{
"texto": "lfb histórias 76-77",
"url": "https://wol.jw.org/pt/wol/pc/r5/lp-t/202026087/10/0"
}
]
},
{
"id": "2026-04-13_14",
"is_apostila": false,
"secao": "NOSSA VIDA CRISTÃ",
"numero": -1,
"titulo_limpo": "Comentários finais",
"recursos": [
{
"tipo": "AUDIO",
"label": "Cântico 57",
"segundos": 164.21,
"origem_tempo": "MP3"
},
{
"tipo": "ORACAO",
"label": "Oração",
"segundos": 351,
"origem_tempo": "CAL"
},
{
"tipo": "PARTE",
"label": "Comentários finais",
"segundos": 180,
"origem_tempo": "WOL"
}
],
"conteudo_texto": [],
"referencias": []
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment