サイトをリバースエンジニアリングしようと、コンテンツタイプ、csrfトークン、ユーザーエージェントを指定して「https://{hostUrl}/topics/timings」にPOSTリクエストを送信してみました。
ボディ(json)は次のようになります。
payload = {
"topic_id": topic_id,
"topic_time": post_count * 60000,
"timings": timings
}
ステータスコードは200が返されますが、https://{hostUrl}/u/USERNAME/activity/readで既読履歴が変更されません。
この投稿を調べましたが、あまり役に立ちませんでした。
Hello,
I’m pulling posts through the discourse API. I’m using python for that. The request calls the following code (which makes a GET request essentially) and returns the .json similar to this post
def topic_by_id(self, topic_id, **kwargs):
return self._get("/t/{0}.json".format(topic_id), **kwargs)
The posts returned have a 'read' flag. The posts I send are read=True, but for the posts I receive they are all marked as read=False, unless I actively log in to Discourse and read the…
コードの大部分は次のとおりです。
def get_csrf(session):
r = session.get(f"https://{hostUrl}/session/csrf.json")
if r.status_code != 200:
raise RuntimeError("CSRFの取得に失敗しました")
data = r.json()
if "csrf" not in data:
raise RuntimeError("レスポンスにCSRFが含まれていません")
return data["csrf"]
def load_topics(session, page):
print(f"[トピック] ページ {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"[既読] {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
Canapin
(Coin-coin le Canapin)
2026 年 2 月 3 日午後 11:54
3
kittenwater:
"timings": timings
これをフラットな "timings[post_number]": [duration_in_ms] として送信した場合はどうなりますか?動作しますか?
「いいね!」 2
どうやら正確には動作しないようです。
こちらが私が変更したコードスニペットです。
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"[既読] {topic_id} → {r.status_code}")
if r.status_code != 200:
print(r.text[:300])
ステータスコードは200のままですが、更新されません。
ありがとうございます。
Canapin
(Coin-coin le Canapin)
2026 年 2 月 5 日午後 1:08
5
コードは問題ないように見えますが、どこから問題が発生しているのかわかりません。
何か明白なことを見落としているだけだと直感的に感じています。
Canapin
(Coin-coin le Canapin)
2026 年 2 月 5 日午後 11:01
7
これは動作します:
def load_topics(session, page):
print(f"[トピック] ページ {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
Canapin
(Coin-coin le Canapin)
2026 年 2 月 6 日午前 11:02
10
パフォーマンス上の理由から、それらの統計情報は定期的なサイドキックジョブによって更新されても驚きません。
スクリプトでタイミングを更新するユースケースは何ですか?
この発言が真実 であると100%の確信を持って言えます!
投稿の閲覧数は数回更新されていますが、トピックの閲覧数は更新されていません。約20時間経過しましたが、投稿の閲覧数は増え続けていますが、トピックの閲覧数は増えていません。
エンドポイントをリバースエンジニアリングしてみたいだけです!クールですね。
少し待ってから戻ってきて、値が変わったかどうかを確認したほうがいいと思います。
Canapin
(Coin-coin le Canapin)
2026 年 2 月 6 日午後 9:12
12
どのジョブか分かれば、/sidekiq/scheduler で sidekiq ジョブを手動でトリガーできます
Canapin
(Coin-coin le Canapin)
2026 年 2 月 6 日午後 9:25
13
おそらく Jobs::DirectoryRefreshDaily でしょう。
Moin
2026 年 2 月 6 日午後 9:28
14
kittenwater:
これらが私が言っている2つです!
ユーザーディレクトリの /u?period=daily または週次でも同じものは見えますか?そこでは、上部に数値がいつ更新されたかを確認できます。
「今日」の数値は1時間に1回更新され、その他の期間は1日に1回のみ更新されると思います。
「いいね!」 1
@Canapin様 、私はウェブサイトの所有者ではありません。一般ユーザーとしてログインしているだけでトリガーできる場合、その方法を教えてください。
@Moin様
私が使用しているサイトではそれが無効になっており、常に「コミュニティメンバーのアクティビティを示すリストがここに表示されます。現在、コミュニティがまだ新しいため、リストは空です!」と表示されます。
Canapin
(Coin-coin le Canapin)
2026 年 2 月 7 日午前 10:45
16
このケースではできません。APIをリバースエンジニアリングするには、一般ユーザーであることは理想的ではありません。
もし可能であれば、Discourseはホスト名やSMTPを必要としなくなったため、ローカルの開発インストール、または安価なVPS(3〜4ドルのサーバーで十分)への本番インストールを試してみてください。
「いいね!」 1
@Canapinさん 、継続的なご協力ありがとうございます。
それをどうすればよいでしょうか?現在、「22トピック既読 」と「260万投稿既読 」と表示されています。