주제 내 게시물을 "읽음"으로 표시

https://{hostUrl}/topics/timings로 content-type, csrf 토큰, user-agent를 포함한 POST 요청을 보내면서 사이트를 역공학으로 분석해 보았습니다.

바디(JSON)는 다음과 같은 형태입니다:

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

응답 상태 코드는 200을 반환하지만, https://{hostUrl}/u/USERNAME/activity/read에서 읽음 이력은 변경되지 않습니다.

다음 게시글을 참고해 보았지만 큰 도움이 되지 않았습니다:

주요 코드 일부를 공유합니다:

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

답변이 아직 필요해서 다시 올립니다.

감사합니다

만약 이를 평탄한 "timings[post_number]": duration 형태로 보내면 어떻게 될까요? 작동할까요?

해당 페이로드를 포함하여 요청을 보내면:

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

타이밍 테이블이 업데이트되고 게시물이 읽음으로 표시되며, /activity/read도 함께 업데이트됩니다.

왜 그렇게 하려고 하시나요? 목적이 무엇인가요?

완전히 작동하는 것 같지는 않습니다.

수정된 코드 조각을 아래에 공유합니다.

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

여전히 200을 반환하지만 업데이트되지 않습니다.

감사합니다

코드는 문제없어 보이는데, 어디서 문제가 발생하는지 모르겠어. :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 
    }    

    # application/json 형식으로 전송하려면 json=payload 사용
    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. 게시물 읽음 기록은 업데이트됩니다 :white_check_mark:
  2. 읽은 게시물 수가 증가합니다 :white_check_mark:

하지만:

  1. 주제 읽음 수는 증가하지 않습니다 :cross_mark:

제가 말한 두 가지가 바로 이것들이에요!

꽤 흥미롭네요.

성능상의 이유로 해당 통계가 정기적인 사이드킥 작업에 의해 업데이트되었다고 해도 놀랍지 않을 것 같습니다.

스크립트로 타이밍을 업데이트하려는 사용 사례가 무엇인가요?

이 진술이 이라는 것을 100% 확신할 수 있습니다!

게시물 읽음 수는 여러 번 업데이트되었지만, 토픽 읽음 수는 그렇지 않았습니다. 업데이트 주기가 다른 건가요? 약 ~20시간이 지났는데 게시물 읽음 수는 계속 증가하고 있지만 토픽 읽음 수는 변하지 않고 있습니다.

단순히 엔드포인트를 역공학(reverse engineer)해 보고 싶을 뿐입니다. 정말 재미있어요.

값이 변경되었는지 확인하기 위해 잠시 기다렸다가 다시 확인하는 것이 좋겠다고 생각합니다.

어떤 작업인지 파악했다면 /sidekiq/scheduler에서 sidekiq 작업을 수동으로 트리거할 수 있습니다 :slight_smile:

아마도 Jobs::DirectoryRefreshDaily일 겁니다.

/u?period=daily 또는 weekly에서 사용자 디렉터리에서도 같은 것을 보실 수 있나요?在那里, 상단에 숫자가 업데이트된 시점을 확인할 수 있습니다.
image

"오늘"의 숫자는 1시간마다 한 번씩 업데이트되고, 다른 시간 범위는 하루에 한 번만 업데이트되는 것 같습니다.

@Canapin, 저는 해당 웹사이트의 소유자가 아닙니다. 일반 사용자로 로그인만 해도 트리거가 가능하다고 한다면, 그 방법을 알려주세요.

@Moin

제가 사용하는 사이트에서는 이 기능이 비활성화되어 있으며, 항상 "커뮤니티 멤버들의 활동이 표시될 목록이 여기에 표시됩니다. 현재 커뮤니티가 아직 새로 만들어져서 목록이 비어 있습니다!"라는 메시지가 반환됩니다.

그분의 경우에는 불가능합니다. 일반 사용자로 API를 역공학하는 것은 이상적이지 않습니다.
가능하다면 로컬 개발용 설치나 저렴한 VPS(3~4달러 서버면 충분합니다)에 프로덕션용 설치를 시도해 보세요. 더 이상 Discourse는 호스트네임이나 SMTP가 필요하지 않으니까요.

Hey @Canapin, 계속 도와주셔서 감사합니다.

그걸 어떻게 해야 할까요? 현재 22개 주제 읽음260만 개 게시물 읽음이라고 표시되어 있습니다.