Files
2026-07-26 18:25:37 +09:00

116 lines
3.8 KiB
JavaScript

import rndstr from "rndstr";
import { Notes, PromoNotes, PromoReads, Users } from "../../../../models/index.js";
import define from "../../define.js";
import { shouldHideEUsersFor } from "../../common/generate-minor-badge-visibility-query.js";
import { readPromo } from "../../common/read-promo.js";
const serviceTags = {
video: "videoservice",
audio: "audioservice",
image: "imageservice",
karaoke: "karaokeservice",
lua4frozen: "lua4frozen"
};
const systemTags = new Set([
"adservice",
"videoservice",
"audioservice",
"imageservice",
"karaokeservice",
"lua4frozen"
]);
function isExplicitAd(note) {
return note.tags.includes("explicit") || note.user?.minorBadges?.includes("E") === true;
}
function canShowExplicit(note, user, hideExplicit) {
if (!isExplicitAd(note)) return true;
if (hideExplicit) return false;
return !(user && shouldHideEUsersFor(user));
}
function hasPollOr(note, fn) {
return note.hasPoll || fn();
}
function hasFileType(note, prefix) {
return note.attachedFileTypes.some((type)=>type.startsWith(prefix));
}
function matchesService(note, service) {
if (service == null) return true;
if (!note.tags.includes(serviceTags[service])) return false;
switch(service){
case "video":
return hasPollOr(note, ()=>hasFileType(note, "video/"));
case "audio":
return hasPollOr(note, ()=>hasFileType(note, "audio/"));
case "image":
return hasPollOr(note, ()=>hasFileType(note, "image/"));
case "karaoke":
return hasFileType(note, "video/") || hasFileType(note, "audio/") && hasFileType(note, "image/");
case "lua4frozen":
return hasPollOr(note, ()=>hasFileType(note, "image/"));
}
}
export const meta = {
tags: [
"notes"
],
requireCredential: false,
requireCredentialPrivateMode: true
};
export const paramDef = {
type: "object",
properties: {
service: {
type: "string",
enum: [
"video",
"audio",
"image",
"karaoke",
"lua4frozen"
],
nullable: true
},
tag: {
type: "string",
nullable: true
},
hideExplicit: {
type: "boolean",
default: false
}
},
required: []
};
export default define(meta, paramDef, async (ps, user)=>{
const readDay = new Date().toISOString().slice(0, 10);
const reads = user ? await PromoReads.findBy({
userId: user.id,
readDay
}) : [];
const readNoteIds = new Set(reads.map((read)=>read.noteId));
const promos = (await PromoNotes.find()).filter((promo)=>promo.expiresAt.getTime() > Date.now() && promo.remainingCredits > 0 && !readNoteIds.has(promo.noteId));
const candidates = [];
for (const promo of promos){
const note = await Notes.findOneBy({
id: promo.noteId
});
if (!note?.tags.includes("adservice")) continue;
note.user = await Users.findOneByOrFail({
id: note.userId
});
if (!canShowExplicit(note, user, ps.hideExplicit)) continue;
if (!matchesService(note, ps.service ?? null)) continue;
candidates.push(note);
}
if (candidates.length === 0) return null;
const requestedTag = ps.tag?.trim().toLowerCase().replace(/^#/, "");
const priority = requestedTag && !systemTags.has(requestedTag) ? candidates.filter((note)=>note.tags.includes(requestedTag)) : [];
const pool = priority.length > 0 ? priority : candidates;
const note = pool[Math.floor(Math.random() * pool.length)];
const promo = promos.find((promo)=>promo.noteId === note.id);
note._prId_ = rndstr("a-z0-9", 8);
if (user && promo) {
await readPromo(note, promo, user);
}
return Notes.pack(note, user);
});