Marcar publicaciones en el tema como "leídas"

Intenté hacer ingeniería inversa del sitio enviando solicitudes POST a “https://{hostUrl}/topics/timings” con content-type, token csrf y user-agent.

Aquí se ve el cuerpo (json):

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

Devuelve un código de estado 200, pero el historial de lectura nunca cambia en https://{hostUrl}/u/USERNAME/activity/read

Intenté buscar en esta publicación, pero no ayudó mucho:

Aquí hay una buena parte del código:

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

    if r.status_code != 200:
        raise RuntimeError("Fallo al obtener CSRF")

    data = r.json()

    if "csrf" not in data:
        raise RuntimeError("No hay CSRF en la respuesta")

    return data["csrf"]

def load_topics(session, page):
    print(f"[Temas] 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"[Leído] {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

Estoy reviviendo esto porque todavía necesito la respuesta.

Gracias

¿Qué pasa si envías esto como un \"timings[post_number]\": [duration_in_ms] plano? ¿Funciona?

2 Me gusta

No parece funcionar exactamente.

Aquí está el fragmento de código que modifiqué.

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"[Read] {topic_id} → {r.status_code}")

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

Aún devuelve 200 pero no se actualiza.

Gracias

El código parece estar bien, no estoy seguro de dónde viene el problema. :confused:
Mi instinto me dice que simplemente nos estamos perdiendo algo obvio en alguna parte :sweat_smile:

Esto funciona:

def load_topics(session, page):
    print(f"[Temas] Cargando 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 
    }    

    # Usar 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"
      }
    )
1 me gusta

¡¡¡Muchísimas gracias!!!

Esto finalmente funciona.

1 me gusta

De hecho, una cosa más.

¡Esto es bastante interesante!

  1. SÍ actualiza el historial de lectura de publicaciones :white_check_mark:
  2. El recuento de publicaciones leídas aumenta :white_check_mark:

PERO:

  1. El recuento de temas leídos no aumenta :cross_mark:

¡Estos son los 2 de los que estoy hablando!

Bastante interesante.

No me sorprendería que esas estadísticas se actualizaran mediante un trabajo secundario normal, por razones de rendimiento.

¿Cuál es su caso de uso para actualizar los tiempos con un script?

¡Puedo decir con un 100% de certeza que esta afirmación es verdadera!
La lectura de las publicaciones se ha actualizado varias veces, pero la lectura de los temas no. ¿Están en intervalos diferentes? Han pasado aproximadamente 20 horas desde entonces y el contador de publicaciones leídas sigue aumentando, pero el de temas leídos no.

¡Solo quiero intentar hacer ingeniería inversa de los endpoints! Es genial.

Creo que debería esperar un poco antes de volver y ver si los valores han cambiado.

Puedes activar el trabajo de sidekiq manualmente en /sidekiq/scheduler si descubres cuál es :slight_smile:

Quizás sea Jobs::DirectoryRefreshDaily.

¿Ves lo mismo en el directorio de usuarios en /u?period=daily o semanalmente? Allí puedes ver cuándo se actualizaron los números en la parte superior.
image

Creo que los números para “hoy” se actualizan una vez por hora, mientras que los otros períodos de tiempo se actualizan solo una vez al día.

1 me gusta

@Canapin, no soy el propietario del sitio web. Si aún puedo activarlo simplemente con haber iniciado sesión como un usuario normal, házmelo saber el método para hacerlo.

@Moin
El sitio que estoy usando tiene eso deshabilitado y siempre devolverá “¡Aquí se mostrará una lista de miembros de la comunidad mostrando su actividad! Por ahora, la lista está vacía porque tu comunidad es muy nueva!”.

En su caso no se puede. Ser un usuario normal no es ideal para la ingeniería inversa de la API.
Si puedes, prueba una instalación de desarrollo local o una instalación de producción en un VPS barato (un servidor de 3-4$ está bien), ya que Discourse ya no requiere un nombre de host ni SMTP.

1 me gusta

Hola @Canapin, gracias por tu continua ayuda.

¿Cómo haría eso? Actualmente dice 22 temas leídos y 2.6M publicaciones leídas