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 { const json = await threadsGraphGet("/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); }