# ===========================================================================
# LEIA ISTO ANTES DE ACRESCENTAR UM ROBO AQUI EMBAIXO.
#
# As regras deste arquivo moram dentro do macro `regras()` e sao REPETIDAS em
# cada grupo de User-agent. A repeticao nao e desleixo, e obrigacao do formato.
#
# No robots.txt um robo obedece a UM grupo so: o mais especifico que casa com o
# nome dele - e ignora o grupo `*` por inteiro. Entao um grupo assim:
#
# User-agent: ClaudeBot
# Crawl-delay: 2
#
# nao "acrescenta um atraso ao ClaudeBot". Ele faz o ClaudeBot parar de enxergar
# TODOS os `Disallow` de baixo - as paginas atras de login, a paginacao, o
# `/admin/`, o `/stripe/`, tudo. A tentativa de segurar o robo mais pesado do
# site o soltaria justamente em cima do que custa mais caro, e em silencio: o
# arquivo continuaria bonito e nada acusaria erro.
#
# Por isso todo grupo termina chamando o macro `regras()`. E a chamada NAO pode
# ser citada com as chaves duplas aqui nesta prosa: o Jinja expande `{`+`{ ... }`+`}`
# dentro de comentario tambem, e o bloco inteiro de regras vazaria para ca.
# `tests/test_robots.py` falha se algum grupo ficar sem a chamada.
#
# Grupos de `User-agent:` consecutivos, sem linha em branco entre eles, contam
# como UM grupo. E por isso que catorze robos cabem em duas listas em vez de
# catorze copias das regras.
# ===========================================================================
# NOTE: /*/events/ (the events list/search page) is intentionally NOT blocked here.
# It already ships its own `` in
# templates/events/events.html, which lets Googlebot crawl it (and follow the links
# to individual event pages) while keeping the thin, AJAX-filtered listing itself out
# of the index - a robots.txt Disallow would instead stop crawling entirely and could
# prevent discovery of/linking to the indexable per-event pages under /events/.
# Individual event pages carry their own conditional noindex once the event has ended;
# see templates/events/event.html.
# ---------------------------------------------------------------------------
# GRUPO 1 - todo mundo. Sem `Crawl-delay` de proposito: o Googlebot ignora
# `Crawl-delay` por politica declarada, e ele e justamente quem manda gente
# para ca. Ele custa 874 s de origem em tres horas, contra 20.616 s da Meta.
User-agent: *
# Allow everything by default, then block private/account areas, checkout/payment
# endpoints, technical (non-content) endpoints and known duplicate/low-value URLs.
Allow: /
Disallow: /*/decks/*/play
Disallow: /*/decks/*/registration
Disallow: /*/decks/*/exportproxy
Disallow: /*/decks/*/exporttxt
Disallow: /*/decks/*/export
Disallow: /*/decks/*/exportimage
Disallow: /*/profile/*
Disallow: /*/videos/*
Disallow: /*/leagues/*
Disallow: /*/teams/*
Disallow: /*/admin/*
Disallow: /*/stripe/*
Disallow: /*/login/*
Disallow: /*/edit$
Disallow: /*/new$
# As tres abaixo sao irmas do `/*/edit$` e entraram pelo mesmo motivo, medido em
# 2026-08-14 no log da origem: 14% de TUDO que chegava nos servidores era resposta
# 307, e 93% desses 307 eram pagina atras de login sendo varrida por robo. Numa
# janela de 12 minutos, num droplet so: `suggestion` 1.697 pedidos, `edit` 919,
# `history` 153, `pimpmydeck` 130. O `suggestion`, campeao isolado, nao estava aqui.
#
# Doi mais do que parece porque um 307 e INCACHEAVEL neste app: o `add_header` do
# `__init__.py` so marca como `public` o que e 2xx ou (301, 308), entao todo 307 sai
# `private, no-cache` e vai na origem toda vez. Conferido de fora: MISS, MISS, MISS.
#
# Nao se perde nada indexavel: as dez rotas que casam com estas linhas tem TODAS
# `@login_required`, ou seja, para um robo elas nunca foram outra coisa senao um
# redirecionamento para o login.
#
# `send` ficou de fora de proposito. Ele nao existe sem query string (`/card/send?
# image=...&name=...`: 102 pedidos, 102 com query, zero sem), entao um `/*/send$`
# jamais dispararia - seria a "regra que vira enfeite" contra a qual o
# `tests/test_robots.py` existe. Quem ja pega esse caso e o `Disallow: /*/card/*?*`
# la embaixo.
Disallow: /*/suggestion$
Disallow: /*/history$
Disallow: /*/pimpmydeck$
Disallow: /*/meta-decks/top-players
Disallow: /*/meta-decks/tournaments
Disallow: /*/app/
Disallow: /*/cardsearch
Disallow: /*/decks/fromdecktxtotojson
Disallow: /*/decks/getmetadeck
# Same story across every section below: the bare page (and its own search/filter page) is
# real, indexable content; the problem is individual-item pages picking up a query string
# (deck_txt, card_path, keyword, page, etc.), which turns every combination into its own
# crawlable, CDN-cached URL that's essentially never requested twice. "Disallow: /X/*?*"
# blocks that without touching the bare page.
#
# Each search/listing page's own filters (e.g. "?card_path=..." on combo-infinite, "?page=2"
# on articles/search) still need to stay crawlable, since that's how these get discovered/
# indexed in the first place - a plain "Allow: /X/?*" for that can't reliably win, though: per
# Google's robots.txt spec, when an Allow and a Disallow both match a URL the LONGER rule (by
# character count) wins, and "/X/?*" is always exactly one "*" shorter than "/X/*?*" - i.e. the
# Disallow above would always win the tie. The "=*" suffix is not decorative - it pads the
# Allow past that length (real query strings always contain "key=value", so this doesn't
# narrow what actually gets matched).
# PAGINACAO FORA DO ALCANCE DOS ROBOS, so a primeira pagina de cada listagem.
#
# Decisao do dono em 2026-08-12, durante o incidente de lentidao. O que ela
# resolve, medido no dia: `/articles/search/` era 6,6% de tudo que chegava nos
# servidores, com a taxa de acerto da CDN em 4,4%. A pagina CACHEIA
# corretamente (conferido: tres pedidos, tres HIT) - o problema e que cada
# `?keyword=X&page=N` e uma URL diferente que quase ninguem pede duas vezes:
# 862 pedidos para 429 URLs distintas, e como a bunny tem dezenas de pontos de
# presenca, duas visitas no mundo costumam ser dois PoPs, logo dois misses.
# Quem caminhava: GPTBot 56%, mais Amazonbot e ClaudeBot.
#
# A PRIMEIRA PAGINA CONTINUA LIVRE porque ela nao tem `page=` na URL: o
# template canonicaliza `?keyword=&game=1&page=1` para o endereco limpo (ver o
# comentario no topo de `templates/articles.html`). Estas regras so pegam quem
# tem `page=` de verdade.
#
# O `?*page=*` de cada linha e MAIS LONGO que o `Allow` que ele disputa, de
# proposito - vence o padrao mais longo, como explicado acima. Encurtar um
# destes faz o Allow voltar a ganhar e a regra vira enfeite. O do `/card/`
# passa por dentro do `card_name=` por essa razao, e nao por capricho.
# `tests/test_robots.py` falha se isso for desfeito.
#
# O QUE SE PERDE, escrito aqui para nao virar surpresa depois: a paginacao
# profunda era `noindex, follow` - o robo entrava, nao indexava, mas SEGUIA os
# links para os artigos. Com `Disallow` ele nao entra, e esse caminho de
# descoberta acaba. O substituto natural seria o sitemap, e o dono informou em
# 2026-08-12 que o sitemap NAO FUNCIONA. Enquanto nao funcionar, artigo antigo
# depende de link interno para ser reencontrado.
Disallow: /*/decks/deckbuilder?*
Disallow: /*/tools/*?*
Disallow: /*/tools/getlands
Disallow: /*/articles/*?*
Allow: /*/articles/search/?*=*
Disallow: /*/articles/search/?*page=*
Disallow: /*/decks/*?*
# Os DOIS formatos, com e sem barra antes do "?", de proposito.
#
# A linha `Disallow: /*/decks/*?*` acima pega qualquer URL de deck com query, e
# estas duas sao a excecao que libera a busca. Em 2026-08-16 os links do site
# passaram a usar `/decks/search/?...` (com barra), que e para onde o servidor
# ja redirecionava - e a excecao, escrita so na forma SEM barra, deixaria de
# casar: a busca de decks inteira cairia no Disallow de cima e sairia do indice.
# A forma sem barra continua aqui porque e a que esta indexada hoje e a que
# chega de link externo; ela responde 308 para a com barra.
# As linhas de `articles` logo acima ja usavam a forma com barra.
Allow: /*/decks/search?*=*
Disallow: /*/decks/search?*page=*
Allow: /*/decks/search/?*=*
Disallow: /*/decks/search/?*page=*
Disallow: /*/tournament/*?*
Disallow: /*/combo-infinite/*?*
Allow: /*/combo-infinite/?*=*
Disallow: /*/combo-infinite/?*page=*
Disallow: /*/meta-decks/*?*
Disallow: /*/meta-decks/*/*?*
Disallow: /*/sets/*?*
Allow: /*/sets/?*=*
Disallow: /*/sets/?*page=*
Disallow: /*/references/*?*
Allow: /*/references/?*=*
Disallow: /*/references/?*page=*
Disallow: /*/card/*?*
Allow: /*/card/?card_name=*
Disallow: /*/card/?card_name=*page=*
# /videos/ is already fully blocked below (Disallow: /*/videos/*), not just its query-string
# variants - left alone here rather than assumed into this pattern.
Disallow: /*/api/*
Disallow: /*/finance/*
Disallow: /*/seller/*
Disallow: /*/push/*
#
# LOCALES QUE NAO SAO PAGINA NOVA, SAO A MESMA PAGINA OUTRA VEZ.
#
# O `before_request` aceita qualquer um dos 228 `language_locale_code` da tabela,
# e manda 307 para um deles conforme o `Accept-Language` do navegador. Mas o
# site so RECONHECE 61: e o que o `get_all_languages_locales` devolve, o que
# alimenta o seletor de idioma e os `hreflang`. Os 167 restantes respondem 200 e
# renderizam `lang="en-us"` - conferido em 01/09/2026 em `/et-ee/`, `/th-th/`,
# `/vi-vn/` e `/hi-in/`: byte por byte a pagina inglesa, noutro endereco.
#
# CADA UM DELES E UMA CHAVE DE CACHE SEPARADA na CDN. Medido no mesmo dia: 27%
# de acerto (site de conteudo fica entre 80% e 95%), 29,2 milhoes de requisicoes
# em 24h e 937 GB puxados da origem so na zona de Magic. A origem respondia em
# ~5s pela medida da bunny contra 0,23s de processamento real no nginx - a
# diferenca era fila, com os droplets em 60-100% de CPU o dia inteiro.
#
# A lista sai do banco (`mysql_share.get_locales_fora_da_lista`), nao esta
# escrita a mao: ligar um locale no admin o tira daqui sozinho.
#
# ISTO NAO RESOLVE SOZINHO, e a limitacao importa: robots.txt fala com robo, e o
# 307 por `Accept-Language` continua mandando GENTE para ca. Enquanto o
# redirecionamento existir, a fragmentacao de cache continua para o trafego
# humano - o conserto completo e o site parar de rotear para locale que ele nao
# reconhece.
Disallow: /af/
Disallow: /af-za/
Disallow: /ar/
Disallow: /ar-ae/
Disallow: /ar-bh/
Disallow: /ar-dz/
Disallow: /ar-iq/
Disallow: /ar-jo/
Disallow: /ar-kw/
Disallow: /ar-lb/
Disallow: /ar-ly/
Disallow: /ar-om/
Disallow: /ar-qa/
Disallow: /ar-sy/
Disallow: /ar-tn/
Disallow: /ar-ye/
Disallow: /az/
Disallow: /az-az/
Disallow: /be/
Disallow: /be-by/
Disallow: /bg/
Disallow: /bg-bg/
Disallow: /bs-ba/
Disallow: /ca/
Disallow: /cs/
Disallow: /cs-cz/
Disallow: /cy/
Disallow: /cy-gb/
Disallow: /da/
Disallow: /da-dk/
Disallow: /de/
Disallow: /dv/
Disallow: /dv-mv/
Disallow: /el/
Disallow: /el-gr/
Disallow: /en/
Disallow: /en-cb/
Disallow: /eo/
Disallow: /es/
Disallow: /et/
Disallow: /et-ee/
Disallow: /eu/
Disallow: /eu-es/
Disallow: /fa/
Disallow: /fa-ir/
Disallow: /fi/
Disallow: /fi-fi/
Disallow: /fo/
Disallow: /fo-fo/
Disallow: /fr/
Disallow: /gl/
Disallow: /gl-es/
Disallow: /gu/
Disallow: /gu-in/
Disallow: /he/
Disallow: /he-il/
Disallow: /hi/
Disallow: /hi-in/
Disallow: /hr/
Disallow: /hr-ba/
Disallow: /hr-hr/
Disallow: /hu/
Disallow: /hu-hu/
Disallow: /hy/
Disallow: /hy-am/
Disallow: /id/
Disallow: /id-id/
Disallow: /is/
Disallow: /is-is/
Disallow: /it/
Disallow: /ja/
Disallow: /ka/
Disallow: /ka-ge/
Disallow: /kk/
Disallow: /kk-kz/
Disallow: /kn/
Disallow: /kn-in/
Disallow: /ko/
Disallow: /kok/
Disallow: /kok-in/
Disallow: /ky/
Disallow: /ky-kg/
Disallow: /lt/
Disallow: /lt-lt/
Disallow: /lv/
Disallow: /lv-lv/
Disallow: /mi/
Disallow: /mi-nz/
Disallow: /mk/
Disallow: /mk-mk/
Disallow: /mn/
Disallow: /mn-mn/
Disallow: /mr/
Disallow: /mr-in/
Disallow: /ms/
Disallow: /ms-bn/
Disallow: /ms-my/
Disallow: /mt/
Disallow: /mt-mt/
Disallow: /nb/
Disallow: /nb-no/
Disallow: /nl/
Disallow: /nl-be/
Disallow: /nn-no/
Disallow: /ns/
Disallow: /ns-za/
Disallow: /pa/
Disallow: /pa-in/
Disallow: /pl/
Disallow: /pl-pl/
Disallow: /ps/
Disallow: /ps-ar/
Disallow: /pt/
Disallow: /qu/
Disallow: /qu-bo/
Disallow: /qu-ec/
Disallow: /qu-pe/
Disallow: /ro/
Disallow: /ro-ro/
Disallow: /ru/
Disallow: /sa/
Disallow: /sa-in/
Disallow: /se/
Disallow: /se-fi/
Disallow: /se-no/
Disallow: /se-se/
Disallow: /sk/
Disallow: /sk-sk/
Disallow: /sl/
Disallow: /sl-si/
Disallow: /sq/
Disallow: /sq-al/
Disallow: /sr-ba/
Disallow: /sr-sp/
Disallow: /sv/
Disallow: /sw/
Disallow: /sw-ke/
Disallow: /syr/
Disallow: /syr-sy/
Disallow: /ta/
Disallow: /ta-in/
Disallow: /te/
Disallow: /te-in/
Disallow: /th/
Disallow: /th-th/
Disallow: /tl/
Disallow: /tl-ph/
Disallow: /tn/
Disallow: /tn-za/
Disallow: /tr/
Disallow: /tr-tr/
Disallow: /ts/
Disallow: /tt/
Disallow: /tt-ru/
Disallow: /uk/
Disallow: /uk-ua/
Disallow: /ur/
Disallow: /ur-pk/
Disallow: /uz/
Disallow: /uz-uz/
Disallow: /vi/
Disallow: /vi-vn/
Disallow: /xh/
Disallow: /xh-za/
Disallow: /zh/
Disallow: /zu/
Disallow: /zu-za/
# ---------------------------------------------------------------------------
# GRUPO 2 - os robos que EXTRAEM conteudo em vez de mandar visita.
#
# Medido em 30/08/2026, no `access.log` da origem, janela de 3 horas num
# droplet (28/08, 14h-17h UTC), somando `upstream_response_time` por robo:
# 51% de TODO o tempo de processamento da origem era robo declarado.
#
# Meta (meta-externalagent + meta-webindexer) 20.616 s 167 mil pedidos
# ClaudeBot 10.578 s 72 mil
# Amazonbot 4.975 s 28 mil
# Bytespider 4.129 s 18 mil
# GPTBot + OAI-SearchBot 2.018 s 11 mil
# PetalBot 918 s 4 mil
# PerplexityBot 332 s 1 mil
# ---
# Googlebot (o que traz busca) 874 s 5 mil
#
# `Crawl-delay: 2` = meio pedido por segundo por robo, ou 43 mil paginas por
# dia. Continua sendo orcamento de rastreio de sobra para um site de conteudo,
# e derruba o ClaudeBot de ~10 pedidos/s para 0,5 - vinte vezes menos.
#
# NEM TODOS OBEDECEM, e isto esta escrito aqui para nao virar surpresa:
# Amazonbot, Bytespider, PetalBot e os robos de SEO do grupo 3 documentam
# obediencia a `Crawl-delay`. A OpenAI nao documenta suporte, e a Meta tambem
# nao - para esses dois a linha e aposta, nao garantia. Se numa medicao futura
# o volume deles nao cair, o passo seguinte e bloqueio, nao atraso.
User-agent: ClaudeBot
User-agent: meta-externalagent
User-agent: meta-webindexer
User-agent: Amazonbot
User-agent: Bytespider
User-agent: GPTBot
User-agent: OAI-SearchBot
User-agent: PerplexityBot
User-agent: PetalBot
Crawl-delay: 2
# Allow everything by default, then block private/account areas, checkout/payment
# endpoints, technical (non-content) endpoints and known duplicate/low-value URLs.
Allow: /
Disallow: /*/decks/*/play
Disallow: /*/decks/*/registration
Disallow: /*/decks/*/exportproxy
Disallow: /*/decks/*/exporttxt
Disallow: /*/decks/*/export
Disallow: /*/decks/*/exportimage
Disallow: /*/profile/*
Disallow: /*/videos/*
Disallow: /*/leagues/*
Disallow: /*/teams/*
Disallow: /*/admin/*
Disallow: /*/stripe/*
Disallow: /*/login/*
Disallow: /*/edit$
Disallow: /*/new$
# As tres abaixo sao irmas do `/*/edit$` e entraram pelo mesmo motivo, medido em
# 2026-08-14 no log da origem: 14% de TUDO que chegava nos servidores era resposta
# 307, e 93% desses 307 eram pagina atras de login sendo varrida por robo. Numa
# janela de 12 minutos, num droplet so: `suggestion` 1.697 pedidos, `edit` 919,
# `history` 153, `pimpmydeck` 130. O `suggestion`, campeao isolado, nao estava aqui.
#
# Doi mais do que parece porque um 307 e INCACHEAVEL neste app: o `add_header` do
# `__init__.py` so marca como `public` o que e 2xx ou (301, 308), entao todo 307 sai
# `private, no-cache` e vai na origem toda vez. Conferido de fora: MISS, MISS, MISS.
#
# Nao se perde nada indexavel: as dez rotas que casam com estas linhas tem TODAS
# `@login_required`, ou seja, para um robo elas nunca foram outra coisa senao um
# redirecionamento para o login.
#
# `send` ficou de fora de proposito. Ele nao existe sem query string (`/card/send?
# image=...&name=...`: 102 pedidos, 102 com query, zero sem), entao um `/*/send$`
# jamais dispararia - seria a "regra que vira enfeite" contra a qual o
# `tests/test_robots.py` existe. Quem ja pega esse caso e o `Disallow: /*/card/*?*`
# la embaixo.
Disallow: /*/suggestion$
Disallow: /*/history$
Disallow: /*/pimpmydeck$
Disallow: /*/meta-decks/top-players
Disallow: /*/meta-decks/tournaments
Disallow: /*/app/
Disallow: /*/cardsearch
Disallow: /*/decks/fromdecktxtotojson
Disallow: /*/decks/getmetadeck
# Same story across every section below: the bare page (and its own search/filter page) is
# real, indexable content; the problem is individual-item pages picking up a query string
# (deck_txt, card_path, keyword, page, etc.), which turns every combination into its own
# crawlable, CDN-cached URL that's essentially never requested twice. "Disallow: /X/*?*"
# blocks that without touching the bare page.
#
# Each search/listing page's own filters (e.g. "?card_path=..." on combo-infinite, "?page=2"
# on articles/search) still need to stay crawlable, since that's how these get discovered/
# indexed in the first place - a plain "Allow: /X/?*" for that can't reliably win, though: per
# Google's robots.txt spec, when an Allow and a Disallow both match a URL the LONGER rule (by
# character count) wins, and "/X/?*" is always exactly one "*" shorter than "/X/*?*" - i.e. the
# Disallow above would always win the tie. The "=*" suffix is not decorative - it pads the
# Allow past that length (real query strings always contain "key=value", so this doesn't
# narrow what actually gets matched).
# PAGINACAO FORA DO ALCANCE DOS ROBOS, so a primeira pagina de cada listagem.
#
# Decisao do dono em 2026-08-12, durante o incidente de lentidao. O que ela
# resolve, medido no dia: `/articles/search/` era 6,6% de tudo que chegava nos
# servidores, com a taxa de acerto da CDN em 4,4%. A pagina CACHEIA
# corretamente (conferido: tres pedidos, tres HIT) - o problema e que cada
# `?keyword=X&page=N` e uma URL diferente que quase ninguem pede duas vezes:
# 862 pedidos para 429 URLs distintas, e como a bunny tem dezenas de pontos de
# presenca, duas visitas no mundo costumam ser dois PoPs, logo dois misses.
# Quem caminhava: GPTBot 56%, mais Amazonbot e ClaudeBot.
#
# A PRIMEIRA PAGINA CONTINUA LIVRE porque ela nao tem `page=` na URL: o
# template canonicaliza `?keyword=&game=1&page=1` para o endereco limpo (ver o
# comentario no topo de `templates/articles.html`). Estas regras so pegam quem
# tem `page=` de verdade.
#
# O `?*page=*` de cada linha e MAIS LONGO que o `Allow` que ele disputa, de
# proposito - vence o padrao mais longo, como explicado acima. Encurtar um
# destes faz o Allow voltar a ganhar e a regra vira enfeite. O do `/card/`
# passa por dentro do `card_name=` por essa razao, e nao por capricho.
# `tests/test_robots.py` falha se isso for desfeito.
#
# O QUE SE PERDE, escrito aqui para nao virar surpresa depois: a paginacao
# profunda era `noindex, follow` - o robo entrava, nao indexava, mas SEGUIA os
# links para os artigos. Com `Disallow` ele nao entra, e esse caminho de
# descoberta acaba. O substituto natural seria o sitemap, e o dono informou em
# 2026-08-12 que o sitemap NAO FUNCIONA. Enquanto nao funcionar, artigo antigo
# depende de link interno para ser reencontrado.
Disallow: /*/decks/deckbuilder?*
Disallow: /*/tools/*?*
Disallow: /*/tools/getlands
Disallow: /*/articles/*?*
Allow: /*/articles/search/?*=*
Disallow: /*/articles/search/?*page=*
Disallow: /*/decks/*?*
# Os DOIS formatos, com e sem barra antes do "?", de proposito.
#
# A linha `Disallow: /*/decks/*?*` acima pega qualquer URL de deck com query, e
# estas duas sao a excecao que libera a busca. Em 2026-08-16 os links do site
# passaram a usar `/decks/search/?...` (com barra), que e para onde o servidor
# ja redirecionava - e a excecao, escrita so na forma SEM barra, deixaria de
# casar: a busca de decks inteira cairia no Disallow de cima e sairia do indice.
# A forma sem barra continua aqui porque e a que esta indexada hoje e a que
# chega de link externo; ela responde 308 para a com barra.
# As linhas de `articles` logo acima ja usavam a forma com barra.
Allow: /*/decks/search?*=*
Disallow: /*/decks/search?*page=*
Allow: /*/decks/search/?*=*
Disallow: /*/decks/search/?*page=*
Disallow: /*/tournament/*?*
Disallow: /*/combo-infinite/*?*
Allow: /*/combo-infinite/?*=*
Disallow: /*/combo-infinite/?*page=*
Disallow: /*/meta-decks/*?*
Disallow: /*/meta-decks/*/*?*
Disallow: /*/sets/*?*
Allow: /*/sets/?*=*
Disallow: /*/sets/?*page=*
Disallow: /*/references/*?*
Allow: /*/references/?*=*
Disallow: /*/references/?*page=*
Disallow: /*/card/*?*
Allow: /*/card/?card_name=*
Disallow: /*/card/?card_name=*page=*
# /videos/ is already fully blocked below (Disallow: /*/videos/*), not just its query-string
# variants - left alone here rather than assumed into this pattern.
Disallow: /*/api/*
Disallow: /*/finance/*
Disallow: /*/seller/*
Disallow: /*/push/*
#
# LOCALES QUE NAO SAO PAGINA NOVA, SAO A MESMA PAGINA OUTRA VEZ.
#
# O `before_request` aceita qualquer um dos 228 `language_locale_code` da tabela,
# e manda 307 para um deles conforme o `Accept-Language` do navegador. Mas o
# site so RECONHECE 61: e o que o `get_all_languages_locales` devolve, o que
# alimenta o seletor de idioma e os `hreflang`. Os 167 restantes respondem 200 e
# renderizam `lang="en-us"` - conferido em 01/09/2026 em `/et-ee/`, `/th-th/`,
# `/vi-vn/` e `/hi-in/`: byte por byte a pagina inglesa, noutro endereco.
#
# CADA UM DELES E UMA CHAVE DE CACHE SEPARADA na CDN. Medido no mesmo dia: 27%
# de acerto (site de conteudo fica entre 80% e 95%), 29,2 milhoes de requisicoes
# em 24h e 937 GB puxados da origem so na zona de Magic. A origem respondia em
# ~5s pela medida da bunny contra 0,23s de processamento real no nginx - a
# diferenca era fila, com os droplets em 60-100% de CPU o dia inteiro.
#
# A lista sai do banco (`mysql_share.get_locales_fora_da_lista`), nao esta
# escrita a mao: ligar um locale no admin o tira daqui sozinho.
#
# ISTO NAO RESOLVE SOZINHO, e a limitacao importa: robots.txt fala com robo, e o
# 307 por `Accept-Language` continua mandando GENTE para ca. Enquanto o
# redirecionamento existir, a fragmentacao de cache continua para o trafego
# humano - o conserto completo e o site parar de rotear para locale que ele nao
# reconhece.
Disallow: /af/
Disallow: /af-za/
Disallow: /ar/
Disallow: /ar-ae/
Disallow: /ar-bh/
Disallow: /ar-dz/
Disallow: /ar-iq/
Disallow: /ar-jo/
Disallow: /ar-kw/
Disallow: /ar-lb/
Disallow: /ar-ly/
Disallow: /ar-om/
Disallow: /ar-qa/
Disallow: /ar-sy/
Disallow: /ar-tn/
Disallow: /ar-ye/
Disallow: /az/
Disallow: /az-az/
Disallow: /be/
Disallow: /be-by/
Disallow: /bg/
Disallow: /bg-bg/
Disallow: /bs-ba/
Disallow: /ca/
Disallow: /cs/
Disallow: /cs-cz/
Disallow: /cy/
Disallow: /cy-gb/
Disallow: /da/
Disallow: /da-dk/
Disallow: /de/
Disallow: /dv/
Disallow: /dv-mv/
Disallow: /el/
Disallow: /el-gr/
Disallow: /en/
Disallow: /en-cb/
Disallow: /eo/
Disallow: /es/
Disallow: /et/
Disallow: /et-ee/
Disallow: /eu/
Disallow: /eu-es/
Disallow: /fa/
Disallow: /fa-ir/
Disallow: /fi/
Disallow: /fi-fi/
Disallow: /fo/
Disallow: /fo-fo/
Disallow: /fr/
Disallow: /gl/
Disallow: /gl-es/
Disallow: /gu/
Disallow: /gu-in/
Disallow: /he/
Disallow: /he-il/
Disallow: /hi/
Disallow: /hi-in/
Disallow: /hr/
Disallow: /hr-ba/
Disallow: /hr-hr/
Disallow: /hu/
Disallow: /hu-hu/
Disallow: /hy/
Disallow: /hy-am/
Disallow: /id/
Disallow: /id-id/
Disallow: /is/
Disallow: /is-is/
Disallow: /it/
Disallow: /ja/
Disallow: /ka/
Disallow: /ka-ge/
Disallow: /kk/
Disallow: /kk-kz/
Disallow: /kn/
Disallow: /kn-in/
Disallow: /ko/
Disallow: /kok/
Disallow: /kok-in/
Disallow: /ky/
Disallow: /ky-kg/
Disallow: /lt/
Disallow: /lt-lt/
Disallow: /lv/
Disallow: /lv-lv/
Disallow: /mi/
Disallow: /mi-nz/
Disallow: /mk/
Disallow: /mk-mk/
Disallow: /mn/
Disallow: /mn-mn/
Disallow: /mr/
Disallow: /mr-in/
Disallow: /ms/
Disallow: /ms-bn/
Disallow: /ms-my/
Disallow: /mt/
Disallow: /mt-mt/
Disallow: /nb/
Disallow: /nb-no/
Disallow: /nl/
Disallow: /nl-be/
Disallow: /nn-no/
Disallow: /ns/
Disallow: /ns-za/
Disallow: /pa/
Disallow: /pa-in/
Disallow: /pl/
Disallow: /pl-pl/
Disallow: /ps/
Disallow: /ps-ar/
Disallow: /pt/
Disallow: /qu/
Disallow: /qu-bo/
Disallow: /qu-ec/
Disallow: /qu-pe/
Disallow: /ro/
Disallow: /ro-ro/
Disallow: /ru/
Disallow: /sa/
Disallow: /sa-in/
Disallow: /se/
Disallow: /se-fi/
Disallow: /se-no/
Disallow: /se-se/
Disallow: /sk/
Disallow: /sk-sk/
Disallow: /sl/
Disallow: /sl-si/
Disallow: /sq/
Disallow: /sq-al/
Disallow: /sr-ba/
Disallow: /sr-sp/
Disallow: /sv/
Disallow: /sw/
Disallow: /sw-ke/
Disallow: /syr/
Disallow: /syr-sy/
Disallow: /ta/
Disallow: /ta-in/
Disallow: /te/
Disallow: /te-in/
Disallow: /th/
Disallow: /th-th/
Disallow: /tl/
Disallow: /tl-ph/
Disallow: /tn/
Disallow: /tn-za/
Disallow: /tr/
Disallow: /tr-tr/
Disallow: /ts/
Disallow: /tt/
Disallow: /tt-ru/
Disallow: /uk/
Disallow: /uk-ua/
Disallow: /ur/
Disallow: /ur-pk/
Disallow: /uz/
Disallow: /uz-uz/
Disallow: /vi/
Disallow: /vi-vn/
Disallow: /xh/
Disallow: /xh-za/
Disallow: /zh/
Disallow: /zu/
Disallow: /zu-za/
# ---------------------------------------------------------------------------
# GRUPO 3 - ferramentas de SEO. Rastreiam o site inteiro para vender relatorio
# de backlink a terceiros e nao mandam UMA visita de volta; juntas custavam
# 3.373 s na mesma janela de tres horas. Dez segundos de intervalo (8.640
# paginas por dia) e generoso para o que elas devolvem.
User-agent: AhrefsBot
User-agent: SemrushBot
User-agent: SERankingBacklinksBot
User-agent: DataForSeoBot
User-agent: MJ12bot
Crawl-delay: 10
# Allow everything by default, then block private/account areas, checkout/payment
# endpoints, technical (non-content) endpoints and known duplicate/low-value URLs.
Allow: /
Disallow: /*/decks/*/play
Disallow: /*/decks/*/registration
Disallow: /*/decks/*/exportproxy
Disallow: /*/decks/*/exporttxt
Disallow: /*/decks/*/export
Disallow: /*/decks/*/exportimage
Disallow: /*/profile/*
Disallow: /*/videos/*
Disallow: /*/leagues/*
Disallow: /*/teams/*
Disallow: /*/admin/*
Disallow: /*/stripe/*
Disallow: /*/login/*
Disallow: /*/edit$
Disallow: /*/new$
# As tres abaixo sao irmas do `/*/edit$` e entraram pelo mesmo motivo, medido em
# 2026-08-14 no log da origem: 14% de TUDO que chegava nos servidores era resposta
# 307, e 93% desses 307 eram pagina atras de login sendo varrida por robo. Numa
# janela de 12 minutos, num droplet so: `suggestion` 1.697 pedidos, `edit` 919,
# `history` 153, `pimpmydeck` 130. O `suggestion`, campeao isolado, nao estava aqui.
#
# Doi mais do que parece porque um 307 e INCACHEAVEL neste app: o `add_header` do
# `__init__.py` so marca como `public` o que e 2xx ou (301, 308), entao todo 307 sai
# `private, no-cache` e vai na origem toda vez. Conferido de fora: MISS, MISS, MISS.
#
# Nao se perde nada indexavel: as dez rotas que casam com estas linhas tem TODAS
# `@login_required`, ou seja, para um robo elas nunca foram outra coisa senao um
# redirecionamento para o login.
#
# `send` ficou de fora de proposito. Ele nao existe sem query string (`/card/send?
# image=...&name=...`: 102 pedidos, 102 com query, zero sem), entao um `/*/send$`
# jamais dispararia - seria a "regra que vira enfeite" contra a qual o
# `tests/test_robots.py` existe. Quem ja pega esse caso e o `Disallow: /*/card/*?*`
# la embaixo.
Disallow: /*/suggestion$
Disallow: /*/history$
Disallow: /*/pimpmydeck$
Disallow: /*/meta-decks/top-players
Disallow: /*/meta-decks/tournaments
Disallow: /*/app/
Disallow: /*/cardsearch
Disallow: /*/decks/fromdecktxtotojson
Disallow: /*/decks/getmetadeck
# Same story across every section below: the bare page (and its own search/filter page) is
# real, indexable content; the problem is individual-item pages picking up a query string
# (deck_txt, card_path, keyword, page, etc.), which turns every combination into its own
# crawlable, CDN-cached URL that's essentially never requested twice. "Disallow: /X/*?*"
# blocks that without touching the bare page.
#
# Each search/listing page's own filters (e.g. "?card_path=..." on combo-infinite, "?page=2"
# on articles/search) still need to stay crawlable, since that's how these get discovered/
# indexed in the first place - a plain "Allow: /X/?*" for that can't reliably win, though: per
# Google's robots.txt spec, when an Allow and a Disallow both match a URL the LONGER rule (by
# character count) wins, and "/X/?*" is always exactly one "*" shorter than "/X/*?*" - i.e. the
# Disallow above would always win the tie. The "=*" suffix is not decorative - it pads the
# Allow past that length (real query strings always contain "key=value", so this doesn't
# narrow what actually gets matched).
# PAGINACAO FORA DO ALCANCE DOS ROBOS, so a primeira pagina de cada listagem.
#
# Decisao do dono em 2026-08-12, durante o incidente de lentidao. O que ela
# resolve, medido no dia: `/articles/search/` era 6,6% de tudo que chegava nos
# servidores, com a taxa de acerto da CDN em 4,4%. A pagina CACHEIA
# corretamente (conferido: tres pedidos, tres HIT) - o problema e que cada
# `?keyword=X&page=N` e uma URL diferente que quase ninguem pede duas vezes:
# 862 pedidos para 429 URLs distintas, e como a bunny tem dezenas de pontos de
# presenca, duas visitas no mundo costumam ser dois PoPs, logo dois misses.
# Quem caminhava: GPTBot 56%, mais Amazonbot e ClaudeBot.
#
# A PRIMEIRA PAGINA CONTINUA LIVRE porque ela nao tem `page=` na URL: o
# template canonicaliza `?keyword=&game=1&page=1` para o endereco limpo (ver o
# comentario no topo de `templates/articles.html`). Estas regras so pegam quem
# tem `page=` de verdade.
#
# O `?*page=*` de cada linha e MAIS LONGO que o `Allow` que ele disputa, de
# proposito - vence o padrao mais longo, como explicado acima. Encurtar um
# destes faz o Allow voltar a ganhar e a regra vira enfeite. O do `/card/`
# passa por dentro do `card_name=` por essa razao, e nao por capricho.
# `tests/test_robots.py` falha se isso for desfeito.
#
# O QUE SE PERDE, escrito aqui para nao virar surpresa depois: a paginacao
# profunda era `noindex, follow` - o robo entrava, nao indexava, mas SEGUIA os
# links para os artigos. Com `Disallow` ele nao entra, e esse caminho de
# descoberta acaba. O substituto natural seria o sitemap, e o dono informou em
# 2026-08-12 que o sitemap NAO FUNCIONA. Enquanto nao funcionar, artigo antigo
# depende de link interno para ser reencontrado.
Disallow: /*/decks/deckbuilder?*
Disallow: /*/tools/*?*
Disallow: /*/tools/getlands
Disallow: /*/articles/*?*
Allow: /*/articles/search/?*=*
Disallow: /*/articles/search/?*page=*
Disallow: /*/decks/*?*
# Os DOIS formatos, com e sem barra antes do "?", de proposito.
#
# A linha `Disallow: /*/decks/*?*` acima pega qualquer URL de deck com query, e
# estas duas sao a excecao que libera a busca. Em 2026-08-16 os links do site
# passaram a usar `/decks/search/?...` (com barra), que e para onde o servidor
# ja redirecionava - e a excecao, escrita so na forma SEM barra, deixaria de
# casar: a busca de decks inteira cairia no Disallow de cima e sairia do indice.
# A forma sem barra continua aqui porque e a que esta indexada hoje e a que
# chega de link externo; ela responde 308 para a com barra.
# As linhas de `articles` logo acima ja usavam a forma com barra.
Allow: /*/decks/search?*=*
Disallow: /*/decks/search?*page=*
Allow: /*/decks/search/?*=*
Disallow: /*/decks/search/?*page=*
Disallow: /*/tournament/*?*
Disallow: /*/combo-infinite/*?*
Allow: /*/combo-infinite/?*=*
Disallow: /*/combo-infinite/?*page=*
Disallow: /*/meta-decks/*?*
Disallow: /*/meta-decks/*/*?*
Disallow: /*/sets/*?*
Allow: /*/sets/?*=*
Disallow: /*/sets/?*page=*
Disallow: /*/references/*?*
Allow: /*/references/?*=*
Disallow: /*/references/?*page=*
Disallow: /*/card/*?*
Allow: /*/card/?card_name=*
Disallow: /*/card/?card_name=*page=*
# /videos/ is already fully blocked below (Disallow: /*/videos/*), not just its query-string
# variants - left alone here rather than assumed into this pattern.
Disallow: /*/api/*
Disallow: /*/finance/*
Disallow: /*/seller/*
Disallow: /*/push/*
#
# LOCALES QUE NAO SAO PAGINA NOVA, SAO A MESMA PAGINA OUTRA VEZ.
#
# O `before_request` aceita qualquer um dos 228 `language_locale_code` da tabela,
# e manda 307 para um deles conforme o `Accept-Language` do navegador. Mas o
# site so RECONHECE 61: e o que o `get_all_languages_locales` devolve, o que
# alimenta o seletor de idioma e os `hreflang`. Os 167 restantes respondem 200 e
# renderizam `lang="en-us"` - conferido em 01/09/2026 em `/et-ee/`, `/th-th/`,
# `/vi-vn/` e `/hi-in/`: byte por byte a pagina inglesa, noutro endereco.
#
# CADA UM DELES E UMA CHAVE DE CACHE SEPARADA na CDN. Medido no mesmo dia: 27%
# de acerto (site de conteudo fica entre 80% e 95%), 29,2 milhoes de requisicoes
# em 24h e 937 GB puxados da origem so na zona de Magic. A origem respondia em
# ~5s pela medida da bunny contra 0,23s de processamento real no nginx - a
# diferenca era fila, com os droplets em 60-100% de CPU o dia inteiro.
#
# A lista sai do banco (`mysql_share.get_locales_fora_da_lista`), nao esta
# escrita a mao: ligar um locale no admin o tira daqui sozinho.
#
# ISTO NAO RESOLVE SOZINHO, e a limitacao importa: robots.txt fala com robo, e o
# 307 por `Accept-Language` continua mandando GENTE para ca. Enquanto o
# redirecionamento existir, a fragmentacao de cache continua para o trafego
# humano - o conserto completo e o site parar de rotear para locale que ele nao
# reconhece.
Disallow: /af/
Disallow: /af-za/
Disallow: /ar/
Disallow: /ar-ae/
Disallow: /ar-bh/
Disallow: /ar-dz/
Disallow: /ar-iq/
Disallow: /ar-jo/
Disallow: /ar-kw/
Disallow: /ar-lb/
Disallow: /ar-ly/
Disallow: /ar-om/
Disallow: /ar-qa/
Disallow: /ar-sy/
Disallow: /ar-tn/
Disallow: /ar-ye/
Disallow: /az/
Disallow: /az-az/
Disallow: /be/
Disallow: /be-by/
Disallow: /bg/
Disallow: /bg-bg/
Disallow: /bs-ba/
Disallow: /ca/
Disallow: /cs/
Disallow: /cs-cz/
Disallow: /cy/
Disallow: /cy-gb/
Disallow: /da/
Disallow: /da-dk/
Disallow: /de/
Disallow: /dv/
Disallow: /dv-mv/
Disallow: /el/
Disallow: /el-gr/
Disallow: /en/
Disallow: /en-cb/
Disallow: /eo/
Disallow: /es/
Disallow: /et/
Disallow: /et-ee/
Disallow: /eu/
Disallow: /eu-es/
Disallow: /fa/
Disallow: /fa-ir/
Disallow: /fi/
Disallow: /fi-fi/
Disallow: /fo/
Disallow: /fo-fo/
Disallow: /fr/
Disallow: /gl/
Disallow: /gl-es/
Disallow: /gu/
Disallow: /gu-in/
Disallow: /he/
Disallow: /he-il/
Disallow: /hi/
Disallow: /hi-in/
Disallow: /hr/
Disallow: /hr-ba/
Disallow: /hr-hr/
Disallow: /hu/
Disallow: /hu-hu/
Disallow: /hy/
Disallow: /hy-am/
Disallow: /id/
Disallow: /id-id/
Disallow: /is/
Disallow: /is-is/
Disallow: /it/
Disallow: /ja/
Disallow: /ka/
Disallow: /ka-ge/
Disallow: /kk/
Disallow: /kk-kz/
Disallow: /kn/
Disallow: /kn-in/
Disallow: /ko/
Disallow: /kok/
Disallow: /kok-in/
Disallow: /ky/
Disallow: /ky-kg/
Disallow: /lt/
Disallow: /lt-lt/
Disallow: /lv/
Disallow: /lv-lv/
Disallow: /mi/
Disallow: /mi-nz/
Disallow: /mk/
Disallow: /mk-mk/
Disallow: /mn/
Disallow: /mn-mn/
Disallow: /mr/
Disallow: /mr-in/
Disallow: /ms/
Disallow: /ms-bn/
Disallow: /ms-my/
Disallow: /mt/
Disallow: /mt-mt/
Disallow: /nb/
Disallow: /nb-no/
Disallow: /nl/
Disallow: /nl-be/
Disallow: /nn-no/
Disallow: /ns/
Disallow: /ns-za/
Disallow: /pa/
Disallow: /pa-in/
Disallow: /pl/
Disallow: /pl-pl/
Disallow: /ps/
Disallow: /ps-ar/
Disallow: /pt/
Disallow: /qu/
Disallow: /qu-bo/
Disallow: /qu-ec/
Disallow: /qu-pe/
Disallow: /ro/
Disallow: /ro-ro/
Disallow: /ru/
Disallow: /sa/
Disallow: /sa-in/
Disallow: /se/
Disallow: /se-fi/
Disallow: /se-no/
Disallow: /se-se/
Disallow: /sk/
Disallow: /sk-sk/
Disallow: /sl/
Disallow: /sl-si/
Disallow: /sq/
Disallow: /sq-al/
Disallow: /sr-ba/
Disallow: /sr-sp/
Disallow: /sv/
Disallow: /sw/
Disallow: /sw-ke/
Disallow: /syr/
Disallow: /syr-sy/
Disallow: /ta/
Disallow: /ta-in/
Disallow: /te/
Disallow: /te-in/
Disallow: /th/
Disallow: /th-th/
Disallow: /tl/
Disallow: /tl-ph/
Disallow: /tn/
Disallow: /tn-za/
Disallow: /tr/
Disallow: /tr-tr/
Disallow: /ts/
Disallow: /tt/
Disallow: /tt-ru/
Disallow: /uk/
Disallow: /uk-ua/
Disallow: /ur/
Disallow: /ur-pk/
Disallow: /uz/
Disallow: /uz-uz/
Disallow: /vi/
Disallow: /vi-vn/
Disallow: /xh/
Disallow: /xh-za/
Disallow: /zh/
Disallow: /zu/
Disallow: /zu-za/
# XML sitemaps generated by update_sitemap.py (served from /sitemaps/.xml)
#
# Cada linha so sai se o ARQUIVO existir. `update_sitemap.py` nao escreve nada para
# categoria vazia, entao declarar a lista fixa fazia lecursos, maratonapop, umgamer,
# semfirula e rendaprime apontarem para 3 a 5 sitemaps que respondem 404 - 22 dos 50
# declarados na rede, medidos em 19/08/2026. `sitemaps_gerados` e o conjunto de
# categorias achadas em disco para `g.website_id` (ver `_categorias_de_sitemap` no
# `__init__.py`); quando ele vem `None` a leitura falhou e declara-se tudo, como antes.
#
# A flag do jogo NAO resolve isto: o robots.txt e por JOGO e o sitemap e por WEBSITE,
# e um website (cardsrealm.com) reune varios jogos - `g.game_has_products` do jogo
# padrao nao diz nada sobre o arquivo do website existir.
Sitemap: https://lecursos.com/sitemaps/sitemap-2-articles-1.xml
# Combos, ferramentas e desafios entraram em 18/08/2026. O de combos e o que
# mais importa: sao ~5.200 paginas, a busca mostra 24 por vez e a paginacao
# esta bloqueada aqui em cima - sem o sitemap nao havia caminho nenhum ate elas.
Sitemap: https://lecursos.com/sitemaps/sitemap-2-tools-1.xml
Sitemap: https://lecursos.com/sitemaps/sitemap-2-challenges-1.xml
# O de referencias o `update_sitemap.py` ja gerava desde sempre e ninguem
# declarava: o arquivo estava la, com as ~758 referencias, e o robots.txt so
# falava de `/references/` para PROIBIR paginacao. Entrou em 18/08/2026.
# RSS/Atom feeds are also valid "Sitemap:" entries per the sitemaps.org protocol and
# help crawlers discover freshly-published content between XML sitemap regenerations.
Sitemap: https://lecursos.com/en-us/en/feed.rss
Sitemap: https://lecursos.com/pt-br/pt/feed.rss