feat: get dating prefs, birthday, voice, personality

This commit is contained in:
trafficlunar 2026-04-13 17:39:04 +01:00
parent 5b498430c8
commit 1ff3823209
5 changed files with 79 additions and 6 deletions

View file

@ -48,7 +48,7 @@ const submitSchema = z.object({
// Save data way // Save data way
miiDataFile: z miiDataFile: z
.instanceof(File) .instanceof(File)
.refine((blob) => blob.size < 1024 * 30, "File too large") // TODO: actual size .refine((blob) => blob.size < 1024 * 1024 * 1.5, "File too large") // TODO: actual size
.optional(), .optional(),
// Manual way // Manual way
@ -207,7 +207,16 @@ export async function POST(request: NextRequest) {
const miiData = miiDataFileBuffer ? CharInfoEx.FromShareMiiFileArrayBuffer(miiDataFileBuffer) : undefined; const miiData = miiDataFileBuffer ? CharInfoEx.FromShareMiiFileArrayBuffer(miiDataFileBuffer) : undefined;
if (way === "savedata") { if (way === "savedata") {
if (!miiData) return rateLimit.sendResponse({ error: "No mii data provided" }, 400); if (!miiData || !miiDataFileBuffer || !miiDataFileArray) return rateLimit.sendResponse({ error: "No valid Mii data provided" }, 400);
const view = new DataView(miiDataFileBuffer);
const parse = (index: number): number => view.getUint8(161 + index * 4);
const age = view.getUint32(0x00e1, true);
const year = view.getUint32(0x00d9, true);
const dontAge = age !== 0xffffffff;
const instructions: Partial<SwitchMiiInstructions> = { const instructions: Partial<SwitchMiiInstructions> = {
head: { head: {
@ -375,7 +384,28 @@ export async function POST(request: NextRequest) {
}, },
height: miiData.height, height: miiData.height,
weight: miiData.build, weight: miiData.build,
// uh oh, no dating prefs, birthday, voice, personality datingPreferences: ([MiiGender.MALE, MiiGender.FEMALE, MiiGender.NONBINARY] as const).filter((_, i) => miiDataFileArray[0x01a9 + i] === 1),
birthday: {
month: parse(17),
day: parse(15),
age: dontAge ? age : new Date().getFullYear() - year,
dontAge,
},
voice: {
speed: parse(6),
pitch: parse(8),
depth: parse(5),
delivery: Math.max(0, view.getInt8(0xc5)), // why is this an integer??
tone: parse(7) + 1,
// preset type?
},
personality: {
movement: parse(4) - 1,
speech: parse(2) - 1,
energy: parse(1) - 1,
thinking: parse(0) - 1,
overall: parse(3) - 1,
},
}; };
minifiedInstructions = minifyInstructions(instructions); minifiedInstructions = minifyInstructions(instructions);

View file

@ -0,0 +1,32 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { RateLimit } from "@/lib/rate-limit";
import { idSchema } from "@/lib/schemas";
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
const rateLimit = new RateLimit(request, 200, "/mii/image");
const check = await rateLimit.handle();
if (check) return check;
const { id: slugId } = await params;
const parsed = idSchema.safeParse(slugId);
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
const miiId = parsed.data;
const mii = await prisma.mii.findUnique({
where: { id: miiId },
});
if (!mii || !mii.miiData) {
return new NextResponse("Not found", { status: 404 });
}
const fileName = `${mii.name}.ltd`;
return new NextResponse(mii.miiData, {
headers: {
"Content-Type": "application/octet-stream",
"Content-Disposition": `attachment; filename="${fileName}"`,
},
});
}

View file

@ -371,9 +371,15 @@ export default async function MiiPage({ params }: Props) {
</div> </div>
{/* Buttons */} {/* Buttons */}
<div className="flex gap-3 w-fit bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg p-4 text-3xl text-orange-400 max-md:place-self-center *:size-12 *:flex *:flex-col *:items-center *:gap-1 **:transition-discrete **:duration-150 *:hover:brightness-75 *:hover:scale-[1.08] *:[&_span]:text-xs"> <div className="flex gap-4 w-fit bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg p-4 text-3xl text-orange-400 max-md:place-self-center *:size-12 *:flex *:flex-col *:items-center *:gap-1 **:transition-discrete **:duration-150 *:hover:brightness-75 *:hover:scale-[1.08] *:[&_span]:text-xs">
<AuthorButtons mii={mii} /> <AuthorButtons mii={mii} />
{mii.miiData && (
<Link aria-label="Download Mii" href={`/mii/${mii.id}/download`} download>
<Icon icon="material-symbols:download" />
<span>Download</span>
</Link>
)}
<ShareMiiButton miiId={mii.id} /> <ShareMiiButton miiId={mii.id} />
<Link aria-label="Report Mii" href={`/report/mii/${mii.id}`}> <Link aria-label="Report Mii" href={`/report/mii/${mii.id}`}>
<Icon icon="material-symbols:flag-rounded" /> <Icon icon="material-symbols:flag-rounded" />
@ -391,7 +397,11 @@ export default async function MiiPage({ params }: Props) {
Instructions Instructions
</h2> </h2>
<p className="text-xs text-amber-800">All instructions are based off of the default Male Mii.</p> <p className="text-xs text-amber-800">
All instructions are based off of the default Male Mii.
<br />
{mii.miiData && "If you're on modded/emulator, you can download the .ltd file above."}
</p>
</div> </div>
{mii.youtubeId && ( {mii.youtubeId && (

View file

@ -252,6 +252,7 @@ export default function MiiInstructions({ instructions, isUsingSaveFile }: Props
{(height || weight || datingPreferences || voice || personality) && ( {(height || weight || datingPreferences || voice || personality) && (
<div className="p-3 border-l-4 border-amber-400 bg-amber-100/50 rounded-r-lg py-2.5 text-amber-950 w-max mb-4"> <div className="p-3 border-l-4 border-amber-400 bg-amber-100/50 rounded-r-lg py-2.5 text-amber-950 w-max mb-4">
<h3 className="font-semibold text-xl text-amber-800 mb-1">Misc</h3> <h3 className="font-semibold text-xl text-amber-800 mb-1">Misc</h3>
<p className="text-xs text-amber-800 mb-4">These contain sliders: 0 is middle, positive is to the right, negative is to the left</p>
<table className="w-full"> <table className="w-full">
<tbody> <tbody>

View file

@ -433,7 +433,7 @@ export default function SubmitForm({ inQueueMiisCount }: Props) {
type="button" type="button"
className={`flex flex-col justify-center items-center rounded-xl p-4 shadow-md border-2 cursor-pointer text-center text-sm transition hover:scale-[1.03] ${way === "savedata" ? "bg-cyan-100 border-cyan-600" : "bg-zinc-50 border-zinc-300 hover:bg-cyan-100 hover:border-cyan-600"}`} className={`flex flex-col justify-center items-center rounded-xl p-4 shadow-md border-2 cursor-pointer text-center text-sm transition hover:scale-[1.03] ${way === "savedata" ? "bg-cyan-100 border-cyan-600" : "bg-zinc-50 border-zinc-300 hover:bg-cyan-100 hover:border-cyan-600"}`}
> >
.ltd file .ltd file (Modded)
</button> </button>
<button <button