mirror of
https://github.com/PretendoNetwork/miiverse-api.git
synced 2026-08-20 07:34:06 -05:00
Barebones move to TypeScript
This commit is contained in:
7088
package-lock.json
generated
7088
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
"main": "./dist/server.js",
|
||||
"scripts": {
|
||||
"lint": "npx eslint .",
|
||||
"build": "npm run lint && npm run clean && npx tsc && npx tsc-alias",
|
||||
"build": "npm run clean && npx tsc && npx tsc-alias",
|
||||
"clean": "rm -rf ./dist",
|
||||
"start": "node .",
|
||||
"start:dev": "NODE_ENV=development node ."
|
||||
@@ -22,7 +22,6 @@
|
||||
"express-session": "^1.17.0",
|
||||
"express-subdomain": "^1.0.5",
|
||||
"fs-extra": "^9.0.0",
|
||||
"grpc": "github:pretendonetwork/grpc-js",
|
||||
"memoizee": "^0.4.15",
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.27",
|
||||
@@ -35,12 +34,15 @@
|
||||
"node-snowflake": "0.0.1",
|
||||
"pako": "^1.0.11",
|
||||
"pngjs": "^5.0.0",
|
||||
"pretendo-grpc-ts": "github:PretendoNetwork/grpc-ts",
|
||||
"sanitize": "^2.1.0",
|
||||
"tga": "^1.0.3",
|
||||
"xmlbuilder": "^15.1.1",
|
||||
"xmlbuilder2": "0.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/morgan": "^1.9.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"eslint": "^8.38.0",
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
const mongoose = require('mongoose');
|
||||
const { account_db: mongooseConfig } = require('../config.json');
|
||||
const { uri, database, options } = mongooseConfig;
|
||||
const logger = require('./logger');
|
||||
|
||||
let pnidConnection;
|
||||
|
||||
function connect() {
|
||||
if(!pnidConnection)
|
||||
pnidConnection = makeNewConnection(`${uri}/${database}`, options);
|
||||
}
|
||||
|
||||
function verifyConnected() {
|
||||
if (!pnidConnection) {
|
||||
throw new Error('Cannot make database requests without being connected');
|
||||
}
|
||||
}
|
||||
|
||||
function makeNewConnection(uri) {
|
||||
pnidConnection = mongoose.createConnection(uri, options);
|
||||
|
||||
pnidConnection.on('error', function (error) {
|
||||
logger.error(`MongoDB connection ${this.name} ${JSON.stringify(error)}`);
|
||||
pnidConnection.close().catch(() => logger.error(`MongoDB failed to close connection ${this.name}`));
|
||||
});
|
||||
|
||||
pnidConnection.on('connected', function () {
|
||||
logger.info(`MongoDB connected ${this.name} / ${uri}`);
|
||||
});
|
||||
|
||||
pnidConnection.on('disconnected', function () {
|
||||
logger.info(`MongoDB disconnected ${this.name}`);
|
||||
});
|
||||
|
||||
return pnidConnection;
|
||||
}
|
||||
|
||||
pnidConnection = makeNewConnection(`${uri}/${database}`, options);
|
||||
|
||||
module.exports = {
|
||||
pnidConnection,
|
||||
connect,
|
||||
verifyConnected
|
||||
};
|
||||
39
src/accountdb.ts
Normal file
39
src/accountdb.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { LOG_INFO, LOG_ERROR } from '@/logger';
|
||||
import { account_db as mongooseConfig } from '../config.json';
|
||||
|
||||
const { uri, database, options } = mongooseConfig;
|
||||
|
||||
export let pnidConnection: mongoose.Connection;
|
||||
|
||||
export function connect() {
|
||||
if(!pnidConnection)
|
||||
pnidConnection = makeNewConnection(`${uri}/${database}`);
|
||||
}
|
||||
|
||||
export function verifyConnected() {
|
||||
if (!pnidConnection) {
|
||||
throw new Error('Cannot make database requests without being connected');
|
||||
}
|
||||
}
|
||||
|
||||
export function makeNewConnection(uri) {
|
||||
pnidConnection = mongoose.createConnection(uri, options as mongoose.ConnectOptions);
|
||||
|
||||
pnidConnection.on('error', function (error) {
|
||||
LOG_ERROR(`MongoDB connection ${this.name} ${JSON.stringify(error)}`);
|
||||
pnidConnection.close().catch(() =>LOG_ERROR(`MongoDB failed to close connection ${this.name}`));
|
||||
});
|
||||
|
||||
pnidConnection.on('connected', function () {
|
||||
LOG_INFO(`MongoDB connected ${this.name} / ${uri}`);
|
||||
});
|
||||
|
||||
pnidConnection.on('disconnected', function () {
|
||||
LOG_INFO(`MongoDB disconnected ${this.name}`);
|
||||
});
|
||||
|
||||
return pnidConnection;
|
||||
}
|
||||
|
||||
pnidConnection = makeNewConnection(`${uri}/${database}`);
|
||||
547
src/database.js
547
src/database.js
@@ -1,547 +0,0 @@
|
||||
const mongoose = require('mongoose');
|
||||
const { mongoose: mongooseConfig } = require('../config.json');
|
||||
const { COMMUNITY } = require('./models/communities');
|
||||
const { CONTENT } = require('./models/content');
|
||||
const { CONVERSATION } = require('./models/conversation');
|
||||
const { ENDPOINT } = require('./models/endpoint');
|
||||
const { NOTIFICATIONS } = require('./models/notifications');
|
||||
const { PNID } = require('./models/pnid');
|
||||
const { POST } = require('./models/post');
|
||||
const { SETTINGS } = require('./models/settings');
|
||||
|
||||
const { uri, database, options } = mongooseConfig;
|
||||
const logger = require('./logger');
|
||||
const accountDB = require('./accountdb');
|
||||
|
||||
let connection;
|
||||
|
||||
async function connect() {
|
||||
await mongoose.connect(`${uri}/${database}`, options);
|
||||
connection = mongoose.connection;
|
||||
connection.on('connected', function () {
|
||||
logger.info(`MongoDB connected ${this.name}`);
|
||||
});
|
||||
connection.on('error', console.error.bind(console, 'connection error:'));
|
||||
connection.on('close', () => {
|
||||
connection.removeAllListeners();
|
||||
});
|
||||
}
|
||||
|
||||
function verifyConnected() {
|
||||
if (!connection) {
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
async function getCommunities(numberOfCommunities) {
|
||||
verifyConnected();
|
||||
if(numberOfCommunities === -1)
|
||||
return COMMUNITY.find({ parent: null, type: 0 });
|
||||
else
|
||||
return COMMUNITY.find({ parent: null, type: 0 }).limit(numberOfCommunities);
|
||||
}
|
||||
|
||||
async function getMostPopularCommunities(numberOfCommunities) {
|
||||
verifyConnected();
|
||||
return COMMUNITY.find({ parent: null, type: 0 }).sort({followers: -1}).limit(numberOfCommunities);
|
||||
}
|
||||
|
||||
async function getNewCommunities(numberOfCommunities) {
|
||||
verifyConnected();
|
||||
return COMMUNITY.find({ parent: null, type: 0 }).sort([['created_at', -1]]).limit(numberOfCommunities);
|
||||
}
|
||||
|
||||
async function getSubCommunities(communityID) {
|
||||
verifyConnected();
|
||||
return COMMUNITY.find({
|
||||
parent: communityID
|
||||
});
|
||||
}
|
||||
|
||||
async function getCommunityByTitleID(title_id) {
|
||||
verifyConnected();
|
||||
return COMMUNITY.findOne({
|
||||
title_id: title_id
|
||||
});
|
||||
}
|
||||
|
||||
async function getCommunityByTitleIDs(title_ids) {
|
||||
verifyConnected();
|
||||
return COMMUNITY.findOne({
|
||||
title_ids: {$in: title_ids}
|
||||
});
|
||||
}
|
||||
|
||||
async function getCommunityByID(community_id) {
|
||||
verifyConnected();
|
||||
return COMMUNITY.findOne({
|
||||
community_id: community_id
|
||||
});
|
||||
}
|
||||
|
||||
async function getTotalPostsByCommunity(community) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
title_id: community.title_id,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).countDocuments();
|
||||
}
|
||||
|
||||
async function getPostByID(postID) {
|
||||
verifyConnected();
|
||||
return POST.findOne({
|
||||
id: postID
|
||||
});
|
||||
}
|
||||
|
||||
async function getPostsByUserID(userID) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
pid: userID,
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
});
|
||||
}
|
||||
|
||||
async function getPostReplies(postID, number) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
parent: postID,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).limit(number);
|
||||
}
|
||||
|
||||
async function getDuplicatePosts(pid, post) {
|
||||
verifyConnected();
|
||||
return POST.findOne({
|
||||
pid: pid,
|
||||
body: post.body,
|
||||
painting: post.painting,
|
||||
screenshot: post.screenshot,
|
||||
parent: null,
|
||||
removed: false
|
||||
});
|
||||
}
|
||||
|
||||
async function getUserPostRepliesAfterTimestamp(post, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
parent: post.pid,
|
||||
created_at: { $lt: post.created_at },
|
||||
message_to_pid: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
async function getNumberUserPostsByID(userID, number) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
pid: userID,
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false
|
||||
}).sort({ created_at: -1}).limit(number);
|
||||
}
|
||||
|
||||
async function getTotalPostsByUserID(userID) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
pid: userID,
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false
|
||||
}).countDocuments();
|
||||
}
|
||||
|
||||
async function getHotPostsByCommunity(community, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
title_id: community.title_id,
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).sort({empathy_count: -1}).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
async function getNumberNewCommunityPostsByID(community, number) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
title_id: community.title_id,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).sort({ created_at: -1}).limit(number);
|
||||
}
|
||||
|
||||
async function getNumberPopularCommunityPostsByID(community, limit, offset) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
title_id: community.title_id,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).sort({ empathy_count: -1}).skip(offset).limit(limit);
|
||||
}
|
||||
|
||||
async function getNumberVerifiedCommunityPostsByID(community, limit, offset) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
title_id: community.title_id,
|
||||
verified: true,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).sort({ created_at: -1}).skip(offset).limit(limit);
|
||||
}
|
||||
|
||||
async function getPostsByCommunity(community, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
community_id: community.olive_community_id,
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).sort({ created_at: -1}).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
async function getPostsByCommunityKey(community, numberOfPosts, search_key) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
community_id: community.olive_community_id,
|
||||
search_key: search_key,
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).sort({ created_at: -1}).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
async function getNewPostsByCommunity(community, limit, offset) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
community_id: community.olive_community_id,
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).sort({ created_at: -1 }).skip(offset).limit(limit);
|
||||
}
|
||||
|
||||
async function getAllUserPosts(pid) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
pid: pid,
|
||||
message_to_pid: null,
|
||||
app_data: { $ne: null }
|
||||
});
|
||||
}
|
||||
|
||||
async function getRemovedUserPosts(pid) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
pid: pid,
|
||||
message_to_pid: null,
|
||||
removed: true
|
||||
});
|
||||
}
|
||||
|
||||
async function getUserPostsAfterTimestamp(post, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
pid: post.pid,
|
||||
created_at: { $lt: post.created_at },
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
async function getUserPostsOffset(pid, limit, offset) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
pid: pid,
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).skip(offset).limit(limit).sort({ created_at: -1});
|
||||
}
|
||||
|
||||
async function getCommunityPostsAfterTimestamp(post, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
title_id: post.title_id,
|
||||
created_at: { $lt: post.created_at },
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
async function getEndpoints() {
|
||||
verifyConnected();
|
||||
return ENDPOINT.find({});
|
||||
}
|
||||
|
||||
async function getEndPoint(accessLevel) {
|
||||
verifyConnected();
|
||||
return ENDPOINT.findOne({
|
||||
server_access_level: accessLevel
|
||||
})
|
||||
}
|
||||
|
||||
async function getUsersSettings(numberOfUsers) {
|
||||
verifyConnected();
|
||||
if(numberOfUsers === -1)
|
||||
return SETTINGS.find({});
|
||||
else
|
||||
return SETTINGS.find({}).limit(numberOfUsers);
|
||||
}
|
||||
|
||||
async function getUsersContent(numberOfUsers) {
|
||||
verifyConnected();
|
||||
if(numberOfUsers === -1)
|
||||
return SETTINGS.find({});
|
||||
else
|
||||
return SETTINGS.find({}).limit(numberOfUsers);
|
||||
}
|
||||
|
||||
async function getUserSettings(pid) {
|
||||
verifyConnected();
|
||||
return SETTINGS.findOne({pid: pid});
|
||||
}
|
||||
|
||||
async function getUserContent(pid) {
|
||||
verifyConnected();
|
||||
return CONTENT.findOne({pid: pid});
|
||||
}
|
||||
|
||||
async function getFollowingUsers(content) {
|
||||
verifyConnected();
|
||||
return SETTINGS.find({
|
||||
pid: content.following_users
|
||||
});
|
||||
}
|
||||
|
||||
async function getFollowedUsers(content) {
|
||||
verifyConnected();
|
||||
return SETTINGS.find({
|
||||
pid: content.followed_users
|
||||
});
|
||||
}
|
||||
|
||||
async function getUserByUsername(user_id) {
|
||||
verifyConnected();
|
||||
return PNID.findOne({
|
||||
"username": new RegExp(`^${user_id}$`, 'i')
|
||||
});
|
||||
}
|
||||
|
||||
async function getNewsFeed(content, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
$or: [
|
||||
{pid: content.followed_users},
|
||||
{pid: content.pid},
|
||||
{community_id: content.followed_communities},
|
||||
],
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false
|
||||
}).limit(numberOfPosts).sort({ created_at: -1});
|
||||
}
|
||||
|
||||
async function getNewsFeedAfterTimestamp(content, numberOfPosts, post) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
$or: [
|
||||
{pid: content.followed_users},
|
||||
{pid: content.pid},
|
||||
{community_id: content.followed_communities},
|
||||
],
|
||||
created_at: { $lt: post.created_at },
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false
|
||||
}).limit(numberOfPosts).sort({ created_at: -1});
|
||||
}
|
||||
|
||||
async function getNewsFeedOffset(content, limit, offset) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
$or: [
|
||||
{pid: content.followed_users},
|
||||
{pid: content.pid},
|
||||
{community_id: content.followed_communities},
|
||||
],
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false
|
||||
}).skip(offset).limit(limit).sort({ created_at: -1});
|
||||
}
|
||||
|
||||
async function getConversations(pid) {
|
||||
verifyConnected();
|
||||
return CONVERSATION.find({
|
||||
"users.pid": pid
|
||||
}).sort({ last_updated: -1});
|
||||
}
|
||||
|
||||
async function getUnreadConversationCount(pid) {
|
||||
verifyConnected();
|
||||
return CONVERSATION.find({
|
||||
"users": { $elemMatch: {
|
||||
'pid': pid,
|
||||
'read': false
|
||||
}}
|
||||
|
||||
}).countDocuments();
|
||||
}
|
||||
|
||||
async function getConversationByID(community_id) {
|
||||
verifyConnected();
|
||||
return CONVERSATION.findOne({
|
||||
type: 3,
|
||||
id: community_id
|
||||
});
|
||||
}
|
||||
|
||||
async function getConversationMessages(community_id, limit, offset) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
community_id: community_id,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).sort({created_at: 1}).skip(offset).limit(limit);
|
||||
}
|
||||
|
||||
async function getConversationByUsers(pids) {
|
||||
verifyConnected();
|
||||
return CONVERSATION.findOne({
|
||||
$and: [
|
||||
{'users.pid': pids[0]},
|
||||
{'users.pid': pids[1]}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async function getLatestMessage(pid, pid2) {
|
||||
verifyConnected();
|
||||
return POST.findOne({
|
||||
$or: [
|
||||
{pid: pid, message_to_pid: pid2},
|
||||
{pid: pid2, message_to_pid: pid}
|
||||
],
|
||||
removed: false
|
||||
})
|
||||
}
|
||||
|
||||
async function getFriendMessages(pid, search_key, limit) {
|
||||
verifyConnected();
|
||||
return POST.find({
|
||||
message_to_pid: pid,
|
||||
search_key: search_key,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).sort({created_at: 1}).limit(limit);
|
||||
}
|
||||
|
||||
async function getPNIDS() {
|
||||
accountDB.verifyConnected();
|
||||
return PNID.find({});
|
||||
}
|
||||
|
||||
async function getPNID(pid) {
|
||||
accountDB.verifyConnected();
|
||||
return PNID.findOne({
|
||||
pid: pid
|
||||
});
|
||||
}
|
||||
|
||||
async function getNotifications(pid, limit, offset) {
|
||||
verifyConnected();
|
||||
return NOTIFICATIONS.find({
|
||||
pid: pid,
|
||||
}).sort({created_at: 1}).skip(offset).limit(limit);
|
||||
}
|
||||
|
||||
async function getNotification(pid, type, reference_id) {
|
||||
verifyConnected();
|
||||
return NOTIFICATIONS.findOne({
|
||||
pid: pid,
|
||||
type: type,
|
||||
reference_id: reference_id
|
||||
})
|
||||
}
|
||||
|
||||
async function getLastNotification(pid) {
|
||||
verifyConnected();
|
||||
return NOTIFICATIONS.findOne({
|
||||
pid: pid
|
||||
}).sort({created_at: -1}).limit(1);
|
||||
}
|
||||
|
||||
async function getUnreadNotificationCount(pid) {
|
||||
verifyConnected();
|
||||
return NOTIFICATIONS.find({
|
||||
pid: pid,
|
||||
read: false
|
||||
}).countDocuments();
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
connect,
|
||||
getCommunities,
|
||||
getMostPopularCommunities,
|
||||
getNewCommunities,
|
||||
getSubCommunities,
|
||||
getCommunityByTitleID,
|
||||
getCommunityByTitleIDs,
|
||||
getCommunityByID,
|
||||
getTotalPostsByCommunity,
|
||||
getPostsByCommunity,
|
||||
getHotPostsByCommunity,
|
||||
getNumberNewCommunityPostsByID,
|
||||
getNumberPopularCommunityPostsByID,
|
||||
getNumberVerifiedCommunityPostsByID,
|
||||
getNewPostsByCommunity,
|
||||
getPostsByCommunityKey,
|
||||
getPostsByUserID,
|
||||
getPostReplies,
|
||||
getUserPostRepliesAfterTimestamp,
|
||||
getNumberUserPostsByID,
|
||||
getTotalPostsByUserID,
|
||||
getPostByID,
|
||||
getDuplicatePosts,
|
||||
getEndpoints,
|
||||
getEndPoint,
|
||||
getUserByUsername,
|
||||
getUserPostsAfterTimestamp,
|
||||
getUserPostsOffset,
|
||||
getCommunityPostsAfterTimestamp,
|
||||
getNewsFeed,
|
||||
getNewsFeedAfterTimestamp,
|
||||
getNewsFeedOffset,
|
||||
getFollowingUsers,
|
||||
getFollowedUsers,
|
||||
getConversations,
|
||||
getConversationByID,
|
||||
getConversationByUsers,
|
||||
getConversationMessages,
|
||||
getUnreadConversationCount,
|
||||
getLatestMessage,
|
||||
getPNID,
|
||||
getPNIDS,
|
||||
getUsersSettings,
|
||||
getUsersContent,
|
||||
getUserSettings,
|
||||
getUserContent,
|
||||
getNotifications,
|
||||
getUnreadNotificationCount,
|
||||
getNotification,
|
||||
getLastNotification,
|
||||
getAllUserPosts,
|
||||
getRemovedUserPosts,
|
||||
getFriendMessages
|
||||
};
|
||||
493
src/database.ts
Normal file
493
src/database.ts
Normal file
@@ -0,0 +1,493 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { verifyConnected as accountDBVerifyConnected } from '@/accountdb';
|
||||
import { LOG_INFO } from '@/logger';
|
||||
import { Community } from '@/models/community';
|
||||
import { Content } from '@/models/content';
|
||||
import { Conversation } from '@/models/conversation';
|
||||
import { Endpoint } from '@/models/endpoint';
|
||||
import { Notification } from '@/models/notification';
|
||||
import { PNID } from '@/models/pnid';
|
||||
import { Post } from '@/models/post';
|
||||
import { Settings } from '@/models/settings';
|
||||
|
||||
import { mongoose as mongooseConfig } from '../config.json';
|
||||
|
||||
const { uri, database, options } = mongooseConfig;
|
||||
|
||||
let connection;
|
||||
|
||||
export async function connect() {
|
||||
await mongoose.connect(`${uri}/${database}`, options as mongoose.ConnectOptions || {});
|
||||
connection = mongoose.connection;
|
||||
connection.on('connected', function () {
|
||||
LOG_INFO(`MongoDB connected ${this.name}`);
|
||||
});
|
||||
connection.on('error', console.error.bind(console, 'connection error:'));
|
||||
connection.on('close', () => {
|
||||
connection.removeAllListeners();
|
||||
});
|
||||
}
|
||||
|
||||
function verifyConnected() {
|
||||
if (!connection) {
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCommunities(numberOfCommunities) {
|
||||
verifyConnected();
|
||||
if (numberOfCommunities === -1)
|
||||
return Community.find({ parent: null, type: 0 });
|
||||
else
|
||||
return Community.find({ parent: null, type: 0 }).limit(numberOfCommunities);
|
||||
}
|
||||
|
||||
export async function getMostPopularCommunities(numberOfCommunities) {
|
||||
verifyConnected();
|
||||
return Community.find({ parent: null, type: 0 }).sort({ followers: -1 }).limit(numberOfCommunities);
|
||||
}
|
||||
|
||||
export async function getNewCommunities(numberOfCommunities) {
|
||||
verifyConnected();
|
||||
return Community.find({ parent: null, type: 0 }).sort([['created_at', -1]]).limit(numberOfCommunities);
|
||||
}
|
||||
|
||||
export async function getSubCommunities(communityID) {
|
||||
verifyConnected();
|
||||
return Community.find({
|
||||
parent: communityID
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCommunityByTitleID(title_id) {
|
||||
verifyConnected();
|
||||
return Community.findOne({
|
||||
title_id: title_id
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCommunityByTitleIDs(title_ids) {
|
||||
verifyConnected();
|
||||
return Community.findOne({
|
||||
title_ids: { $in: title_ids }
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCommunityByID(community_id) {
|
||||
verifyConnected();
|
||||
return Community.findOne({
|
||||
community_id: community_id
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTotalPostsByCommunity(community) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
title_id: community.title_id,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).countDocuments();
|
||||
}
|
||||
|
||||
export async function getPostByID(postID) {
|
||||
verifyConnected();
|
||||
return Post.findOne({
|
||||
id: postID
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPostsByUserID(userID) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
pid: userID,
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPostReplies(postID, number) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
parent: postID,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).limit(number);
|
||||
}
|
||||
|
||||
export async function getDuplicatePosts(pid, post) {
|
||||
verifyConnected();
|
||||
return Post.findOne({
|
||||
pid: pid,
|
||||
body: post.body,
|
||||
painting: post.painting,
|
||||
screenshot: post.screenshot,
|
||||
parent: null,
|
||||
removed: false
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUserPostRepliesAfterTimestamp(post, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
parent: post.pid,
|
||||
created_at: { $lt: post.created_at },
|
||||
message_to_pid: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
export async function getNumberUserPostsByID(userID, number) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
pid: userID,
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false
|
||||
}).sort({ created_at: -1 }).limit(number);
|
||||
}
|
||||
|
||||
export async function getTotalPostsByUserID(userID) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
pid: userID,
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false
|
||||
}).countDocuments();
|
||||
}
|
||||
|
||||
export async function getHotPostsByCommunity(community, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
title_id: community.title_id,
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).sort({ empathy_count: -1 }).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
export async function getNumberNewCommunityPostsByID(community, number) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
title_id: community.title_id,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).sort({ created_at: -1 }).limit(number);
|
||||
}
|
||||
|
||||
export async function getNumberPopularCommunityPostsByID(community, limit, offset) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
title_id: community.title_id,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).sort({ empathy_count: -1 }).skip(offset).limit(limit);
|
||||
}
|
||||
|
||||
export async function getNumberVerifiedCommunityPostsByID(community, limit, offset) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
title_id: community.title_id,
|
||||
verified: true,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).sort({ created_at: -1 }).skip(offset).limit(limit);
|
||||
}
|
||||
|
||||
export async function getPostsByCommunity(community, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
community_id: community.olive_community_id,
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).sort({ created_at: -1 }).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
export async function getPostsByCommunityKey(community, numberOfPosts, search_key) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
community_id: community.olive_community_id,
|
||||
search_key: search_key,
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).sort({ created_at: -1 }).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
export async function getNewPostsByCommunity(community, limit, offset) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
community_id: community.olive_community_id,
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).sort({ created_at: -1 }).skip(offset).limit(limit);
|
||||
}
|
||||
|
||||
export async function getAllUserPosts(pid) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
pid: pid,
|
||||
message_to_pid: null,
|
||||
app_data: { $ne: null }
|
||||
});
|
||||
}
|
||||
|
||||
export async function getRemovedUserPosts(pid) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
pid: pid,
|
||||
message_to_pid: null,
|
||||
removed: true
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUserPostsAfterTimestamp(post, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
pid: post.pid,
|
||||
created_at: { $lt: post.created_at },
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
export async function getUserPostsOffset(pid, limit, offset) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
pid: pid,
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).skip(offset).limit(limit).sort({ created_at: -1 });
|
||||
}
|
||||
|
||||
export async function getCommunityPostsAfterTimestamp(post, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
title_id: post.title_id,
|
||||
created_at: { $lt: post.created_at },
|
||||
parent: null,
|
||||
removed: false,
|
||||
app_data: { $ne: null }
|
||||
}).limit(numberOfPosts);
|
||||
}
|
||||
|
||||
export async function getEndpoints() {
|
||||
verifyConnected();
|
||||
return Endpoint.find({});
|
||||
}
|
||||
|
||||
export async function getEndPoint(accessLevel) {
|
||||
verifyConnected();
|
||||
return Endpoint.findOne({
|
||||
server_access_level: accessLevel
|
||||
})
|
||||
}
|
||||
|
||||
export async function getUsersSettings(numberOfUsers) {
|
||||
verifyConnected();
|
||||
if (numberOfUsers === -1)
|
||||
return Settings.find({});
|
||||
else
|
||||
return Settings.find({}).limit(numberOfUsers);
|
||||
}
|
||||
|
||||
export async function getUsersContent(numberOfUsers) {
|
||||
verifyConnected();
|
||||
if (numberOfUsers === -1)
|
||||
return Settings.find({});
|
||||
else
|
||||
return Settings.find({}).limit(numberOfUsers);
|
||||
}
|
||||
|
||||
export async function getUserSettings(pid) {
|
||||
verifyConnected();
|
||||
return Settings.findOne({ pid: pid });
|
||||
}
|
||||
|
||||
export async function getUserContent(pid) {
|
||||
verifyConnected();
|
||||
return Content.findOne({ pid: pid });
|
||||
}
|
||||
|
||||
export async function getFollowingUsers(content) {
|
||||
verifyConnected();
|
||||
return Settings.find({
|
||||
pid: content.following_users
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFollowedUsers(content) {
|
||||
verifyConnected();
|
||||
return Settings.find({
|
||||
pid: content.followed_users
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUserByUsername(user_id) {
|
||||
verifyConnected();
|
||||
return PNID.findOne({
|
||||
"username": new RegExp(`^${user_id}$`, 'i')
|
||||
});
|
||||
}
|
||||
|
||||
export async function getNewsFeed(content, numberOfPosts) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
$or: [
|
||||
{ pid: content.followed_users },
|
||||
{ pid: content.pid },
|
||||
{ community_id: content.followed_communities },
|
||||
],
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false
|
||||
}).limit(numberOfPosts).sort({ created_at: -1 });
|
||||
}
|
||||
|
||||
export async function getNewsFeedAfterTimestamp(content, numberOfPosts, post) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
$or: [
|
||||
{ pid: content.followed_users },
|
||||
{ pid: content.pid },
|
||||
{ community_id: content.followed_communities },
|
||||
],
|
||||
created_at: { $lt: post.created_at },
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false
|
||||
}).limit(numberOfPosts).sort({ created_at: -1 });
|
||||
}
|
||||
|
||||
export async function getNewsFeedOffset(content, limit, offset) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
$or: [
|
||||
{ pid: content.followed_users },
|
||||
{ pid: content.pid },
|
||||
{ community_id: content.followed_communities },
|
||||
],
|
||||
parent: null,
|
||||
message_to_pid: null,
|
||||
removed: false
|
||||
}).skip(offset).limit(limit).sort({ created_at: -1 });
|
||||
}
|
||||
|
||||
export async function getConversations(pid) {
|
||||
verifyConnected();
|
||||
return Conversation.find({
|
||||
"users.pid": pid
|
||||
}).sort({ last_updated: -1 });
|
||||
}
|
||||
|
||||
export async function getUnreadConversationCount(pid) {
|
||||
verifyConnected();
|
||||
return Conversation.find({
|
||||
"users": {
|
||||
$elemMatch: {
|
||||
'pid': pid,
|
||||
'read': false
|
||||
}
|
||||
}
|
||||
|
||||
}).countDocuments();
|
||||
}
|
||||
|
||||
export async function getConversationByID(community_id) {
|
||||
verifyConnected();
|
||||
return Conversation.findOne({
|
||||
type: 3,
|
||||
id: community_id
|
||||
});
|
||||
}
|
||||
|
||||
export async function getConversationMessages(community_id, limit, offset) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
community_id: community_id,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).sort({ created_at: 1 }).skip(offset).limit(limit);
|
||||
}
|
||||
|
||||
export async function getConversationByUsers(pids) {
|
||||
verifyConnected();
|
||||
return Conversation.findOne({
|
||||
$and: [
|
||||
{ 'users.pid': pids[0] },
|
||||
{ 'users.pid': pids[1] }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
export async function getLatestMessage(pid, pid2) {
|
||||
verifyConnected();
|
||||
return Post.findOne({
|
||||
$or: [
|
||||
{ pid: pid, message_to_pid: pid2 },
|
||||
{ pid: pid2, message_to_pid: pid }
|
||||
],
|
||||
removed: false
|
||||
})
|
||||
}
|
||||
|
||||
export async function getFriendMessages(pid, search_key, limit) {
|
||||
verifyConnected();
|
||||
return Post.find({
|
||||
message_to_pid: pid,
|
||||
search_key: search_key,
|
||||
parent: null,
|
||||
removed: false
|
||||
}).sort({ created_at: 1 }).limit(limit);
|
||||
}
|
||||
|
||||
export async function getPNIDS() {
|
||||
accountDBVerifyConnected();
|
||||
return PNID.find({});
|
||||
}
|
||||
|
||||
export async function getPNID(pid) {
|
||||
accountDBVerifyConnected();
|
||||
return PNID.findOne({
|
||||
pid: pid
|
||||
});
|
||||
}
|
||||
|
||||
export async function getNotifications(pid, limit, offset) {
|
||||
verifyConnected();
|
||||
return Notification.find({
|
||||
pid: pid,
|
||||
}).sort({ created_at: 1 }).skip(offset).limit(limit);
|
||||
}
|
||||
|
||||
export async function getNotification(pid, type, reference_id) {
|
||||
verifyConnected();
|
||||
return Notification.findOne({
|
||||
pid: pid,
|
||||
type: type,
|
||||
reference_id: reference_id
|
||||
})
|
||||
}
|
||||
|
||||
export async function getLastNotification(pid) {
|
||||
verifyConnected();
|
||||
return Notification.findOne({
|
||||
pid: pid
|
||||
}).sort({ created_at: -1 }).limit(1);
|
||||
}
|
||||
|
||||
export async function getUnreadNotificationCount(pid) {
|
||||
verifyConnected();
|
||||
return Notification.find({
|
||||
pid: pid,
|
||||
read: false
|
||||
}).countDocuments();
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
const fs = require('fs-extra');
|
||||
require('colors');
|
||||
import fs from 'fs-extra';
|
||||
import colors from 'colors';
|
||||
|
||||
colors.enable();
|
||||
|
||||
const root = __dirname;
|
||||
fs.ensureDirSync(`${root}/logs`);
|
||||
@@ -12,7 +14,7 @@ const streams = {
|
||||
info: fs.createWriteStream(`${root}/logs/info.log`)
|
||||
};
|
||||
|
||||
function success(input) {
|
||||
export function LOG_SUCCESS(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`;
|
||||
streams.success.write(`${input}\n`);
|
||||
@@ -20,7 +22,7 @@ function success(input) {
|
||||
console.log(`${input}`.green.bold);
|
||||
}
|
||||
|
||||
function error(input) {
|
||||
export function LOG_ERROR(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`;
|
||||
streams.error.write(`${input}\n`);
|
||||
@@ -28,7 +30,7 @@ function error(input) {
|
||||
console.log(`${input}`.red.bold);
|
||||
}
|
||||
|
||||
function warn(input) {
|
||||
export function LOG_WARN(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`;
|
||||
streams.warn.write(`${input}\n`);
|
||||
@@ -36,17 +38,10 @@ function warn(input) {
|
||||
console.log(`${input}`.yellow.bold);
|
||||
}
|
||||
|
||||
function info(input) {
|
||||
export function LOG_INFO(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`;
|
||||
streams.info.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.cyan.bold);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
success,
|
||||
error,
|
||||
warn,
|
||||
info
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
const config = require('../../config.json');
|
||||
const util = require('../util/util');
|
||||
const xml = require("object-to-xml");
|
||||
const db = require("../database");
|
||||
import xml from 'object-to-xml';
|
||||
import { getPNID, getEndPoint } from '@/database';
|
||||
import { decodeParamPack, processServiceToken } from '@/util';
|
||||
|
||||
async function auth(req, res, next) {
|
||||
if(/*req.path.includes('/topics') || */req.path.includes('/v1/status'))
|
||||
@@ -10,7 +9,7 @@ async function auth(req, res, next) {
|
||||
let paramPackData = req.headers["x-nintendo-parampack"];
|
||||
|
||||
if(paramPackData)
|
||||
paramPackData = paramPackData = util.decodeParamPack(paramPackData);
|
||||
paramPackData = paramPackData = decodeParamPack(paramPackData);
|
||||
else if(req.path.includes('/users/'))
|
||||
return next();
|
||||
|
||||
@@ -20,16 +19,16 @@ async function auth(req, res, next) {
|
||||
if(!token || !paramPackData)
|
||||
badAuth(res);
|
||||
else {
|
||||
const pid = util.processServiceToken(token);
|
||||
const pid = processServiceToken(token);
|
||||
|
||||
if(pid === null)
|
||||
badAuth(res);
|
||||
else {
|
||||
let user = await db.getPNID(pid), discovery;
|
||||
let user = await getPNID(pid), discovery;
|
||||
if(user)
|
||||
discovery = await db.getEndPoint(user.server_access_level);
|
||||
discovery = await getEndPoint(user.server_access_level);
|
||||
else
|
||||
discovery = await db.getEndPoint('prod');
|
||||
discovery = await getEndPoint('prod');
|
||||
|
||||
if(discovery.status !== 0) return serverError(res, discovery);
|
||||
|
||||
@@ -122,4 +121,4 @@ function serverError(res, discovery) {
|
||||
res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
|
||||
}
|
||||
|
||||
module.exports = auth;
|
||||
export default auth;
|
||||
@@ -1,38 +0,0 @@
|
||||
const xmlbuilder = require('xmlbuilder');
|
||||
|
||||
const VALID_CLIENT_ID_SECRET_PAIRS = {
|
||||
// 'Key' is the client ID, 'Value' is the client secret
|
||||
'a2efa818a34fa16b8afbc8a74eba3eda': 'c91cdb5658bd4954ade78533a339cf9a', // Possibly WiiU exclusive?
|
||||
'daf6227853bcbdce3d75baee8332b': '3eff548eac636e2bf45bb7b375e7b6b0', // Possibly 3DS exclusive?
|
||||
'ea25c66c26b403376b4c5ed94ab9cdea': 'd137be62cb6a2b831cad8c013b92fb55', // Possibly 3DS exclusive?
|
||||
};
|
||||
|
||||
|
||||
function nintendoClientHeaderCheck(request, response, next) {
|
||||
response.set('Content-Type', 'text/xml');
|
||||
response.set('Server', 'Nintendo 3DS (http)');
|
||||
response.set('X-Nintendo-Date', new Date().getTime());
|
||||
|
||||
const {headers} = request;
|
||||
|
||||
if (
|
||||
!headers['x-nintendo-client-id'] ||
|
||||
!headers['x-nintendo-client-secret'] ||
|
||||
!VALID_CLIENT_ID_SECRET_PAIRS[headers['x-nintendo-client-id']] ||
|
||||
headers['x-nintendo-client-secret'] !== VALID_CLIENT_ID_SECRET_PAIRS[headers['x-nintendo-client-id']]
|
||||
) {
|
||||
return response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
cause: 'client_id',
|
||||
code: '0004',
|
||||
message: 'API application invalid or incorrect application credentials'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = nintendoClientHeaderCheck;
|
||||
43
src/middleware/client-header.ts
Normal file
43
src/middleware/client-header.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import { getValueFromHeaders } from '@/util';
|
||||
|
||||
const VALID_CLIENT_ID_SECRET_PAIRS: { [key: string]: string } = {
|
||||
// * 'Key' is the client ID, 'Value' is the client secret
|
||||
'a2efa818a34fa16b8afbc8a74eba3eda': 'c91cdb5658bd4954ade78533a339cf9a', // * Possibly WiiU exclusive?
|
||||
'daf6227853bcbdce3d75baee8332b': '3eff548eac636e2bf45bb7b375e7b6b0', // * Possibly 3DS exclusive?
|
||||
'ea25c66c26b403376b4c5ed94ab9cdea': 'd137be62cb6a2b831cad8c013b92fb55', // * Possibly 3DS exclusive?
|
||||
};
|
||||
|
||||
|
||||
function nintendoClientHeaderCheck(request: express.Request, response: express.Response, next: express.NextFunction): void {
|
||||
response.set('Content-Type', 'text/xml');
|
||||
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');
|
||||
|
||||
if (
|
||||
!clientId ||
|
||||
!clientSecret ||
|
||||
!VALID_CLIENT_ID_SECRET_PAIRS[clientId] ||
|
||||
clientSecret !== VALID_CLIENT_ID_SECRET_PAIRS[clientId]
|
||||
) {
|
||||
response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
cause: 'client_id',
|
||||
code: '0004',
|
||||
message: 'API application invalid or incorrect application credentials'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
export default nintendoClientHeaderCheck;
|
||||
@@ -1,50 +0,0 @@
|
||||
const xmlbuilder = require('xmlbuilder');
|
||||
const database = require('../database');
|
||||
|
||||
async function PNIDMiddleware(request, response, next) {
|
||||
const { headers } = request;
|
||||
|
||||
if (!headers.authorization || !(headers.authorization.startsWith('Bearer') || headers.authorization.startsWith('Basic'))) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const [type, token] = headers.authorization.split(' ');
|
||||
let user;
|
||||
|
||||
if (type === 'Basic') {
|
||||
user = await database.getUserBasic(token);
|
||||
} else {
|
||||
user = await database.getUserBearer(token);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
response.status(401);
|
||||
|
||||
if (type === 'Bearer') {
|
||||
return response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
cause: 'access_token',
|
||||
code: '0005',
|
||||
message: 'Invalid access token'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
}
|
||||
|
||||
return response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
code: '1105',
|
||||
message: 'Email address, username, or password, is not valid'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
}
|
||||
|
||||
request.pnid = user;
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = PNIDMiddleware;
|
||||
@@ -8,7 +8,7 @@ const sessionStore = {};
|
||||
|
||||
function sessionMiddlware(request, response, next) {
|
||||
const ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
|
||||
|
||||
|
||||
if (!sessionStore[ip]) {
|
||||
sessionStore[ip] = {};
|
||||
}
|
||||
@@ -20,4 +20,4 @@ function sessionMiddlware(request, response, next) {
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = sessionMiddlware;
|
||||
export default sessionMiddlware;
|
||||
@@ -1,35 +0,0 @@
|
||||
const { document: xmlParser } = require('xmlbuilder2');
|
||||
|
||||
function XMLMiddleware(request, response, next) {
|
||||
if (request.method == 'POST' || request.method == 'PUT') {
|
||||
const headers = request.headers;
|
||||
let body = '';
|
||||
|
||||
if (
|
||||
!headers['content-type'] ||
|
||||
!headers['content-type'].toLowerCase().includes('xml')
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
request.setEncoding('utf-8');
|
||||
request.on('data', (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
|
||||
request.on('end', () => {
|
||||
try {
|
||||
request.body = xmlParser(body);
|
||||
request.body = request.body.toObject();
|
||||
} catch (error) {
|
||||
return next();
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = XMLMiddleware;
|
||||
57
src/middleware/xml-parser.ts
Normal file
57
src/middleware/xml-parser.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import express from 'express';
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import { document as xmlParser } from 'xmlbuilder2';
|
||||
import { getValueFromHeaders, mapToObject } from '@/util';
|
||||
|
||||
function XMLMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void {
|
||||
if (request.method == 'POST' || request.method == 'PUT') {
|
||||
const contentType: string | undefined = getValueFromHeaders(request.headers, 'content-type');
|
||||
const contentLength: string | undefined = getValueFromHeaders(request.headers, 'content-length');
|
||||
let body: string = '';
|
||||
|
||||
if (
|
||||
!contentType ||
|
||||
!contentType.toLowerCase().includes('xml')
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (
|
||||
!contentLength ||
|
||||
parseInt(contentLength) === 0
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
request.setEncoding('utf-8');
|
||||
request.on('data', (chunk: string) => {
|
||||
body += chunk;
|
||||
});
|
||||
|
||||
request.on('end', () => {
|
||||
try {
|
||||
request.body = xmlParser(body);
|
||||
request.body = request.body.toObject();
|
||||
request.body = mapToObject(request.body);
|
||||
} catch (error) {
|
||||
response.status(401);
|
||||
|
||||
// TODO: This is not a real error code, check to see if better one exists
|
||||
return response.send(xmlbuilder.create({
|
||||
errors: {
|
||||
error: {
|
||||
code: '0004',
|
||||
message: 'XML parse error'
|
||||
}
|
||||
}
|
||||
}).end());
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
export default XMLMiddleware;
|
||||
@@ -1,6 +1,7 @@
|
||||
const { Schema, model } = require('mongoose');
|
||||
import { Schema, model } from 'mongoose';
|
||||
import { ICommunity, ICommunityMethods, CommunityModel } from '@/types/mongoose/community';
|
||||
|
||||
const CommunitySchema = new Schema({
|
||||
const CommunitySchema = new Schema<ICommunity, CommunityModel, ICommunityMethods>({
|
||||
platform_id: Number,
|
||||
name: String,
|
||||
description: String,
|
||||
@@ -19,8 +20,8 @@ const CommunitySchema = new Schema({
|
||||
* 3: Private Community
|
||||
*/
|
||||
type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
parent: {
|
||||
type: String,
|
||||
@@ -31,7 +32,7 @@ const CommunitySchema = new Schema({
|
||||
default: undefined
|
||||
},
|
||||
created_at: {
|
||||
type: Date,
|
||||
type: Date,
|
||||
default: new Date(),
|
||||
},
|
||||
empathy_count: {
|
||||
@@ -64,37 +65,32 @@ const CommunitySchema = new Schema({
|
||||
app_data: String,
|
||||
});
|
||||
|
||||
CommunitySchema.methods.upEmpathy = async function() {
|
||||
const empathy = this.get('empathy_count');
|
||||
CommunitySchema.method('upEmpathy', async function upEmpathy(): Promise<void> {
|
||||
const empathy = this.get('empathy_count');
|
||||
this.set('empathy_count', empathy + 1);
|
||||
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
CommunitySchema.methods.downEmpathy = async function() {
|
||||
const empathy = this.get('empathy_count');
|
||||
CommunitySchema.method('downEmpathy', async function downEmpathy(): Promise<void> {
|
||||
const empathy = this.get('empathy_count');
|
||||
this.set('empathy_count', empathy - 1);
|
||||
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
CommunitySchema.methods.upFollower = async function() {
|
||||
const followers = this.get('followers');
|
||||
CommunitySchema.method('upFollower', async function upFollower(): Promise<void> {
|
||||
const followers = this.get('followers');
|
||||
this.set('followers', followers + 1);
|
||||
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
CommunitySchema.methods.downFollower = async function() {
|
||||
const followers = this.get('followers');
|
||||
CommunitySchema.method('downFollower', async function downFollower(): Promise<void> {
|
||||
const followers = this.get('followers');
|
||||
this.set('followers', followers - 1);
|
||||
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
const COMMUNITY = model('COMMUNITY', CommunitySchema);
|
||||
|
||||
module.exports = {
|
||||
CommunitySchema,
|
||||
COMMUNITY
|
||||
};
|
||||
export const Community: CommunityModel = model<ICommunity, CommunityModel>('Community', CommunitySchema);
|
||||
@@ -1,6 +1,7 @@
|
||||
const { Schema, model } = require('mongoose');
|
||||
import { Schema, model } from 'mongoose';
|
||||
import { IContent, IContentMethods, ContentModel } from '@/types/mongoose/content';
|
||||
|
||||
const ContentSchema = new Schema({
|
||||
const ContentSchema = new Schema<IContent, ContentModel, IContentMethods>({
|
||||
pid: Number,
|
||||
followed_communities: {
|
||||
type: [String],
|
||||
@@ -13,48 +14,43 @@ const ContentSchema = new Schema({
|
||||
following_users: {
|
||||
type: [Number],
|
||||
default: [0]
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
ContentSchema.methods.addToCommunities = async function(postID) {
|
||||
ContentSchema.method('addToCommunities', async function addToCommunities(postID) {
|
||||
const communities = this.get('followed_communities');
|
||||
communities.addToSet(postID);
|
||||
await this.save();
|
||||
}
|
||||
});
|
||||
|
||||
ContentSchema.methods.removeFromCommunities = async function(postID) {
|
||||
ContentSchema.method('removeFromCommunities', async function removeFromCommunities(postID) {
|
||||
const communities = this.get('followed_communities');
|
||||
communities.pull(postID);
|
||||
await this.save();
|
||||
}
|
||||
});
|
||||
|
||||
ContentSchema.methods.addToUsers = async function(postID) {
|
||||
ContentSchema.method('addToUsers', async function addToUsers(postID) {
|
||||
const users = this.get('followed_users');
|
||||
users.addToSet(postID);
|
||||
await this.save();
|
||||
}
|
||||
});
|
||||
|
||||
ContentSchema.methods.removeFromUsers = async function(postID) {
|
||||
ContentSchema.method('removeFromUsers', async function removeFromUsers(postID) {
|
||||
const users = this.get('followed_users');
|
||||
users.pull(postID);
|
||||
await this.save();
|
||||
}
|
||||
});
|
||||
|
||||
ContentSchema.methods.addToFollowers = async function(postID) {
|
||||
ContentSchema.method('addToFollowers', async function addToFollowers(postID) {
|
||||
const users = this.get('following_users');
|
||||
users.addToSet(postID);
|
||||
await this.save();
|
||||
}
|
||||
});
|
||||
|
||||
ContentSchema.methods.removeFromFollowers = async function(postID) {
|
||||
ContentSchema.method('removeFromFollowers', async function removeFromFollowers(postID) {
|
||||
const users = this.get('following_users');
|
||||
users.pull(postID);
|
||||
await this.save();
|
||||
}
|
||||
});
|
||||
|
||||
const CONTENT = model('CONTENT', ContentSchema);
|
||||
|
||||
module.exports = {
|
||||
ContentSchema,
|
||||
CONTENT
|
||||
};
|
||||
export const Content: ContentModel = model<IContent, ContentModel>('Content', ContentSchema);
|
||||
@@ -1,67 +0,0 @@
|
||||
const { Schema, model } = require('mongoose');
|
||||
const moment = require("moment");
|
||||
const snowflake = require('node-snowflake').Snowflake;
|
||||
|
||||
const user = new Schema({
|
||||
pid: Number,
|
||||
official: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
read: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const ConversationSchema = new Schema({
|
||||
id: {
|
||||
type: String,
|
||||
default: snowflake.nextId()
|
||||
},
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: new Date(),
|
||||
},
|
||||
last_updated: {
|
||||
type: Date,
|
||||
default: new Date(),
|
||||
},
|
||||
message_preview: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
users: [user]
|
||||
});
|
||||
|
||||
ConversationSchema.methods.newMessage = async function(message, fromPid) {
|
||||
if(this.users[0].pid === fromPid) {
|
||||
this.users[1].read = false;
|
||||
this.markModified('users[1].read');
|
||||
}
|
||||
else {
|
||||
this.users[0].read = false;
|
||||
this.markModified('users[0].read');
|
||||
}
|
||||
this.set('last_updated', moment(new Date()));
|
||||
this.set('message_preview', message);
|
||||
await this.save();
|
||||
}
|
||||
|
||||
ConversationSchema.methods.markAsRead = async function(pid) {
|
||||
let users = this.get('users');
|
||||
if(users[0].pid === pid)
|
||||
users[0].read = true;
|
||||
else if(users[1].pid === pid)
|
||||
users[1].read = true;
|
||||
this.set('users', users)
|
||||
this.markModified('users');
|
||||
await this.save();
|
||||
}
|
||||
|
||||
const CONVERSATION = model('CONVERSATION', ConversationSchema);
|
||||
|
||||
module.exports = {
|
||||
ConversationSchema: ConversationSchema,
|
||||
CONVERSATION: CONVERSATION
|
||||
};
|
||||
61
src/models/conversation.ts
Normal file
61
src/models/conversation.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Schema, model } from 'mongoose';
|
||||
import moment from 'moment';
|
||||
import { Snowflake } from 'node-snowflake';
|
||||
import { IConversation, IConversationMethods, ConversationModel } from '@/types/mongoose/conversation';
|
||||
|
||||
const ConversationSchema = new Schema<IConversation, ConversationModel, IConversationMethods>({
|
||||
id: {
|
||||
type: String,
|
||||
default: Snowflake.nextId()
|
||||
},
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: new Date(),
|
||||
},
|
||||
last_updated: {
|
||||
type: Date,
|
||||
default: new Date(),
|
||||
},
|
||||
message_preview: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
users: [{
|
||||
pid: Number,
|
||||
official: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
read: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
ConversationSchema.method('newMessage', async function newMessage(message, senderPID) {
|
||||
if(this.users[0].pid === senderPID) {
|
||||
this.users[1].read = false;
|
||||
this.markModified('users[1].read');
|
||||
}
|
||||
else {
|
||||
this.users[0].read = false;
|
||||
this.markModified('users[0].read');
|
||||
}
|
||||
this.set('last_updated', moment(new Date()));
|
||||
this.set('message_preview', message);
|
||||
await this.save();
|
||||
});
|
||||
|
||||
ConversationSchema.method('markAsRead', async function markAsRead(pid) {
|
||||
let users = this.get('users');
|
||||
if(users[0].pid === pid)
|
||||
users[0].read = true;
|
||||
else if(users[1].pid === pid)
|
||||
users[1].read = true;
|
||||
this.set('users', users)
|
||||
this.markModified('users');
|
||||
await this.save();
|
||||
});
|
||||
|
||||
export const Conversation: ConversationModel = model<IConversation, ConversationModel>('Conversation', ConversationSchema);
|
||||
@@ -1,19 +0,0 @@
|
||||
const { Schema, model } = require('mongoose');
|
||||
|
||||
const endpointSchema = new Schema({
|
||||
status: Number,
|
||||
server_access_level: String,
|
||||
topics: Boolean,
|
||||
guest_access: Boolean,
|
||||
host: String,
|
||||
api_host: String,
|
||||
portal_host: String,
|
||||
n3ds_host: String
|
||||
});
|
||||
|
||||
const ENDPOINT = model('ENDPOINT', endpointSchema);
|
||||
|
||||
module.exports = {
|
||||
endpointSchema,
|
||||
ENDPOINT
|
||||
};
|
||||
15
src/models/endpoint.ts
Normal file
15
src/models/endpoint.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Schema, model } from 'mongoose';
|
||||
import { IEndpoint, IEndpointMethods, EndpointModel } from '@/types/mongoose/endpoint';
|
||||
|
||||
const endpointSchema = new Schema<IEndpoint, EndpointModel, IEndpointMethods>({
|
||||
status: Number,
|
||||
server_access_level: String,
|
||||
topics: Boolean,
|
||||
guest_access: Boolean,
|
||||
host: String,
|
||||
api_host: String,
|
||||
portal_host: String,
|
||||
n3ds_host: String
|
||||
});
|
||||
|
||||
export const Endpoint: EndpointModel = model<IEndpoint, EndpointModel>('Endpoint', endpointSchema);
|
||||
22
src/models/notification.ts
Normal file
22
src/models/notification.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Schema, model } from 'mongoose';
|
||||
import { INotification, INotificationMethods, NotificationModel } from '@/types/mongoose/notification';
|
||||
|
||||
const NotificationSchema = new Schema<INotification, NotificationModel, INotificationMethods>({
|
||||
pid: String,
|
||||
type: String,
|
||||
link: String,
|
||||
objectID: String,
|
||||
users: [{
|
||||
user: String,
|
||||
timestamp: Date
|
||||
}],
|
||||
read: Boolean,
|
||||
lastUpdated: Date
|
||||
});
|
||||
|
||||
NotificationSchema.method('markRead', async function markRead() {
|
||||
this.set('read', true);
|
||||
await this.save();
|
||||
});
|
||||
|
||||
export const Notification: NotificationModel = model<INotification, NotificationModel>('Notification', NotificationSchema);
|
||||
@@ -1,26 +0,0 @@
|
||||
const { Schema, model } = require('mongoose');
|
||||
|
||||
const NotificationSchema = new Schema({
|
||||
pid: String,
|
||||
type: String,
|
||||
link: String,
|
||||
objectID: String,
|
||||
users: [{
|
||||
user: String,
|
||||
timestamp: Date
|
||||
}],
|
||||
read: Boolean,
|
||||
lastUpdated: Date
|
||||
});
|
||||
|
||||
NotificationSchema.methods.markRead = async function() {
|
||||
this.set('read', true);
|
||||
await this.save();
|
||||
};
|
||||
|
||||
const NOTIFICATION = model('NOTIFICATION', NotificationSchema);
|
||||
|
||||
module.exports = {
|
||||
NotificationSchema,
|
||||
NOTIFICATION
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
const mongoose = require('mongoose');
|
||||
const {pnidConnection} = require('../accountdb');
|
||||
import mongoose from 'mongoose';
|
||||
import { pnidConnection } from '@/accountdb';
|
||||
import { IPNID, IPNIDMethods, PNIDModel } from '@/types/mongoose/pnid';
|
||||
|
||||
const PNIDSchema = new mongoose.Schema({
|
||||
const PNIDSchema = new mongoose.Schema<IPNID, PNIDModel, IPNIDMethods>({
|
||||
access_level: {
|
||||
type: Number,
|
||||
default: 0 // -1: banned, 0: standard, 1: tester, 2: mod, 3: dev
|
||||
@@ -31,12 +32,7 @@ const PNIDSchema = new mongoose.Schema({
|
||||
latest_webhook_timestamp: Number
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const PNID = pnidConnection.model('PNID', PNIDSchema);
|
||||
|
||||
module.exports = {
|
||||
PNID,
|
||||
};
|
||||
export const PNID: PNIDModel = pnidConnection.model<IPNID, PNIDModel>('PNID', PNIDSchema);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { Schema, model } = require('mongoose');
|
||||
import { Schema, model } from 'mongoose';
|
||||
import { IPost, IPostMethods, PostModel } from '@/types/mongoose/post';
|
||||
|
||||
const PostSchema = new Schema({
|
||||
const PostSchema = new Schema<IPost, PostModel, IPostMethods>({
|
||||
id: String,
|
||||
title_id: String,
|
||||
screen_name: String,
|
||||
@@ -75,10 +76,11 @@ const PostSchema = new Schema({
|
||||
default: false
|
||||
},
|
||||
removed_reason: String,
|
||||
yeahs: [Number]
|
||||
yeahs: [Number],
|
||||
number: Number
|
||||
});
|
||||
|
||||
PostSchema.methods.upReply = async function() {
|
||||
PostSchema.method('upReply', async function upReply() {
|
||||
const replyCount = this.get('reply_count');
|
||||
if(replyCount + 1 < 0)
|
||||
this.set('reply_count', 0);
|
||||
@@ -86,9 +88,9 @@ PostSchema.methods.upReply = async function() {
|
||||
this.set('reply_count', replyCount + 1);
|
||||
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
PostSchema.methods.downReply = async function() {
|
||||
PostSchema.method('downReply', async function downReply() {
|
||||
const replyCount = this.get('reply_count');
|
||||
if(replyCount - 1 < 0)
|
||||
this.set('reply_count', 0);
|
||||
@@ -96,23 +98,18 @@ PostSchema.methods.downReply = async function() {
|
||||
this.set('reply_count', replyCount - 1);
|
||||
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
PostSchema.methods.remove = async function(reason) {
|
||||
PostSchema.method('remove', async function remove(reason) {
|
||||
this.set('remove', true);
|
||||
this.set('removed_reason', reason)
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
PostSchema.methods.unRemove = async function(reason) {
|
||||
PostSchema.method('unRemove', async function unRemove(reason) {
|
||||
this.set('remove', false);
|
||||
this.set('removed_reason', reason)
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
const POST = model('POST', PostSchema);
|
||||
|
||||
module.exports = {
|
||||
PostSchema,
|
||||
POST
|
||||
};
|
||||
export const Post: PostModel = model<IPost, PostModel>('Post', PostSchema);
|
||||
@@ -1,18 +0,0 @@
|
||||
const { Schema, model } = require('mongoose');
|
||||
|
||||
const ReportSchema = new Schema({
|
||||
pid: String,
|
||||
post_id: String,
|
||||
reason: Number,
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
const REPORT = model('REPORT', ReportSchema);
|
||||
|
||||
module.exports = {
|
||||
ReportSchema,
|
||||
REPORT
|
||||
};
|
||||
14
src/models/report.ts
Normal file
14
src/models/report.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Schema, model } from 'mongoose';
|
||||
import { IReport, IReportMethods, ReportModel } from '@/types/mongoose/report';
|
||||
|
||||
const ReportSchema = new Schema<IReport, ReportModel, IReportMethods>({
|
||||
pid: String,
|
||||
post_id: String,
|
||||
reason: Number,
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
export const Report: ReportModel = model<IReport, ReportModel>('Report', ReportSchema);
|
||||
@@ -1,6 +1,7 @@
|
||||
const { Schema, model } = require('mongoose');
|
||||
import { Schema, model } from 'mongoose';
|
||||
import { ISettings, ISettingsMethods, SettingsModel } from '@/types/mongoose/settings';
|
||||
|
||||
const SettingsSchema = new Schema({
|
||||
const SettingsSchema = new Schema<ISettings, SettingsModel, ISettingsMethods>({
|
||||
pid: Number,
|
||||
screen_name: String,
|
||||
account_status: {
|
||||
@@ -47,49 +48,44 @@ const SettingsSchema = new Schema({
|
||||
}
|
||||
});
|
||||
|
||||
SettingsSchema.methods.updateComment = async function(comment) {
|
||||
SettingsSchema.method('updateComment', async function updateComment(comment) {
|
||||
this.set('profile_comment', comment);
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
SettingsSchema.methods.updateSkill = async function(skill) {
|
||||
SettingsSchema.method('updateSkill', async function updateSkill(skill) {
|
||||
this.set('game_skill', skill);
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
SettingsSchema.methods.commentVisible = async function(active) {
|
||||
SettingsSchema.method('commentVisible', async function commentVisible(active) {
|
||||
this.set('profile_comment_visibility', active);
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
SettingsSchema.methods.skillVisible = async function(active) {
|
||||
SettingsSchema.method('skillVisible', async function skillVisible(active) {
|
||||
this.set('game_skill_visibility', active);
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
SettingsSchema.methods.birthdayVisible = async function(active) {
|
||||
SettingsSchema.method('birthdayVisible', async function birthdayVisible(active) {
|
||||
this.set('birthday_visibility', active);
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
SettingsSchema.methods.relationshipVisible = async function(active) {
|
||||
SettingsSchema.method('relationshipVisible', async function relationshipVisible(active) {
|
||||
this.set('relationship_visibility', active);
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
SettingsSchema.methods.countryVisible = async function(active) {
|
||||
SettingsSchema.method('countryVisible', async function countryVisible(active) {
|
||||
this.set('country_visibility', active);
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
SettingsSchema.methods.favCommunityVisible = async function(active) {
|
||||
SettingsSchema.method('favCommunityVisible', async function favCommunityVisible(active) {
|
||||
this.set('profile_favorite_community_visibility', active);
|
||||
await this.save();
|
||||
};
|
||||
});
|
||||
|
||||
const SETTINGS = model('SETTINGS', SettingsSchema);
|
||||
|
||||
module.exports = {
|
||||
SettingsSchema,
|
||||
SETTINGS
|
||||
};
|
||||
export const Settings: SettingsModel = model<ISettings, SettingsModel>('Settings', SettingsSchema);
|
||||
@@ -1,76 +0,0 @@
|
||||
process.title = 'Pretendo - Miiverse';
|
||||
const express = require('express');
|
||||
const morgan = require('morgan');
|
||||
const xmlparser = require('./middleware/xml-parser');
|
||||
const database = require('./database');
|
||||
const logger = require('./logger');
|
||||
const config = require('../config.json');
|
||||
const auth = require('./middleware/auth');
|
||||
|
||||
const { http: { port } } = config;
|
||||
const app = express();
|
||||
|
||||
const miiverse = require('./services/miiverse-api');
|
||||
const xml = require("object-to-xml");
|
||||
|
||||
app.set('etag', false);
|
||||
app.disable('x-powered-by');
|
||||
|
||||
// Create router
|
||||
logger.info('Setting up Middleware');
|
||||
app.use(morgan('dev'));
|
||||
app.use(express.json());
|
||||
|
||||
app.use(express.urlencoded({
|
||||
extended: true,
|
||||
limit: '5mb',
|
||||
parameterLimit: 100000
|
||||
}));
|
||||
app.use(xmlparser);
|
||||
app.use(auth);
|
||||
|
||||
// import the servers into one
|
||||
app.use(miiverse);
|
||||
|
||||
// 404 handler
|
||||
logger.info('Creating 404 status handler');
|
||||
app.use((req, res) => {
|
||||
//logger.warn(request.protocol + '://' + request.get('host') + request.originalUrl);
|
||||
res.set("Content-Type", "application/xml");
|
||||
res.statusCode = 404;
|
||||
let response = {
|
||||
result: {
|
||||
has_error: 1,
|
||||
version: 1,
|
||||
code: 404,
|
||||
message: "Not Found"
|
||||
}
|
||||
};
|
||||
return res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
|
||||
});
|
||||
|
||||
// non-404 error handler
|
||||
logger.info('Creating non-404 status handler');
|
||||
app.use((error, req, res) => {
|
||||
const status = error.status || 500;
|
||||
res.set("Content-Type", "application/xml");
|
||||
res.statusCode = 404;
|
||||
let response = {
|
||||
result: {
|
||||
has_error: 1,
|
||||
version: 1,
|
||||
code: status,
|
||||
message: "Not Found"
|
||||
}
|
||||
};
|
||||
return res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
|
||||
});
|
||||
|
||||
// Starts the server
|
||||
logger.info('Starting server');
|
||||
|
||||
database.connect().then(() => {
|
||||
app.listen(port, () => {
|
||||
logger.success(`Server started on port ${port}`);
|
||||
});
|
||||
});
|
||||
76
src/server.ts
Normal file
76
src/server.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
process.title = 'Pretendo - Miiverse';
|
||||
|
||||
import express from 'express';
|
||||
import morgan from 'morgan';
|
||||
import xml from 'object-to-xml';
|
||||
import { connect as connectDatabase } from '@/database';
|
||||
import { LOG_INFO, LOG_SUCCESS } from '@/logger';
|
||||
import config from '../config.json';
|
||||
import xmlparser from '@/middleware/xml-parser';
|
||||
import auth from '@/middleware/auth';
|
||||
|
||||
import miiverse from '@/services/miiverse-api';
|
||||
|
||||
const { http: { port } } = config;
|
||||
const app = express();
|
||||
|
||||
app.set('etag', false);
|
||||
app.disable('x-powered-by');
|
||||
|
||||
// Create router
|
||||
LOG_INFO('Setting up Middleware');
|
||||
app.use(morgan('dev'));
|
||||
app.use(express.json());
|
||||
|
||||
app.use(express.urlencoded({
|
||||
extended: true,
|
||||
limit: '5mb',
|
||||
parameterLimit: 100000
|
||||
}));
|
||||
app.use(xmlparser);
|
||||
app.use(auth);
|
||||
|
||||
// import the servers into one
|
||||
app.use(miiverse);
|
||||
|
||||
// 404 handler
|
||||
LOG_INFO('Creating 404 status handler');
|
||||
app.use((req, res) => {
|
||||
//logger.warn(request.protocol + '://' + request.get('host') + request.originalUrl);
|
||||
res.set('Content-Type', 'application/xml');
|
||||
res.statusCode = 404;
|
||||
const response = {
|
||||
result: {
|
||||
has_error: 1,
|
||||
version: 1,
|
||||
code: 404,
|
||||
message: 'Not Found'
|
||||
}
|
||||
};
|
||||
return res.send('<?xml version="1.0" encoding="UTF-8"?>\n' + xml(response));
|
||||
});
|
||||
|
||||
// non-404 error handler
|
||||
LOG_INFO('Creating non-404 status handler');
|
||||
app.use((error, req, res, _next) => {
|
||||
const status = error.status || 500;
|
||||
res.set('Content-Type', 'application/xml');
|
||||
res.statusCode = 404;
|
||||
const response = {
|
||||
result: {
|
||||
has_error: 1,
|
||||
version: 1,
|
||||
code: status,
|
||||
message: 'Not Found'
|
||||
}
|
||||
};
|
||||
return res.send('<?xml version="1.0" encoding="UTF-8"?>\n' + xml(response));
|
||||
});
|
||||
|
||||
// Starts the server
|
||||
LOG_INFO('Starting server');
|
||||
connectDatabase().then(() => {
|
||||
app.listen(port, () => {
|
||||
LOG_SUCCESS(`Server started on port ${port}`);
|
||||
});
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
const express = require('express');
|
||||
const subdomain = require('express-subdomain');
|
||||
const sessionMiddleware = require('../../middleware/session');
|
||||
const pnidMiddleware = require('../../middleware/pnid');
|
||||
const logger = require('../../logger');
|
||||
const routes = require('./routes');
|
||||
|
||||
// Main router for endpointsindex.js
|
||||
const router = express.Router();
|
||||
|
||||
// Router to handle the subdomain restriction
|
||||
const discovery = express.Router();
|
||||
const api = express.Router();
|
||||
|
||||
// Create subdomains
|
||||
logger.info('[MIIVERSE] Creating \'discovery\' subdomain');
|
||||
router.use(subdomain('discovery.olv', discovery));
|
||||
logger.info('[MIIVERSE] Creating \'api\' subdomain');
|
||||
router.use(subdomain('api.olv', api));
|
||||
router.use(subdomain('api-test.olv', api));
|
||||
router.use(subdomain('api-dev.olv', api));
|
||||
|
||||
|
||||
logger.info('[MIIVERSE] Importing middleware');
|
||||
discovery.use(sessionMiddleware);
|
||||
discovery.use(pnidMiddleware);
|
||||
|
||||
// Setup routes
|
||||
discovery.use('/v1/endpoint', routes.DISCOVERY);
|
||||
api.use('/v1/posts', routes.POST);
|
||||
api.use('/v1/posts.search', routes.POST);
|
||||
api.use('/v1/friend_messages', routes.MESSAGE);
|
||||
api.use('/v1/communities/', routes.COMMUNITY);
|
||||
api.use('/v1/people/', routes.PEOPLE);
|
||||
api.use('/v1/topics/', routes.TOPICS);
|
||||
api.use('/v1/users/', routes.USERS);
|
||||
api.use('/v1/status/', routes.PING);
|
||||
|
||||
module.exports = router;
|
||||
44
src/services/miiverse-api/index.ts
Normal file
44
src/services/miiverse-api/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import express from 'express';
|
||||
import subdomain from 'express-subdomain';
|
||||
import sessionMiddleware from '@/middleware/session';
|
||||
import { LOG_INFO } from '@/logger';
|
||||
|
||||
import DISCOVERY from '@/services/miiverse-api/routes/discovery';
|
||||
import POST from '@/services/miiverse-api/routes/post';
|
||||
import MESSAGE from '@/services/miiverse-api/routes/message';
|
||||
import COMMUNITY from '@/services/miiverse-api/routes/communities';
|
||||
import PEOPLE from '@/services/miiverse-api/routes/people';
|
||||
import TOPICS from '@/services/miiverse-api/routes/topics';
|
||||
import USERS from '@/services/miiverse-api/routes/users';
|
||||
import PING from '@/services/miiverse-api/routes/ping';
|
||||
|
||||
// Main router for endpointsindex.js
|
||||
const router = express.Router();
|
||||
|
||||
// Router to handle the subdomain restriction
|
||||
const discovery = express.Router();
|
||||
const api = express.Router();
|
||||
|
||||
// Create subdomains
|
||||
LOG_INFO('[MIIVERSE] Creating \'discovery\' subdomain');
|
||||
router.use(subdomain('discovery.olv', discovery));
|
||||
LOG_INFO('[MIIVERSE] Creating \'api\' subdomain');
|
||||
router.use(subdomain('api.olv', api));
|
||||
router.use(subdomain('api-test.olv', api));
|
||||
router.use(subdomain('api-dev.olv', api));
|
||||
|
||||
LOG_INFO('[MIIVERSE] Importing middleware');
|
||||
discovery.use(sessionMiddleware);
|
||||
|
||||
// Setup routes
|
||||
discovery.use('/v1/endpoint', DISCOVERY);
|
||||
api.use('/v1/posts', POST);
|
||||
api.use('/v1/posts.search', POST);
|
||||
api.use('/v1/friend_messages', MESSAGE);
|
||||
api.use('/v1/communities/', COMMUNITY);
|
||||
api.use('/v1/people/', PEOPLE);
|
||||
api.use('/v1/topics/', TOPICS);
|
||||
api.use('/v1/users/', USERS);
|
||||
api.use('/v1/status/', PING);
|
||||
|
||||
export default router;
|
||||
@@ -1,19 +1,27 @@
|
||||
const express = require('express');
|
||||
import express from 'express';
|
||||
import multer from 'multer';
|
||||
import {
|
||||
getSubCommunities,
|
||||
getMostPopularCommunities,
|
||||
getNewCommunities,
|
||||
getCommunityByTitleID,
|
||||
getUserContent,
|
||||
getCommunityByTitleIDs
|
||||
} from '@/database';
|
||||
import comPostGen from '@/util/xmlResponseGenerator';
|
||||
import { decodeParamPack } from '@/util';
|
||||
import { Community } from "@/models/community";
|
||||
import { Post } from "@/models/post";
|
||||
|
||||
const router = express.Router();
|
||||
const database = require('../../../database');
|
||||
const comPostGen = require('../../../util/xmlResponseGenerator');
|
||||
const util = require('../../../util/util');
|
||||
const {COMMUNITY} = require("../../../models/communities");
|
||||
const {POST} = require("../../../models/post");
|
||||
const multer = require('multer')
|
||||
|
||||
/* GET post titles. */
|
||||
router.get('/', async function (req, res) {
|
||||
const paramPack = util.decodeParamPack(req.headers["x-nintendo-parampack"]);
|
||||
let community = await database.getCommunityByTitleID(paramPack.title_id);
|
||||
const paramPack = decodeParamPack(req.headers["x-nintendo-parampack"]);
|
||||
let community = await getCommunityByTitleID(paramPack.title_id);
|
||||
if(!community) res.sendStatus(404);
|
||||
|
||||
let communities = await database.getSubCommunities(community.olive_community_id);
|
||||
let communities = await getSubCommunities(community.olive_community_id);
|
||||
if(!communities) res.sendStatus(404);
|
||||
communities.unshift(community);
|
||||
let response = await comPostGen.Communities(communities);
|
||||
@@ -22,7 +30,7 @@ router.get('/', async function (req, res) {
|
||||
});
|
||||
|
||||
router.get('/popular', async function (req, res) {
|
||||
let community = await database.getMostPopularCommunities(100);
|
||||
let community = await getMostPopularCommunities(100);
|
||||
if (community != null) {
|
||||
res.contentType("application/json");
|
||||
res.send(community);
|
||||
@@ -30,7 +38,7 @@ router.get('/popular', async function (req, res) {
|
||||
});
|
||||
|
||||
router.get('/new', async function (req, res) {
|
||||
let community = await database.getNewCommunities(100);
|
||||
let community = await getNewCommunities(100);
|
||||
if (community != null) {
|
||||
res.contentType("application/json");
|
||||
res.send(community);
|
||||
@@ -38,17 +46,21 @@ router.get('/new', async function (req, res) {
|
||||
});
|
||||
|
||||
router.get('/:appID/posts', async function (req, res) {
|
||||
const paramPack = util.decodeParamPack(req.headers["x-nintendo-parampack"]);
|
||||
let community = await COMMUNITY.findOne({ community_id: req.params.appID });
|
||||
const paramPack = decodeParamPack(req.headers["x-nintendo-parampack"]);
|
||||
let community = await Community.findOne({ community_id: req.params.appID });
|
||||
if(!community)
|
||||
community = await database.getCommunityByTitleID(paramPack.title_id);
|
||||
community = await getCommunityByTitleID(paramPack.title_id);
|
||||
if(!community)
|
||||
res.sendStatus(404);
|
||||
let query = {
|
||||
community_id: community.olive_community_id,
|
||||
removed: false,
|
||||
app_data: { $ne: null },
|
||||
message_to_pid: { $eq: null }
|
||||
message_to_pid: { $eq: null },
|
||||
search_key: null,
|
||||
is_spoiler: null,
|
||||
painting: null,
|
||||
pid: null
|
||||
}
|
||||
|
||||
if(req.query.search_key)
|
||||
@@ -59,7 +71,7 @@ router.get('/:appID/posts', async function (req, res) {
|
||||
if(req.query.type === 'memo')
|
||||
query.painting = { $ne: null };
|
||||
if(req.query.by === 'followings') {
|
||||
let userContent = await database.getUserContent(req.pid);
|
||||
let userContent = await getUserContent(req.pid);
|
||||
query.pid = userContent.following_users;
|
||||
}
|
||||
else if(req.query.by === 'self')
|
||||
@@ -67,7 +79,7 @@ router.get('/:appID/posts', async function (req, res) {
|
||||
|
||||
let posts;
|
||||
if(req.query.distinct_pid === '1')
|
||||
posts = await POST.aggregate([
|
||||
posts = await Post.aggregate([
|
||||
{ $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
|
||||
@@ -75,7 +87,7 @@ router.get('/:appID/posts', async function (req, res) {
|
||||
{ $limit: (req.query.limit ? Number(req.query.limit) : 10) } // only return the top 10 results
|
||||
]);
|
||||
else
|
||||
posts = await POST.find(query).sort({ created_at: -1}).limit(parseInt(req.query.limit));
|
||||
posts = await Post.find(query).sort({ created_at: -1}).limit(parseInt(req.query.limit as string));
|
||||
|
||||
/* Build formatted response and send it off. */
|
||||
let options = {
|
||||
@@ -90,12 +102,12 @@ router.get('/:appID/posts', async function (req, res) {
|
||||
|
||||
// Handler for POST on '/v1/communities'
|
||||
router.post('/', multer().none(), async function (req, res) {
|
||||
const paramPack = util.decodeParamPack(req.headers["x-nintendo-parampack"]);
|
||||
let parent_community = await database.getCommunityByTitleIDs(paramPack.title_id);
|
||||
const paramPack = decodeParamPack(req.headers["x-nintendo-parampack"]);
|
||||
let parent_community = await getCommunityByTitleIDs(paramPack.title_id);
|
||||
if(!parent_community) res.sendStatus(404);
|
||||
|
||||
let num_communities = await COMMUNITY.count();
|
||||
let new_community = new COMMUNITY({
|
||||
let num_communities = await Community.count();
|
||||
let new_community = new Community({
|
||||
platform_id: 0, // WiiU
|
||||
name: req.body.name,
|
||||
description: req.body.description,
|
||||
@@ -118,4 +130,4 @@ router.post('/', multer().none(), async function (req, res) {
|
||||
res.send(response);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
@@ -1,17 +1,18 @@
|
||||
const express = require('express');
|
||||
const xml = require('object-to-xml');
|
||||
const database = require('../../../database');
|
||||
import express from 'express';
|
||||
import xml from 'object-to-xml';
|
||||
import { getPNID, getEndPoint } from '@/database';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/* GET discovery server. */
|
||||
router.get('/', async function (req, res) {
|
||||
let user = await database.getPNID(req.pid);
|
||||
let user = await getPNID(req.pid);
|
||||
|
||||
let discovery;
|
||||
if(user)
|
||||
discovery = await database.getEndPoint(user.server_access_level);
|
||||
discovery = await getEndPoint(user.server_access_level);
|
||||
else
|
||||
discovery = await database.getEndPoint('prod');
|
||||
discovery = await getEndPoint('prod');
|
||||
|
||||
let message = '', error = 0;
|
||||
switch(discovery.status) {
|
||||
@@ -79,4 +80,4 @@ router.get('/', async function (req, res) {
|
||||
res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
@@ -1,10 +0,0 @@
|
||||
module.exports = {
|
||||
DISCOVERY: require('./discovery'),
|
||||
POST: require('./post'),
|
||||
MESSAGE: require('./message'),
|
||||
COMMUNITY: require('./communities'),
|
||||
PEOPLE: require('./people'),
|
||||
TOPICS: require('./topics'),
|
||||
USERS: require('./users'),
|
||||
PING: require('./ping')
|
||||
};
|
||||
@@ -1,27 +1,28 @@
|
||||
const express = require('express');
|
||||
import crypto from "node:crypto";
|
||||
import express from 'express';
|
||||
import multer from 'multer';
|
||||
import { Snowflake } from 'node-snowflake';
|
||||
import moment from 'moment';
|
||||
import xml from 'object-to-xml';
|
||||
import { getFriends, decodeParamPack, processPainting, uploadCDNAsset } from '@/util';
|
||||
import { getPNID, getConversationByUsers, getUserSettings, getConversationByID, getFriendMessages } from '@/database';
|
||||
import { Post } from '@/models/post';
|
||||
import { Conversation } from '@/models/conversation';
|
||||
|
||||
const router = express.Router();
|
||||
const moment = require('moment');
|
||||
const xml = require('object-to-xml');
|
||||
const { POST } = require('../../../models/post');
|
||||
const { CONVERSATION } = require('../../../models/conversation');
|
||||
const util = require('../../../util/util');
|
||||
const database = require('../../../database');
|
||||
const multer = require('multer');
|
||||
const crypto = require("crypto");
|
||||
const snowflake = require('node-snowflake').Snowflake;
|
||||
const upload = multer();
|
||||
|
||||
router.post('/', upload.none(), async function (req, res) {
|
||||
let user = await database.getPNID(req.pid);
|
||||
let user2 = await database.getPNID(req.body.message_to_pid);
|
||||
let conversation = await database.getConversationByUsers([user.pid, user2.pid]);
|
||||
let userSettings = await database.getUserSettings(req.pid), user2Settings = await database.getUserSettings(user2.pid), postID = await generatePostUID(21);
|
||||
let friends = await util.data.getFriends(user2.pid);
|
||||
let user = await getPNID(req.pid);
|
||||
let user2 = await getPNID(req.body.message_to_pid);
|
||||
let conversation = await getConversationByUsers([user.pid, user2.pid]);
|
||||
let userSettings = await getUserSettings(req.pid), user2Settings = await getUserSettings(user2.pid), postID = await generatePostUID(21);
|
||||
let friends = await getFriends(user2.pid);
|
||||
if(!conversation) {
|
||||
if(!user || !user2 || userSettings || userSettings)
|
||||
return res.sendStatus(422)
|
||||
let document = {
|
||||
id: snowflake.nextId(),
|
||||
id: Snowflake.nextId(),
|
||||
users: [
|
||||
{
|
||||
pid: user.pid,
|
||||
@@ -35,31 +36,31 @@ router.post('/', upload.none(), async function (req, res) {
|
||||
},
|
||||
]
|
||||
};
|
||||
const newConversations = new CONVERSATION(document);
|
||||
const newConversations = new Conversation(document);
|
||||
await newConversations.save();
|
||||
conversation = await database.getConversationByID(document.id);
|
||||
conversation = await getConversationByID(document.id);
|
||||
}
|
||||
if(!conversation)
|
||||
return res.sendStatus(404);
|
||||
if(!friends || friends.indexOf(req.pid) === -1)
|
||||
if(!friends || friends.pids.indexOf(req.pid) === -1)
|
||||
return res.sendStatus(422);
|
||||
|
||||
if(req.body.body === '' && req.body.painting === '' && req.body.screenshot === '') {
|
||||
res.status(422);
|
||||
return res.redirect(`/friend_messages/${conversation.id}`);
|
||||
}
|
||||
let paramPackData = util.decodeParamPack(req.headers["x-nintendo-parampack"]);
|
||||
let paramPackData = decodeParamPack(req.headers["x-nintendo-parampack"]);
|
||||
let appData = "", painting = "", paintingURI = "", screenshot = null;
|
||||
if (req.body.app_data)
|
||||
appData = req.body.app_data.replace(/[^A-Za-z0-9+/=\s]/g, "");
|
||||
if (req.body.painting) {
|
||||
painting = req.body.painting.replace(/\0/g, "").trim();
|
||||
paintingURI = await util.processPainting(painting, true);
|
||||
await util.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read');
|
||||
paintingURI = await processPainting(painting, true);
|
||||
await uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read');
|
||||
}
|
||||
if (req.body.screenshot) {
|
||||
screenshot = req.body.screenshot.replace(/\0/g, "").trim();
|
||||
await util.uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read');
|
||||
await uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read');
|
||||
}
|
||||
|
||||
let miiFace;
|
||||
@@ -90,7 +91,7 @@ router.post('/', upload.none(), async function (req, res) {
|
||||
body = body.substring(0,280);
|
||||
const document = {
|
||||
title_id: paramPackData.title_id,
|
||||
community_id: community.olive_community_id,
|
||||
community_id: conversation.id,
|
||||
screen_name: user.mii.name,
|
||||
body: body,
|
||||
app_data: appData,
|
||||
@@ -107,17 +108,17 @@ router.post('/', upload.none(), async function (req, res) {
|
||||
is_spoiler: (req.body.spoiler) ? 1 : 0,
|
||||
is_app_jumpable: req.body.is_app_jumpable,
|
||||
language_id: req.body.language_id,
|
||||
mii: PNID.mii.data,
|
||||
mii_face_url: `https://mii.olv.pretendo.cc/mii/${PNID.pid}/${miiFace}`,
|
||||
mii: user.mii.data,
|
||||
mii_face_url: `https://mii.olv.pretendo.cc/mii/${user.pid}/${miiFace}`,
|
||||
pid: req.pid,
|
||||
platform_id: paramPackData.platform_id,
|
||||
region_id: paramPackData.region_id,
|
||||
verified: (PNID.access_level === 2 || PNID.access_level === 3),
|
||||
verified: (user.access_level === 2 || user.access_level === 3),
|
||||
message_to_pid: req.body.message_to_pid,
|
||||
parent: null,
|
||||
removed: false
|
||||
};
|
||||
const newPost = new POST(document);
|
||||
const newPost = new Post(document);
|
||||
newPost.save();
|
||||
res.sendStatus(200);
|
||||
let postPreviewText;
|
||||
@@ -131,9 +132,9 @@ router.post('/', upload.none(), async function (req, res) {
|
||||
});
|
||||
|
||||
router.get('/', async function(req, res) {
|
||||
let limit = parseInt(req.query.limit), search_key = req.query.search_key;
|
||||
let posts = await database.getFriendMessages(req.pid, search_key, limit);
|
||||
posts = posts.length === 0 ? " " : posts
|
||||
let limit = parseInt(req.query.limit as string), search_key = req.query.search_key;
|
||||
let posts = await getFriendMessages(req.pid, search_key, limit);
|
||||
|
||||
let postBody = [];
|
||||
for(let post of posts) {
|
||||
console.log(post)
|
||||
@@ -179,13 +180,15 @@ router.get('/', async function(req, res) {
|
||||
});
|
||||
|
||||
router.post('/:post_id/empathies', upload.none(), async function (req, res) {
|
||||
let pid = util.processServiceToken(req.headers["x-nintendo-servicetoken"]);
|
||||
const post = await database.getPostByID(req.params.post_id);
|
||||
// TODO - FOR JEMMA! FIX THIS! MISSING SCHEMA METHODS
|
||||
/*
|
||||
let pid = processServiceToken(req.headers["x-nintendo-servicetoken"]);
|
||||
const post = await getPostByID(req.params.post_id);
|
||||
if(pid === null) {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
let user = await database.getUserByPID(pid);
|
||||
let user = await getUserByPID(pid);
|
||||
if(user.likes.indexOf(post.id) === -1 && user.id !== post.pid)
|
||||
{
|
||||
post.upEmpathy();
|
||||
@@ -194,13 +197,14 @@ router.post('/:post_id/empathies', upload.none(), async function (req, res) {
|
||||
}
|
||||
else
|
||||
res.sendStatus(403);
|
||||
*/
|
||||
});
|
||||
|
||||
async function generatePostUID(length) {
|
||||
let id = Buffer.from(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(length * 2))), 'binary').toString('base64').replace(/[+/]/g, "").substring(0, length);
|
||||
const inuse = await POST.findOne({ id });
|
||||
const inuse = await Post.findOne({ id });
|
||||
id = (inuse ? await generatePostUID(length) : id);
|
||||
return id;
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
@@ -1,24 +1,26 @@
|
||||
const express = require('express');
|
||||
import express from 'express';
|
||||
import xmlGenerator from '@/util/xmlResponseGenerator';
|
||||
import { getUserContent, getFollowedUsers } from '@/database';
|
||||
import { getFriends } from '@/util';
|
||||
import { Post } from "@/models/post";
|
||||
|
||||
const router = express.Router();
|
||||
const database = require('../../../database');
|
||||
const xmlGenerator = require('../../../util/xmlResponseGenerator');
|
||||
const {POST} = require("../../../models/post");
|
||||
const util = require('../../../util/util')
|
||||
|
||||
/* GET post titles. */
|
||||
router.get('/', async function (req, res) {
|
||||
let userContent = await database.getUserContent(req.pid);
|
||||
let userContent = await getUserContent(req.pid);
|
||||
if(!userContent) return res.sendStatus(404);
|
||||
let query = {
|
||||
removed: false,
|
||||
is_spoiler: 0,
|
||||
app_data: { $eq: null },
|
||||
parent: { $eq: null },
|
||||
message_to_pid: { $eq: null }
|
||||
message_to_pid: { $eq: null },
|
||||
pid: null
|
||||
}
|
||||
|
||||
if(req.query.relation === 'friend') {
|
||||
let friends = await util.getFriends(req.pid);
|
||||
let friends = await getFriends(req.pid);
|
||||
if(!friends) return res.sendStatus(204);
|
||||
query.pid = { $in: friends.pids };
|
||||
}
|
||||
@@ -26,11 +28,11 @@ router.get('/', async function (req, res) {
|
||||
query.pid = { $in: userContent.followed_users.map(i=>Number(i)) };
|
||||
}
|
||||
else if(req.query.pid) {
|
||||
query.pid = { $in: req.query.pid.map(i=>Number(i)) }
|
||||
query.pid = { $in: (req.query.pid as string[]).map(i=>Number(i)) }
|
||||
}
|
||||
let posts;
|
||||
if(req.query.distinct_pid === '1')
|
||||
posts = await POST.aggregate([
|
||||
posts = await Post.aggregate([
|
||||
{ $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
|
||||
@@ -38,9 +40,9 @@ router.get('/', async function (req, res) {
|
||||
{ $limit: (req.query.limit ? Number(req.query.limit) : 10) } // only return the top 10 results
|
||||
]);
|
||||
else if(req.query.is_hot === '1')
|
||||
posts = await POST.find(query).sort({ empathy_count: -1}).limit(parseInt(req.query.limit));
|
||||
posts = await Post.find(query).sort({ empathy_count: -1}).limit(parseInt(req.query.limit as string));
|
||||
else
|
||||
posts = await POST.find(query).sort({ created_at: -1}).limit(parseInt(req.query.limit));
|
||||
posts = await Post.find(query).sort({ created_at: -1}).limit(parseInt(req.query.limit as string));
|
||||
|
||||
/* Build formatted response and send it off. */
|
||||
let options = {
|
||||
@@ -53,11 +55,11 @@ router.get('/', async function (req, res) {
|
||||
});
|
||||
|
||||
router.get('/:pid/following', async function (req, res) {
|
||||
let user = await database.getUserContent(req.params.pid);
|
||||
let user = await getUserContent(req.params.pid);
|
||||
if(!user) res.sendStatus(404);
|
||||
let people = await database.getFollowedUsers(user);
|
||||
let people = await getFollowedUsers(user);
|
||||
if(!people) res.sendStatus(404);
|
||||
res.send(await xmlGenerator.Following(people));
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
@@ -1,15 +1,16 @@
|
||||
const express = require('express');
|
||||
import express from 'express';
|
||||
import { getEndpoints } from '@/database';
|
||||
|
||||
const router = express.Router();
|
||||
const database = require('../../../database');
|
||||
|
||||
router.get('/', async function(req, res) {
|
||||
res.send('Pong!');
|
||||
});
|
||||
|
||||
router.get('/database', async function(req, res) {
|
||||
let document = await database.getEndpoints();
|
||||
let document = await getEndpoints();
|
||||
if(document)
|
||||
res.send('DB Connection Working! :D');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
@@ -1,16 +1,25 @@
|
||||
const express = require('express');
|
||||
import crypto from "node:crypto";
|
||||
import express from 'express';
|
||||
import multer from 'multer';
|
||||
import { Snowflake } from 'node-snowflake';
|
||||
import xml from 'object-to-xml';
|
||||
import communityPostGen from '@/util/xmlResponseGenerator';
|
||||
import { processServiceToken, decodeParamPack, processPainting, uploadCDNAsset } from '@/util';
|
||||
import {
|
||||
getPostByID,
|
||||
getUserContent,
|
||||
getPostReplies,
|
||||
getPNID,
|
||||
getUserSettings,
|
||||
getCommunityByID,
|
||||
getCommunityByTitleID,
|
||||
getDuplicatePosts
|
||||
} from '@/database';
|
||||
import { Post } from '@/models/post';
|
||||
const { Community } = require("@/models/community");
|
||||
|
||||
const router = express.Router();
|
||||
const xml = require('object-to-xml');
|
||||
const { POST } = require('../../../models/post');
|
||||
const util = require('../../../util/util');
|
||||
const database = require('../../../database');
|
||||
const multer = require('multer');
|
||||
const snowflake = require('node-snowflake').Snowflake;
|
||||
const communityPostGen = require('../../../util/xmlResponseGenerator');
|
||||
const {COMMUNITY} = require("../../../models/communities");
|
||||
const processHeaders = require("../../../util/util");
|
||||
const comPostGen = require("../../../util/xmlResponseGenerator");
|
||||
const crypto = require("crypto");
|
||||
|
||||
const upload = multer();
|
||||
|
||||
/* GET post titles. */
|
||||
@@ -19,8 +28,8 @@ router.post('/', upload.none(), async function (req, res) { await newPost(req, r
|
||||
router.post('/:post_id/replies', upload.none(), async function (req, res) { await newPost(req, res)});
|
||||
|
||||
router.post('/:post_id.delete', async function (req, res) {
|
||||
const post = await database.getPostByID(req.params.post_id);
|
||||
let user = await database.getUserContent(req.pid);
|
||||
const post = await getPostByID(req.params.post_id);
|
||||
let user = await getUserContent(req.pid);
|
||||
if(!post || !user)
|
||||
return res.sendStatus(504);
|
||||
if(post.pid === user.pid) {
|
||||
@@ -32,10 +41,10 @@ router.post('/:post_id.delete', async function (req, res) {
|
||||
});
|
||||
|
||||
router.post('/:post_id/empathies', upload.none(), async function (req, res) {
|
||||
const post = await database.getPostByID(req.params.post_id);
|
||||
const post = await getPostByID(req.params.post_id);
|
||||
if(!post) res.sendStatus(404);
|
||||
if(post.yeahs.indexOf(req.pid) === -1) {
|
||||
await POST.updateOne({
|
||||
await Post.updateOne({
|
||||
id: post.id,
|
||||
yeahs: {
|
||||
$ne: req.pid
|
||||
@@ -51,7 +60,7 @@ router.post('/:post_id/empathies', upload.none(), async function (req, res) {
|
||||
});
|
||||
}
|
||||
else if(post.yeahs.indexOf(req.pid) !== -1) {
|
||||
await POST.updateOne({
|
||||
await Post.updateOne({
|
||||
id: post.id,
|
||||
yeahs: {
|
||||
$eq: req.pid
|
||||
@@ -70,16 +79,16 @@ router.post('/:post_id/empathies', upload.none(), async function (req, res) {
|
||||
});
|
||||
|
||||
router.get('/:post_id/replies', async function (req, res) {
|
||||
let pid = util.processServiceToken(req.headers["x-nintendo-servicetoken"]);
|
||||
const post = await database.getPostByID(req.params.post_id);
|
||||
let pid = processServiceToken(req.headers["x-nintendo-servicetoken"]);
|
||||
const post = await getPostByID(req.params.post_id);
|
||||
if(!post)
|
||||
return res.sendStatus(404);
|
||||
const posts = await database.getPostReplies(post.id, req.query.limit)
|
||||
if(!posts.length === 0)
|
||||
const posts = await getPostReplies(post.id, req.query.limit)
|
||||
if(!posts || posts.length === 0)
|
||||
return res.sendStatus(404);
|
||||
let options = {
|
||||
name: 'replies',
|
||||
with_mii: req.query.with_mii === 1,
|
||||
with_mii: req.query.with_mii as string === '1',
|
||||
topic_tag: true
|
||||
}
|
||||
/* Build formatted response and send it off. */
|
||||
@@ -89,7 +98,7 @@ router.get('/:post_id/replies', async function (req, res) {
|
||||
});
|
||||
|
||||
router.get('', async function (req, res) {
|
||||
const post = await database.getPostByID(req.query.post_id);
|
||||
const post = await getPostByID(req.query.post_id);
|
||||
if(!post) {
|
||||
res.set("Content-Type", "application/xml");
|
||||
res.statusCode = 404;
|
||||
@@ -106,23 +115,21 @@ router.get('', async function (req, res) {
|
||||
else res.send(await communityPostGen.QueryResponse(post));
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
async function newPost(req, res) {
|
||||
let PNID = await database.getPNID(req.pid), userSettings = await database.getUserSettings(req.pid), postID = await generatePostUID(21), parentPost = null;
|
||||
let paramPackData = util.decodeParamPack(req.headers["x-nintendo-parampack"]);
|
||||
let PNID = await getPNID(req.pid), userSettings = await getUserSettings(req.pid), postID = await generatePostUID(21), parentPost = null;
|
||||
let paramPackData = decodeParamPack(req.headers["x-nintendo-parampack"]);
|
||||
let community_id = req.body.community_id;
|
||||
|
||||
let community = await database.getCommunityByID(community_id)
|
||||
let community = await getCommunityByID(community_id)
|
||||
if(!community)
|
||||
community = await COMMUNITY.findOne({olive_community_id: community_id});
|
||||
community = await Community.findOne({olive_community_id: community_id});
|
||||
if(!community)
|
||||
community = await database.getCommunityByTitleID(paramPackData.title_id);
|
||||
community = await getCommunityByTitleID(paramPackData.title_id);
|
||||
|
||||
if(!community || userSettings.account_status !== 0 || community.community_id === 'announcements')
|
||||
return res.sendStatus(403);
|
||||
if(req.params.post_id) {
|
||||
parentPost = await database.getPostByID(req.params.post_id.toString());
|
||||
parentPost = await getPostByID(req.params.post_id.toString());
|
||||
if(!parentPost)
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
@@ -137,12 +144,12 @@ async function newPost(req, res) {
|
||||
appData = req.body.app_data.replace(/[^A-Za-z0-9+/=\s]/g, "");
|
||||
if (req.body.painting) {
|
||||
painting = req.body.painting.replace(/\0/g, "").trim();
|
||||
paintingURI = await util.processPainting(painting, true);
|
||||
await util.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read');
|
||||
paintingURI = await processPainting(painting, true);
|
||||
await uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read');
|
||||
}
|
||||
if (req.body.screenshot) {
|
||||
screenshot = req.body.screenshot.replace(/\0/g, "").trim();
|
||||
await util.uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read');
|
||||
await uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read');
|
||||
}
|
||||
|
||||
let miiFace;
|
||||
@@ -201,7 +208,7 @@ async function newPost(req, res) {
|
||||
parent: parentPost ? parentPost.id : null,
|
||||
removed: false
|
||||
};
|
||||
let duplicatePost = await database.getDuplicatePosts(req.pid, document);
|
||||
let duplicatePost = await getDuplicatePosts(req.pid, document);
|
||||
if(duplicatePost || document.body === '' && document.painting === '' && document.screenshot === '') {
|
||||
res.set("Content-Type", "application/xml");
|
||||
res.statusCode = 400;
|
||||
@@ -216,7 +223,7 @@ async function newPost(req, res) {
|
||||
};
|
||||
return res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
|
||||
}
|
||||
const newPost = new POST(document);
|
||||
const newPost = new Post(document);
|
||||
newPost.save();
|
||||
if(parentPost) {
|
||||
parentPost.reply_count = parentPost.reply_count + 1;
|
||||
@@ -227,7 +234,10 @@ async function newPost(req, res) {
|
||||
|
||||
async function generatePostUID(length) {
|
||||
let id = Buffer.from(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(length * 2))), 'binary').toString('base64').replace(/[+/]/g, "").substring(0, length);
|
||||
const inuse = await POST.findOne({ id });
|
||||
const inuse = await Post.findOne({ id });
|
||||
id = (inuse ? await generatePostUID(length) : id);
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
export default router;
|
||||
@@ -1,19 +1,21 @@
|
||||
const express = require('express');
|
||||
import express from 'express';
|
||||
import memoize from 'memoize';
|
||||
import { getPNID, getEndPoint } from '@/database';
|
||||
import { Post } from '@/models/post';
|
||||
import { Community } from '@/models/community';
|
||||
import comPostGen from '@/util/xmlResponseGenerator';
|
||||
|
||||
const router = express.Router();
|
||||
const database = require('../../../database');
|
||||
const {POST} = require('../../../models/post');
|
||||
const {COMMUNITY} = require('../../../models/communities');
|
||||
const comPostGen = require('../../../util/xmlResponseGenerator');
|
||||
let memoize = require("memoizee");
|
||||
memoized = memoize(comPostGen.topics, { async: true, maxAge: 1000 * 60 * 60 });
|
||||
|
||||
const memoized = memoize(comPostGen.topics, { async: true, maxAge: 1000 * 60 * 60 });
|
||||
|
||||
/* GET post titles. */
|
||||
router.get('/', async function (req, res) {
|
||||
let user = await database.getPNID(req.pid), discovery;
|
||||
let user = await getPNID(req.pid), discovery;
|
||||
if(user)
|
||||
discovery = await database.getEndPoint(user.server_access_level);
|
||||
discovery = await getEndPoint(user.server_access_level);
|
||||
else
|
||||
discovery = await database.getEndPoint('prod');
|
||||
discovery = await getEndPoint('prod');
|
||||
if(!discovery.topics) return res.sendStatus(404);
|
||||
|
||||
let communities = await calculateMostPopularCommunities(24, 10);
|
||||
@@ -27,7 +29,7 @@ router.get('/', async function (req, res) {
|
||||
async function calculateMostPopularCommunities(hours, limit) {
|
||||
const now = new Date();
|
||||
const last24Hours = new Date(now.getTime() - hours * 60 * 60 * 1000);
|
||||
const posts = await POST.find({ created_at: { $gte: last24Hours }, message_to_pid: null });
|
||||
const posts = await Post.find({ created_at: { $gte: last24Hours }, message_to_pid: null });
|
||||
if(!posts) return;
|
||||
const communityIds = {};
|
||||
for (const post of posts) {
|
||||
@@ -35,12 +37,12 @@ async function calculateMostPopularCommunities(hours, limit) {
|
||||
communityIds[communityId] = (communityIds[communityId] || 0) + 1;
|
||||
}
|
||||
const communities = Object.entries(communityIds)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.sort((a, b) => (b[1] as number) - (a[1] as number))
|
||||
.map((entry) => entry[0]);
|
||||
if(communities.size < limit)
|
||||
return COMMUNITY.find().limit(limit).sort({followers: -1});
|
||||
if(communities.length < limit)
|
||||
return Community.find().limit(limit).sort({followers: -1});
|
||||
|
||||
let response = await COMMUNITY.aggregate([
|
||||
let response = await Community.aggregate([
|
||||
{ $match: { olive_community_id: { $in: communities }, parent: null } },
|
||||
{$addFields: {
|
||||
index: { $indexOfArray: [ communities, "$olive_community_id" ] }
|
||||
@@ -54,4 +56,4 @@ async function calculateMostPopularCommunities(hours, limit) {
|
||||
else return response;
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
@@ -1,6 +1,7 @@
|
||||
const express = require('express');
|
||||
import express from 'express';
|
||||
import xml from 'object-to-xml';
|
||||
|
||||
const router = express.Router();
|
||||
const xml = require('object-to-xml');
|
||||
|
||||
router.get('/:pid/notifications', async function(req, res) {
|
||||
let type = req.query.type, title_id = req.query.title_id;
|
||||
@@ -19,4 +20,4 @@ router.get('/:pid/notifications', async function(req, res) {
|
||||
return res.send("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xml(response));
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
16
src/types/common/param-pack.ts
Normal file
16
src/types/common/param-pack.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface ParamPack {
|
||||
title_id: string;
|
||||
access_key: string;
|
||||
platform_id: string;
|
||||
region_id: string;
|
||||
language_id: string;
|
||||
country_id: string;
|
||||
area_id: string;
|
||||
network_restriction: string;
|
||||
friend_restriction: string;
|
||||
rating_restriction: string;
|
||||
rating_organization: string;
|
||||
transferable_id: string;
|
||||
tz_name: string;
|
||||
utc_offset: string;
|
||||
}
|
||||
3
src/types/common/safe-qs.ts
Normal file
3
src/types/common/safe-qs.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface SafeQs {
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
10
src/types/express.d.ts
vendored
Normal file
10
src/types/express.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// to make the file a module and avoid the TypeScript error
|
||||
export {}
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
pid: number;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/types/memoize.d.ts
vendored
Normal file
3
src/types/memoize.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
declare module 'memoize';
|
||||
|
||||
// TODO - Add proper types
|
||||
43
src/types/mongoose/community.ts
Normal file
43
src/types/mongoose/community.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Model, Types, HydratedDocument } from 'mongoose';
|
||||
|
||||
enum COMMUNITY_TYPE {
|
||||
Main = 0,
|
||||
Sub = 1,
|
||||
Announcement = 2,
|
||||
Private = 3
|
||||
}
|
||||
|
||||
export interface ICommunity {
|
||||
platform_id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
open: boolean;
|
||||
allows_comments: boolean;
|
||||
type: COMMUNITY_TYPE;
|
||||
parent: string;
|
||||
admins: Types.Array<number>;
|
||||
created_at: Date;
|
||||
empathy_count: number;
|
||||
followers: number;
|
||||
has_shop_page: number;
|
||||
icon: string;
|
||||
title_ids: Types.Array<string>;
|
||||
title_id: Types.Array<string>;
|
||||
community_id: string;
|
||||
olive_community_id: string;
|
||||
is_recommended: number;
|
||||
app_data: string;
|
||||
}
|
||||
|
||||
export interface ICommunityMethods {
|
||||
upEmpathy(): Promise<void>
|
||||
downEmpathy(): Promise<void>
|
||||
upFollower(): Promise<void>
|
||||
downFollower(): Promise<void>
|
||||
}
|
||||
|
||||
interface ICommunityQueryHelpers {}
|
||||
|
||||
export interface CommunityModel extends Model<ICommunity, ICommunityQueryHelpers, ICommunityMethods> {}
|
||||
|
||||
export type HydratedCommunityDocument = HydratedDocument<ICommunity, ICommunityMethods>
|
||||
23
src/types/mongoose/content.ts
Normal file
23
src/types/mongoose/content.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Model, Types, HydratedDocument } from 'mongoose';
|
||||
|
||||
export interface IContent {
|
||||
pid: number;
|
||||
followed_communities: Types.Array<string>;
|
||||
followed_users: Types.Array<number>;
|
||||
following_users: Types.Array<number>;
|
||||
}
|
||||
|
||||
export interface IContentMethods {
|
||||
addToCommunities(): Promise<void>
|
||||
removeFromCommunities(): Promise<void>
|
||||
addToUsers(): Promise<void>
|
||||
removeFromUsers(): Promise<void>
|
||||
addToFollowers(): Promise<void>
|
||||
removeFromFollowers(): Promise<void>
|
||||
}
|
||||
|
||||
interface IContentQueryHelpers {}
|
||||
|
||||
export interface ContentModel extends Model<IContent, IContentQueryHelpers, IContentMethods> {}
|
||||
|
||||
export type HydratedContentDocument = HydratedDocument<IContent, IContentMethods>
|
||||
26
src/types/mongoose/conversation.ts
Normal file
26
src/types/mongoose/conversation.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Model, Types, HydratedDocument } from 'mongoose';
|
||||
|
||||
export type ConversationUser = {
|
||||
pid: number;
|
||||
official: boolean;
|
||||
read: boolean;
|
||||
}
|
||||
|
||||
export interface IConversation {
|
||||
id: string;
|
||||
created_at: Date;
|
||||
last_updated: Date;
|
||||
message_preview: string,
|
||||
users: Types.Array<ConversationUser>;
|
||||
}
|
||||
|
||||
export interface IConversationMethods {
|
||||
newMessage(message: string, senderPID: number): Promise<void>
|
||||
markAsRead(pid: number): Promise<void>
|
||||
}
|
||||
|
||||
interface IConversationQueryHelpers {}
|
||||
|
||||
export interface ConversationModel extends Model<IConversation, IConversationQueryHelpers, IConversationMethods> {}
|
||||
|
||||
export type HydratedConversationDocument = HydratedDocument<IConversation, IConversationMethods>
|
||||
20
src/types/mongoose/endpoint.ts
Normal file
20
src/types/mongoose/endpoint.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Model, HydratedDocument } from 'mongoose';
|
||||
|
||||
export interface IEndpoint {
|
||||
status: number;
|
||||
server_access_level: string;
|
||||
topics: boolean;
|
||||
guest_access: boolean;
|
||||
host: string;
|
||||
api_host: string;
|
||||
portal_host: string;
|
||||
n3ds_host: string;
|
||||
}
|
||||
|
||||
export interface IEndpointMethods {}
|
||||
|
||||
interface IEndpointQueryHelpers {}
|
||||
|
||||
export interface EndpointModel extends Model<IEndpoint, IEndpointQueryHelpers, IEndpointMethods> {}
|
||||
|
||||
export type HydratedEndpointDocument = HydratedDocument<IEndpoint, IEndpointMethods>
|
||||
26
src/types/mongoose/notification.ts
Normal file
26
src/types/mongoose/notification.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Model, Types, HydratedDocument } from 'mongoose';
|
||||
|
||||
export type NotificationUser = {
|
||||
user: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface INotification {
|
||||
pid: string;
|
||||
type: string;
|
||||
link: string;
|
||||
objectID: string;
|
||||
users: Types.Array<NotificationUser>;
|
||||
read: boolean;
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
export interface INotificationMethods {
|
||||
markRead(): Promise<void>
|
||||
}
|
||||
|
||||
interface INotificationQueryHelpers {}
|
||||
|
||||
export interface NotificationModel extends Model<INotification, INotificationQueryHelpers, INotificationMethods> {}
|
||||
|
||||
export type HydratedNotificationDocument = HydratedDocument<INotification, INotificationMethods>
|
||||
42
src/types/mongoose/pnid.ts
Normal file
42
src/types/mongoose/pnid.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Model, HydratedDocument } from 'mongoose';
|
||||
|
||||
enum ACCESS_LEVEL {
|
||||
Banned = -1,
|
||||
Standard = 0,
|
||||
Tester = 1,
|
||||
Mod = 2,
|
||||
Developer = 3
|
||||
};
|
||||
|
||||
type SERVER_ACCESS_LEVEL = 'prod' | 'test' | 'dev';
|
||||
|
||||
export interface IPNID {
|
||||
access_level: ACCESS_LEVEL;
|
||||
server_access_level: SERVER_ACCESS_LEVEL;
|
||||
pid: number;
|
||||
username: string;
|
||||
birthdate: string;
|
||||
country: string;
|
||||
mii: {
|
||||
name: string;
|
||||
data: string;
|
||||
};
|
||||
connections: {
|
||||
stripe: {
|
||||
customer_id: string;
|
||||
subscription_id: string;
|
||||
price_id: string;
|
||||
tier_level: number;
|
||||
tier_name: string;
|
||||
latest_webhook_timestamp: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPNIDMethods {}
|
||||
|
||||
interface IPNIDQueryHelpers {}
|
||||
|
||||
export interface PNIDModel extends Model<IPNID, IPNIDQueryHelpers, IPNIDMethods> {}
|
||||
|
||||
export type HydratedPNIDDocument = HydratedDocument<IPNID, IPNIDMethods>
|
||||
50
src/types/mongoose/post.ts
Normal file
50
src/types/mongoose/post.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Model, Types, HydratedDocument } from 'mongoose';
|
||||
|
||||
export interface IPost {
|
||||
id: string;
|
||||
title_id: string;
|
||||
screen_name: string;
|
||||
body: string;
|
||||
app_data: string;
|
||||
painting: string;
|
||||
screenshot: string;
|
||||
screenshot_length: number;
|
||||
search_key: Types.Array<string>;
|
||||
topic_tag: string;
|
||||
community_id: string;
|
||||
created_at: number;
|
||||
feeling_id: number;
|
||||
is_autopost: number;
|
||||
is_community_private_autopost: number;
|
||||
is_spoiler: number;
|
||||
is_app_jumpable: number;
|
||||
empathy_count: number;
|
||||
country_id: number;
|
||||
language_id: number;
|
||||
mii: string;
|
||||
mii_face_url: string;
|
||||
pid: number;
|
||||
platform_id: number;
|
||||
region_id: number;
|
||||
parent: string;
|
||||
reply_count: number;
|
||||
verified: boolean;
|
||||
message_to_pid: string;
|
||||
removed: boolean;
|
||||
removed_reason: string;
|
||||
yeahs: Types.Array<number>;
|
||||
number: number;
|
||||
}
|
||||
|
||||
export interface IPostMethods {
|
||||
upReply(): Promise<void>;
|
||||
downReply(): Promise<void>;
|
||||
remove(reason: string): Promise<void>;
|
||||
unRemove(reason: string): Promise<void>;
|
||||
}
|
||||
|
||||
interface IPostQueryHelpers {}
|
||||
|
||||
export interface PostModel extends Model<IPost, IPostQueryHelpers, IPostMethods> {}
|
||||
|
||||
export type HydratedPostDocument = HydratedDocument<IPost, IPostMethods>
|
||||
16
src/types/mongoose/report.ts
Normal file
16
src/types/mongoose/report.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Model, HydratedDocument } from 'mongoose';
|
||||
|
||||
export interface IReport {
|
||||
pid: string;
|
||||
post_id: string;
|
||||
reason: number;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export interface IReportMethods {}
|
||||
|
||||
interface IReportQueryHelpers {}
|
||||
|
||||
export interface ReportModel extends Model<IReport, IReportQueryHelpers, IReportMethods> {}
|
||||
|
||||
export type HydratedReportDocument = HydratedDocument<IReport, IReportMethods>
|
||||
35
src/types/mongoose/settings.ts
Normal file
35
src/types/mongoose/settings.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Model, HydratedDocument } from 'mongoose';
|
||||
|
||||
export interface ISettings {
|
||||
pid: number;
|
||||
screen_name: string;
|
||||
account_status: number;
|
||||
ban_lift_date: Date;
|
||||
ban_reason: string;
|
||||
profile_comment: string;
|
||||
profile_comment_visibility: boolean;
|
||||
game_skill: number;
|
||||
game_skill_visibility: boolean;
|
||||
birthday_visibility: boolean;
|
||||
relationship_visibility: boolean;
|
||||
country_visibility: boolean;
|
||||
profile_favorite_community_visibility: boolean;
|
||||
receive_notifications: boolean;
|
||||
}
|
||||
|
||||
export interface ISettingsMethods {
|
||||
updateComment(comment: string): Promise<void>;
|
||||
updateSkill(skill: number): Promise<void>;
|
||||
commentVisible(active: boolean): Promise<void>;
|
||||
skillVisible(active: boolean): Promise<void>;
|
||||
birthdayVisible(active: boolean): Promise<void>;
|
||||
relationshipVisible(active: boolean): Promise<void>;
|
||||
countryVisible(active: boolean): Promise<void>;
|
||||
favCommunityVisible(active: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
interface ISettingsQueryHelpers {}
|
||||
|
||||
export interface SettingsModel extends Model<ISettings, ISettingsQueryHelpers, ISettingsMethods> {}
|
||||
|
||||
export type HydratedSettingsDocument = HydratedDocument<ISettings, ISettingsMethods>
|
||||
310
src/util.ts
Normal file
310
src/util.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import crypto from 'node:crypto';
|
||||
import { IncomingHttpHeaders } from 'node:http';
|
||||
import NodeRSA from 'node-rsa';
|
||||
import fs from 'fs-extra';
|
||||
import TGA from 'tga';
|
||||
import pako from 'pako';
|
||||
import { PNG } from 'pngjs';
|
||||
import bmp from "bmp-js";
|
||||
import aws from 'aws-sdk';
|
||||
import { createChannel, createClient, Metadata } from 'nice-grpc';
|
||||
import { FriendsDefinition } from 'pretendo-grpc-ts/src/friends/friends_service';
|
||||
import { ParsedQs } from 'qs';
|
||||
import { getPNID } from '@/database';
|
||||
import { LOG_ERROR } from '@/logger';
|
||||
import { Settings } from '@/models/settings';
|
||||
import { Content } from '@/models/content';
|
||||
import { SafeQs } from '@/types/common/safe-qs';
|
||||
import { ParamPack } from '@/types/common/param-pack';
|
||||
|
||||
const config = require('../../config.json');
|
||||
|
||||
const { ip, port, api_key } = config.grpc.friends;
|
||||
const channel = createChannel(`${ip}:${port}`);
|
||||
const client = createClient(FriendsDefinition, channel);
|
||||
|
||||
const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com');
|
||||
const s3 = new aws.S3({
|
||||
endpoint: spacesEndpoint,
|
||||
accessKeyId: config.aws.spaces.key,
|
||||
secretAccessKey: config.aws.spaces.secret
|
||||
});
|
||||
|
||||
export async function create_user(pid, experience, notifications, region) {
|
||||
const pnid = await getPNID(pid);
|
||||
if(!pnid)
|
||||
return;
|
||||
let newSettings = {
|
||||
pid: pid,
|
||||
screen_name: pnid.mii.name,
|
||||
game_skill: experience,
|
||||
receive_notifications: notifications,
|
||||
}
|
||||
let newContent = {
|
||||
pid: pid
|
||||
}
|
||||
const newSettingsObj = new Settings(newSettings);
|
||||
await newSettingsObj.save();
|
||||
|
||||
const newContentObj = new Content(newContent);
|
||||
await newContentObj.save();
|
||||
}
|
||||
|
||||
export function decodeParamPack(paramPack): ParamPack {
|
||||
/* Decode base64 */
|
||||
let dec = Buffer.from(paramPack, "base64").toString("ascii").slice(1, -1).split("\\");
|
||||
/* Remove starting and ending '/', split into array */
|
||||
/* Parameters are in the format [name, val, name, val]. Copy into out{}. */
|
||||
const out = {};
|
||||
for (let i = 0; i < dec.length; i += 2) {
|
||||
out[dec[i].trim()] = dec[i + 1].trim();
|
||||
}
|
||||
return out as ParamPack;
|
||||
}
|
||||
|
||||
export function processServiceToken(token) {
|
||||
try
|
||||
{
|
||||
let B64token = Buffer.from(token, 'base64');
|
||||
let decryptedToken = this.decryptToken(B64token);
|
||||
return decryptedToken.readUInt32LE(0x2);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function decryptToken(token) {
|
||||
// Access and refresh tokens use a different format since they must be much smaller
|
||||
// Assume a small length means access or refresh token
|
||||
if (token.length <= 32) {
|
||||
const cryptoPath = `${__dirname}/../certs/access`;
|
||||
const aesKey = Buffer.from(fs.readFileSync(`${cryptoPath}/aes.key`, { encoding: 'utf8' }), 'hex');
|
||||
|
||||
const iv = Buffer.alloc(16);
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv);
|
||||
|
||||
let decryptedBody = decipher.update(token);
|
||||
decryptedBody = Buffer.concat([decryptedBody, decipher.final()]);
|
||||
|
||||
return decryptedBody;
|
||||
}
|
||||
|
||||
const cryptoPath = `${__dirname}/../certs/access`;
|
||||
|
||||
const cryptoOptions = {
|
||||
private_key: fs.readFileSync(`${cryptoPath}/private.pem`),
|
||||
hmac_secret: config.account_server_secret
|
||||
};
|
||||
|
||||
const privateKey = new NodeRSA(cryptoOptions.private_key, 'pkcs1-private-pem', {
|
||||
environment: 'browser',
|
||||
encryptionScheme: {
|
||||
'hash': 'sha256',
|
||||
}
|
||||
});
|
||||
|
||||
const cryptoConfig = token.subarray(0, 0x82);
|
||||
const signature = token.subarray(0x82, 0x96);
|
||||
const encryptedBody = token.subarray(0x96);
|
||||
|
||||
const encryptedAESKey = cryptoConfig.subarray(0, 128);
|
||||
const point1 = cryptoConfig.readInt8(0x80);
|
||||
const point2 = cryptoConfig.readInt8(0x81);
|
||||
|
||||
const iv = Buffer.concat([
|
||||
Buffer.from(encryptedAESKey.subarray(point1, point1 + 8)),
|
||||
Buffer.from(encryptedAESKey.subarray(point2, point2 + 8))
|
||||
]);
|
||||
|
||||
try {
|
||||
const decryptedAESKey = privateKey.decrypt(encryptedAESKey);
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-128-cbc', decryptedAESKey, iv);
|
||||
|
||||
let decryptedBody = decipher.update(encryptedBody);
|
||||
decryptedBody = Buffer.concat([decryptedBody, decipher.final()]);
|
||||
|
||||
const hmac = crypto.createHmac('sha1', cryptoOptions.hmac_secret).update(decryptedBody);
|
||||
const calculatedSignature = hmac.digest();
|
||||
|
||||
if (Buffer.compare(calculatedSignature, signature) !== 0) {
|
||||
LOG_ERROR('Token signature did not match');
|
||||
return null;
|
||||
}
|
||||
|
||||
return decryptedBody;
|
||||
}
|
||||
catch (e) {
|
||||
LOG_ERROR('Failed to decrypt token. Probably a NNID from the topics request');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function processPainting(painting, isTGA) {
|
||||
if (isTGA) {
|
||||
let paintingBuffer = Buffer.from(painting, 'base64');
|
||||
let output = '';
|
||||
try {
|
||||
output = pako.inflate(paintingBuffer);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
let tga = new TGA(Buffer.from(output));
|
||||
let png = new PNG({
|
||||
width: tga.width,
|
||||
height: tga.height
|
||||
});
|
||||
png.data = tga.pixels;
|
||||
return PNG.sync.write(png);
|
||||
//return `data:image/png;base64,${pngBuffer.toString('base64')}`;
|
||||
}
|
||||
else {
|
||||
let paintingBuffer = Buffer.from(painting, 'base64');
|
||||
let bitmap = bmp.decode(paintingBuffer)
|
||||
const tga = this.createBMPTgaBuffer(bitmap.width, bitmap.height, bitmap.data, false);
|
||||
|
||||
let output;
|
||||
try
|
||||
{
|
||||
output = pako.deflate(tga, {level: 6});
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
return new Buffer(output).toString('base64')
|
||||
}
|
||||
}
|
||||
|
||||
export function nintendoPasswordHash(password, pid) {
|
||||
const pidBuffer = Buffer.alloc(4);
|
||||
pidBuffer.writeUInt32LE(pid);
|
||||
|
||||
const unpacked = Buffer.concat([
|
||||
pidBuffer,
|
||||
Buffer.from('\x02\x65\x43\x46'),
|
||||
Buffer.from(password)
|
||||
]);
|
||||
return crypto.createHash('sha256').update(unpacked).digest().toString('hex');
|
||||
}
|
||||
|
||||
export function createBMPTgaBuffer(width, height, pixels, dontFlipY) {
|
||||
var buffer = Buffer.alloc(18 + pixels.length);
|
||||
// write header
|
||||
buffer.writeInt8(0, 0);
|
||||
buffer.writeInt8(0, 1);
|
||||
buffer.writeInt8(2, 2);
|
||||
buffer.writeInt16LE(0, 3);
|
||||
buffer.writeInt16LE(0, 5);
|
||||
buffer.writeInt8(0, 7);
|
||||
buffer.writeInt16LE(0, 8);
|
||||
buffer.writeInt16LE(0, 10);
|
||||
buffer.writeInt16LE(width, 12);
|
||||
buffer.writeInt16LE(height, 14);
|
||||
buffer.writeInt8(32, 16);
|
||||
buffer.writeInt8(8, 17);
|
||||
|
||||
var offset = 18;
|
||||
for (var i = 0; i < height; i++) {
|
||||
for (var j = 0; j < width; j++) {
|
||||
var idx = ((dontFlipY ? i : height - i - 1) * width + j) * 4;
|
||||
buffer.writeUInt8(pixels[idx + 1], offset++); // b
|
||||
buffer.writeUInt8(pixels[idx + 2], offset++); // g
|
||||
buffer.writeUInt8(pixels[idx + 3], offset++); // r
|
||||
buffer.writeUInt8(255, offset++); // a
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export async function uploadCDNAsset(bucket, key, data, acl) {
|
||||
const awsPutParams = {
|
||||
Body: data,
|
||||
Key: key,
|
||||
Bucket: bucket,
|
||||
ACL: acl
|
||||
};
|
||||
|
||||
await s3.putObject(awsPutParams).promise();
|
||||
}
|
||||
|
||||
export async function getFriends(pid) {
|
||||
return await client.getUserFriendPIDs({
|
||||
pid: pid
|
||||
}, {
|
||||
metadata: Metadata({
|
||||
'X-API-Key': api_key
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function getFriendRequests(pid) {
|
||||
const requests = await client.getUserFriendRequestsIncoming({
|
||||
pid: pid
|
||||
}, {
|
||||
metadata: Metadata({
|
||||
'X-API-Key': api_key
|
||||
})
|
||||
});
|
||||
return requests.friendRequests;
|
||||
}
|
||||
|
||||
export function makeSafeQs(query: ParsedQs): SafeQs {
|
||||
const entries = Object.entries(query);
|
||||
const output: SafeQs = {};
|
||||
|
||||
for (const [key, value] of entries) {
|
||||
if (typeof value !== 'string') {
|
||||
// * ignore non-strings
|
||||
continue;
|
||||
}
|
||||
|
||||
output[key] = value;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export function getValueFromQueryString(qs: ParsedQs, key: string): string | undefined {
|
||||
let property: string | ParsedQs | string[] | ParsedQs[] | SafeQs | undefined = qs[key];
|
||||
let value: string | undefined;
|
||||
|
||||
if (property) {
|
||||
if (Array.isArray(property)) {
|
||||
property = property[0];
|
||||
}
|
||||
|
||||
if (typeof property !== 'string') {
|
||||
property = makeSafeQs(<ParsedQs>property);
|
||||
value = (<SafeQs>property)[key];
|
||||
} else {
|
||||
value = <string>property;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function getValueFromHeaders(headers: IncomingHttpHeaders, key: string): string | undefined {
|
||||
let header: string | string[] | undefined = headers[key];
|
||||
let value: string | undefined;
|
||||
|
||||
if (header) {
|
||||
if (Array.isArray(header)) {
|
||||
header = header[0];
|
||||
}
|
||||
|
||||
value = header;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function mapToObject(map: Map<any, any>): object {
|
||||
return Object.fromEntries(Array.from(map.entries(), ([ k, v ]) => v instanceof Map ? [ k, mapToObject(v) ] : [ k, v ]));
|
||||
}
|
||||
329
src/util/util.js
329
src/util/util.js
@@ -1,329 +0,0 @@
|
||||
const crypto = require('crypto');
|
||||
const NodeRSA = require('node-rsa');
|
||||
const fs = require('fs-extra');
|
||||
const database = require('../database');
|
||||
const logger = require('../logger');
|
||||
const grpc = require('nice-grpc');
|
||||
const grpcServices = require('grpc');
|
||||
const config = require('../../config.json');
|
||||
const { SETTINGS } = require('../models/settings');
|
||||
const { CONTENT } = require('../models/content');
|
||||
const { NOTIFICATIONS } = require('../models/notifications');
|
||||
const { FriendsDefinition } = grpcServices.friends.service;
|
||||
const TGA = require('tga');
|
||||
const pako = require('pako');
|
||||
const PNG = require('pngjs').PNG;
|
||||
const bmp = require("bmp-js");
|
||||
const aws = require('aws-sdk');
|
||||
const { ip, port, api_key } = config.grpc.friends;
|
||||
const channel = grpc.createChannel(`${ip}:${port}`);
|
||||
const client = grpc.createClient(FriendsDefinition, channel);
|
||||
|
||||
const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com');
|
||||
const s3 = new aws.S3({
|
||||
endpoint: spacesEndpoint,
|
||||
accessKeyId: config.aws.spaces.key,
|
||||
secretAccessKey: config.aws.spaces.secret
|
||||
});
|
||||
|
||||
async function saveNotification(pid, type, title, content, reference_id, link) {
|
||||
let notification = {
|
||||
pid: pid,
|
||||
type: type,
|
||||
title: title,
|
||||
content: content,
|
||||
reference_id: reference_id,
|
||||
link: link,
|
||||
}
|
||||
let newNotification = new NOTIFICATIONS(notification);
|
||||
return await newNotification.save();
|
||||
}
|
||||
|
||||
let methods = {
|
||||
create_user: async function(pid, experience, notifications, region) {
|
||||
const pnid = await database.getPNID(pid);
|
||||
if(!pnid)
|
||||
return;
|
||||
let newSettings = {
|
||||
pid: pid,
|
||||
screen_name: pnid.mii.name,
|
||||
game_skill: experience,
|
||||
receive_notifications: notifications,
|
||||
}
|
||||
let newContent = {
|
||||
pid: pid
|
||||
}
|
||||
const newSettingsObj = new SETTINGS(newSettings);
|
||||
await newSettingsObj.save();
|
||||
|
||||
const newContentObj = new CONTENT(newContent);
|
||||
await newContentObj.save();
|
||||
},
|
||||
decodeParamPack: function (paramPack) {
|
||||
/* Decode base64 */
|
||||
let dec = Buffer.from(paramPack, "base64").toString("ascii");
|
||||
/* Remove starting and ending '/', split into array */
|
||||
dec = dec.slice(1, -1).split("\\");
|
||||
/* Parameters are in the format [name, val, name, val]. Copy into out{}. */
|
||||
const out = {};
|
||||
for (let i = 0; i < dec.length; i += 2) {
|
||||
out[dec[i].trim()] = dec[i + 1].trim();
|
||||
}
|
||||
return out;
|
||||
},
|
||||
processServiceToken: function(token) {
|
||||
try
|
||||
{
|
||||
let B64token = Buffer.from(token, 'base64');
|
||||
let decryptedToken = this.decryptToken(B64token);
|
||||
return decryptedToken.readUInt32LE(0x2);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
},
|
||||
decryptToken: function(token) {
|
||||
// Access and refresh tokens use a different format since they must be much smaller
|
||||
// Assume a small length means access or refresh token
|
||||
if (token.length <= 32) {
|
||||
const cryptoPath = `${__dirname}/../certs/access`;
|
||||
const aesKey = Buffer.from(fs.readFileSync(`${cryptoPath}/aes.key`, { encoding: 'utf8' }), 'hex');
|
||||
|
||||
const iv = Buffer.alloc(16);
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv);
|
||||
|
||||
let decryptedBody = decipher.update(token);
|
||||
decryptedBody = Buffer.concat([decryptedBody, decipher.final()]);
|
||||
|
||||
return decryptedBody;
|
||||
}
|
||||
|
||||
const cryptoPath = `${__dirname}/../certs/access`;
|
||||
|
||||
const cryptoOptions = {
|
||||
private_key: fs.readFileSync(`${cryptoPath}/private.pem`),
|
||||
hmac_secret: config.account_server_secret
|
||||
};
|
||||
|
||||
const privateKey = new NodeRSA(cryptoOptions.private_key, 'pkcs1-private-pem', {
|
||||
environment: 'browser',
|
||||
encryptionScheme: {
|
||||
'hash': 'sha256',
|
||||
}
|
||||
});
|
||||
|
||||
const cryptoConfig = token.subarray(0, 0x82);
|
||||
const signature = token.subarray(0x82, 0x96);
|
||||
const encryptedBody = token.subarray(0x96);
|
||||
|
||||
const encryptedAESKey = cryptoConfig.subarray(0, 128);
|
||||
const point1 = cryptoConfig.readInt8(0x80);
|
||||
const point2 = cryptoConfig.readInt8(0x81);
|
||||
|
||||
const iv = Buffer.concat([
|
||||
Buffer.from(encryptedAESKey.subarray(point1, point1 + 8)),
|
||||
Buffer.from(encryptedAESKey.subarray(point2, point2 + 8))
|
||||
]);
|
||||
|
||||
try {
|
||||
const decryptedAESKey = privateKey.decrypt(encryptedAESKey);
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-128-cbc', decryptedAESKey, iv);
|
||||
|
||||
let decryptedBody = decipher.update(encryptedBody);
|
||||
decryptedBody = Buffer.concat([decryptedBody, decipher.final()]);
|
||||
|
||||
const hmac = crypto.createHmac('sha1', cryptoOptions.hmac_secret).update(decryptedBody);
|
||||
const calculatedSignature = hmac.digest();
|
||||
|
||||
if (Buffer.compare(calculatedSignature, signature) !== 0) {
|
||||
logger.error('Token signature did not match');
|
||||
return null;
|
||||
}
|
||||
|
||||
return decryptedBody;
|
||||
}
|
||||
catch (e) {
|
||||
logger.error('Failed to decrypt token. Probably a NNID from the topics request');
|
||||
return null;
|
||||
}
|
||||
},
|
||||
processPainting: async function (painting, isTGA) {
|
||||
if (isTGA) {
|
||||
let paintingBuffer = Buffer.from(painting, 'base64');
|
||||
let output = '';
|
||||
try {
|
||||
output = pako.inflate(paintingBuffer);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
let tga = new TGA(Buffer.from(output));
|
||||
let png = new PNG({
|
||||
width: tga.width,
|
||||
height: tga.height
|
||||
});
|
||||
png.data = tga.pixels;
|
||||
return PNG.sync.write(png);
|
||||
//return `data:image/png;base64,${pngBuffer.toString('base64')}`;
|
||||
}
|
||||
else {
|
||||
let paintingBuffer = Buffer.from(painting, 'base64');
|
||||
let bitmap = bmp.decode(paintingBuffer)
|
||||
const tga = this.createBMPTgaBuffer(bitmap.width, bitmap.height, bitmap.data, false);
|
||||
|
||||
let output;
|
||||
try
|
||||
{
|
||||
output = pako.deflate(tga, {level: 6});
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
return new Buffer(output).toString('base64')
|
||||
}
|
||||
},
|
||||
nintendoPasswordHash: function(password, pid) {
|
||||
const pidBuffer = Buffer.alloc(4);
|
||||
pidBuffer.writeUInt32LE(pid);
|
||||
|
||||
const unpacked = Buffer.concat([
|
||||
pidBuffer,
|
||||
Buffer.from('\x02\x65\x43\x46'),
|
||||
Buffer.from(password)
|
||||
]);
|
||||
return crypto.createHash('sha256').update(unpacked).digest().toString('hex');
|
||||
},
|
||||
resizeImage: function (file, width, height) {
|
||||
sharp(file)
|
||||
.resize({ height: height, width: width })
|
||||
.toBuffer()
|
||||
.then(data => {
|
||||
return data;
|
||||
});
|
||||
},
|
||||
createBMPTgaBuffer: function(width, height, pixels, dontFlipY) {
|
||||
var buffer = Buffer.alloc(18 + pixels.length);
|
||||
// write header
|
||||
buffer.writeInt8(0, 0);
|
||||
buffer.writeInt8(0, 1);
|
||||
buffer.writeInt8(2, 2);
|
||||
buffer.writeInt16LE(0, 3);
|
||||
buffer.writeInt16LE(0, 5);
|
||||
buffer.writeInt8(0, 7);
|
||||
buffer.writeInt16LE(0, 8);
|
||||
buffer.writeInt16LE(0, 10);
|
||||
buffer.writeInt16LE(width, 12);
|
||||
buffer.writeInt16LE(height, 14);
|
||||
buffer.writeInt8(32, 16);
|
||||
buffer.writeInt8(8, 17);
|
||||
|
||||
var offset = 18;
|
||||
for (var i = 0; i < height; i++) {
|
||||
for (var j = 0; j < width; j++) {
|
||||
var idx = ((dontFlipY ? i : height - i - 1) * width + j) * 4;
|
||||
buffer.writeUInt8(pixels[idx + 1], offset++); // b
|
||||
buffer.writeUInt8(pixels[idx + 2], offset++); // g
|
||||
buffer.writeUInt8(pixels[idx + 3], offset++); // r
|
||||
buffer.writeUInt8(255, offset++); // a
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
},
|
||||
uploadCDNAsset: async function(bucket, key, data, acl) {
|
||||
const awsPutParams = {
|
||||
Body: data,
|
||||
Key: key,
|
||||
Bucket: bucket,
|
||||
ACL: acl
|
||||
};
|
||||
|
||||
await s3.putObject(awsPutParams).promise();
|
||||
},
|
||||
newNotification: async function(pid, type, reference_id, origin_pid, title, content) {
|
||||
let user = await database.getUserSettings(origin_pid);
|
||||
/**
|
||||
* 0 like
|
||||
* 1 reply
|
||||
* 2 new follower
|
||||
* 3 other
|
||||
*/
|
||||
|
||||
if(type === 1)
|
||||
return await saveNotification(pid, type, `${user.screen_name} Replied to your post.`, content, reference_id, `/posts/${reference_id}`);
|
||||
else if(type === 2)
|
||||
return await saveNotification(pid, type, `${user.screen_name} Followed you!`, '', reference_id, `/users/show?pid=${origin_pid}`);
|
||||
|
||||
let lastNotification = await database.getLastNotification(pid);
|
||||
if(lastNotification && lastNotification.type === 0 && lastNotification.reference_id === reference_id) {
|
||||
let post = await database.getPostByID(reference_id);
|
||||
let newTitle = '';
|
||||
switch (post.empathy_count) {
|
||||
case 1:
|
||||
newTitle = `${user.screen_name} Yeahed your post!`;
|
||||
break;
|
||||
case 2:
|
||||
newTitle = `${user.screen_name} and 1 other Yeahed your post!`;
|
||||
break;
|
||||
default:
|
||||
newTitle = `${user.screen_name} and ${post.empathy_count - 1} others Yeahed your post!`;
|
||||
break;
|
||||
}
|
||||
lastNotification.title = newTitle;
|
||||
await lastNotification.save();
|
||||
}
|
||||
else if(type === 0) {
|
||||
let post = await database.getPostByID(reference_id);
|
||||
let newTitle = '';
|
||||
switch (post.empathy_count) {
|
||||
case 1:
|
||||
newTitle = `${user.screen_name} Yeahed your post!`;
|
||||
break;
|
||||
case 2:
|
||||
newTitle = `${user.screen_name} and 1 other Yeahed your post!`;
|
||||
break;
|
||||
default:
|
||||
newTitle = `${user.screen_name} and ${post.empathy_count - 1} others Yeahed your post!`;
|
||||
break;
|
||||
}
|
||||
let newContent;
|
||||
if(!post.body) {
|
||||
if(post.screenshot)
|
||||
newContent = 'Screenshot Post';
|
||||
else if(post.painting)
|
||||
newContent = 'Drawing Post';
|
||||
}
|
||||
else
|
||||
newContent = post.body;
|
||||
return await saveNotification(pid, type, newTitle, newContent, reference_id, `/posts/${post.id}`);
|
||||
}
|
||||
else
|
||||
return await saveNotification(pid, type, title, content, reference_id, '');
|
||||
|
||||
},
|
||||
getFriends: async function(pid) {
|
||||
return await client.getUserFriendPIDs({
|
||||
pid: pid
|
||||
}, {
|
||||
metadata: grpc.Metadata({
|
||||
'X-API-Key': api_key
|
||||
})
|
||||
})
|
||||
},
|
||||
getFriendRequests: async function(pid) {
|
||||
const requests = await client.getUserFriendRequestsIncoming({
|
||||
pid: pid
|
||||
}, {
|
||||
metadata: grpc.Metadata({
|
||||
'X-API-Key': api_key
|
||||
})
|
||||
});
|
||||
return requests.friendRequests;
|
||||
}
|
||||
};
|
||||
module.exports = methods;
|
||||
@@ -1,6 +1,6 @@
|
||||
const xmlbuilder = require("xmlbuilder");
|
||||
const moment = require("moment");
|
||||
const database = require('../database');
|
||||
import xmlbuilder from 'xmlbuilder';
|
||||
import moment from 'moment';
|
||||
import { getNumberNewCommunityPostsByID } from '@/database';
|
||||
|
||||
class XmlResponseGenerator {
|
||||
/**
|
||||
@@ -17,7 +17,7 @@ class XmlResponseGenerator {
|
||||
.e("request_name", "replies").up()
|
||||
.e("posts");
|
||||
for (const post of posts) {
|
||||
postObj(xml, post, options);
|
||||
postObj(xml, post, options, {});
|
||||
}
|
||||
xml = xml.up();
|
||||
return xml.end({ pretty: true, allowEmpty: true });
|
||||
@@ -41,7 +41,7 @@ class XmlResponseGenerator {
|
||||
.up()
|
||||
.e("posts");
|
||||
for (const post of posts) {
|
||||
postObj(xml, post, options);
|
||||
postObj(xml, post, options, {});
|
||||
}
|
||||
xml = xml.up();
|
||||
return xml.end({ pretty: true, allowEmpty: true });
|
||||
@@ -122,7 +122,7 @@ class XmlResponseGenerator {
|
||||
.e("has_error", "0").up()
|
||||
.e("version", "1").up()
|
||||
.e("post");
|
||||
postObj(xml, post, { with_mii: true });
|
||||
postObj(xml, post, { with_mii: true }, {});
|
||||
xml = xml.up();
|
||||
return xml.end({ pretty: true, allowEmpty: true });
|
||||
}
|
||||
@@ -139,7 +139,7 @@ class XmlResponseGenerator {
|
||||
.e("version", "1").up()
|
||||
.e("request_name", "posts.search").up()
|
||||
.e("posts");
|
||||
postObj(xml, post, { with_mii: true });
|
||||
postObj(xml, post, { with_mii: true }, {});
|
||||
xml = xml.up();
|
||||
return xml.end({ pretty: true, allowEmpty: true });
|
||||
}
|
||||
@@ -158,7 +158,7 @@ class XmlResponseGenerator {
|
||||
.e("expire", expirationDate.format('YYYY-MM-DD HH:MM:SS')).up()
|
||||
.e("topics");
|
||||
for (const community of communities) {
|
||||
let posts = await database.getNumberNewCommunityPostsByID(community, 30);
|
||||
let posts = await getNumberNewCommunityPostsByID(community, 30);
|
||||
xml = xml.e('topic')
|
||||
.e('empathy_count', community.empathy_count).up()
|
||||
.e('has_shop_page', community.has_shop_page).up()
|
||||
@@ -224,7 +224,7 @@ class XmlResponseGenerator {
|
||||
for (const post of posts) {
|
||||
xml = xml.e("person")
|
||||
.e("posts")
|
||||
postObj(xml, post, options);
|
||||
postObj(xml, post, options, {});
|
||||
xml = xml.up().up();
|
||||
}
|
||||
xml = xml.up();
|
||||
@@ -247,7 +247,7 @@ function postObj(xml, post, options, community) {
|
||||
xml.e("body", post.body ? post.body.replace(/[^A-Za-z\d\s-_!@#$%^&*(){}+=,.<>/?;:'"\[\]]/g, "").replace(/[\n\r]+/gm, '') : "").up()
|
||||
.e("community_id", options.topics ? community.community_id : post.community_id).up()
|
||||
.e("country_id", post.country_id ? post.country_id : 254).up()
|
||||
.e("created_at", new moment(post.created_at).format('YYYY-MM-DD HH:MM:SS')).up()
|
||||
.e("created_at", moment(post.created_at).format('YYYY-MM-DD HH:MM:SS')).up()
|
||||
.e("feeling_id", post.feeling_id).up()
|
||||
.e("id", post.id).up()
|
||||
.e("is_autopost", post.is_autopost).up()
|
||||
@@ -289,6 +289,4 @@ function postObj(xml, post, options, community) {
|
||||
xml.e("title_id", post.title_id).up().up()
|
||||
}
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = XmlResponseGenerator;
|
||||
}
|
||||
export default XmlResponseGenerator;
|
||||
Reference in New Issue
Block a user