import rndstr from "rndstr"; import type { Note } from "@/models/entities/note.js"; import type { User } from "@/models/entities/user.js"; import { Notes, UserProfiles, NoteReactions } from "@/models/index.js"; import { generateMutedUserQuery } from "./generate-muted-user-query.js"; import { generateBlockedUserQuery } from "./generate-block-query.js"; // TODO: リアクション、Renote、返信などをしたノートは除外する export async function injectFeatured(timeline: Note[], user?: User | null) { if (timeline.length < 5) return; if (user) { const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (!profile.injectFeaturedNote) return; } const max = 30; const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで const query = Notes.createQueryBuilder("note") .addSelect("note.score") .where("note.userHost IS NULL") .andWhere("note.score > 0") .andWhere("note.createdAt > :date", { date: new Date(Date.now() - day) }) .andWhere(`note.visibility = 'public'`) .innerJoinAndSelect("note.user", "user"); if (user) { query.andWhere("note.userId != :userId", { userId: user.id }); generateMutedUserQuery(query, user); generateBlockedUserQuery(query, user); const reactionQuery = NoteReactions.createQueryBuilder("reaction") .select("reaction.noteId") .where("reaction.userId = :userId", { userId: user.id }); query.andWhere(`note.id NOT IN (${reactionQuery.getQuery()})`); } const notes = await query.orderBy("note.score", "DESC").take(max).getMany(); if (notes.length === 0) return; // Pick random one const featured = notes[Math.floor(Math.random() * notes.length)]; (featured as any)._featuredId_ = rndstr("a-z0-9", 8); // Inject featured timeline.splice(3, 0, featured); }