haixunMaster/lib/threads-api/search.ts

62 lines
1.6 KiB
TypeScript
Raw Normal View History

2026-06-21 12:50:31 +00:00
import { rankAndDedupe, type RankedPost, type RawPost } from "@/lib/ranking";
import { threadsGraphGet } from "./client";
import type { ThreadsApiCredentials } from "./types";
interface ThreadsSearchItem {
id?: string;
text?: string;
permalink?: string;
username?: string;
timestamp?: string;
like_count?: number;
reply_count?: number;
repost_count?: number;
quote_count?: number;
}
interface ThreadsSearchResponse {
data?: ThreadsSearchItem[];
paging?: {
next?: string;
cursors?: { after?: string };
};
}
function toRawPost(item: ThreadsSearchItem): RawPost | null {
if (!item.text?.trim()) return null;
return {
externalId: item.id,
text: item.text.trim(),
permalink: item.permalink,
authorName: item.username,
postedAt: item.timestamp ? new Date(item.timestamp) : undefined,
likeCount: item.like_count,
replyCount: item.reply_count,
repostCount: item.repost_count,
};
}
export async function keywordSearchViaThreadsApi(
credentials: ThreadsApiCredentials,
input: {
query: string;
limit?: number;
searchType?: "TOP" | "RECENT";
}
): Promise<RankedPost[]> {
const json = await threadsGraphGet<ThreadsSearchResponse>("/keyword_search", {
access_token: credentials.accessToken,
q: input.query,
search_type: input.searchType ?? "TOP",
fields:
"id,text,permalink,username,timestamp,like_count,reply_count,repost_count,quote_count",
});
const posts = (json.data ?? [])
.map(toRawPost)
.filter((post): post is RawPost => !!post);
return rankAndDedupe(posts, input.limit ?? 20);
}