Отметить сообщения в теме как «прочитанные»

I tried to reverse engineer the site by sending POST requests to “https://{hostUrl}/topics/timings” with content-type, csrf token, and user-agent.

Here is what the body (json) looks like:

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

It returns a status code of 200 but the read history never changes at https://{hostUrl}/u/USERNAME/activity/read

I tried to look into this post but it wasn’t much help:

Here is a good amount of the code:

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

    if r.status_code != 200:
        raise RuntimeError("Failed to get CSRF")

    data = r.json()

    if "csrf" not in data:
        raise RuntimeError("No CSRF in response")

    return data["csrf"]

def load_topics(session, page):
    print(f"[Topics] Page {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"[Read] {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

I’m bumping this because I still need the answer.

Thanks

What if you send this as a flat "timings[post_number]": duration? Does it work?

If I send a request containing that payload:

{
  "timings[1]": 10000,
  "topic_time": 10000,
  "topic_id": 6,
}

It updates the timing tables and the post is marked as read, and /activity/read is updated as well.

Why are you trying to do that? What is the purpose?

2 лайка

It doesn’t seem to exactly work.

Here is the code snippet which I modified.

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])

It still returns 200 but isn’t updated.

Thanks

Код выглядит в порядке, не уверен, в чём проблема. :confused:
Интуиция подсказывает, что мы просто упустили что-то очевидное где-то :sweat_smile:

Это работает:

def load_topics(session, page):
    print(f"[Topics] Загрузка страницы {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 
    }    

    # Используйте json=payload для отправки в формате 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 лайка

Огромное спасибо!!!

Наконец-то это работает.

1 лайк

На самом деле, ещё один момент.

Это довольно интересно!

  1. История прочитанных постов действительно обновляется :white_check_mark:
  2. Счётчик прочитанных постов увеличивается :white_check_mark:

НО:

  1. Счётчик прочитанных тем не увеличивается :cross_mark:

Именно об этих двух вещах я и говорю!

Действительно интересно.

Я бы не удивился, если бы эти данные обновлялись в рамках обычной работы помощника, ради производительности.

Какой у вас сценарий использования для обновления временных меток с помощью скрипта?

Я могу со 100% уверенностью сказать, что это утверждение верно!

Счётчик прочитанных постов обновлялся несколько раз, а счётчик прочитанных тем — нет. У них разные интервалы обновления? Прошло уже ~20 часов, счётчик прочитанных постов продолжает расти, а счётчик прочитанных тем остаётся прежним.

Я просто хочу попробовать реверс-инжинирить эти эндпоинты! Это круто.

Думаю, мне стоит немного подождать, прежде чем возвращаться и проверять, изменились ли значения.

Вы можете вручную запустить задачу Sidekiq в /sidekiq/scheduler, если найдете нужную :slight_smile:

Возможно, это Jobs::DirectoryRefreshDaily.

Вы видите то же самое в каталоге пользователей по адресу /u?period=daily или еженедельно? Там вверху указано, когда были обновлены цифры.
image

Полагаю, цифры за «сегодня» обновляются раз в час, а за другие периоды — только раз в день.

1 лайк

@Canapin, я не владелец этого сайта. Если я всё ещё могу это сделать, просто будучи обычным зарегистрированным пользователем, сообщите мне, как это сделать.

@Moin

На сайте, который я использую, эта функция отключена, и он всегда возвращает сообщение: «Здесь будет отображаться список участников сообщества с их активностью. На данный момент список пуст, потому что ваше сообщество ещё совсем новое!»

В его случае это невозможно. Быть обычным пользователем не идеально для реверс-инжиниринга API.
Если есть возможность, попробуйте локальную установку для разработки или установку в продакшн на дешёвом VPS (подойдёт сервер за 3–4 доллара), так как Discourse больше не требует указания хостнейма или SMTP.

1 лайк

Привет, @Canapin, спасибо за постоянную помощь.

Как мне это сделать? Сейчас там написано 22 темы прочитано и 2,6 млн сообщений прочитано

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.