fix: hashtag timeline returns empty results
- inReplyTo stored as empty string "" for non-replies in timeline-store.js;
filters using {$exists: false} missed these docs → hashtag/public timelines empty
- Fix: omit inReplyTo/quoteUrl fields entirely when empty (undefined spread)
- Fix: update all {$exists: false} filters to also match {inReplyTo: ""}
- Fix: hashtag timeline query uses case-insensitive prefix regex so
"politics" matches "on/politics", "wandern" matches stored "wandern"
- DB migration: unset inReplyTo:"" on 620 existing ap_timeline documents
This commit is contained in:
@@ -439,7 +439,7 @@ router.get("/api/v1/accounts/:id/statuses", tokenRequired, scopeRequired("read",
|
||||
];
|
||||
}
|
||||
if (req.query.exclude_replies === "true") {
|
||||
baseFilter.inReplyTo = { $exists: false };
|
||||
baseFilter.$or = [{ inReplyTo: { $exists: false } }, { inReplyTo: "" }];
|
||||
}
|
||||
if (req.query.exclude_reblogs === "true") {
|
||||
baseFilter.type = { $ne: "boost" };
|
||||
|
||||
@@ -114,7 +114,7 @@ router.get("/api/v1/timelines/public", async (req, res, next) => {
|
||||
// Public timeline: only public visibility, no context items, no replies
|
||||
const baseFilter = {
|
||||
isContext: { $ne: true },
|
||||
inReplyTo: { $exists: false },
|
||||
$or: [{ inReplyTo: { $exists: false } }, { inReplyTo: "" }],
|
||||
visibility: "public",
|
||||
};
|
||||
|
||||
@@ -220,11 +220,18 @@ router.get("/api/v1/timelines/tag/:hashtag", async (req, res, next) => {
|
||||
const limit = parseLimit(req.query.limit);
|
||||
const hashtag = req.params.hashtag;
|
||||
|
||||
// Match the hashtag case-insensitively and as a prefix segment
|
||||
// (e.g. "politics" matches "on/politics", "politics/foo").
|
||||
// Escape to prevent regex injection — hashtag comes from a URL param.
|
||||
const escapedHashtag = hashtag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const tagRegex = new RegExp(`(^|/)${escapedHashtag}(/|$)`, "i");
|
||||
|
||||
const baseFilter = {
|
||||
isContext: { $ne: true },
|
||||
inReplyTo: { $exists: false },
|
||||
// inReplyTo is absent on non-replies; legacy docs may store "" — exclude both
|
||||
$or: [{ inReplyTo: { $exists: false } }, { inReplyTo: "" }],
|
||||
visibility: { $in: ["public", "unlisted"] },
|
||||
category: hashtag,
|
||||
category: tagRegex,
|
||||
};
|
||||
|
||||
const { filter, sort, reverse } = buildPaginationQuery(baseFilter, {
|
||||
|
||||
@@ -348,11 +348,12 @@ export async function extractObjectData(object, options = {}) {
|
||||
// Attachment extraction failed — non-critical
|
||||
}
|
||||
|
||||
// In-reply-to — Fedify uses replyTargetId (non-fetching)
|
||||
const inReplyTo = object.replyTargetId?.href || "";
|
||||
// In-reply-to — Fedify uses replyTargetId (non-fetching).
|
||||
// Only set when non-empty — $exists:false filters depend on field absence.
|
||||
const inReplyTo = object.replyTargetId?.href || undefined;
|
||||
|
||||
// Quote URL — Fedify reads quoteUrl / _misskey_quote / quoteUri
|
||||
const quoteUrl = object.quoteUrl?.href || "";
|
||||
const quoteUrl = object.quoteUrl?.href || undefined;
|
||||
|
||||
// Interaction counts — not fetched at ingest time. The three collection
|
||||
// fetches (getReplies, getLikes, getShares) each trigger an HTTP round-trip
|
||||
@@ -382,8 +383,8 @@ export async function extractObjectData(object, options = {}) {
|
||||
photo,
|
||||
video,
|
||||
audio,
|
||||
inReplyTo,
|
||||
quoteUrl,
|
||||
...(inReplyTo ? { inReplyTo } : {}),
|
||||
...(quoteUrl ? { quoteUrl } : {}),
|
||||
counts,
|
||||
pollOptions,
|
||||
votersCount,
|
||||
|
||||
Reference in New Issue
Block a user