116 lines
3.8 KiB
JavaScript
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);
|
|
});
|