Added auth with account server for account creation. Patrially added support for checking PNID for access level to sign into admin panel

This commit is contained in:
CaramelKat 2021-11-24 11:42:59 -06:00
parent 090642042b
commit 8a92943875
27 changed files with 533 additions and 389 deletions

25
package-lock.json generated
View File

@ -1224,6 +1224,21 @@
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
"integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
},
"lodash.foreach": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
"integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM="
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@ -1362,6 +1377,16 @@
"resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz",
"integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ=="
},
"mongoose-unique-validator": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mongoose-unique-validator/-/mongoose-unique-validator-3.0.0.tgz",
"integrity": "sha512-WwC09CN2OQ39ONCEN2quR2L5CLHau3p2VYegRMitoVwdKA738uuJRlrTHXt5J6WyJBSloCILxwDZ5qbu4yEXcA==",
"requires": {
"lodash.foreach": "^4.1.0",
"lodash.get": "^4.0.2",
"lodash.merge": "^4.6.2"
}
},
"morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",

View File

@ -29,6 +29,7 @@
"memory-cache": "^0.2.0",
"moment": "^2.29.1",
"mongoose": "^5.10.14",
"mongoose-unique-validator": "^3.0.0",
"morgan": "^1.10.0",
"node-rsa": "^1.1.1",
"node-snowflake": "0.0.1",

41
src/accountdb.js Normal file
View File

@ -0,0 +1,41 @@
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 makeNewConnection(uri) {
//const db = mongoose.createConnection(`${uri}/${database}`, options);
const db = mongoose.createConnection(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
db.on('error', function (error) {
logger.error(`MongoDB connection ${this.name} ${JSON.stringify(error)}`);
db.close().catch(() => console.log(`MongoDB :: failed to close connection ${this.name}`));
});
db.on('connected', function () {
logger.info(`MongoDB connected ${this.name} / ${uri}`);
});
db.on('disconnected', function () {
logger.info(`MongoDB disconnected ${this.name}`);
});
return db;
}
pnidConnection = makeNewConnection(`${uri}/${database}`, options);
module.exports = {
pnidConnection,
connect
};

View File

@ -4,9 +4,6 @@ const fs = require('fs-extra');
const database = require('./database');
const logger = require('./logger');
const config = require('./config.json');
const xmlParser = require('xml2json');
const request = require("request");
const moment = require('moment');
const { USER } = require('./models/user');
const translations = require('./translations')
var HashMap = require('hashmap');
@ -14,9 +11,6 @@ let TGA = require('tga');
let pako = require('pako');
let PNG = require('pngjs').PNG;
var bmp = require("bmp-js");
const { toImage } = require('indexed-image-converter');
const imagePixels = require('image-pixels');
let communityMap = new HashMap();
let userMap = new HashMap();
@ -53,43 +47,26 @@ function nameCache() {
}
let methods = {
create_user: function(pid, experience, notifications, region) {
return new Promise(function(resolve, reject) {
database.connect().then(async fun => {
await request({
url: "http://" + config.account_server_domain + "/v1/api/miis?pids=" + pid,
headers: {
'X-Nintendo-Client-ID': config["X-Nintendo-Client-ID"],
'X-Nintendo-Client-Secret': config["X-Nintendo-Client-Secret"]
}
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
let xml = xmlParser.toJson(body, {object: true});
const newUsr = {
pid: pid,
created_at: new Date(),
user_id: xml.miis.mii.user_id,
account_status: 0,
mii: xml.miis.mii.data,
game_skill: experience,
notifications: notifications,
official: false,
country: region,
};
const newUsrObj = new USER(newUsr);
newUsrObj.save();
resolve(newUsr);
}
else
{
console.log('fail');
reject();
}
});
});
});
create_user: async function(pid, experience, notifications, region) {
const pnid = await database.getPNID(pid);
if(!pnid)
return;
const newUsr = {
pid: pid,
created_at: new Date(),
user_id: pnid.mii.name,
pnid: pnid.username,
birthday: new Date(pnid.birthdate),
account_status: 0,
mii: pnid.mii.data,
game_skill: experience,
notifications: notifications,
official: pnid.access_level === 3,
country: region,
};
const newUsrObj = new USER(newUsr);
await newUsrObj.save();
console.log(newUsrObj);
},
decodeParamPack: function (paramPack) {
/* Decode base64 */

View File

@ -1,19 +1,23 @@
const mongoose = require('mongoose');
const { mongoose: mongooseConfig } = require('./config.json');
const { TOPIC } = require('./models/topic');
const { ENDPOINT } = require('./models/endpoint');
const { COMMUNITY } = require('./models/communities');
const { POST } = require('./models/post');
const { USER } = require('./models/user');
const { CONVERSATION } = require('./models/conversation');
const { uri, database, options } = mongooseConfig;
const {PNID} = require('./models/pnid');
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();
@ -26,28 +30,10 @@ function verifyConnected() {
}
}
async function getTopicByName(topicName) {
verifyConnected();
if (typeof topicName !== 'string') {
return null;
function verifyAccConnected() {
if (!PNID) {
accountDB.connect();
}
return TOPIC.findOne({
name: topicName
});
}
async function getTopicByCommunityID(communityID) {
verifyConnected();
if (typeof communityID !== 'string') {
return null;
}
return TOPIC.findOne({
community_id: communityID
});
}
async function getCommunities(numberOfCommunities) {
@ -272,7 +258,7 @@ async function getUserByUsername(user_id) {
verifyConnected();
return USER.findOne({
user_id: new RegExp(`^${user_id}$`, 'i')
pnid: new RegExp(`^${user_id}$`, 'i')
});
}
@ -322,6 +308,7 @@ async function getConversation(pid, pid2) {
}
async function getLatestMessage(pid, pid2) {
verifyConnected();
return POST.findOne({
$or: [
{pid: pid, message_to_pid: pid2},
@ -330,6 +317,18 @@ async function getLatestMessage(pid, pid2) {
})
}
async function getPNIDS() {
verifyAccConnected();
return await PNID.find({});
}
async function getPNID(pid) {
verifyAccConnected();
return await PNID.findOne({
pid: pid
});
}
module.exports = {
connect,
getCommunities,
@ -368,4 +367,6 @@ module.exports = {
getConversations,
getConversation,
getLatestMessage,
};
getPNID,
getPNIDS
};

View File

@ -98,4 +98,4 @@ const COMMUNITY = model('COMMUNITY', CommunitySchema);
module.exports = {
CommunitySchema,
COMMUNITY
};
};

View File

@ -1,26 +1,58 @@
const { Schema, model } = require('mongoose');
const snowflake = require('node-snowflake').Snowflake;
const ConversationSchema = new Schema({
id: {
type: String,
default: snowflake.nextId()
},
created_at: {
type: Date,
default: new Date(),
},
id: {
type: String,
default: 0
last_message_sent: {
type: Date,
default: new Date(),
},
pids: [Object],
message_preview: {
type: String,
default: ""
},
pid1: {
pid: String,
official: {
type: Boolean,
default: false
},
screen_name: String,
read: Boolean
},
pid2: {
pid: String,
official: {
type: Boolean,
default: false
},
screen_name: String,
read: Boolean
}
});
ConversationSchema.methods.addUser = async function(pid) {
const conversation = this.get('pids');
conversation.addToSet(pid);
await this.save();
}
ConversationSchema.methods.removeUser = async function(pid) {
const conversation = this.get('pids');
conversation.pull(pid);
ConversationSchema.methods.newMessage = async function(message, fromPid) {
const pid1 = this.get('pid1');
const pid2 = this.get('pid2');
if(pid1.pid === fromPid) {
pid1.read = true
pid2.read = false
}
else {
pid1.read = false
pid2.read = true
}
this.set('pid1', pid1);
this.set('pid2', pid2);
this.set('last_message_sent', new Date());
this.set('message_preview', message);
await this.save();
}
@ -29,4 +61,4 @@ const CONVERSATION = model('CONVERSATION', ConversationSchema);
module.exports = {
ConversationSchema: ConversationSchema,
CONVERSATION: CONVERSATION
};
};

52
src/models/pnid.js Normal file
View File

@ -0,0 +1,52 @@
const mongoose = require('mongoose');
const {pnidConnection} = require('../accountdb');
const PNIDSchema = new mongoose.Schema({
access_level: {
type: Number,
default: 0 // 0: standard, 1: tester, 2: mod?, 3: dev
},
server_access_level: {
type: String,
default: 'prod' // everyone is in production by default
},
pid: {
type: Number,
unique: true
},
username: {
type: String,
unique: true,
minlength: 6,
maxlength: 16
},
password: String,
birthdate: String,
country: String,
language: String,
region: Number,
mii: {
name: String,
primary: Boolean,
data: String,
id: {
type: Number,
unique: true
},
hash: {
type: String,
unique: true
},
image_url: String,
image_id: {
type: Number,
unique: true
},
},
});
const PNID = pnidConnection.model('PNID', PNIDSchema);
module.exports = {
PNID,
};

View File

@ -71,6 +71,10 @@ const PostSchema = new Schema({
message_to_pid: {
type: String,
default: null
},
conversation_id: {
type: String,
default: null
}
});
@ -108,4 +112,4 @@ const POST = model('POST', PostSchema);
module.exports = {
PostSchema,
POST
};
};

View File

@ -1,79 +0,0 @@
const { Schema, model } = require('mongoose');
const { PostSchema } = require('./post');
const titleIdsSchema = new Schema({
title_id: String,
});
const personSchema = new Schema({
posts: {
type: [PostSchema],
default: undefined
}
});
const TopicSchema = new Schema({
empathy_count: {
type: Number,
default: 0
},
has_shop_page: {
type: Number,
default: 0
},
icon: String,
title_ids: {
type: [titleIdsSchema],
default: undefined
},
title_id: String,
community_id: String,
is_recommended: {
type: Number,
default: 0
},
name: String,
people: {
type: [personSchema],
default: undefined
},
});
TopicSchema.methods.upEmpathy = async function() {
const empathy = this.get('empathy_count');
this.set('empathy_count', empathy + 1);
await this.save();
};
TopicSchema.methods.downEmpathy = async function() {
const empathy = this.get('empathy_count');
this.set('empathy_count', empathy - 1);
await this.save();
};
TopicSchema.pre('save', async function(next) {
if (!this.isModified('password')) {
return next();
}
this.set('usernameLower', this.get('username').toLowerCase());
await this.generatePID();
await this.generateNEXPassword();
await this.generateEmailValidationCode();
await this.generateEmailValidationToken();
const primaryHash = util.nintendoPasswordHash(this.get('password'), this.get('pid'));
const hash = bcrypt.hashSync(primaryHash, 10);
this.set('password', hash);
next();
});
const MiiverseTopic = model('MiiverseTopic', PostSchema);
module.exports = {
TopicSchema,
MiiverseTopic
};

View File

@ -11,6 +11,7 @@ const UserSchema = new Schema({
pid: Number,
created_at: Date,
user_id: String,
pnid: String,
birthday: Date,
country: String,
pfp_uri: String,
@ -274,4 +275,4 @@ const USER = model('USER', UserSchema);
module.exports = {
UserSchema,
USER
};
};

View File

@ -66,4 +66,4 @@ database.connect().then(() => {
app.listen(port, () => {
logger.success(`Server started on port ${port}`);
});
});
});

View File

@ -14,9 +14,10 @@ var upload = multer({ storage: storage });
router.get('/communities/all', async function(req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
if(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
res.send(await database.getCommunities(-1))
}
@ -26,9 +27,10 @@ router.get('/communities/all', async function(req, res) {
router.get('/communities/:communityID', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
if(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
res.send(await database.getCommunityByID(req.params.communityID))
}
@ -38,9 +40,10 @@ router.get('/communities/:communityID', async function (req, res) {
router.post('/communities/:communityID/delete', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1) {
if(pnid.access_level !== 3) {
logger.audit('[' + user.user_id + ' - ' + user.pid + '] attempted to delete a community and is not authorized');
throw new Error('Invalid credentials supplied');
}
@ -59,9 +62,10 @@ router.post('/communities/:communityID/delete', async function (req, res) {
router.post('/communities/:communityID/update', upload.fields([{name: 'browserIcon', maxCount: 1}, { name: 'CTRbrowserHeader', maxCount: 1}, { name: 'WiiUbrowserHeader', maxCount: 1}]), async function(req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1) {
if(pnid.access_level !== 3) {
logger.audit('[' + user.user_id + ' - ' + user.pid + '] attempted to update a community and is not authorized');
throw new Error('Invalid credentials supplied');
}
@ -113,10 +117,11 @@ router.post('/communities/:communityID/update', upload.fields([{name: 'browserIc
router.post('/communities/new', upload.fields([{name: 'browserIcon', maxCount: 1}, { name: 'CTRbrowserHeader', maxCount: 1}, { name: 'WiiUbrowserHeader', maxCount: 1}]), async function(req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1) {
if(pnid.access_level !== 3) {
logger.audit('[' + user.user_id + ' - ' + user.pid + '] attempted to create a community and is not authorized');
throw new Error('Invalid credentials supplied');
}
@ -160,10 +165,11 @@ router.post('/communities/new', upload.fields([{name: 'browserIcon', maxCount: 1
router.post('/communities/:communityID/sub/new', upload.fields([{name: 'browserIcon', maxCount: 1}, { name: 'CTRbrowserHeader', maxCount: 1}, { name: 'WiiUbrowserHeader', maxCount: 1}]), async function(req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1) {
if(pnid.access_level !== 3) {
logger.audit('[' + user.user_id + ' - ' + user.pid + '] attempted to create a community and is not authorized');
throw new Error('Invalid credentials supplied');
}
@ -216,10 +222,11 @@ router.post('/communities/:communityID/sub/new', upload.fields([{name: 'browserI
router.post('/discovery/update', upload.none(), async function(req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
if(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
let endpoint = await database.getServerConfig();
endpoint.has_error = req.body.has_error;
@ -232,10 +239,11 @@ router.post('/discovery/update', upload.none(), async function(req, res) {
router.get('/users/all', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
if(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
res.send(await database.getUsers(-1))
@ -246,10 +254,11 @@ router.get('/users/all', async function (req, res) {
router.get('/users/loadPosts', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
if(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
let post = await database.getPostByID(req.query.postID);
@ -276,10 +285,11 @@ router.get('/users/loadPosts', async function (req, res) {
router.get('/users/:userID', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1)
if(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
res.send(await database.getUserByPID(req.params.userID))
@ -310,9 +320,10 @@ router.post('/users/:userID/update', upload.none(), async function(req, res) {
router.post('/posts/:postID/delete', async function(req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1) {
if(pnid.access_level !== 3) {
logger.audit('[' + user.user_id + ' - ' + user.pid + '] attempted to delete a post and is not authorized');
throw new Error('Invalid credentials supplied');
}
@ -338,10 +349,11 @@ router.get('/notifications', async function(req, res) {
router.post('/posts/new', upload.none(), async function(req, res) {
let user = await database.getUserByPID(req.pid);
let pnid = await database.getPNID(req.pid)
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(user.pid) === -1) {
if(pnid.access_level !== 3) {
logger.audit('[' + user.user_id + ' - ' + user.pid + '] attempted to create a community and is not authorized');
throw new Error('Invalid credentials supplied');
}

View File

@ -282,9 +282,11 @@ router.post('/login', upload.none(), async function (req, res) {
port = 'https://'
let user_id = req.body.user_id;
let user = await database.getUserByUsername(user_id);
let pnid = await database.getPNID(user.pid)
console.log(user.pid)
let password = req.body.password;
if(user !== null && password !== null) {
if(config.authorized_PNIDs.indexOf(user.pid) === -1) {
if(user !== null && password !== null && pnid !== null) {
if(pnid.access_level !== 3) {
logger.audit('[' + user.user_id + ' - ' + user.pid + '] is not authorized to access the application');
throw new Error('User is not authorized to access the application');
}
@ -308,7 +310,6 @@ router.post('/login', upload.none(), async function (req, res) {
res.send(body);
}
else {
console.log(body)
res.statusCode = 403;
let response = {
error_code: 403,
@ -318,8 +319,14 @@ router.post('/login', upload.none(), async function (req, res) {
}
});
}
else
throw new Error('Invalid account ID or password');
else {
res.statusCode = 403;
let response = {
error_code: 403,
message: 'Invalid account ID or password'
};
res.send(response);
}
});
module.exports = router;

View File

@ -59,7 +59,7 @@ router.get('/:communityID', async function (req, res) {
if(community === null)
return res.sendStatus(404);
let communityMap = await util.data.getCommunityHash();
let newPosts = await database.getNumberNewCommunityPostsByID(community, 5);
let newPosts = await database.getNumberNewCommunityPostsByID(community, 10);
let totalNumPosts = await database.getTotalPostsByCommunity(community)
res.render(req.directory + '/community.ejs', {
// EJS variable and server-side variable
@ -82,7 +82,7 @@ router.get('/:communityID/:type', async function (req, res) {
res.redirect('/communities/announcements')
let community = await database.getCommunityByID(req.params.communityID.toString());
let communityMap = await util.data.getCommunityHash();
let newPosts = await database.getNumberNewCommunityPostsByID(community, 5);
let newPosts = await database.getNumberNewCommunityPostsByID(community, 10);
let totalNumPosts = await database.getTotalPostsByCommunity(community)
res.render(req.directory + '/community.ejs', {
// EJS variable and server-side variable

View File

@ -9,7 +9,7 @@ var router = express.Router();
router.get('/', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let communityMap = await util.data.getCommunityHash();
let posts = await database.getNewsFeed(user, 3);
let posts = await database.getNewsFeed(user, 10);
res.render(req.directory + '/feed.ejs', {
moment: moment,
user: user,

View File

@ -3,29 +3,100 @@ var xml = require('object-to-xml');
const database = require('../../../../database');
const util = require('../../../../authentication');
const config = require('../../../../config.json');
const { CONVERSATION } = require('../../../../models/conversation');
const { POST } = require('../../../../models/post');
var moment = require('moment');
const snowflake = require('node-snowflake').Snowflake;
var router = express.Router();
router.get('/', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let conversations = await database.getConversations(user.pid);
let userMap = await util.data.getUserHash();
let recentMessages = [];
for (let recentMessage of conversations) {
let pid = recentMessage.pids.indexOf(user.pid) === 0 ? recentMessage.pids[1] : recentMessage.pids[0];
let message = await database.getLatestMessage(req.pid, pid);
if(message)
recentMessages.push(message)
}
console.log(recentMessages);
let conversations = await database.getConversations(user.pid.toString());
res.render(req.directory + '/messages.ejs', {
moment: moment,
database: database,
user: user,
conversations: conversations,
recentMessages: recentMessages,
cdnURL: config.CDN_domain,
userMap: userMap,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
});
});
router.post('/new', async function (req, res, next) {
let conversation = await database.getConversationByID(req.body.conversationID);
if(!conversation) {
let user = await database.getUserByPID(req.pid);
let user2 = await database.getUserByPID(req.body.message_to_pid);
if(!user || !user2)
return res.sendStatus(422)
let doc = {
message_preview: req.body.body,
pids: [
{
pid: user.pid.toString(),
official: user.official,
screen_name: user.user_id,
read: true
},
{
pid: user2.pid.toString(),
official: user2.official,
screen_name: user2.user_id,
read: false
}
]
}
const newConversation = new CONVERSATION(doc);
await newConversation.save();
}
else {
let messageType = '';
if(req.body.screenshot)
messageType = '(Screenshot)';
else if(req.body.drawing)
messageType = '(Drawing)';
else
messageType = req.body.body;
await conversation.newMessage(messageType, req.pid.toString())
}
conversation = await database.getConversation(req.pid.toString(), req.body.message_to_pid.toString())
let user = await database.getUserByPID(req.pid);
const document = {
screen_name: user.user_id,
body: req.body.body,
painting: req.body.raw,
painting_uri: req.body.drawing,
created_at: new Date(),
id: snowflake.nextId(),
mii: user.mii,
mii_face_url: `https://mii.olv.pretendo.cc/${user.pid}/normal_face.png`,
pid: user.pid,
verified: user.official,
parent: null,
message_to_pid: req.body.message_to_pid,
conversation_id: conversation.id
};
const newPost = new POST(document);
newPost.save();
res.sendStatus(200);
});
router.get('/:message_id', async function (req, res) {
let conversation = await database.getConversationByID(req.params.message_id.toString())
if(!conversation) {
return res.sendStatus(404);
}
let position = conversation.pids[0].pid === req.pid.toString() ? 1 : 0;
let user = await database.getUserByPID(req.pid);
let user2 = await database.getUserByPID(conversation.pids[position].pid);
let messages = await database.getMessagesByID(conversation.id, 100)
res.render(req.directory + '/message_thread.ejs', {
moment: moment,
user: user,
user2: user2,
conversation: conversation,
messages: messages,
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
});

View File

@ -68,7 +68,7 @@ router.post('/:post_id/new', upload.none(), async function (req, res, next) {
let painting = "", paintingURI = "";
if (req.body.painting && req.body.painting !== 'eJztwTEBACAMA7DCNRlIQRbu4ZoEviTJTNvjZNUFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL55fYLL3w==') {
painting = req.body.painting.replace(/\0/g, "").trim();
paintingURI = await util.data.processPainting(painting);
paintingURI = await util.data.processPainting(painting, true);
}
let screenshot = "";
if (req.body.screenshot) {

View File

@ -3,6 +3,7 @@ var xml = require('object-to-xml');
const database = require('../../../../database');
const util = require('../../../../authentication');
const config = require('../../../../config.json');
var moment = require('moment');
var router = express.Router();

View File

@ -106,6 +106,8 @@ router.get('/drawing/:image_id.png', async function (req, res) {
router.get('/notifications.json', async function (req, res) {
let user = await database.getUserByPID(req.pid);
if(!user)
return res.sendStatus(403);
if(user.notification_list) {
res.send(
{

View File

@ -127,7 +127,7 @@
<div class="community-page-header" style="background-image: url('<%= cdnURL %>/banner/<%= community.community_id %>.png')"></div>
<div class="community-page-header-overlay"></div>
<div class="community-page-back-button" onclick="back()">
<p class="user-page-back-button-text">Go back</p>
<p class="user-page-back-button-text"><%= lang.global.go_back %></p>
</div>
<div id="community-new-post-wrapper" <%if(user.pid === 1000000000 || user.account_status !== 0) {%> style="display: none" <%}%>>
<div class="new-post-button-text" onclick="showNewPostScreen()">
@ -206,4 +206,4 @@
<br>
<body onload="stopLoading(); wiiuBrowser.showLoadingIcon(!1);wiiuBrowser.lockUserOperation(false);">
</body>
</html>
</html>

View File

@ -582,10 +582,11 @@ td.messages-list-wrapper.end {
position: absolute;
min-width: 2px;
height: 2px;
margin-left: 20px;
margin-left: 12px;
border: 3px solid #673DB6;
background-color: #673DB6;
-webkit-border-radius: 20px;
margin-top: -13px;
}
.message-page-verified-user-badge {
@ -611,15 +612,15 @@ td.messages-list-wrapper.end {
}
.message-list-title {
margin-top: -5px;
margin-top: 0;
display: inline-block;
height: 60px;
height: 75px;
}
.message-list-preview {
position: absolute;
margin-top: -45px;
margin-top: -70px;
}
.message-list-icon {
@ -643,20 +644,21 @@ td.messages-list-wrapper.end {
}
#message-viewer {
display: none;
margin-top: 50px;
margin-bottom: 50px;
margin-left: -13px;
}
.message-viewer-top-banner {
top: 0;
width: 975px;
width: 1060px;
background-color: rgba(255,255,255,1);
border: 1.5px solid #d6d2d2;
margin-top: 15px;
margin-left: 15px;
position: fixed;
z-index: 10;
border-radius: 10px;
}
.message-viewer-top-banner-back-button {
@ -685,7 +687,7 @@ td.messages-list-wrapper.end {
.message-viewer-bottom-banner {
bottom: 0;
width: 975px;
width: 1060px;
min-height: 75px;
background-color: rgba(255,255,255,1);
border: 1.5px solid #d6d2d2;
@ -699,8 +701,8 @@ td.messages-list-wrapper.end {
.message-viewer-top-cover {
background-color: rgba(232,236,236,1);
position: fixed;
height: 15px;
width: 1053px;
height: 25px;
width: 1065px;
z-index: 10;
margin-top: -135px;
margin-left: 15px;
@ -709,8 +711,8 @@ td.messages-list-wrapper.end {
.message-viewer-bottom-cover {
background-color: rgba(232,236,236,1);
position: fixed;
height: 20px;
width: 1053px;
height: 30px;
width: 1065px;
z-index: 1;
margin-top: 565px;
margin-left: 15px;
@ -739,7 +741,7 @@ td.messages-list-wrapper.end {
}
.message-viewer-bubble-receive-timestamp {
left: -860px;
left: -945px;
font-weight: bold;
margin-top: -15px;
text-align: right;
@ -747,7 +749,7 @@ td.messages-list-wrapper.end {
}
.message-viewer-bubble-sent {
left: 470px;
left: 555px;
background-color: #673DB6;
border: 3px solid #673DB6;
border-radius: 10px;
@ -770,7 +772,7 @@ td.messages-list-wrapper.end {
}
.message-viewer-bubble-sent-timestamp {
left: -65px;
left: -30px;
font-weight: bold;
margin-top: -15px;
text-align: right;
@ -809,7 +811,7 @@ td.messages-list-wrapper.end {
margin-top:20px;
font-size: 25px;
white-space: nowrap;
width: 800px;
width: 890px;
height: 65px;
}
@ -1219,6 +1221,7 @@ h4 {
background-color: #8f8e8e;
box-shadow: 0 3px 7px 0 rgba(0, 0, 0, 0.5);
-webkit-box-shadow: 0 3px 7px 0 rgba(0, 0, 0, 0.5);
margin-bottom: 20px;
}
.community-page-post-user-icon.verified{

View File

@ -329,174 +329,60 @@ function followUser(user) {
}
}
function loadMessagesTab() {
alert('Not Implemented. Check back soon!');
/*wiiuBrowser.showLoadingIcon(!0)
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
document.getElementById("main").innerHTML = this.responseText;
loadMessages()
}
};
xhttp.open("GET", "/messages.html" + '?ajax=true', true);
xhttp.send();
document.getElementById('nav-bar-profile').classList.remove('selected')
document.getElementById('nav-bar-activity-feed').classList.remove('selected')
document.getElementById('nav-bar-communities').classList.remove('selected')
document.getElementById('nav-bar-messages').classList.remove('selected')
document.getElementById('nav-bar-news').classList.remove('selected')
document.getElementById('nav-bar-messages').classList.add('selected')
wiiuSound.playSoundByName("SE_WAVE_MENU", 1);
wiiuBrowser.showLoadingIcon(!1)*/
}
function loadMessages() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var notificationObj = JSON.parse(this.responseText);
/**/
var messagesTable = '';
var messagePreview = '';
var readIndicator = '';
for(var i = 0; i < notificationObj.messages.length; i++) {
if(notificationObj.messages[i].message_content.length > 64) {
messagePreview = notificationObj.messages[i].message_content.substring(0, 61) + '...';
}
else
messagePreview = notificationObj.messages[i].message_content;
if(notificationObj.messages[i].is_read) {
readIndicator =
' <td class="messages-unread-badge-wrapper">\n' +
' <div></div>\n' +
' </td>\n'
}
else {
readIndicator =
' <td class="messages-unread-badge-wrapper">\n' +
' <div class="unread-badge"></div>\n' +
' </td>\n'
}
if(i === 0) {
messagesTable +=
'<tr class="message-wrapper" onclick="showMessage(' + notificationObj.messages[i].id + ')">\n' +
readIndicator +
' <td class="messages-list-wrapper top">\n' +
' <img class="message-list-icon" src="/icons/mario-kart.jpg">\n' +
' </td>\n' +
' <td class="messages-list-wrapper top"></td>\n' +
' <td class="messages-list-wrapper top">\n' +
' <h2 class="post-username">' + notificationObj.messages[i].screen_name + '</h2>\n' +
' </td>\n' +
' <td class="messages-list-wrapper top"></td>\n' +
' <td class="messages-list-wrapper top message-list-timestamp">\n' +
' <h4>' + notificationObj.messages[i].created_at.substring(0, notificationObj.messages[i].created_at.indexOf(" ")) +'</h4>\n' +
' </td>\n' +
' <td class="messages-list-wrapper top"></td>\n' +
' <td class="messages-list-wrapper top">\n' +
' <h3 class="message-list-content">' + messagePreview + '</h3>\n' +
' </td>\n' +
' <td class="messages-list-wrapper top end"></td>\n' +
' </tr>'
}
else {
messagesTable +=
'<tr class="message-wrapper" onclick="showMessage(' + notificationObj.messages[i].id + ')">\n' +
readIndicator +
' <td class="messages-list-wrapper">\n' +
' <img class="message-list-icon" src="/icons/mario-kart.jpg">\n' +
' </td>\n' +
' <td class="messages-list-wrapper"></td>\n' +
' <td class="messages-list-wrapper">\n' +
' <h2 class="post-username">' + notificationObj.messages[i].screen_name + '</h2>\n' +
' </td>\n' +
' <td class="messages-list-wrapper"></td>\n' +
' <td class="messages-list-wrapper message-list-timestamp">\n' +
' <h4>' + notificationObj.messages[i].created_at.substring(0, notificationObj.messages[i].created_at.indexOf(" ")) +'</h4>\n' +
' </td>\n' +
' <td class="messages-list-wrapper"></td>\n' +
' <td class="messages-list-wrapper">\n' +
' <h3 class="message-list-content">' + messagePreview + '</h3>\n' +
' </td>\n' +
' <td class="messages-list-wrapper end"></td>\n' +
' </tr>'
}
}
document.getElementById('messages-list').innerHTML = messagesTable;
}
};
xhttp.open("GET", "/notifications.json", true);
xhttp.send();
}
function showMessage(messageID) {
var scrollHeight = document.body.scrollHeight;
pjax.loadUrl('/messages/' + messageID);
wiiuBrowser.showLoadingIcon(!0)
wiiuSound.playSoundByName('SE_OLV_OK', 1)
storeScrollPosition();
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var messageOBJ = JSON.parse(this.responseText);
var threadContents = '';
for(var i = 0; i < messageOBJ.messages.length; i++) {
if(messageOBJ.messages[i].received) {
threadContents +=
'<div class="message-viewer-bubble-receive">\n' +
' <p class="message-viewer-bubble-receive-text">' + messageOBJ.messages[i].message_content + '</p>\n' +
' </div>\n' +
'<div class="message-viewer-bubble-receive-timestamp"><p>' + messageOBJ.messages[i].created_at + '</p></div>\n';
}
else {
threadContents +=
'<div class="message-viewer-bubble-sent">\n' +
' <p class="message-viewer-bubble-sent-text">' + messageOBJ.messages[i].message_content + '</p>\n' +
' </div>\n' +
'<div class="message-viewer-bubble-sent-timestamp"><p>' + messageOBJ.messages[i].created_at + '</p></div>\n';
}
}
document.getElementById('message-viewer-content').innerHTML = threadContents;
document.getElementById("message-viewer-name").innerHTML = messageOBJ.screen_name;
document.getElementById('message-viewer-content').style.display = 'block';
document.getElementById('message-viewer').style.display = 'block';
document.getElementById('title').style.display = 'none';
document.getElementById('messages-list-wrapper').style.display = 'none';
window.scrollTo(0,document.body.scrollHeight);
wiiuBrowser.showLoadingIcon(!1)
wiiuSound.playSoundByName('SE_OLV_OK', 1);
var interval = setInterval(function () {
if(document.body.scrollHeight !== scrollHeight) {
window.scroll(0, document.body.scrollHeight);
clearInterval(interval);
}
};
xhttp.open("GET", "/message_thread.json?id=" + messageID, true);
xhttp.send();
}, 100);
}
function hideMessage() {
document.getElementById('message-viewer-content').style.display = 'none';
document.getElementById('message-viewer').style.display = 'none';
document.getElementById('title').style.display = 'block';
document.getElementById('messages-list-wrapper').style.display = 'block';
restoreLastScrollPosition()
wiiuSound.playSoundByName('SE_OLV_CANCEL', 1)
}
function sendMessage() {
function sendMessage(conversationID, pid) {
var today = new Date();
var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
var dateTime = date +' '+time;
var messageContents = document.getElementById("message-viewer-input").value;
if(messageContents.length === 0)
return;
var currentThread = document.getElementById('message-viewer-content').innerHTML;
var newMessage =
'\n<div class="message-viewer-bubble-sent">\n' +
' <p class="message-viewer-bubble-sent-text">' + messageContents + '</p>\n' +
' </div>\n' +
'<div class="message-viewer-bubble-sent-timestamp"><p>' + dateTime + '</p></div>\n';
document.getElementById("message-viewer-input").value = '';
document.getElementById('message-viewer-content').innerHTML = currentThread + newMessage
window.scrollTo(0,document.body.scrollHeight);
var params = "conversationID=" + conversationID + "&message_to_pid=" + pid + "&body=" + messageContents;
var xhr = new XMLHttpRequest();
xhr.open("POST", '/messages/new', true);
xhr.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var scrollHeight = document.body.scrollHeight;
document.getElementById("message-viewer-input").value = '';
document.getElementById('message-viewer-content').innerHTML = currentThread + newMessage
var interval = setInterval(function () {
if(document.body.scrollHeight > scrollHeight) {
window.scroll(0, document.body.scrollHeight);
clearInterval(interval);
}
}, 100);
}
if (this.readyState === 4 && (this.status === 423 || this.status === 404)) {
wiiuErrorViewer.openByCodeAndMessage(5986000 + this.status, 'Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
}
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(params);
}
function sendPainting() {
function sendPainting(conversationID, pid) {
wiiuMemo.open(false);
var drawing = wiiuMemo.getImage(false)
var drawing = wiiuMemo.getImage(false);
var rawDrawing = wiiuMemo.getImage(true);
if(drawing !== blankMemo) {
if(confirm("Send the Drawing?")) {
var today = new Date();
@ -508,11 +394,30 @@ function sendPainting() {
'\n<div class="message-viewer-bubble-sent">\n' +
' <img class="message-viewer-bubble-sent-memo" src="data:image/bmp;base64,' + drawing + '" >\n' +
' </div>\n' +
'<div class="message-viewer-bubble-sent-timestamp"><p>' + dateTime + '</p></div>\n' +
'<img src=\'\' onerror=\'window.scrollTo(0,document.body.scrollHeight);\'>';
'<div class="message-viewer-bubble-sent-timestamp"><p>' + dateTime + '</p></div>\n';
var scrollHeight = document.body.scrollHeight;
document.getElementById('message-viewer-content').innerHTML = currentThread + newMessage;
wiiuMemo.reset();
alert('Sent!');
var params = "conversationID=" + conversationID + "&message_to_pid=" + pid + "&raw=" + rawDrawing + "&&drawing=" + drawing;
var xhr = new XMLHttpRequest();
xhr.open("POST", '/messages/new', true);
xhr.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
document.getElementById("message-viewer-input").value = '';
document.getElementById('message-viewer-content').innerHTML = currentThread + newMessage
var interval = setInterval(function () {
if(document.body.scrollHeight > scrollHeight) {
window.scroll(0, document.body.scrollHeight);
clearInterval(interval);
}
}, 100);
}
if (this.readyState === 4 && (this.status === 423 || this.status === 404)) {
wiiuErrorViewer.openByCodeAndMessage(5986000 + this.status, 'Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
}
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(params);
}
else
alert('Canceled');
@ -629,7 +534,7 @@ function loadFeedPosts() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
document.getElementsByClassName('community-page-posts-wrapper')[0].innerHTML += this.responseText;
document.getElementById('community-posts-inner-body').innerHTML += this.responseText;
initCommunityUsers();
}
else if(this.readyState === 4 && this.status === 204)
@ -968,4 +873,4 @@ if (typeof wiiuBOSS === 'undefined') {
return {};
},
};
}
}

View File

@ -115,7 +115,7 @@
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label"><%= lang.user_page.birthday %></h4>
<h4 class="community-page-table-text"><%if(user.birthday_visibility){%>N/A<%}else {%><%= lang.global.private %><%}%></h4>
<h4 class="community-page-table-text"><%if(user.birthday_visibility){%><%=moment.utc(user.birthday).format("MMM Do")%><%}else {%><%= lang.global.private %><%}%></h4>
</div>
</td>
<td>
@ -196,4 +196,4 @@
<br>
<body onload="stopLoading(); wiiuBrowser.showLoadingIcon(!1);wiiuBrowser.lockUserOperation(false);">
</body>
</html>
</html>

View File

@ -0,0 +1,91 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="<%= cdnURL %>/css/juxt.css">
<script src="<%= cdnURL %>/js/pjax.min.js"></script>
<script src="<%= cdnURL %>/js/juxt.js"></script>
<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-195842548-1', 'auto');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->
</head>
<body>
<%- include('nav_bar', { selection: 3 }); %>
<div id="main">
<div id="message-viewer">
<div class="message-viewer-top-cover"></div>
<div class="message-viewer-bottom-cover"></div>
<div class="message-viewer-top-banner">
<table>
<tbody>
<tr>
<td>
<div class="message-viewer-top-banner-back-button" onclick="back()"></div>
</td>
<td>
<img id="message-viewer-icon" src="<%= mii_image_CDN %>/<%= user2.pid %>/normal_face.png">
</td>
<td>
<h2 id="message-viewer-name"><%= user2.user_id %></h2>
</td>
</tr>
</tbody>
</table>
</div>
<div id="message-viewer-content" style="display: block;">
<% messages.forEach(function(message) { %>
<%if(message.pid === user.pid) {%>
<div class="message-viewer-bubble-sent">
<%if(message.painting) {%>
<img class="message-viewer-bubble-sent-memo" src="data:image/bmp;base64,<%= message.painting_uri %>">
<%}else {%>
<p class="message-viewer-bubble-sent-text"><%= message.body %></p>
<%}%>
</div>
<div class="message-viewer-bubble-sent-timestamp"><p><%= moment(message.created_at).fromNow()%></p></div>
<%} else if(message.pid === user2.pid) {%>
<div class="message-viewer-bubble-receive">
<%if(message.painting) {%>
<img class="message-viewer-bubble-sent-memo" src="data:image/bmp;base64,<%= message.painting_uri %>">
<%}else {%>
<p class="message-viewer-bubble-receive-text"><%= message.body %></p>
<%}%>
</div>
<div class="message-viewer-bubble-receive-timestamp"><p><%= moment(message.created_at).fromNow()%></p></div>
<%}%>
<%});%>
</div>
<div class="message-viewer-bottom-banner">
<table>
<tbody>
<tr>
<td>
<div id="message-viewer-attach-button" onclick="sendPainting('<%= conversation.id %>', '<%= user2.pid %>')"></div>
</td>
<td>
<form action="javascript:sendMessage('<%= conversation.id %>', '<%= user2.pid %>')">
<input id="message-viewer-input" type="text" placeholder="Send message..." name="message">
</form>
</td>
<td>
<div id="message-viewer-submit-button" onclick="sendMessage('<%= conversation.id %>', '<%= user2.pid %>')"></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<img src="" onerror="wiiuBrowser.showLoadingIcon(!1);">
</div>
<body onload="stopLoading();wiiuBrowser.lockUserOperation(false);">
</body>
</html>

View File

@ -27,34 +27,31 @@
<p class="no-posts-text"><%= lang.messages.coming_soon %></p>
<%} else { %>
<% conversations.forEach(function(conversation) { %>
<%
var position = conversation.pid1.pid === user.pid.toString() ? conversation.pid2 : conversation.pid1;
%>
<tr class="message-wrapper" onclick="showMessage('<%= conversation.id %>')">
<td class="messages-unread-badge-wrapper">
<div class="unread-badge"></div>
<%if(!position.read) {%><div class="unread-badge"></div><%}%>
</td>
<td>
<%
var position = conversation.pids.indexOf(user.pid) === 0 ? 1 : 0;
var message = recentMessages.filter(message => {
return (message.pid.toString() === user.pid.toString() && message.message_to_pid === conversation.pids[position]) || (message.pid.toString() === conversation.pids[position] && message.message_to_pid === user.pid.toString());
})
%>
<%if(message.verified) {%>
<img class="community-page-post-user-icon verified" src="<%= mii_image_CDN %>/<%= conversation.pids[position] %>/normal_face.png">
<%if(position.official) {%>
<img class="community-page-post-user-icon verified" src="<%= mii_image_CDN %>/<%= position.pid %>/normal_face.png">
<span class="message-page-verified-user-badge community-page-verified" style=""></span>
<%} else {%>
<img class="community-page-post-user-icon" src="<%= mii_image_CDN %>/<%= conversation.pids[position] %>/normal_face.png">
<img class="community-page-post-user-icon" src="<%= mii_image_CDN %>/<%= position.pid %>/normal_face.png">
<span class="community-page-verified-user-badge community-page-verified" style="display: none;"></span>
<%}%>
</td>
<td>
<h2 class="message-list-title"><%= userMap.get(message) %> </h2>
<h4 class="message-list-timestamp"> - <%= moment(message.created_at).fromNow() %></h4>
<h2 class="message-list-title"><%= position.screen_name %></h2>
<h4 class="message-list-timestamp"> - <%= moment(conversation.last_message_sent).fromNow() %></h4>
</td>
</tr>
<tr>
<td></td>
<td></td>
<td colspan="2" class="message-list-preview"><%= message.body %></td>
<td colspan="2" class="message-list-preview"><%= conversation.message_preview %></td>
</tr>
<% }); %>
<%}%>

View File

@ -76,7 +76,7 @@
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label"><%= lang.user_page.birthday %></h4>
<h4 class="community-page-table-text"><%if(user.birthday_visibility){%>N/A<%}else {%>Private<%}%></h4>
<h4 class="community-page-table-text"><%if(user.birthday_visibility){%><%=moment.utc(user.birthday).format("MMM Do")%><%}else {%>Private<%}%></h4>
</div>
</td>
<td>
@ -153,4 +153,4 @@
<br>
<body onload="stopLoading(); wiiuBrowser.showLoadingIcon(!1);wiiuBrowser.lockUserOperation(false);">
</body>
</html>
</html>