Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ VINO_JP_TV_PROGRAM_DETAILS_BASE_URL=https://...
VINO_JP_TV_SEASON_CAST_URL=https://...

VINO_JP_STAFF_PIDS=pid1,pid2...

VINO_JP_MII_IMAGE_PNG_BASE_URL=https://mii-unsecure.ariankordi.net/miis/image.png
2 changes: 2 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const env = createEnv({

// yoinked from google's ai on having an array as an .env var :/
VINO_JP_STAFF_PIDS: z.string().transform((str) => str.split(",").map((s) => s.trim())),

VINO_JP_MII_IMAGE_PNG_BASE_URL: z.string().url()
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,
Expand Down
38 changes: 14 additions & 24 deletions src/routes/api/act.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { db } from "../../utils/db.ts";
import { z } from "zod";
import { BskyClient } from "../../utils/bsky.ts";
import { env } from "../../env.ts";
import NnidResolver from "../../utils/NnidResolver.mjs";
import crypto from "crypto";
import { logger } from "../../utils/logger.ts";
import { getRealIpFromRequest } from "../../utils/other.ts";

// Key must be 32 bytes for AES-256
const AES_KEY = Buffer.from(env.VINO_JP_CONFIG_BSKY_AES_KEY, "base64");
Expand Down Expand Up @@ -54,6 +56,10 @@ router.post(
const countryCode = token.country;
const principalId = token.pid;

if (principalId === undefined) {
return res.status(400).json({ status: "error" });
}

const existing = await db("account")
.where({ pid: principalId })
.first();
Expand All @@ -67,22 +73,20 @@ router.post(
});
}

const checkPID = await fetch(
`https://mii-unsecure.ariankordi.net/mii_data/?pid=${principalId}&api_id=1&force_refresh=1`
);
if (!checkPID.ok) {
let miiResponse;
try {
miiResponse = await NnidResolver.miiFromPid(String(principalId));
} catch (err) {
console.warn(
`Mii Unsecure Pretendo fetching error for : ${token.pid} ${token.serial_number}`
`Mii data fetching error for: ${token.pid} ${token.serial_number}`, err
);
return res.status(500).json({
status: "error_not_pretendo",
});
}

const checkPIDData = await checkPID.json() as any;

const mii_name = checkPIDData!.name!;
const mii_data = checkPIDData!.data!;
const mii_name = miiResponse.name;
const mii_data = miiResponse.miiData;

const mii = new Mii(Buffer.from(mii_data, "base64"));

Expand Down Expand Up @@ -134,21 +138,7 @@ router.post(
const tvProviderTzChosen = data.tv_provider_tz;

// Extract the user's IP
let ip =
req.headers["cf-connecting-ip"] ||
req.headers["x-forwarded-for"] ||
req.connection.remoteAddress ||
req.ip;

// If x-forwarded-for contains multiple IPs, take the first
if (typeof ip === "string" && ip.includes(",")) {
ip = ip.split(",")[0];
}

// Strip IPv6 prefix
if (typeof ip === "string" && ip.startsWith("::ffff:")) {
ip = ip.substring(7);
}
const ip = getRealIpFromRequest(req);

const ipReq = await fetch(`https://ipwho.is/${ip}`);
const ipInfo = await ipReq.json() as any;
Expand Down
3 changes: 2 additions & 1 deletion src/routes/api/miis.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express, { type Request, type Response, type Router } from "express";

import NodeCache from "node-cache";
import { env } from "../../env.ts";

const router: Router = express.Router();

Expand All @@ -23,7 +24,7 @@ router.get("/", async (req: Request, res: Response) => {
}

const url =
`https://mii-unsecure.ariankordi.net/miis/image.png?verifyCRC16=1&${query}`;
`${env.VINO_JP_MII_IMAGE_PNG_BASE_URL}?verifyCRC16=1&${query}`;

const response = await fetch(url);

Expand Down
40 changes: 9 additions & 31 deletions src/routes/api/social.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import express, { type Request, type Response, type Router } from "express";
import multer from "multer";
import { env } from "../../env.ts";
import { getMiiImageUrl, expressionFromFeeling } from "../../utils/other.ts";
import NnidResolver from "../../utils/NnidResolver.mjs";
import crypto from "crypto";
import { BskyClient } from "../../utils/bsky.ts";
import { parseServiceToken } from "../../utils/serviceToken.ts";
Expand Down Expand Up @@ -156,7 +158,7 @@ router.get(
"/getUserData/:pid",
async (req: Request, res: Response): Promise<any> => {
try {
const pid = req.params.pid!;
const pid = req.params.pid as string;
if (!pid) return res.status(400).json({ error: "Missing pid" });

const account = await db("account")
Expand Down Expand Up @@ -188,15 +190,7 @@ router.get(
latest_post_id = JSON.parse(cachedUserData).latest_post_id;
} else {
try {
const miiResp = await fetch(
`https://mii-unsecure.ariankordi.net/mii_data/?pid=${pid}&api_id=1`
);

if (miiResp.ok) {
const miiData = await miiResp.json() as any;
user_id = miiData?.user_id || null;
}

user_id = (await NnidResolver.miiFromPid(pid)).userId;
} catch (err) {
console.error("Mii fetch error:", err);
}
Expand Down Expand Up @@ -454,38 +448,22 @@ router.post(
if (post && post.length > 0) {
const postIdForLink = post[0];

const getFeelingQueryFromNumber = (
feeling_id: number
): string => {
switch (feeling_id) {
case 1:
return "smile_open_mouth";
case 2:
return "like_wink_left";
case 3:
return "surprise_open_mouth";
case 4:
return "frustrated";
case 5:
return "sorrow";
default:
return "normal";
}
};

try {
const webhookUrl = env.VINO_JP_CONFIG_DC_WEBHOOK_URL;

const miiName = account.mii_name || "Unknown Mii";
const miiImage = `https://mii-unsecure.ariankordi.net/miis/image.png?verifyCRC16=0&width=128&expression=${getFeelingQueryFromNumber(feelingId)}&data=${encodeURIComponent(account.mii_data)}&type=face`;
const expression = expressionFromFeeling(feelingId);
const miiImageUrl = getMiiImageUrl(account.mii_data,
`type=face&width=128&expression=${expression}`);

const isSpoilerPost = safeIsSpoiler === 1;

let embed: any;

if (isSpoilerPost) {
embed = {
author: { name: miiName, icon_url: miiImage },
author: { name: miiName, icon_url: miiImageUrl },
title: postForm.topic_tag || "Untitled Topic",
url: `https://projectrose.cafe/tvii/olv/topic/${encodeURIComponent(postForm.topic_tag)}`,
description: `**[Spoiler, View in browser](https://projectrose.cafe/tvii/olv/post/${encodeURIComponent(postIdForLink!)})**`,
Expand All @@ -497,7 +475,7 @@ router.post(
description += `\n\n[View in browser](https://projectrose.cafe/tvii/olv/post/${encodeURIComponent(postIdForLink!)})`;

embed = {
author: { name: miiName, icon_url: miiImage },
author: { name: miiName, icon_url: miiImageUrl },
title: postForm.topic_tag || "Untitled Topic",
url: `https://projectrose.cafe/tvii/olv/topic/${encodeURIComponent(postForm.topic_tag)}`,
description,
Expand Down
20 changes: 12 additions & 8 deletions src/routes/api/title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import path from "path";
import { fileURLToPath } from "url";
import Redis from "ioredis";
import { env } from "../../env.ts";
import { getMiiImageUrl } from "../../utils/other.ts";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand Down Expand Up @@ -126,14 +127,15 @@ router.get("/", async (req: Request, res: Response) => {
const randomBgPath =
backgrounds[Math.floor(Math.random() * backgrounds.length)];

const mii_url =
"https://mii-unsecure.ariankordi.net/miis/image.png?data=" +
encodeURIComponent(randomUser.mii_data) +
"&type=face&width=226&resourceType=middle&texResolution=168&verifyCRC16=0";

const [bg, mii, mii_bg, name_bg] = await Promise.all([
const miiImageUrl = getMiiImageUrl(
randomUser.mii_data,
`type=face&width=226&texResolution=168`);
const [bg, miiImage, mii_bg, name_bg] = await Promise.all([
loadImage(randomBgPath!),
loadImage(mii_url),
loadImage(miiImageUrl).catch((err: unknown) => {
console.warn("Mii image unavailable while obtaining card", err);
return null;
}),
loadImage(mii_bg_path),
loadImage(sign_path)
]);
Expand All @@ -144,7 +146,9 @@ router.get("/", async (req: Request, res: Response) => {

ctx.drawImage(bg, 0, 0);
ctx.drawImage(mii_bg, mii_x, mii_y);
ctx.drawImage(mii, mii_x + 4, mii_y + 4);
if (miiImage) {
ctx.drawImage(miiImage, mii_x + 4, mii_y + 4);
}
ctx.drawImage(name_bg, 184, 310);

// title
Expand Down
89 changes: 34 additions & 55 deletions src/routes/ui/vino-jp.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import express, { type Request, type Response, type Router } from "express";
import { join } from "path";
import { parseServiceToken } from "../../utils/serviceToken.ts";
import { db } from "../..//utils/db.ts";
import { getRegion } from "../..//utils/other.ts";
import { db } from "../../utils/db.ts";
import { getRealIpFromRequest, getRegion } from "../../utils/other.ts";
import Mii from "@pretendonetwork/mii-js";
import NnidResolver from "../../utils/NnidResolver.mjs";

const router: Router = express.Router();

Expand Down Expand Up @@ -54,7 +55,7 @@ router.get("/index.html", async (req: Request, res: Response): Promise<any> => {

const token = parseServiceToken(req);

if (!token.ok) {
if (!token.ok || token.pid === undefined) {
return res.sendStatus(404);
}

Expand Down Expand Up @@ -98,61 +99,39 @@ router.get("/index.html", async (req: Request, res: Response): Promise<any> => {

if (now.getTime() - lastUpdate.getTime() > oneHour) {
try {
const updateMiiData = await fetch(
`https://mii-unsecure.ariankordi.net/mii_data/?pid=${token.pid}&api_id=1&force_refresh=1`
);

if (updateMiiData.ok) {
const PIDData = await updateMiiData.json() as any;

const mii_name = PIDData.name;
const mii_data = PIDData.data;

const mii = new Mii(Buffer.from(mii_data, "base64"));
const mii_bday = mii.birthDay + "/" + mii.birthMonth;

// Extract real IP (Cloudflare first)
let ip =
req.headers["cf-connecting-ip"] ||
req.headers["x-forwarded-for"] ||
req.connection.remoteAddress ||
req.ip;

if (typeof ip === "string" && ip.includes(",")) {
ip = ip.split(",")[0];
}

if (typeof ip === "string" && ip.startsWith("::ffff:")) {
ip = ip.substring(7);
}

// timezone lookup
const ipReq = await fetch(`https://ipwho.is/${ip}`);
const ipInfo = await ipReq.json() as any;

if (
ipInfo?.success &&
ipInfo?.timezone &&
typeof ipInfo.timezone.offset === "number"
) {
utc_offset = ipInfo.timezone.offset;
}

Object.assign(updateValues, {
mii_name,
mii_data,
mii_bday,
utc_offset,
last_data_update: new Date().toISOString(),
});

console.log(`PNID Data + UTC updated for PID ${token.pid}`);
} else {
updateValues.last_data_update = new Date().toISOString();
const userData = await NnidResolver.miiFromPid(String(token.pid));
const mii_name = userData.name;
const mii_data = userData.miiData;

const mii = new Mii(Buffer.from(mii_data, "base64"));
const mii_bday = mii.birthDay + "/" + mii.birthMonth;

const ip = getRealIpFromRequest(req);

// timezone lookup
const ipReq = await fetch(`https://ipwho.is/${ip}`);
const ipInfo = await ipReq.json() as any;

if (
ipInfo?.success &&
ipInfo?.timezone &&
typeof ipInfo.timezone.offset === "number"
) {
utc_offset = ipInfo.timezone.offset;
}

Object.assign(updateValues, {
mii_name,
mii_data,
mii_bday,
utc_offset
});

console.log(`PNID Data + UTC updated for PID ${token.pid}`);
} catch (err) {
console.warn("Mii/IP update failed:", err);
updateValues.last_data_update = new Date().toISOString();
} finally {
updateValues.last_data_update = new Date().toISOString();
}
}

Expand Down
Loading