removed unnecessary type defs. prefer inference

This commit is contained in:
Jonathan Barrow 2023-10-02 14:55:52 -04:00
parent 6076536996
commit 78c61249c5
No known key found for this signature in database
GPG key ID: E86E9FE9049C741F
20 changed files with 254 additions and 247 deletions

View file

@ -29,7 +29,7 @@
"no-extra-semi": "off",
"@typescript-eslint/no-extra-semi": "error",
"@typescript-eslint/no-empty-interface": "warn",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/typedef": "error",
"@typescript-eslint/explicit-function-return-type": "error",
"keyword-spacing": "off",

View file

@ -3,7 +3,7 @@ import colors from 'colors';
colors.enable();
const root: string = process.env.PN_MIIVERSE_API_LOGGER_PATH ? process.env.PN_MIIVERSE_API_LOGGER_PATH : `${__dirname}/..`;
const root = process.env.PN_MIIVERSE_API_LOGGER_PATH ? process.env.PN_MIIVERSE_API_LOGGER_PATH : `${__dirname}/..`;
fs.ensureDirSync(`${root}/logs`);
const streams = {
@ -15,7 +15,7 @@ const streams = {
} as const;
export function LOG_SUCCESS(input: string): void {
const time: Date = new Date();
const time = new Date();
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`;
streams.success.write(`${input}\n`);
@ -23,7 +23,7 @@ export function LOG_SUCCESS(input: string): void {
}
export function LOG_ERROR(input: string): void {
const time: Date = new Date();
const time = new Date();
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`;
streams.error.write(`${input}\n`);
@ -31,7 +31,7 @@ export function LOG_ERROR(input: string): void {
}
export function LOG_WARN(input: string): void {
const time: Date = new Date();
const time = new Date();
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`;
streams.warn.write(`${input}\n`);
@ -39,7 +39,7 @@ export function LOG_WARN(input: string): void {
}
export function LOG_INFO(input: string): void {
const time: Date = new Date();
const time = new Date();
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`;
streams.info.write(`${input}\n`);

View file

@ -4,7 +4,6 @@ import { z } from 'zod';
import { GetUserDataResponse } from 'pretendo-grpc-ts/dist/account/get_user_data_rpc';
import { getEndpoint } from '@/database';
import { getUserAccountData, getValueFromHeaders, decodeParamPack, getPIDFromServiceToken } from '@/util';
import { ParamPack } from '@/types/common/param-pack';
import { HydratedEndpointDocument } from '@/types/mongoose/endpoint';
const ParamPackSchema = z.object({
@ -30,7 +29,7 @@ async function auth(request: express.Request, response: express.Response, next:
return next();
}
let encryptedToken: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-servicetoken');
let encryptedToken = getValueFromHeaders(request.headers, 'x-nintendo-servicetoken');
if (!encryptedToken) {
encryptedToken = getValueFromHeaders(request.headers, 'olive service token');
}
@ -44,13 +43,13 @@ async function auth(request: express.Request, response: express.Response, next:
return badAuth(response, 16, 'BAD_TOKEN');
}
const paramPack: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-parampack');
const paramPack = getValueFromHeaders(request.headers, 'x-nintendo-parampack');
if (!paramPack) {
return badAuth(response, 17, 'NO_PARAM');
}
const paramPackData: ParamPack = decodeParamPack(paramPack);
const paramPackCheck: z.SafeParseReturnType<ParamPack, ParamPack> = ParamPackSchema.safeParse(paramPackData);
const paramPackData = decodeParamPack(paramPack);
const paramPackCheck = ParamPackSchema.safeParse(paramPackData);
if (!paramPackCheck.success) {
console.log(paramPackCheck.error);
return badAuth(response, 18, 'BAD_PARAM');
@ -67,6 +66,7 @@ async function auth(request: express.Request, response: express.Response, next:
}
let discovery: HydratedEndpointDocument | null;
if (user) {
discovery = await getEndpoint(user.serverAccessLevel);
} else {
@ -103,8 +103,8 @@ function badAuth(response: express.Response, errorCode: number, message: string)
}
function serverError(response: express.Response, discovery: HydratedEndpointDocument): void {
let message: string = '';
let error: number = 0;
let message = '';
let error = 0;
switch (discovery.status) {
case 1 :

View file

@ -15,14 +15,14 @@ function nintendoClientHeaderCheck(request: express.Request, response: express.R
response.set('Server', 'Nintendo 3DS (http)');
response.set('X-Nintendo-Date', new Date().getTime().toString());
const clientId: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-client-id');
const clientSecret: string | undefined = getValueFromHeaders(request.headers, 'x-nintendo-client-secret');
const clientID = getValueFromHeaders(request.headers, 'x-nintendo-client-id');
const clientSecret = getValueFromHeaders(request.headers, 'x-nintendo-client-secret');
if (
!clientId ||
!clientID ||
!clientSecret ||
!VALID_CLIENT_ID_SECRET_PAIRS[clientId] ||
clientSecret !== VALID_CLIENT_ID_SECRET_PAIRS[clientId]
!VALID_CLIENT_ID_SECRET_PAIRS[clientID] ||
clientSecret !== VALID_CLIENT_ID_SECRET_PAIRS[clientID]
) {
response.type('application/xml');
response.send(xmlbuilder.create({

View file

@ -1,7 +1,7 @@
import crypto from 'node:crypto';
import moment from 'moment';
import { Schema, model } from 'mongoose';
import { HydratedPostDocument, IPost, IPostMethods, PostModel } from '@/types/mongoose/post';
import { IPost, IPostMethods, PostModel } from '@/types/mongoose/post';
import { HydratedCommunityDocument } from '@/types/mongoose/community';
import { PostToJSONOptions } from '@/types/mongoose/post-to-json-options';
import { PostPainting, PostScreenshot } from '@/types/common/post';
@ -122,9 +122,9 @@ PostSchema.method('unRemove', async function unRemove(reason) {
});
PostSchema.method('generatePostUID', async function generatePostUID(length: number) {
const id: string = Buffer.from(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(length * 2))), 'binary').toString('base64').replace(/[+/]/g, '').substring(0, length);
const id = Buffer.from(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(length * 2))), 'binary').toString('base64').replace(/[+/]/g, '').substring(0, length);
const inuse: HydratedPostDocument | null = await Post.findOne({ id });
const inuse = await Post.findOne({ id });
if (inuse) {
await this.generatePostUID(length);

View file

@ -53,7 +53,7 @@ app.use((_request: express.Request, response: express.Response) => {
// non-404 error handler
LOG_INFO('Creating non-404 status handler');
app.use((error: any, _request: express.Request, response: express.Response, _next: express.NextFunction) => {
const status: number = error.status || 500;
const status = error.status || 500;
response.type('application/xml');
response.status(404);

View file

@ -12,11 +12,9 @@ import { getValueFromQueryString } from '@/util';
import { LOG_WARN } from '@/logger';
import { Community } from '@/models/community';
import { Post } from '@/models/post';
import { CreateNewCommunityBody } from '@/types/common/create-new-community-body';
import { HydratedCommunityDocument } from '@/types/mongoose/community';
import { SubCommunityQuery } from '@/types/mongoose/subcommunity-query';
import { CommunityPostsQuery } from '@/types/mongoose/community-posts-query';
import { HydratedContentDocument } from '@/types/mongoose/content';
import { HydratedPostDocument, IPost } from '@/types/mongoose/post';
import { ParamPack } from '@/types/common/param-pack';
@ -27,7 +25,7 @@ const createNewCommunitySchema = z.object({
app_data: z.string().optional()
});
const router: express.Router = express.Router();
const router = express.Router();
function respondCommunityError(response: express.Response, httpStatusCode: number, errorCode: number): void {
response.status(httpStatusCode).send(xmlbuilder.create({
@ -45,20 +43,21 @@ function respondCommunityNotFound(response: express.Response): void {
respondCommunityError(response, 404, 919);
}
async function commonGetSubCommunity(paramPack: ParamPack, communityID: string | undefined): Promise<HydratedCommunityDocument | null> {
const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(paramPack.title_id);
const parentCommunity = await getCommunityByTitleID(paramPack.title_id);
if (!parentCommunity) {
return null;
}
const query: SubCommunityQuery = {
const query = {
parent: parentCommunity.olive_community_id,
community_id: communityID
};
const community: HydratedCommunityDocument | null = await Community.findOne(query);
const community = await Community.findOne(query);
if (!community) {
return null;
}
@ -70,16 +69,17 @@ async function commonGetSubCommunity(paramPack: ParamPack, communityID: string |
router.get('/', async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id);
const parentCommunity = await getCommunityByTitleID(request.paramPack.title_id);
if (!parentCommunity) {
respondCommunityNotFound(response);
return;
}
const type: string | undefined = getValueFromQueryString(request.query, 'type')[0];
const limitString: string | undefined = getValueFromQueryString(request.query, 'limit')[0];
const type = getValueFromQueryString(request.query, 'type')[0];
const limitString = getValueFromQueryString(request.query, 'limit')[0];
let limit = 4;
let limit: number = 4;
if (limitString) {
limit = parseInt(limitString);
}
@ -102,7 +102,7 @@ router.get('/', async function (request: express.Request, response: express.Resp
query.user_favorites = request.pid;
}
const communities: HydratedCommunityDocument[] = await Community.find(query).limit(limit);
const communities = await Community.find(query).limit(limit);
const json: Record<string, any> = {
result: {
@ -119,18 +119,23 @@ router.get('/', async function (request: express.Request, response: express.Resp
});
}
response.send(xmlbuilder.create(json, { separateArrayItems: true }).end({ pretty: true, allowEmpty: true }));
response.send(xmlbuilder.create(json, {
separateArrayItems: true
}).end({
pretty: true,
allowEmpty: true
}));
});
router.get('/popular', async function (_request: express.Request, response: express.Response): Promise<void> {
const popularCommunities: HydratedCommunityDocument[] = await getMostPopularCommunities(100);
const popularCommunities = await getMostPopularCommunities(100);
response.type('application/json');
response.send(popularCommunities);
});
router.get('/new', async function (_request: express.Request, response: express.Response): Promise<void> {
const newCommunities: HydratedCommunityDocument[] = await getNewCommunities(100);
const newCommunities = await getNewCommunities(100);
response.type('application/json');
response.send(newCommunities);
@ -139,7 +144,7 @@ router.get('/new', async function (_request: express.Request, response: express.
router.get('/:communityID/posts', async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
let community: HydratedCommunityDocument | null = await Community.findOne({
let community = await Community.findOne({
community_id: request.params.communityID
});
@ -158,15 +163,15 @@ router.get('/:communityID/posts', async function (request: express.Request, resp
message_to_pid: { $eq: null }
};
const searchKey: string | undefined = getValueFromQueryString(request.query, 'search_key')[0];
const allowSpoiler: string | undefined = getValueFromQueryString(request.query, 'allow_spoiler')[0];
const postType: string | undefined = getValueFromQueryString(request.query, 'type')[0];
const queryBy: string | undefined = getValueFromQueryString(request.query, 'by')[0];
const distinctPID: string | undefined = getValueFromQueryString(request.query, 'distinct_pid')[0];
const limitString: string | undefined = getValueFromQueryString(request.query, 'limit')[0];
const withMii: string | undefined = getValueFromQueryString(request.query, 'with_mii')[0];
const searchKey = getValueFromQueryString(request.query, 'search_key')[0];
const allowSpoiler = getValueFromQueryString(request.query, 'allow_spoiler')[0];
const postType = getValueFromQueryString(request.query, 'type')[0];
const queryBy = getValueFromQueryString(request.query, 'by')[0];
const distinctPID = getValueFromQueryString(request.query, 'distinct_pid')[0];
const limitString = getValueFromQueryString(request.query, 'limit')[0];
const withMii = getValueFromQueryString(request.query, 'with_mii')[0];
let limit: number = 10;
let limit = 10;
if (limitString) {
limit = parseInt(limitString);
@ -190,7 +195,7 @@ router.get('/:communityID/posts', async function (request: express.Request, resp
}
if (queryBy === 'followings') {
const userContent: HydratedContentDocument | null = await getUserContent(request.pid);
const userContent = await getUserContent(request.pid);
if (!userContent) {
LOG_WARN(`USER PID ${request.pid} HAS NO USER CONTENT`);
@ -203,15 +208,16 @@ router.get('/:communityID/posts', async function (request: express.Request, resp
}
let posts: HydratedPostDocument[];
if (distinctPID && distinctPID === '1') {
posts = await Post.aggregate([
const unhydratedPosts = await Post.aggregate<IPost>([
{ $match: query }, // filter based on input query
{ $sort: { created_at: -1 } }, // sort by 'created_at' in descending order
{ $group: { _id: '$pid', doc: { $first: '$$ROOT' } } }, // remove any duplicate 'pid' elements
{ $replaceRoot: { newRoot: '$doc' } }, // replace the root with the 'doc' field
{ $limit: limit } // only return the top 10 results
]);
posts = posts.map((post: IPost) => Post.hydrate(post));
posts = unhydratedPosts.map((post: IPost) => Post.hydrate(post));
} else {
posts = await Post.find(query).sort({ created_at: -1 }).limit(limit);
}
@ -238,20 +244,25 @@ router.get('/:communityID/posts', async function (request: express.Request, resp
});
}
response.send(xmlbuilder.create(json, { separateArrayItems: true }).end({ pretty: true, allowEmpty: true }));
response.send(xmlbuilder.create(json, {
separateArrayItems: true
}).end({
pretty: true,
allowEmpty: true
}));
});
// Handler for POST on '/v1/communities'
router.post('/', multer().none(), async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const parentCommunity: HydratedCommunityDocument | null = await getCommunityByTitleID(request.paramPack.title_id);
const parentCommunity = await getCommunityByTitleID(request.paramPack.title_id);
if (!parentCommunity) {
return respondCommunityNotFound(response);
}
// TODO - Better error codes, maybe do defaults?
const bodyCheck: z.SafeParseReturnType<CreateNewCommunityBody, CreateNewCommunityBody> = createNewCommunitySchema.safeParse(request.body);
const bodyCheck = createNewCommunitySchema.safeParse(request.body);
if (!bodyCheck.success) {
return respondCommunityError(response, 400, 20);
}
@ -273,30 +284,30 @@ router.post('/', multer().none(), async function (request: express.Request, resp
}
// Each user can only have 4 subcommunities per title
const ownedQuery: SubCommunityQuery = {
const ownedQuery = {
parent: parentCommunity.olive_community_id,
owner: request.pid
};
const ownedSubcommunityCount: number = await Community.countDocuments(ownedQuery);
const ownedSubcommunityCount = await Community.countDocuments(ownedQuery);
if (ownedSubcommunityCount >= 4) {
return respondCommunityError(response, 401, 911);
}
// Each user can only have 16 favorite subcommunities per title
const favoriteQuery: SubCommunityQuery = {
const favoriteQuery = {
parent: parentCommunity.olive_community_id,
user_favorites: request.pid
};
const ownedFavoriteCount: number = await Community.countDocuments(favoriteQuery);
const ownedFavoriteCount = await Community.countDocuments(favoriteQuery);
if (ownedFavoriteCount >= 16) {
return respondCommunityError(response, 401, 912);
}
const communitiesCount: number = await Community.count();
const communityId: number = (parseInt(parentCommunity.community_id) + (5000 * communitiesCount)); // Change this to auto increment
const community: HydratedCommunityDocument = await Community.create({
const communitiesCount = await Community.count();
const communityID = (parseInt(parentCommunity.community_id) + (5000 * communitiesCount)); // Change this to auto increment
const community = await Community.create({
platform_id: 0, // WiiU
name: request.body.name,
description: request.body.description || '',
@ -308,8 +319,8 @@ router.post('/', multer().none(), async function (request: express.Request, resp
owner: request.pid,
icon: request.body.icon,
title_id: request.paramPack.title_id,
community_id: communityId.toString(),
olive_community_id: communityId.toString(),
community_id: communityID.toString(),
olive_community_id: communityID.toString(),
app_data: request.body.app_data || '',
user_favorites: [request.pid]
});
@ -321,13 +332,17 @@ router.post('/', multer().none(), async function (request: express.Request, resp
request_name: 'community',
community: community.json()
}
}).end({ pretty: true, allowEmpty: true }));
}).end({
pretty: true,
allowEmpty: true
}));
});
router.post('/:community_id.delete', multer().none(), async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const community: HydratedCommunityDocument | null = await commonGetSubCommunity(request.paramPack, request.params.community_id);
const community = await commonGetSubCommunity(request.paramPack, request.params.community_id);
if (!community) {
respondCommunityNotFound(response);
return;
@ -353,19 +368,20 @@ router.post('/:community_id.delete', multer().none(), async function (request: e
router.post('/:community_id.favorite', multer().none(), async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const community: HydratedCommunityDocument | null = await commonGetSubCommunity(request.paramPack, request.params.community_id);
const community = await commonGetSubCommunity(request.paramPack, request.params.community_id);
if (!community) {
respondCommunityNotFound(response);
return;
}
// Each user can only have 16 favorite subcommunities per title
const favoriteQuery: SubCommunityQuery = {
const favoriteQuery = {
parent: community.parent,
user_favorites: request.pid
};
const ownedFavoriteCount: number = await Community.countDocuments(favoriteQuery);
const ownedFavoriteCount = await Community.countDocuments(favoriteQuery);
if (ownedFavoriteCount >= 16) {
return respondCommunityError(response, 401, 914);
}
@ -379,13 +395,16 @@ router.post('/:community_id.favorite', multer().none(), async function (request:
request_name: 'community',
community: community.json()
}
}).end({ pretty: true, allowEmpty: true }));
}).end({
pretty: true,
allowEmpty: true
}));
});
router.post('/:community_id.unfavorite', multer().none(), async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const community: HydratedCommunityDocument | null = await commonGetSubCommunity(request.paramPack, request.params.community_id);
const community = await commonGetSubCommunity(request.paramPack, request.params.community_id);
if (!community) {
respondCommunityNotFound(response);
return;
@ -405,14 +424,18 @@ router.post('/:community_id.unfavorite', multer().none(), async function (reques
request_name: 'community',
community: community.json()
}
}).end({ pretty: true, allowEmpty: true }));
}).end({
pretty: true,
allowEmpty: true
}));
});
router.post('/:community_id', multer().none(), async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const community: HydratedCommunityDocument | null = await commonGetSubCommunity(request.paramPack, request.params.community_id);
const community = await commonGetSubCommunity(request.paramPack, request.params.community_id);
if (!community) {
respondCommunityNotFound(response);
return;
@ -448,7 +471,10 @@ router.post('/:community_id', multer().none(), async function (request: express.
request_name: 'community',
community: community.json()
}
}).end({ pretty: true, allowEmpty: true }));
}).end({
pretty: true,
allowEmpty: true
}));
});
export default router;

View file

@ -10,11 +10,7 @@ import { getConversationByUsers, getUserSettings, getFriendMessages } from '@/da
import { LOG_WARN } from '@/logger';
import { Post } from '@/models/post';
import { Conversation } from '@/models/conversation';
import { SendMessageBody } from '@/types/common/send-message-body';
import { FormattedMessage } from '@/types/common/formatted-message';
import { HydratedConversationDocument } from '@/types/mongoose/conversation';
import { HydratedSettingsDocument } from '@/types/mongoose/settings';
import { HydratedPostDocument } from '@/types/mongoose/post';
const sendMessageSchema = z.object({
message_to_pid: z.string().transform(Number),
@ -24,25 +20,25 @@ const sendMessageSchema = z.object({
app_data: z.string().optional()
});
const router: express.Router = express.Router();
const upload: multer.Multer = multer();
const router = express.Router();
const upload = multer();
router.post('/', upload.none(), async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
// TODO - Better error codes, maybe do defaults?
const bodyCheck: z.SafeParseReturnType<SendMessageBody, SendMessageBody> = sendMessageSchema.safeParse(request.body);
const bodyCheck = sendMessageSchema.safeParse(request.body);
if (!bodyCheck.success) {
response.status(422);
return;
}
const recipientPID: number = bodyCheck.data.message_to_pid;
let messageBody: string = bodyCheck.data.body;
const painting: string = bodyCheck.data.painting?.replace(/\0/g, '').trim() || '';
const screenshot: string = bodyCheck.data.screenshot?.trim().replace(/\0/g, '').trim() || '';
const appData: string = bodyCheck.data.app_data?.replace(/[^A-Za-z0-9+/=\s]/g, '').trim() || '';
const recipientPID = bodyCheck.data.message_to_pid;
let messageBody = bodyCheck.data.body;
const painting = bodyCheck.data.painting?.replace(/\0/g, '').trim() || '';
const screenshot = bodyCheck.data.screenshot?.trim().replace(/\0/g, '').trim() || '';
const appData = bodyCheck.data.app_data?.replace(/[^A-Za-z0-9+/=\s]/g, '').trim() || '';
if (isNaN(recipientPID)) {
response.status(422);
@ -76,11 +72,11 @@ router.post('/', upload.none(), async function (request: express.Request, respon
return;
}
let conversation: HydratedConversationDocument | null = await getConversationByUsers([sender.pid, recipient.pid]);
let conversation = await getConversationByUsers([sender.pid, recipient.pid]);
if (!conversation) {
const userSettings: HydratedSettingsDocument | null = await getUserSettings(request.pid);
const user2Settings: HydratedSettingsDocument | null = await getUserSettings(recipient.pid);
const userSettings = await getUserSettings(request.pid);
const user2Settings = await getUserSettings(recipient.pid);
if (!sender || !recipient || userSettings || user2Settings) {
response.sendStatus(422);
@ -109,14 +105,14 @@ router.post('/', upload.none(), async function (request: express.Request, respon
return;
}
const friendPIDs: number[] = await getUserFriendPIDs(recipient.pid);
const friendPIDs = await getUserFriendPIDs(recipient.pid);
if (friendPIDs.indexOf(request.pid) === -1) {
response.sendStatus(422);
return;
}
let miiFace: string = 'normal_face.png';
let miiFace = 'normal_face.png';
switch (parseInt(request.body.feeling_id)) {
case 1:
miiFace = 'smile_open_mouth.png';
@ -179,7 +175,7 @@ router.post('/', upload.none(), async function (request: express.Request, respon
});
if (painting) {
const paintingBuffer: Buffer | null = await processPainting(painting);
const paintingBuffer = await processPainting(painting);
if (paintingBuffer) {
await uploadCDNAsset('pn-cdn', `paintings/${request.pid}/${post.id}.png`, paintingBuffer, 'public-read');
@ -189,7 +185,7 @@ router.post('/', upload.none(), async function (request: express.Request, respon
}
if (screenshot) {
const screenshotBuffer: Buffer = Buffer.from(screenshot, 'base64');
const screenshotBuffer = Buffer.from(screenshot, 'base64');
await uploadCDNAsset('pn-cdn', `screenshots/${request.pid}/${post.id}.jpg`, screenshotBuffer, 'public-read');
@ -214,10 +210,10 @@ router.post('/', upload.none(), async function (request: express.Request, respon
router.get('/', async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const limitString: string | undefined = getValueFromQueryString(request.query, 'limit')[0];
const limitString = getValueFromQueryString(request.query, 'limit')[0];
// TODO - Is this the limit?
let limit: number = 10;
let limit = 10;
if (limitString) {
limit = parseInt(limitString);
@ -232,9 +228,9 @@ router.get('/', async function (request: express.Request, response: express.Resp
return;
}
const searchKey: string[] = getValueFromQueryString(request.query, 'search_key');
const searchKey = getValueFromQueryString(request.query, 'search_key');
const messages: HydratedPostDocument[] = await getFriendMessages(request.pid.toString(), searchKey, limit);
const messages = await getFriendMessages(request.pid.toString(), searchKey, limit);
const postBody: FormattedMessage[] = [];
for (const message of messages) {

View file

@ -4,18 +4,16 @@ import moment from 'moment';
import { getUserContent, getFollowedUsers } from '@/database';
import { getValueFromQueryString, getUserFriendPIDs } from '@/util';
import { Post } from '@/models/post';
import { HydratedContentDocument } from '@/types/mongoose/content';
import { CommunityPostsQuery } from '@/types/mongoose/community-posts-query';
import { HydratedPostDocument, IPost } from '@/types/mongoose/post';
import { HydratedSettingsDocument } from '@/types/mongoose/settings';
const router: express.Router = express.Router();
const router = express.Router();
/* GET post titles. */
router.get('/', async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const userContent: HydratedContentDocument | null = await getUserContent(request.pid);
const userContent = await getUserContent(request.pid);
if (!userContent) {
response.sendStatus(404);
@ -30,12 +28,12 @@ router.get('/', async function (request: express.Request, response: express.Resp
message_to_pid: { $eq: null }
};
const relation: string | undefined = getValueFromQueryString(request.query, 'relation')[0];
const distinctPID: string | undefined = getValueFromQueryString(request.query, 'distinct_pid')[0];
const limitString: string | undefined = getValueFromQueryString(request.query, 'limit')[0];
const withMii: string | undefined = getValueFromQueryString(request.query, 'with_mii')[0];
const relation = getValueFromQueryString(request.query, 'relation')[0];
const distinctPID = getValueFromQueryString(request.query, 'distinct_pid')[0];
const limitString = getValueFromQueryString(request.query, 'limit')[0];
const withMii = getValueFromQueryString(request.query, 'with_mii')[0];
let limit: number = 10;
let limit = 10;
if (limitString) {
limit = parseInt(limitString);
@ -50,15 +48,16 @@ router.get('/', async function (request: express.Request, response: express.Resp
} else if (relation === 'following') {
query.pid = { $in: userContent.followed_users };
} else if (request.query.pid) {
const pidInputs: string[] = getValueFromQueryString(request.query, 'pid');
const pids: number[] = pidInputs.map(pid => Number(pid)).filter(pid => !isNaN(pid));
const pidInputs = getValueFromQueryString(request.query, 'pid');
const pids = pidInputs.map(pid => Number(pid)).filter(pid => !isNaN(pid));
query.pid = { $in: pids };
}
let posts: HydratedPostDocument[];
if (distinctPID === '1') {
posts = await Post.aggregate([
const unhydratedPosts = await Post.aggregate<IPost>([
{ $match: query }, // filter based on input query
{ $sort: { created_at: -1 } }, // sort by 'created_at' in descending order
{ $group: { _id: '$pid', doc: { $first: '$$ROOT' } } }, // remove any duplicate 'pid' elements
@ -66,7 +65,7 @@ router.get('/', async function (request: express.Request, response: express.Resp
{ $limit: limit } // only return the top 10 results
]);
posts = posts.map((post: IPost) => Post.hydrate(post));
posts = unhydratedPosts.map((post: IPost) => Post.hydrate(post));
} else if (request.query.is_hot === '1') {
posts = await Post.find(query).sort({ empathy_count: -1}).limit(limit);
} else {
@ -98,27 +97,32 @@ router.get('/', async function (request: express.Request, response: express.Resp
});
}
response.send(xmlbuilder.create(json, { separateArrayItems: true }).end({ pretty: true, allowEmpty: true }));
response.send(xmlbuilder.create(json, {
separateArrayItems: true
}).end({
pretty: true,
allowEmpty: true
}));
});
router.get('/:pid/following', async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const pid: number = parseInt(request.params.pid);
const pid = parseInt(request.params.pid);
if (isNaN(pid)) {
response.sendStatus(404);
return;
}
const userContent: HydratedContentDocument | null = await getUserContent(pid);
const userContent = await getUserContent(pid);
if (!userContent) {
response.sendStatus(404);
return;
}
const people: HydratedSettingsDocument[] = await getFollowedUsers(userContent);
const people = await getFollowedUsers(userContent);
const json: Record<string, any> = {
result: {

View file

@ -16,9 +16,7 @@ import {
import { LOG_WARN } from '@/logger';
import { Post } from '@/models/post';
import { Community } from '@/models/community';
import { HydratedPostDocument, IPost } from '@/types/mongoose/post';
import { HydratedContentDocument } from '@/types/mongoose/content';
import { HydratedSettingsDocument } from '@/types/mongoose/settings';
import { HydratedPostDocument } from '@/types/mongoose/post';
const newPostSchema = z.object({
community_id: z.string().optional(),
@ -35,8 +33,8 @@ const newPostSchema = z.object({
language_id: z.string()
});
const router: express.Router = express.Router();
const upload: multer.Multer = multer();
const router = express.Router();
const upload = multer();
/* GET post titles. */
router.post('/', upload.none(), newPost);
@ -46,8 +44,8 @@ router.post('/:post_id/replies', upload.none(), newPost);
router.post('/:post_id.delete', async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const post: HydratedPostDocument | null = await getPostByID(request.params.post_id);
const userContent: HydratedContentDocument | null = await getUserContent(request.pid);
const post = await getPostByID(request.params.post_id);
const userContent = await getUserContent(request.pid);
if (!post || !userContent) {
response.sendStatus(504);
@ -65,7 +63,7 @@ router.post('/:post_id.delete', async function (request: express.Request, respon
router.post('/:post_id/empathies', upload.none(), async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const post: HydratedPostDocument | null = await getPostByID(request.params.post_id);
const post = await getPostByID(request.params.post_id);
if (!post) {
response.sendStatus(404);
@ -110,9 +108,9 @@ router.post('/:post_id/empathies', upload.none(), async function (request: expre
router.get('/:post_id/replies', async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const limitString: string | undefined = getValueFromQueryString(request.query, 'limit')[0];
const limitString = getValueFromQueryString(request.query, 'limit')[0];
let limit: number = 10; // TODO - Is there a real limit?
let limit = 10; // TODO - Is there a real limit?
if (limitString) {
limit = parseInt(limitString);
@ -122,14 +120,14 @@ router.get('/:post_id/replies', async function (request: express.Request, respon
limit = 10;
}
const post: HydratedPostDocument | null = await getPostByID(request.params.post_id);
const post = await getPostByID(request.params.post_id);
if (!post) {
response.sendStatus(404);
return;
}
const posts: HydratedPostDocument[] = await getPostReplies(post.id, limit);
const posts = await getPostReplies(post.id, limit);
if (posts.length === 0) {
response.sendStatus(404);
return;
@ -159,7 +157,7 @@ router.get('/:post_id/replies', async function (request: express.Request, respon
router.get('/', async function (request: express.Request, response: express.Response): Promise<void> {
response.type('application/xml');
const postID: string | undefined = getValueFromQueryString(request.query, 'post_id')[0];
const postID = getValueFromQueryString(request.query, 'post_id')[0];
if (!postID) {
response.type('application/xml');
@ -175,7 +173,7 @@ router.get('/', async function (request: express.Request, response: express.Resp
return;
}
const post: HydratedPostDocument | null = await getPostByID(postID);
const post = await getPostByID(postID);
if (!post) {
response.status(404);
@ -222,7 +220,7 @@ async function newPost(request: express.Request, response: express.Response): Pr
return;
}
const userSettings: HydratedSettingsDocument | null = await getUserSettings(request.pid);
const userSettings = await getUserSettings(request.pid);
const bodyCheck = newPostSchema.safeParse(request.body);
if (!userSettings || !bodyCheck.success) {
@ -230,21 +228,21 @@ async function newPost(request: express.Request, response: express.Response): Pr
return;
}
const communityID: string | undefined = bodyCheck.data.community_id || '';
let messageBody: string | undefined = bodyCheck.data.body;
const painting: string = bodyCheck.data.painting?.replace(/\0/g, '').trim() || '';
const screenshot: string = bodyCheck.data.screenshot?.replace(/\0/g, '').trim() || '';
const appData: string = bodyCheck.data.app_data?.replace(/[^A-Za-z0-9+/=\s]/g, '').trim() || '';
const feelingID: number = parseInt(bodyCheck.data.feeling_id);
let searchKey: string | string[] = bodyCheck.data.search_key || [];
const topicTag: string | undefined = bodyCheck.data.topic_tag || '';
const autopost: string = bodyCheck.data.is_autopost;
const spoiler: string | undefined = bodyCheck.data.is_spoiler;
const jumpable: string | undefined = bodyCheck.data.is_app_jumpable;
const languageID: number = parseInt(bodyCheck.data.language_id);
const countryID: number = parseInt(request.paramPack.country_id);
const platformID: number = parseInt(request.paramPack.platform_id);
const regionID: number = parseInt(request.paramPack.region_id);
const communityID = bodyCheck.data.community_id || '';
let messageBody = bodyCheck.data.body;
const painting = bodyCheck.data.painting?.replace(/\0/g, '').trim() || '';
const screenshot = bodyCheck.data.screenshot?.replace(/\0/g, '').trim() || '';
const appData = bodyCheck.data.app_data?.replace(/[^A-Za-z0-9+/=\s]/g, '').trim() || '';
const feelingID = parseInt(bodyCheck.data.feeling_id);
let searchKey = bodyCheck.data.search_key || [];
const topicTag = bodyCheck.data.topic_tag || '';
const autopost = bodyCheck.data.is_autopost;
const spoiler = bodyCheck.data.is_spoiler;
const jumpable = bodyCheck.data.is_app_jumpable;
const languageID = parseInt(bodyCheck.data.language_id);
const countryID = parseInt(request.paramPack.country_id);
const platformID = parseInt(request.paramPack.platform_id);
const regionID = parseInt(request.paramPack.region_id);
if (
isNaN(feelingID) ||
@ -294,7 +292,7 @@ async function newPost(request: express.Request, response: express.Response): Pr
}
}
let miiFace: string = 'normal_face.png';
let miiFace = 'normal_face.png';
switch (parseInt(request.body.feeling_id)) {
case 1:
miiFace = 'smile_open_mouth.png';
@ -330,7 +328,7 @@ async function newPost(request: express.Request, response: express.Response): Pr
searchKey = [searchKey];
}
const document: IPost = {
const document = {
id: '', // * This gets changed when saving the document for the first time
title_id: request.paramPack.title_id,
community_id: community.olive_community_id,
@ -375,10 +373,10 @@ async function newPost(request: express.Request, response: express.Response): Pr
return;
}
const post: HydratedPostDocument = await Post.create(document);
const post = await Post.create(document);
if (painting) {
const paintingBuffer: Buffer | null = await processPainting(painting);
const paintingBuffer = await processPainting(painting);
if (paintingBuffer) {
await uploadCDNAsset('pn-cdn', `paintings/${request.pid}/${post.id}.png`, paintingBuffer, 'public-read');
@ -388,7 +386,7 @@ async function newPost(request: express.Request, response: express.Response): Pr
}
if (screenshot) {
const screenshotBuffer: Buffer = Buffer.from(screenshot, 'base64');
const screenshotBuffer = Buffer.from(screenshot, 'base64');
await uploadCDNAsset('pn-cdn', `screenshots/${request.pid}/${post.id}.jpg`, screenshotBuffer, 'public-read');

View file

@ -1,15 +1,14 @@
import express from 'express';
import { getEndpoints } from '@/database';
import { HydratedEndpointDocument } from '@/types/mongoose/endpoint';
const router: express.Router = express.Router();
const router = express.Router();
router.get('/', function(_request: express.Request, response: express.Response): void {
response.send('Pong!');
});
router.get('/database', async function(_request: express.Request, response: express.Response): Promise<void> {
const endpoints: HydratedEndpointDocument[] = await getEndpoints();
const endpoints = await getEndpoints();
if (endpoints && endpoints.length <= 0) {
response.send('DB Connection Working! :D');

View file

@ -7,6 +7,7 @@ import Cache from '@/cache';
import { getEndpoint } from '@/database';
import { Post } from '@/models/post';
import { Community } from '@/models/community';
import { IPost } from '@/types/mongoose/post';
import { HydratedEndpointDocument } from '@/types/mongoose/endpoint';
import { HydratedCommunityDocument } from '@/types/mongoose/community';
import { WWPData, WWPPost, WWPTopic } from '@/types/common/wara-wara-plaza';
@ -43,7 +44,7 @@ router.get('/', async function (request: express.Request, response: express.Resp
}
if (!WARA_WARA_PLAZA_CACHE.valid()) {
const communities: HydratedCommunityDocument[] = await calculateMostPopularCommunities(24, 10);
const communities = await calculateMostPopularCommunities(24, 10);
if (communities.length < 10) {
response.sendStatus(404);
@ -54,8 +55,14 @@ router.get('/', async function (request: express.Request, response: express.Resp
}
const data = WARA_WARA_PLAZA_CACHE.get() || {};
const xml = xmlbuilder.create(data, {
separateArrayItems: true
}).end({
pretty: true,
allowEmpty: true
});
response.send(xmlbuilder.create(data, { separateArrayItems: true }).end({ pretty: true, allowEmpty: true }));
response.send(xml);
});
async function generateTopicsData(communities: HydratedCommunityDocument[]): Promise<WWPData> {
@ -66,17 +73,17 @@ async function generateTopicsData(communities: HydratedCommunityDocument[]): Pro
for (let i = 0; i < communities.length; i++) {
const community = communities[i];
const empathies = await Post.aggregate([
const empathies = await Post.aggregate<{ _id: null; total: number; }>([
{
$match: {
community_id: community.olive_community_id
}
},
{
$group : {
_id : null,
total : {
$sum : '$empathy_count'
$group: {
_id: null,
total: {
$sum: '$empathy_count'
}
}
},
@ -163,10 +170,10 @@ async function generateTopicsData(communities: HydratedCommunityDocument[]): Pro
};
}
async function getCommunityPeople(community: HydratedCommunityDocument, hours = 24): Promise<any> {
async function getCommunityPeople(community: HydratedCommunityDocument, hours = 24): Promise<{ _id: number; post: IPost }[]> {
const now = new Date();
const last24Hours = new Date(now.getTime() - hours * 60 * 60 * 1000);
const people = await Post.aggregate([
const people = await Post.aggregate<{ _id: number; post: IPost }>([
{
$match: {
title_id: {
@ -220,10 +227,7 @@ async function calculateMostPopularCommunities(hours: number, limit: number): Pr
throw new Error('Invalid date');
}
const validCommunities: {
_id: null;
communities: [string];
}[] = await Community.aggregate([
const validCommunities = await Community.aggregate<{ _id: null; communities: string[]; }>([
{
$match: {
type: 0,
@ -240,16 +244,13 @@ async function calculateMostPopularCommunities(hours: number, limit: number): Pr
}
]);
const communityIDs: [string] = validCommunities[0].communities;
const communityIDs = validCommunities[0].communities;
if (!communityIDs) {
throw new Error('No communities found');
}
const popularCommunities: {
_id: string;
count: number;
}[] = await Post.aggregate([
const popularCommunities = await Post.aggregate<{ _id: null; count: number; }>([
{
$match: {
created_at: {

View file

@ -2,12 +2,12 @@ import express from 'express';
import xmlbuilder from 'xmlbuilder';
import { getValueFromQueryString } from '@/util';
const router: express.Router = express.Router();
const router = express.Router();
router.get('/:pid/notifications', function(request: express.Request, response: express.Response): void {
const type: string | undefined = getValueFromQueryString(request.query, 'type')[0];
const titleID: string | undefined = getValueFromQueryString(request.query, 'title_id')[0];
const pid: string | undefined = getValueFromQueryString(request.query, 'pid')[0];
const type = getValueFromQueryString(request.query, 'type')[0];
const titleID = getValueFromQueryString(request.query, 'title_id')[0];
const pid = getValueFromQueryString(request.query, 'pid')[0];
console.log(type);
console.log(titleID);

View file

@ -5,7 +5,7 @@ import { getUserAccountData } from '@/util';
import { getEndpoint } from '@/database';
import { HydratedEndpointDocument } from '@/types/mongoose/endpoint';
const router: express.Router = express.Router();
const router = express.Router();
/* GET discovery server. */
router.get('/', async function (request: express.Request, response: express.Response): Promise<void> {
@ -35,10 +35,10 @@ router.get('/', async function (request: express.Request, response: express.Resp
return;
}
let message: string = '';
let errorCode: number = 0;
let message = '';
let errorCode = 0;
switch (discovery.status) {
case 0 :
case 0:
response.send(xmlbuilder.create({
result: {
has_error: 0,
@ -53,15 +53,15 @@ router.get('/', async function (request: express.Request, response: express.Resp
}).end({ pretty: true }));
return ;
case 1 :
case 1:
message = 'SYSTEM_UPDATE_REQUIRED';
errorCode = 1;
break;
case 2 :
case 2:
message = 'SETUP_NOT_COMPLETE';
errorCode = 2;
break;
case 3 :
case 3:
message = 'SERVICE_MAINTENANCE';
errorCode = 3;
break;
@ -69,20 +69,20 @@ router.get('/', async function (request: express.Request, response: express.Resp
message = 'SERVICE_CLOSED';
errorCode = 4;
break;
case 5 :
case 5:
message = 'PARENTAL_CONTROLS_ENABLED';
errorCode = 5;
break;
case 6 :
case 6:
message = 'POSTING_LIMITED_PARENTAL_CONTROLS';
errorCode = 6;
break;
case 7 :
case 7:
message = 'NNID_BANNED';
errorCode = 7;
response.type('application/xml');
break;
default :
default:
message = 'SERVER_ERROR';
errorCode = 15;
response.type('application/xml');

View file

@ -1,6 +0,0 @@
export interface CreateNewCommunityBody {
name: string;
description?: string;
icon: string;
app_data?: string;
}

View file

@ -1,4 +0,0 @@
export interface CryptoOptions {
private_key: Buffer;
hmac_secret: string;
}

View file

@ -1,7 +0,0 @@
export interface SendMessageBody {
message_to_pid: number;
body: string;
painting?: string;
screenshot?: string;
app_data?: string;
}

View file

@ -11,30 +11,28 @@ import { ParamPack } from '@/types/common/param-pack';
import { config } from '@/config-manager';
import { Token } from '@/types/common/token';
import { FriendsClient, FriendsDefinition } from 'pretendo-grpc-ts/dist/friends/friends_service';
import { GetUserFriendPIDsResponse } from 'pretendo-grpc-ts/dist/friends/get_user_friend_pids_rpc';
import { GetUserFriendRequestsIncomingResponse } from 'pretendo-grpc-ts/dist/friends/get_user_friend_requests_incoming_rpc';
import { FriendsDefinition } from 'pretendo-grpc-ts/dist/friends/friends_service';
import { FriendRequest } from 'pretendo-grpc-ts/dist/friends/friend_request';
import { AccountClient, AccountDefinition } from 'pretendo-grpc-ts/dist/account/account_service';
import { AccountDefinition } from 'pretendo-grpc-ts/dist/account/account_service';
import { GetUserDataResponse } from 'pretendo-grpc-ts/dist/account/get_user_data_rpc';
// * nice-grpc doesn't export ChannelImplementation so this can't be typed
const gRPCFriendsChannel = createChannel(`${config.grpc.friends.ip}:${config.grpc.friends.port}`);
const gRPCFriendsClient: FriendsClient = createClient(FriendsDefinition, gRPCFriendsChannel);
const gRPCFriendsClient = createClient(FriendsDefinition, gRPCFriendsChannel);
const gRPCAccountChannel = createChannel(`${config.grpc.account.ip}:${config.grpc.account.port}`);
const gRPCAccountClient: AccountClient = createClient(AccountDefinition, gRPCAccountChannel);
const gRPCAccountClient = createClient(AccountDefinition, gRPCAccountChannel);
const s3: aws.S3 = new aws.S3({
const s3 = new aws.S3({
endpoint: new aws.Endpoint(config.s3.endpoint),
accessKeyId: config.s3.key,
secretAccessKey: config.s3.secret
});
export function decodeParamPack(paramPack: string): ParamPack {
const values: string[] = Buffer.from(paramPack, 'base64').toString().split('\\');
const entries: string[][] = values.filter(value => value).reduce((entries: string[][], value: string, index: number) => {
const values = Buffer.from(paramPack, 'base64').toString().split('\\');
const entries = values.filter(value => value).reduce((entries: string[][], value: string, index: number) => {
if (0 === index % 2) {
entries.push([value]);
} else {
@ -49,13 +47,13 @@ export function decodeParamPack(paramPack: string): ParamPack {
export function getPIDFromServiceToken(token: string): number {
try {
const decryptedToken: Buffer = decryptToken(Buffer.from(token, 'base64'));
const decryptedToken = decryptToken(Buffer.from(token, 'base64'));
if (!decryptedToken) {
return 0;
}
const unpackedToken: Token = unpackToken(decryptedToken);
const unpackedToken = unpackToken(decryptedToken);
return unpackedToken.pid;
} catch (e) {
@ -65,14 +63,14 @@ export function getPIDFromServiceToken(token: string): number {
}
export function decryptToken(token: Buffer): Buffer {
const iv: Buffer = Buffer.alloc(16);
const iv = Buffer.alloc(16);
const expectedChecksum: number = token.readUint32BE();
const encryptedBody: Buffer = token.subarray(4);
const expectedChecksum = token.readUint32BE();
const encryptedBody = token.subarray(4);
const decipher: crypto.Decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(config.aes_key, 'hex'), iv);
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(config.aes_key, 'hex'), iv);
const decrypted: Buffer = Buffer.concat([
const decrypted = Buffer.concat([
decipher.update(encryptedBody),
decipher.final()
]);
@ -96,7 +94,7 @@ export function unpackToken(token: Buffer): Token {
}
export function processPainting(painting: string): Buffer | null {
const paintingBuffer: Buffer = Buffer.from(painting, 'base64');
const paintingBuffer = Buffer.from(painting, 'base64');
let output: Uint8Array;
try {
@ -106,8 +104,8 @@ export function processPainting(painting: string): Buffer | null {
return null;
}
const tga: TGA = new TGA(Buffer.from(output));
const png: PNG = new PNG({
const tga = new TGA(Buffer.from(output));
const png = new PNG({
width: tga.width,
height: tga.height
});
@ -118,7 +116,7 @@ export function processPainting(painting: string): Buffer | null {
}
export async function uploadCDNAsset(bucket: string, key: string, data: Buffer, acl: string): Promise<void> {
const awsPutParams: aws.S3.PutObjectRequest = {
const awsPutParams = {
Body: data,
Key: key,
Bucket: bucket,
@ -129,7 +127,7 @@ export async function uploadCDNAsset(bucket: string, key: string, data: Buffer,
}
export async function getUserFriendPIDs(pid: number): Promise<number[]> {
const response: GetUserFriendPIDsResponse = await gRPCFriendsClient.getUserFriendPIDs({
const response = await gRPCFriendsClient.getUserFriendPIDs({
pid: pid
}, {
metadata: Metadata({
@ -141,7 +139,7 @@ export async function getUserFriendPIDs(pid: number): Promise<number[]> {
}
export async function getUserFriendRequestsIncoming(pid: number): Promise<FriendRequest[]> {
const response: GetUserFriendRequestsIncomingResponse = await gRPCFriendsClient.getUserFriendRequestsIncoming({
const response = await gRPCFriendsClient.getUserFriendRequestsIncoming({
pid: pid
}, {
metadata: Metadata({
@ -163,7 +161,7 @@ export function getUserAccountData(pid: number): Promise<GetUserDataResponse> {
}
export function getValueFromQueryString(qs: ParsedQs, key: string): string[] {
const property: string | string[] | undefined = qs[key] as string | string[];
const property = qs[key] as string | string[];
if (property) {
if (Array.isArray(property)) {
@ -177,7 +175,7 @@ export function getValueFromQueryString(qs: ParsedQs, key: string): string[] {
}
export function getValueFromHeaders(headers: IncomingHttpHeaders, key: string): string | undefined {
let header: string | string[] | undefined = headers[key];
let header = headers[key];
let value: string | undefined;
if (header) {

View file

@ -4,7 +4,7 @@ import crypto from 'node:crypto';
import newman from 'newman';
import { Collection, CollectionDefinition } from 'postman-collection';
import qs from 'qs';
import axios, { AxiosResponse } from 'axios';
import axios from 'axios';
import { create as parseXML } from 'xmlbuilder2';
import { table } from 'table';
import ora from 'ora';
@ -29,11 +29,11 @@ interface TestResult {
error?: string
}
const USERNAME: string = process.env.PN_MIIVERSE_API_TESTING_USERNAME?.trim() || '';
const PASSWORD: string = process.env.PN_MIIVERSE_API_TESTING_PASSWORD?.trim() || '';
const DEVICE_ID: string = process.env.PN_MIIVERSE_API_TESTING_DEVICE_ID?.trim() || '';
const SERIAL_NUMBER: string = process.env.PN_MIIVERSE_API_TESTING_SERIAL_NUMBER?.trim() || '';
const CERTIFICATE: string = process.env.PN_MIIVERSE_API_TESTING_CONSOLE_CERT?.trim() || '';
const USERNAME = process.env.PN_MIIVERSE_API_TESTING_USERNAME?.trim() || '';
const PASSWORD = process.env.PN_MIIVERSE_API_TESTING_PASSWORD?.trim() || '';
const DEVICE_ID = process.env.PN_MIIVERSE_API_TESTING_DEVICE_ID?.trim() || '';
const SERIAL_NUMBER = process.env.PN_MIIVERSE_API_TESTING_SERIAL_NUMBER?.trim() || '';
const CERTIFICATE = process.env.PN_MIIVERSE_API_TESTING_CONSOLE_CERT?.trim() || '';
if (!USERNAME) {
throw new Error('PNID username missing. Required for requesting service tokens. Set PN_MIIVERSE_API_TESTING_USERNAME');
@ -55,13 +55,13 @@ if (!CERTIFICATE) {
throw new Error('Console certificate missing. Required for requesting service tokens. Set PN_MIIVERSE_API_TESTING_CONSOLE_CERT');
}
const BASE_URL: string = 'https://account.pretendo.cc';
const API_URL: string = `${BASE_URL}/v1/api`;
const MAPPED_IDS_URL: string = `${API_URL}/admin/mapped_ids`;
const ACCESS_TOKEN_URL: string = `${API_URL}/oauth20/access_token/generate`;
const SERVICE_TOKEN_URL: string = `${API_URL}/provider/service_token/@me?client_id=87cd32617f1985439ea608c2746e4610`;
const BASE_URL = 'https://account.pretendo.cc';
const API_URL = `${BASE_URL}/v1/api`;
const MAPPED_IDS_URL = `${API_URL}/admin/mapped_ids`;
const ACCESS_TOKEN_URL = `${API_URL}/oauth20/access_token/generate`;
const SERVICE_TOKEN_URL = `${API_URL}/provider/service_token/@me?client_id=87cd32617f1985439ea608c2746e4610`;
const DEFAULT_HEADERS: Record<string, string> = {
const DEFAULT_HEADERS = {
'X-Nintendo-Client-ID': 'a2efa818a34fa16b8afbc8a74eba3eda',
'X-Nintendo-Client-Secret': 'c91cdb5658bd4954ade78533a339cf9a',
'X-Nintendo-Device-ID': DEVICE_ID,
@ -70,10 +70,10 @@ const DEFAULT_HEADERS: Record<string, string> = {
};
export function nintendoPasswordHash(password: string, pid: number): string {
const pidBuffer: Buffer = Buffer.alloc(4);
const pidBuffer = Buffer.alloc(4);
pidBuffer.writeUInt32LE(pid);
const unpacked: Buffer = Buffer.concat([
const unpacked = Buffer.concat([
pidBuffer,
Buffer.from('\x02\x65\x43\x46'),
Buffer.from(password)
@ -83,7 +83,7 @@ export function nintendoPasswordHash(password: string, pid: number): string {
}
async function apiGetRequest(url: string, headers = {}): Promise<Record<string, any>> {
const response: AxiosResponse<any, any> = await axios.get(url, {
const response = await axios.get(url, {
headers: Object.assign(headers, DEFAULT_HEADERS),
validateStatus: () => true
});
@ -102,7 +102,7 @@ async function apiGetRequest(url: string, headers = {}): Promise<Record<string,
}
async function apiPostRequest(url: string, body: string): Promise<Record<string, any>> {
const response: AxiosResponse<any, any> = await axios.post(url, body, {
const response = await axios.post(url, body, {
headers: DEFAULT_HEADERS,
validateStatus: () => true,
});
@ -121,26 +121,26 @@ async function apiPostRequest(url: string, body: string): Promise<Record<string,
}
async function getPID(username: string): Promise<number> {
const response: Record<string, any> = await apiGetRequest(`${MAPPED_IDS_URL}?input_type=user_id&output_type=pid&input=${username}`);
const response = await apiGetRequest(`${MAPPED_IDS_URL}?input_type=user_id&output_type=pid&input=${username}`);
return Number(response.mapped_ids.mapped_id.out_id);
}
async function getAccessToken(username: string, passwordHash: string): Promise<string> {
const data: string = qs.stringify({
const data = qs.stringify({
grant_type: 'password',
user_id: username,
password: passwordHash,
password_type: 'hash',
});
const response: Record<string, any> = await apiPostRequest(ACCESS_TOKEN_URL, data);
const response = await apiPostRequest(ACCESS_TOKEN_URL, data);
return response.OAuth20.access_token.token;
}
async function getMiiverseServiceToken(accessToken: string): Promise<string> {
const response: Record<string, any> = await apiGetRequest(SERVICE_TOKEN_URL, {
const response = await apiGetRequest(SERVICE_TOKEN_URL, {
'X-Nintendo-Title-ID': '0005001010040100',
Authorization: `Bearer ${accessToken}`
});
@ -193,10 +193,10 @@ function peopleRoutesTest(serviceToken: string): Promise<TestResult[]> {
async function main(): Promise<void> {
const tokensSpinner = ora('Acquiring account tokens').start();
const pid: number = await getPID(USERNAME);
const passwordHash: string = nintendoPasswordHash(PASSWORD, pid);
const accessToken: string = await getAccessToken(USERNAME, passwordHash);
const serviceToken: string = await getMiiverseServiceToken(accessToken);
const pid = await getPID(USERNAME);
const passwordHash = nintendoPasswordHash(PASSWORD, pid);
const accessToken = await getAccessToken(USERNAME, passwordHash);
const serviceToken = await getMiiverseServiceToken(accessToken);
tokensSpinner.succeed();

View file

@ -11,6 +11,8 @@
"allowJs": true,
"target": "es2022",
"noEmitOnError": true,
"noImplicitAny": true,
"strictPropertyInitialization": true,
"paths": {
"@/*": ["./*"]
}