Marcar posts no tópico como "lidos"

Tentei fazer a engenharia reversa do site enviando requisições POST para “https://{hostUrl}/topics/timings” com content-type, token csrf e user-agent.

Aqui está como o corpo (json) se parece:

payload = {
  "topic_id": topic_id,
  "topic_time": post_count * 60000,
  "timings": timings
}

Ele retorna um código de status 200, mas o histórico de leitura nunca muda em https://{hostUrl}/u/USERNAME/activity/read

Tentei procurar neste post, mas não ajudou muito:

Aqui está uma boa parte do código:

def get_csrf(session):
    r = session.get(f"https://{hostUrl}/session/csrf.json")

    if r.status_code != 200:
        raise RuntimeError("Falha ao obter CSRF")

    data = r.json()

    if "csrf" not in data:
        raise RuntimeError("Nenhum CSRF na resposta")

    return data["csrf"]

def load_topics(session, page):
    print(f"[Tópicos] Página {page}")

    r = session.get(
        f"https://{hostUrl}/latest.json?page={page}"
    )

    if r.status_code != 200:
        return []

    data = r.json()

    return [
        {
            "id": t["id"],
            "posts_count": t["posts_count"]
        }
        for t in data["topic_list"]["topics"]
    ]

def mark_post_as_read(session, topic_id, post_count):
    url = f"https://{hostUrl}/topics/timings"

    timings = {
        str(i): 60000
        for i in range(1, post_count + 1)
    }

    payload = {
        "topic_id": topic_id,
        "topic_time": post_count * 60000,
        "timings": timings
    }

    csrf = get_csrf(session)

    r = session.post(
        url,
        json=payload,
        headers={
            "X-CSRF-Token": csrf,
            "User-Agent": "Mozilla/5.0",
            "Content-Type": "application/json"
        }
    )

    print(f"[Leitura] {topic_id} → {r.status_code}")

    if r.status_code != 200:
        print(r.text[:300])

def tab_worker(session):
    page = 1

    while True:
        topics = load_topics(session, page)

        if not topics:
            break

        for t in topics:
            mark_post_as_read(
                session,
                t["id"],
                t["posts_count"]
            )

            time.sleep(0.4)

        page += 1

Estou voltando a este tópico porque ainda preciso da resposta.

Obrigado

E se você enviar isso como um \"timings[post_number]\": [duration_in_ms] plano? Funciona?

2 curtidas

Não parece funcionar exatamente.

Aqui está o trecho de código que modifiquei.

def mark_post_as_read(session, topic_id, post_count):
    url = f"https://{hostUrl}/topics/timings"

    payload = {
        "topic_id": topic_id,
        "topic_time": post_count * 60000
    }

    for i in range(1, post_count):
        payload[f"timings[{i}]"] = 60000

    csrf = get_csrf(session)

    r = session.post(
        url,
        json=payload,
        headers={
            "X-CSRF-Token": csrf,
            "User-Agent": "Mozilla/5.0",
            "Content-Type": "application/json"
        }
    )

    print(f"[Lido] {topic_id} → {r.status_code}")

    if r.status_code != 200:
        print(r.text[:300])

Ainda retorna 200, mas não é atualizado.

Obrigado

O código parece bom, não tenho certeza de onde vem o problema. :confused:
Meu instinto me diz que estamos apenas perdendo algo óbvio em algum lugar :sweat_smile:

Isto funciona:

def load_topics(session, page):
    print(f"[Tópicos] Carregando página {page}")
    r = session.get(f"https://{hostUrl}/latest.json?page={page}")
    if r.status_code != 200:
        return []
    return [{"id": t["id"], "posts_count": t["posts_count"]} for t in r.json()["topic_list"]["topics"]]
    timings = {
        str(i): 60000
        for i in range(1, post_count + 1)
    }
    payload = {
        "topic_id": topic_id,
        "topic_time": post_count * 60000,
        "timings": timings 
    }    

    # Use json=payload para enviar como application/json
    r = session.post(url, json=payload, headers = {
        "X-CSRF-Token": csrf,
        "User-Agent": "Mozilla/5.0",
        "X-Requested-With": "XMLHttpRequest",
        "Content-Type": "application/json"
      }
    )
2 curtidas

MUITO obrigado!!!

Isso finalmente funciona.

1 curtida

Na verdade, mais uma coisa.

Isto é bem interessante!

  1. Ele ATUALIZA o histórico de leitura de posts :white_check_mark:
  2. A contagem de posts lidos aumenta :white_check_mark:

MAS:

  1. A contagem de tópicos lidos não aumenta :cross_mark:

Estes são os 2 sobre os quais estou falando!

Bem interessante.

Eu não ficaria surpreso se essas estatísticas fossem atualizadas por um trabalho secundário regular, por razões de desempenho.

Qual é o seu caso de uso para atualizar os tempos com um script?

Posso dizer com 100% de certeza que esta afirmação é verdadeira!
A contagem de posts lidos foi atualizada várias vezes, mas a de tópicos lidos não. Eles estão em intervalos diferentes? Já se passaram ~20 horas e a contagem de posts lidos continua aumentando, mas a de tópicos lidos não.

Eu só quero tentar fazer a engenharia reversa dos endpoints! É legal.

Acho que devo esperar um pouco antes de voltar e ver se os valores mudaram.

Você pode acionar o job do sidekiq manualmente em /sidekiq/scheduler se descobrir qual é :slight_smile:

Talvez seja Jobs::DirectoryRefreshDaily.

Você vê o mesmo no diretório de usuários em /u?period=daily ou semanalmente? Lá, você pode ver quando os números foram atualizados no topo.
image

Eu acho que os números para “hoje” são atualizados uma vez por hora, enquanto os outros períodos de tempo são atualizados apenas uma vez por dia.

1 curtida

@Canapin, eu não sou o proprietário do site. Se eu ainda puder acioná-lo apenas estando logado como um usuário normal, me diga o método para fazer isso.

@Moin
O site que estou usando tem isso desativado e sempre retornará “Uma lista de membros da comunidade mostrando sua atividade será exibida aqui. Por enquanto, a lista está vazia porque sua comunidade é muito nova!”

No caso dele, você não pode. Ser um usuário comum não é ideal para fazer a engenharia reversa da API.
Se puder, tente uma instalação de desenvolvimento local ou uma instalação de produção em um VPS barato (um servidor de 3-4 dólares serve), já que o Discourse não exige mais um nome de host ou SMTP.

1 curtida

Olá @Canapin, obrigado por ajudar continuamente.

Como eu faria isso? Atualmente diz 22 tópicos lidos e 2,6M posts lidos