Database refactor (#46)

* Created the new database models, removed the user model (this *will* break everything)

https://tenor.com/uqNk.gif

* began implementing the updated database models in requests. While all should be working now, more testing is still required

* cleaned up now unused middleware. More work on implementing the db rewrite

* Finished moving messages over to new db model. Fixed a bug where the userMap was pulling an incorrect value for the scrren_name.

* More work on dynamic notifications

* moved notifications render to be async in order to support database calls for dynamic notification content.

* Fixed followers not displaying on user pages. Cleaned up a lot of the UI. Implemented dynamic notifications system

* Fixed cdn urls on the Wii U pages. Fixed a bug with messages pages showing your own name and Mii in threads. Added a flag for removing a post

* Updated some localization text. Added check to prevent notifications breaking when a reference ID does not resolve to a valid user. Updated the following list to use the correct cdn url for community icons.

* Fixed typo in header URL for the announcements page

* Update drawing and screenshot domains to resolve http errors when loading some images

* Removed add friend button. Added profile badges to me page. Fixed typo in paintings domain that prevented them from loading

* Fixed (most) of the web frontend.

* updated auth urls

* Updated post meta data domains

* Fixed post length check not being performed. Updated portal css to prevent text from running off the screen

* Added service notice messages to Juxt web login

* Fixed bug that prevented users from posting. Added additional filtering to new post body content

* Added missing doctype flag at beginning of portal files. Re-worked notification object in DB and fixed bug where notifications would not be marked read. Added initial schema to support reporting a post

* UI redesign (#45)

* Added PID to all requests. Added 404 page. Added new CSS. Updated communities and all communities page

* Added community, feed, messages, and post template redesigns. Fixed relevant endpoints

* Added redesigned user pages. Added dedicated endpoint for images. Moved user endpoint to be /users/:id

* Added support to Yeah! on a post. Began trimming down the juxt.js file. General code cleanup

* Added settings page. More code cleanup

* Updated Yeah! function to update the parent element and the yeah count text

* Added badges and other profile content to the profile page. Added ratelimit to empathy count.  Yeah count is now updated locally before confirming with the server

* Moved partial templates to separate directory. Combined me and user pages to reduce repeated code. Added support for viewing followers and following. Added support for only sending template content for user pages when the pjax flag is set (yay faster loading times on the Wii U!)

* Added support for tab view in communities page. Fixed Yeah! button not working when new tab is loaded. Added post view page with replies.

* Added support for message threads

* Added support for loading more posts in user pages and communities. Implemented proper error page.

* Added support for making posts to communities

* Added support for following users

* Implemented saving settings. Added support for sending messages. Added option to send screenshot in user messages

* Added support for replies. Added notification templates.

* Added support for new account creation. Added additional checks for user accounts existing before displaying a page.

* Finished implementing new user setup.
Known Issues:
[ ] Notifications are not implemented
[ ] You cannot select your game experience on the first_run.ejs page using the d-pad
[ ] Spoilers are not respected in post view
[ ] Messages button does not work on profile pages
[ ] Back button is not implemented
[ ] While loading, the messages page does not always scroll to the bottom of the page

* Fixed auth being flagged for the background image (lol)

* Fixed titles page not showing new communities. Fixed rate limit to address duplicate posts from being created. Fixed alignment of replies on post page. General code cleanup

* Fixed user tabs not loading correctly. Fixed community links on user pages not loading. Fixed the bug where all tabs would increasingly send more requests each time you click one. Created new handling for duplicate posts error pages

* Locked user operation when new post is created. Added 1 level of proxy trust to (hopefully) fix the rate limit issue.

* Added message to indicate features that aren't ready yet. Updated dependencies. Added another layer of trust to the rate limiter. Addressed invalid config from updated mongoose library.

* Fixed load more being shown on every post. Fixed new message button on profile pages not working. Addressed sounds not being played consistently when interacting with the UI. Added minified css and js. Added support for refreshing the page (Y) and going back (B) using the button inputs on the gamepad.

* Began implementation of notifications. Implemented back button on nav bar. Increased message thread limit.

* Added support for new account creation. Added additional checks for user accounts existing before displaying a page.

* Fixed some characters incorrectly being caught by the text filter. Fixed the nav-bar sometimes not re-appearing when using the back button. Fixed an error where attempting to reply to a post in the announcements community would result in being soft-locked in the applet with a forbidden message. Fixed the nav-bar showing a scroll bar on the 404 page

* Fixed nav-bar sometimes having a scroll bar. Replaced the popular communities query to now look at the number of new posts in the last 24 hours to determine the order

* Fixed issue where follow notifications would stack no matter how long the last notification was last updated.

* Tweaked some logic with notifications

* WIP community structure changes

* Fixed /titles/show redirecting to the wrong place. Minor css tweaks, code cleanup

* Added support for spoiler posts

* Addressed issue where using certain unicode characters in a name would reverse the text on a notification

* Began work on fixing Juxt web client

* Reworked web UI to work with rewrite. Lots and lots of small bug fixes

* Added error page for message threads on web. CSS tweaks. Added lang tag to html documents

* Update login cookie domain

* Updated cdn URL's for web interface

* updated web cdn (again, sorry)

* Fixed url for the mii image on nav-bar for users on Chromium browsers

* updating CDN url for messages and notifications

* fixed web user page banner

* Fixed crash when web token fails to fetch. Fixed typo on communities page that prevented it from loading sometimes

* Fixed crash when web token fails to fetch. Fixed typo on communities page that prevented it from loading sometimes

* (hopefully) Fixed negative yeahs from occurring

* Update post.js to simplify empathy logic

* Added view to see the most recent people to yeah! a post. Added tab to user page to view yeahed! posts. CSS tweaks

* Updated yeah endpoint to actually show posts in the order of most recently yeahed!

* Fixed scrolling in activity feed. Fixed tapping back button refreshing page. Added support for Related Communities. Fixed Related communities showing up in popular page. CSS tweaks

* 3DS redesign start. Added communities and all communities page

* Finished styling the users yeah list. Added support for displaying topic tags in the post body. Fixed spoiler button not disappearing after clicking it. Added additional check to ensure yeah count cannot be negative.

* Added support for browsing topic tags in the applet

* Added check to open communities from WWP

* Re-added support for downloading user data. Fixed mii images not working on Juxt mobile.

* Updated web icons and manifest

* Updated check for newer Mii name to actually update the user settings object

* Added Content-Disposition header to user data export file

* More 3DS work. Changed post ID format to be inline with the official format

* Finished schema refactor and related upkeep on portal endpoint TODO: web and ctr

* Fixed posts and user page on web. Added missing elements on PNID schema. Fixed head partials

* Fixed communities db query not including announcement communities. Added check to see if community is sub community when displaying icons

* Updated post id length. Updated schema for reports

* Fixed notifications schema

* Fixed new messages not being created. Changed new message button to check for being friends instead of mutual followers. Fixed follower notifications not working correctly. Added friends tab to user profile page. Added support for viewing pending friend requests (WIP, somewhat broken). Fixed notification and message badges not showing up on the Wii U.

* Updated to new token format

* Fixed token variable name in processServiceToken

* updated token format

* Implemented /titles and /titles/:id/new on 3DS. Began implementing new custom Pjax module for 3DS. Changed friends GRPC variable names in anticipation of adding support for accounts GRPC functionality

* Reworked auth middleware to support new login system. Fix bug where usernames were not shown in notifications in the web client. Added error page for when a user attempts to log into Juxt on the web for the first time. Updated dependencies

* Refactored auth middleware. Added typescript and linter config files. Updated grpc dependency to use typescript version (still needs to be build manually...will fix tomorrow). Added minified 3DS css and js files.

* Finished refactor of auth middleware. Removed account database connection and updated relevant files to reflect this change. General code cleanup

* Fixed typo in auth middleware

* CSS tweaks on 3DS. Added new cdn domain for 3DS to test

* Removed unused admin files. Updated header files for Portal, CTR, and Web to use built in CF cache instead of BunnyCDN

* Removed unused admin files. Updated header files for Portal, CTR, and Web to use built in CF cache instead of BunnyCDN

* Added support for the activity feed, user page, and topic tags on 3DS. Added additional error pages for missing features on 3DS

* Adding support for posting from the 3DS. Added pjax support for tabs. Added support for notifications and viewing friend requests. Added support for bmp paintings from the 3DS. CSS tweaks and bug fixes.

* Added support for the first run experience on 3DS

* Added support for following users and communities on 3DS. Added check to see if title supports screenshots before allowing user to select them

* Added support for following users and communities on 3DS. Added check to see if title supports screenshots before allowing user to select them

* Fixed alignment issue with follow button when related communities are displayed. Added support for viewing related communities

* Added support for viewing and sending messages on 3DS. Moved friend requests tab to the user page

* Added support for reporting a post on the Wii U

* Added report type for piracy, fixed typo

* Added support for removing your own posts and replies

* Added support for viewing user reports on Juxt web. Added deleted profile page. Added bandwidth to the 404 page. CSS fixes

* Fixed posts not being marked as verified from the applet. Added support for reporting replies. Added support for moderators deleting posts and replies from the applet.

* Added more meta-data to reports for tracking purposes. Reports are now filtered out of the admin page if they have already been resolved. Fixed artifact issue with the nav_bar in the web endpoint

* Updated auth middleware to catch issues with authenticating with the account server.

* Fixed web manifest not being allowed through auth middleware

* Fixed manifest images not loading in chrome

* Updated manifest to use absolute url to fix the PWA breaking on mobile

* fixed PWA start path (sorry, it worked the first time I tested)

* Fixed font rendering on posts on the mobile site. Updated the forgot password link on the login page. Fixed user banners not being loaded.

* temp - added explicit beta checks in middleware

* updated beta checks

* Updated auth to support different domains. Started working on segregated auth middleware

---------

Co-authored-by: CaramelKat <git@jaysoftware.dev>
Co-authored-by: Jemma Poffinbarger <git@jemsoftware.dev>
Co-authored-by: Jonathan Barrow <jonbarrow1998@gmail.com>

---------

Co-authored-by: Jemma <git@jemsoftware.dev>
Co-authored-by: CaramelKat <git@jaysoftware.dev>
Co-authored-by: Jonathan Barrow <jonbarrow1998@gmail.com>
This commit is contained in:
Jemma 2023-10-22 15:08:33 -05:00 committed by GitHub
parent 7ee24db09d
commit 2e4febb3e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
178 changed files with 18683 additions and 14818 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
dist
*.min.js

59
.eslintrc.json Normal file
View File

@ -0,0 +1,59 @@
{
"env": {
"node": true,
"commonjs": true,
"es6": true
},
"parser": "@typescript-eslint/parser",
"globals": {
"BigInt": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"plugins": [
"@typescript-eslint"
],
"rules": {
"require-atomic-updates": "warn",
"no-case-declarations": "off",
"no-empty": "off",
"no-console": "off",
"linebreak-style": "off",
"no-global-assign": "off",
"prefer-const": "error",
"no-var": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-extra-semi": "off",
"@typescript-eslint/no-extra-semi": "error",
"@typescript-eslint/no-empty-interface": "warn",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/typedef": "error",
"@typescript-eslint/explicit-function-return-type": "error",
"keyword-spacing": "off",
"@typescript-eslint/keyword-spacing": "error",
"curly": "error",
"brace-style": "error",
"one-var": [
"error",
"never"
],
"indent": [
"error",
"tab",
{
"SwitchCase": 1
}
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}

View File

@ -3,12 +3,13 @@
"port": 80
},
"account_server_domain": "",
"account_server": "",
"account_server_secret": "",
"X-Nintendo-Client-ID": "",
"X-Nintendo-Client-Secret": "",
"mongoose": {
"uri": "mongodb://localhost:27017",
"database": "name",
"database": "database",
"options": {
"useNewUrlParser": true,
"useUnifiedTopology": true
@ -16,13 +17,16 @@
},
"account_db": {
"uri": "mongodb://localhost:27017",
"database": "name",
"database": "database",
"options": {
"useNewUrlParser": true,
"useUnifiedTopology": true
}
},
"authorized_PNIDs" : [
0
]
}
"aws": {
"spaces": {
"key": "",
"secret": ""
}
}
}

10309
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,10 @@
"description": "Web frontend for the Pretendo Network miiverse replacement, Juxt.",
"main": "./src/server",
"scripts": {
"start": "node ."
"start": "node .",
"lint": "npx eslint .",
"build": "npm run lint && npm run clean && npx tsc && npx tsc-alias",
"clean": "rimraf ./dist"
},
"repository": {
"type": "git",
@ -18,37 +21,45 @@
},
"homepage": "https://github.com/PretendoNetwork/juxt-web#readme",
"dependencies": {
"aws-sdk": "^2.1192.0",
"body-parser": "^1.19.0",
"colors": "^1.4.0",
"cookie-parser": "^1.4.5",
"crc": "^4.3.2",
"date-fns": "^2.29.3",
"express": "^4.17.1",
"express-rate-limit": "^6.7.0",
"fs-extra": "^9.0.1",
"hashmap": "^2.4.0",
"image-pixels": "^2.2.2",
"image-pixels": "^1.1.1",
"memory-cache": "^0.2.0",
"moment": "^2.29.1",
"mongoose": "^6.0.13",
"mongoose-fuzzy-search-next": "^1.0.13",
"mongoose-unique-validator": "^3.0.0",
"morgan": "^1.10.0",
"nice-grpc": "^2.1.4",
"node-rsa": "^1.1.1",
"node-snowflake": "0.0.1",
"pako": "^2.0.2",
"pngjs": "^6.0.0",
"sharp": "^0.28.1",
"pretendo-grpc": "github:PretendoNetwork/grpc-ts",
"sharp": "^0.31.3",
"tga": "^1.0.4",
"xml2js": "^0.4.23",
"xmlbuilder": "^15.1.1",
"xmlbuilder2": "^2.4.0"
"xmlbuilder2": "^3.0.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.59.11",
"@typescript-eslint/parser": "^5.59.11",
"bmp-js": "^0.1.0",
"ejs": "^3.1.5",
"eslint": "^8.42.0",
"express-slow-down": "^1.4.0",
"express-subdomain": "^1.0.5",
"multer": "^1.4.2",
"object-to-xml": "^2.0.0",
"request": "^2.88.2",
"xml2json": "^0.12.0"
"tsc-alias": "^1.8.6",
"typescript": "^5.1.3"
}
}

View File

@ -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
};

View File

@ -1,16 +1,19 @@
const mongoose = require('mongoose');
const { mongoose: mongooseConfig } = require('../config.json');
const { ENDPOINT } = require('./models/endpoint');
const { COMMUNITY } = require('./models/communities');
const { POST } = require('./models/post');
const { USER } = require('./models/user');
const { CONTENT } = require('./models/content');
const { CONVERSATION } = require('./models/conversation');
const { ENDPOINT } = require('./models/endpoint');
const { NOTIFICATION } = require('./models/notifications');
const { POST } = require('./models/post');
const { SETTINGS } = require('./models/settings');
const { REPORT } = require('./models/report');
const { uri, database, options } = mongooseConfig;
const { PNID } = require('./models/pnid');
const logger = require('./logger');
const accountDB = require('./accountdb');
let connection;
mongoose.set('strictQuery', true);
async function connect() {
await mongoose.connect(`${uri}/${database}`, options);
@ -33,9 +36,9 @@ function verifyConnected() {
async function getCommunities(numberOfCommunities) {
verifyConnected();
if(numberOfCommunities === -1)
return COMMUNITY.find({ parent: null, type: 0 });
return COMMUNITY.find({ parent: null, type: [0,2] });
else
return COMMUNITY.find({ parent: null, type: 0 }).limit(numberOfCommunities);
return COMMUNITY.find({ parent: null, type: [0,2] }).limit(numberOfCommunities);
}
async function getMostPopularCommunities(numberOfCommunities) {
@ -65,21 +68,21 @@ async function getCommunityByTitleID(title_id) {
async function getCommunityByID(community_id) {
verifyConnected();
return COMMUNITY.findOne({
community_id: community_id
olive_community_id: community_id
});
}
async function getTotalPostsByCommunity(community) {
verifyConnected();
return POST.find({
title_id: community.title_id,
parent: null
community_id: community.olive_community_id,
parent: null,
removed: false
}).countDocuments();
}
async function getPostByID(postID) {
verifyConnected();
return POST.findOne({
id: postID
});
@ -89,14 +92,16 @@ async function getPostsByUserID(userID) {
verifyConnected();
return POST.find({
pid: userID,
parent: null
parent: null,
removed: false
});
}
async function getPostReplies(postID, number) {
verifyConnected();
return POST.find({
parent: postID
parent: postID,
removed: false
}).limit(number);
}
@ -107,7 +112,8 @@ async function getDuplicatePosts(pid, post) {
body: post.body,
painting: post.painting,
screenshot: post.screenshot,
parent: null
parent: null,
removed: false
});
}
@ -116,7 +122,8 @@ async function getUserPostRepliesAfterTimestamp(post, numberOfPosts) {
return POST.find({
parent: post.pid,
created_at: { $lt: post.created_at },
message_to_pid: null
message_to_pid: null,
removed: false
}).limit(numberOfPosts);
}
@ -125,7 +132,8 @@ async function getNumberUserPostsByID(userID, number) {
return POST.find({
pid: userID,
parent: null,
message_to_pid: null
message_to_pid: null,
removed: false
}).sort({ created_at: -1}).limit(number);
}
@ -134,68 +142,93 @@ async function getTotalPostsByUserID(userID) {
return POST.find({
pid: userID,
parent: null,
message_to_pid: null
message_to_pid: null,
removed: false
}).countDocuments();
}
async function getHotPostsByCommunity(community, numberOfPosts) {
verifyConnected();
return POST.find({
title_id: community.title_id,
parent: null
community_id: community.olive_community_id,
parent: null,
removed: false
}).sort({empathy_count: -1}).limit(numberOfPosts);
}
async function getNumberNewCommunityPostsByID(community, number) {
verifyConnected();
return POST.find({
title_id: community.title_id,
parent: null
community_id: community.olive_community_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
community_id: community.olive_community_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,
community_id: community.olive_community_id,
verified: true,
parent: null
parent: null,
removed: false
}).sort({ created_at: -1}).skip(offset).limit(limit);
}
async function getPostsByCommunity(community, numberOfPosts) {
verifyConnected();
return POST.find({
title_id: community.title_id,
parent: null
community_id: community.olive_community_id,
parent: null,
removed: false
}).limit(numberOfPosts);
}
async function getPostsByCommunityKey(community, numberOfPosts, search_key) {
verifyConnected();
return POST.find({
title_id: community.title_id,
community_id: community.olive_community_id,
search_key: search_key,
parent: null
parent: null,
removed: false
}).limit(numberOfPosts);
}
async function getNewPostsByCommunity(community, limit, offset) {
verifyConnected();
return POST.find({
community_id: community.community_id,
parent: null
community_id: community.olive_community_id,
parent: null,
removed: false
}).sort({ created_at: -1 }).skip(offset).limit(limit);
}
async function getAllUserPosts(pid) {
verifyConnected();
return POST.find({
pid: pid,
message_to_pid: 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({
@ -203,6 +236,7 @@ async function getUserPostsAfterTimestamp(post, numberOfPosts) {
created_at: { $lt: post.created_at },
parent: null,
message_to_pid: null,
removed: false
}).limit(numberOfPosts);
}
@ -211,118 +245,113 @@ async function getUserPostsOffset(pid, limit, offset) {
return POST.find({
pid: pid,
parent: null,
message_to_pid: null
message_to_pid: null,
removed: false
}).skip(offset).limit(limit).sort({ created_at: -1});
}
async function getCommunityPostsAfterTimestamp(post, numberOfPosts) {
verifyConnected();
return POST.find({
title_id: post.title_id,
community_id: post.community_id,
created_at: { $lt: post.created_at },
parent: null
parent: null,
removed: false
}).limit(numberOfPosts);
}
async function pushNewNotificationByPID(PID, content, link) {
async function getEndpoints() {
verifyConnected();
return USER.update(
{ pid: PID }, { $push: { notification_list: { content: content, link: link, read: false, created_at: Date() }}});
return ENDPOINT.find({});
}
async function pushNewNotificationToAll(content, link) {
verifyConnected();
return USER.updateMany(
{}, { $push: { notification_list: { content: content, link: link, read: false, created_at: Date() }}});
}
async function getDiscoveryHosts() {
async function getEndPoint(accessLevel) {
verifyConnected();
return ENDPOINT.findOne({
version: 1
});
server_access_level: accessLevel
})
}
async function getUsers(numberOfUsers) {
async function getUsersSettings(numberOfUsers) {
verifyConnected();
if(numberOfUsers === -1)
return USER.find({});
return SETTINGS.find({});
else
return USER.find({}).limit(numberOfUsers);
return SETTINGS.find({}).limit(numberOfUsers);
}
async function getFollowingUsers(user) {
async function getUsersContent(numberOfUsers) {
verifyConnected();
return USER.find({
pid: user.following_users
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(user) {
async function getFollowedUsers(content) {
verifyConnected();
return USER.find({
pid: user.followed_users
return SETTINGS.find({
pid: content.followed_users
});
}
async function getUserByPID(PID) {
verifyConnected();
return USER.findOne({
pid: PID
});
}
async function getUserByUsername(user_id) {
verifyConnected();
return USER.findOne({
pnid: new RegExp(`^${user_id}$`, 'i')
});
}
async function getServerConfig() {
verifyConnected();
return ENDPOINT.findOne();
}
async function getNewsFeed(user, numberOfPosts) {
async function getNewsFeed(content, numberOfPosts) {
verifyConnected();
return POST.find({
$or: [
{pid: user.followed_users},
{pid: user.pid},
{community_id: user.followed_communities},
{pid: content.followed_users},
{pid: content.pid},
{community_id: content.followed_communities},
],
parent: null,
message_to_pid: null
message_to_pid: null,
removed: false
}).limit(numberOfPosts).sort({ created_at: -1});
}
async function getNewsFeedAfterTimestamp(user, numberOfPosts, post) {
async function getNewsFeedAfterTimestamp(content, numberOfPosts, post) {
verifyConnected();
return POST.find({
$or: [
{pid: user.followed_users},
{pid: user.pid},
{community_id: user.followed_communities},
{pid: content.followed_users},
{pid: content.pid},
{community_id: content.followed_communities},
],
created_at: { $lt: post.created_at },
parent: null,
message_to_pid: null
message_to_pid: null,
removed: false
}).limit(numberOfPosts).sort({ created_at: -1});
}
async function getNewsFeedOffset(user, limit, offset) {
async function getNewsFeedOffset(content, limit, offset) {
verifyConnected();
return POST.find({
$or: [
{pid: user.followed_users},
{pid: user.pid},
{community_id: user.followed_communities},
{pid: content.followed_users},
{pid: content.pid},
{community_id: content.followed_communities},
],
parent: null,
message_to_pid: null
message_to_pid: null,
removed: false
}).skip(offset).limit(limit).sort({ created_at: -1});
}
@ -356,7 +385,8 @@ async function getConversationMessages(community_id, limit, offset) {
verifyConnected();
return POST.find({
community_id: community_id,
parent: null
parent: null,
removed: false
}).sort({created_at: 1}).skip(offset).limit(limit);
}
@ -376,22 +406,66 @@ async function getLatestMessage(pid, pid2) {
$or: [
{pid: pid, message_to_pid: pid2},
{pid: pid2, message_to_pid: pid}
]
],
removed: false
})
}
async function getPNIDS() {
accountDB.verifyConnected();
return PNID.find({});
async function getNotifications(pid, limit, offset) {
verifyConnected();
return NOTIFICATION.find({
pid: pid,
}).sort({lastUpdated: -1}).skip(offset).limit(limit);
}
async function getPNID(pid) {
accountDB.verifyConnected();
return PNID.findOne({
async function getNotification(pid, type, reference_id) {
verifyConnected();
return NOTIFICATION.findOne({
pid: pid,
type: type,
reference_id: reference_id
})
}
async function getLastNotification(pid) {
verifyConnected();
return NOTIFICATION.findOne({
pid: pid
});
}).sort({lastUpdated: -1}).limit(1);
}
async function getUnreadNotificationCount(pid) {
verifyConnected();
return NOTIFICATION.find({
pid: pid,
read: false
}).countDocuments();
}
async function getAllReports(offset, limit) {
verifyConnected();
return REPORT.find().sort({created_at: -1}).skip(offset).limit(limit);
}
async function getAllOpenReports(offset, limit) {
verifyConnected();
return REPORT.find({ resolved: false }).sort({created_at: -1}).skip(offset).limit(limit);
}
async function getReportsByUser(pid, offset, limit) {
verifyConnected();
return REPORT.find({ reported_by: pid }).sort({created_at: -1}).skip(offset).limit(limit);
}
async function getReportsByPost(postID, offset, limit) {
verifyConnected();
return REPORT.find({ post_id: postID }).sort({created_at: -1}).skip(offset).limit(limit);
}
async function getReportById(id) {
verifyConnected();
return REPORT.findById(id);
}
module.exports = {
@ -403,7 +477,6 @@ module.exports = {
getCommunityByTitleID,
getCommunityByID,
getTotalPostsByCommunity,
getDiscoveryHosts,
getPostsByCommunity,
getHotPostsByCommunity,
getNumberNewCommunityPostsByID,
@ -418,15 +491,11 @@ module.exports = {
getTotalPostsByUserID,
getPostByID,
getDuplicatePosts,
getUsers,
getUserByPID,
getUserByUsername,
getEndpoints,
getEndPoint,
getUserPostsAfterTimestamp,
getUserPostsOffset,
getCommunityPostsAfterTimestamp,
getServerConfig,
pushNewNotificationByPID,
pushNewNotificationToAll,
getNewsFeed,
getNewsFeedAfterTimestamp,
getNewsFeedOffset,
@ -438,6 +507,19 @@ module.exports = {
getConversationMessages,
getUnreadConversationCount,
getLatestMessage,
getPNID,
getPNIDS
getUsersSettings,
getUsersContent,
getUserSettings,
getUserContent,
getNotifications,
getUnreadNotificationCount,
getNotification,
getLastNotification,
getAllUserPosts,
getRemovedUserPosts,
getAllReports,
getAllOpenReports,
getReportsByUser,
getReportsByPost,
getReportById
};

View File

@ -1,104 +1,136 @@
const config = require('../../config.json');
const util = require('../util');
const moment = require("moment/moment");
const db = require('../database');
function auth(request, response, next) {
if(request.path.includes('/css/') || request.path.includes('/fonts/')
|| request.path.includes('/js/') || request.path.includes('/icons/')
|| request.path.includes('/banner/') || request.path.includes('/drawing/')
|| request.path.includes('/screenshot/') || request.path.includes('/web/')) {
if(request.subdomains.indexOf('juxt') !== -1) {
async function auth(request, response, next) {
// Web files
if(isStartOfPath(request.path, '/css/') ||
isStartOfPath(request.path, '/fonts/') ||
isStartOfPath(request.path, '/js/') ||
request.path === '/favicon.ico' ||
isStartOfPath(request.path, '/web/') ||
isStartOfPath(request.path, '/images/') ||
isStartOfPath(request.path, '/image/')) {
request.lang = util.data.processLanguage();
if(includes(request, 'juxt'))
request.directory = 'web';
request.lang = util.data.processLanguage();
}
else {
request.directory = request.subdomains[1];
}
else
request.directory = includes(request, 'portal') ? 'portal' : 'ctr';
return next()
}
if(request.subdomains.indexOf('juxt') !== -1) {
request.directory = 'web';
if(request.path === '/login' || request.path === '/favicon.ico' ||
(request.path.includes('/posts/') && !request.path.includes('/empathy'))) {
request.lang = util.data.processLanguage();
request.pid = util.data.processServiceToken(request.cookies.access_token) || 1000000000;
request.paramPackData = null;
request.directory = 'web';
return next();
// Get pid and fetch user data
if(request.cookies.access_token) {
try {
request.user = await util.data.getUserDataFromToken(request.cookies.access_token);
}
else {
if(request.cookies.access_token === undefined || request.cookies.access_token === null)
{
return response.redirect('/login');
}
let pid = util.data.processServiceToken(request.cookies.access_token);
if(pid === null)
{
return response.redirect('/login');
}
else {
request.lang = util.data.processLanguage();
request.pid = pid;
request.paramPackData = null;
request.directory = 'web';
return next();
}
catch(e) {
return response.render('web/login.ejs', {toast: 'Unable to reach the account server. Try again later.', cdnURL: config.CDN_domain,});
}
request.pid = request.user ? request.user.pid : null;
}
else if(request.headers["x-nintendo-servicetoken"]) {
request.pid = request.headers["x-nintendo-servicetoken"] ? await util.data.processServiceToken(request.headers["x-nintendo-servicetoken"]) : null;
request.user = request.pid ? await util.data.getUserDataFromPid(request.pid) : null;
}
// Set headers
request.paramPackData = request.headers["x-nintendo-parampack"] ? util.data.decodeParamPack(request.headers["x-nintendo-parampack"]) : null;
response.header('X-Nintendo-WhiteList', config.whitelist);
// Ban check
if(request.user) {
if (request.user.serverAccessLevel !== 'test' && request.user.serverAccessLevel !== 'dev') {
response.status(500);
return response.send('No access. Must be tester or dev');
}
// Set moderator status
request.moderator = request.user.accessLevel >= 2;
const user = await db.getUserSettings(request.pid);
if(user && moment(user.ban_lift_date) <= moment() && user.account_status !== 3) {
user.account_status = 0;
await user.save()
}
// This includes ban checks for both Juxt specifically and the account server, ideally this should be squashed
// assuming we support more gradual bans on PNID's
if(user && (user.account_status < 0 || user.account_status > 1 || request.user.accessLevel < 0))
{
response.render(request.directory + '/partials/ban_notification.ejs', {
user: user,
moment: moment,
cdnURL: config.CDN_domain,
lang: request.lang,
pid: request.pid
});
}
}
if(request.subdomains.indexOf('admin') !== -1) {
if(request.path === '/login' || request.path === '/token' || request.path === '/favicon.ico') {
return next()
// Juxt Website
if(includes(request, 'juxt')) {
request.lang = util.data.processLanguage();
request.token = request.cookies.access_token;
request.paramPackData = null;
request.directory = 'web';
// Open access pages
if(isStartOfPath(request.path, '/users/') ||
(isStartOfPath(request.path, '/titles/') && request.path !== '/titles/show') ||
(isStartOfPath(request.path, '/posts/') && !request.path.includes('/empathy'))) {
if(!request.pid)
request.pid = 1000000000;
return next();
}
else {
if(request.cookies.token === undefined || request.cookies.token === null)
{
return response.redirect('/login');
}
let pid = util.data.processServiceToken(request.cookies.token);
if(pid === null)
{
return response.redirect('/login');
}
else {
request.pid = pid;
return next();
}
// Login endpoint
if(request.path === '/login') {
if(request.pid)
return response.redirect('/titles/show?src=login');
return next();
}
if(!request.pid)
return response.redirect('/login');
return next();
}
else {
let token = request.cookies.token || request.headers["x-nintendo-servicetoken"];
if(!token) {
return response.render('portal/ban_notification.ejs', {
// This section includes checks if a user is a developer and adds exceptions for these cases
if(!request.pid) {
return response.render('portal/partials/ban_notification.ejs', {
user: null,
error: "Unable to parse service token. Are you using a Nintendo Network ID?"
});
}
if(request.user.accessLevel < 3 && !request.paramPackData) {
return response.render('portal/partials/ban_notification.ejs', {
user: null,
error: "Missing auth headers"
});
}
else {
let pid = util.data.processServiceToken(token);
let paramPackData;
if(request.headers["x-nintendo-parampack"])
paramPackData = util.data.decodeParamPack(request.headers["x-nintendo-parampack"]);
else
paramPackData = null;
if(pid === null) {
return response.render('portal/ban_notification.ejs', {
user: null,
error: "Unable to parse service token. Are you using a Nintendo Network ID?"
});
}
else {
response.header('X-Nintendo-WhiteList', config.whitelist);
let paramPack = request.headers["x-nintendo-parampack"] || undefined;
request.lang = util.data.processLanguage(paramPack);
request.pid = pid;
request.paramPackData = paramPackData;
request.directory = request.subdomains[1];
return next();
}
if(!request.user) {
return response.render('portal/partials/ban_notification.ejs', {
user: null,
error: "Unable to fetch user data. Please try again later."
});
}
let userAgent = request.headers['user-agent'];
if(request.user.accessLevel < 3 && (request.cookies.access_token || (!userAgent.includes('Nintendo WiiU') && !userAgent.includes('Nintendo 3DS'))))
return response.render('portal/partials/ban_notification.ejs', {
user: null,
error: "Invalid authentication method used."
});
request.lang = util.data.processLanguage(request.paramPackData);
request.directory = includes(request, 'portal') ? 'portal' : 'ctr';
return next();
}
}
function isStartOfPath(path, value) {
return path.indexOf(value) === 0;
}
function includes(request, domain) {
return request.subdomains.findIndex(element => element.includes(domain)) !== -1
}
module.exports = auth;

View File

@ -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;

View File

@ -0,0 +1,71 @@
const config = require('../../config.json');
const util = require('../util');
const moment = require("moment/moment");
const db = require('../database');
async function auth(request, response, next) {
// Get pid and fetch user data
if(request.headers["x-nintendo-servicetoken"]) {
request.pid = request.headers["x-nintendo-servicetoken"] ? await util.data.processServiceToken(request.headers["x-nintendo-servicetoken"]) : null;
request.user = request.pid ? await util.data.getUserDataFromPid(request.pid) : null;
}
// Set headers
request.paramPackData = request.headers["x-nintendo-parampack"] ? util.data.decodeParamPack(request.headers["x-nintendo-parampack"]) : null;
response.header('X-Nintendo-WhiteList', config.whitelist);
// Ban check
if(request.user) {
// Set moderator status
request.moderator = request.user.accessLevel >= 2;
const user = await db.getUserSettings(request.pid);
if(user && moment(user.ban_lift_date) <= moment() && user.account_status !== 3) {
user.account_status = 0;
await user.save()
}
// This includes ban checks for both Juxt specifically and the account server, ideally this should be squashed
// assuming we support more gradual bans on PNID's
if(user && (user.account_status < 0 || user.account_status > 1 || request.user.accessLevel < 0))
{
response.render(request.directory + '/partials/ban_notification.ejs', {
user: user,
moment: moment,
cdnURL: config.CDN_domain,
lang: request.lang,
pid: request.pid
});
}
}
// This section includes checks if a user is a developer and adds exceptions for these cases
if(!request.pid) {
return response.render('portal/partials/ban_notification.ejs', {
user: null,
error: "Unable to parse service token. Are you using a Nintendo Network ID?"
});
}
if(request.user.accessLevel < 3 && !request.paramPackData) {
return response.render('portal/partials/ban_notification.ejs', {
user: null,
error: "Missing auth headers"
});
}
if(!request.user) {
return response.render('portal/partials/ban_notification.ejs', {
user: null,
error: "Unable to fetch user data. Please try again later."
});
}
let userAgent = request.headers['user-agent'];
if(request.user.accessLevel < 3 && (request.cookies.access_token || (!userAgent.includes('Nintendo WiiU') && !userAgent.includes('Nintendo 3DS'))))
return response.render('portal/partials/ban_notification.ejs', {
user: null,
error: "Invalid authentication method used."
});
request.lang = util.data.processLanguage(request.paramPackData);
request.directory = request.subdomains[1];
return next();
}
module.exports = auth;

View File

@ -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;

View File

@ -1,7 +0,0 @@
const slowDown = require("express-slow-down");
module.exports = resetPasswordSpeedLimiter = slowDown({
windowMs: 2 * 60 * 1000,
delayAfter: 10,
delayMs: 100
});

View File

@ -1,23 +0,0 @@
// super basic and there's probably a much better way to do this
// this will only be used during the registration process, to track the progress of the user
// express-session uses cookies which the WiiU does not support during the registration process
// temp, in-memory session storage
const sessionStore = {};
function sessionMiddlware(request, response, next) {
const ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
if (!sessionStore[ip]) {
sessionStore[ip] = {};
}
const session = sessionStore[ip];
request.session = session;
return next();
}
module.exports = sessionMiddlware;

View File

@ -0,0 +1,33 @@
const util = require('../util');
async function staticFiles(request, response, next) {
// Web files
if(isStartOfPath(request.path, '/css/') ||
isStartOfPath(request.path, '/fonts/') ||
isStartOfPath(request.path, '/js/') ||
request.path === '/favicon.ico' ||
isStartOfPath(request.path, '/web/') ||
isStartOfPath(request.path, '/images/') ||
isStartOfPath(request.path, '/image/')) {
request.lang = util.data.processLanguage();
if(request.subdomains.includes('juxt'))
request.directory = 'web';
else
request.directory = request.subdomains[1];
return next()
}
else if(request.path === "/") {
return response.redirect('/titles/show');
}
else
return response.sendStatus(404);
}
function isStartOfPath(path, value) {
return path.indexOf(value) === 0;
}
module.exports = staticFiles;

68
src/middleware/webAuth.js Normal file
View File

@ -0,0 +1,68 @@
const config = require('../../config.json');
const util = require('../util');
const moment = require("moment/moment");
const db = require('../database');
async function auth(request, response, next) {
// Get pid and fetch user data
request.lang = util.data.processLanguage();
request.paramPackData = null;
request.directory = 'web';
request.token = request.cookies.access_token;
if(request.cookies.access_token) {
try {
request.user = await util.data.getUserDataFromToken(request.token);
}
catch(e) {
console.log(e);
if(request.path === "/login") {
return next();
}
return response.render('web/login.ejs', {toast: 'Unable to reach the account server. Try again later.', cdnURL: config.CDN_domain,});
}
request.pid = request.user ? request.user.pid : null;
}
// Ban check
if(request.user) {
// Set moderator status
request.moderator = request.user.accessLevel >= 2;
const user = await db.getUserSettings(request.pid);
if(user && moment(user.ban_lift_date) <= moment() && user.account_status !== 3) {
user.account_status = 0;
await user.save()
}
// This includes ban checks for both Juxt specifically and the account server, ideally this should be squashed
// assuming we support more gradual bans on PNID's
if(user && (user.account_status < 0 || user.account_status > 1 || request.user.accessLevel < 0))
{
return response.render('web/login.ejs', {toast: 'Your account has been suspended. For more information, log into https://pretendo.network/account', cdnURL: config.CDN_domain,});
}
}
// Open access pages
if(isStartOfPath(request.path, '/users/') ||
(isStartOfPath(request.path, '/titles/') && request.path !== '/titles/show') ||
(isStartOfPath(request.path, '/posts/') && !request.path.includes('/empathy'))) {
if(!request.pid)
request.pid = 1000000000;
return next();
}
// Login endpoint
if(request.path === '/login') {
if(request.pid)
return response.redirect('/titles/show?src=login');
return next();
}
console.log(request.route);
if(!request.user && request.path !== '/')
return response.redirect('/login');
return next();
}
function isStartOfPath(path, value) {
return path.indexOf(value) === 0;
}
module.exports = auth;

View File

@ -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;

View File

@ -1,5 +1,4 @@
const { Schema, model } = require('mongoose');
const moment = require("moment");
const CommunitySchema = new Schema({
platform_id: Number,
@ -9,6 +8,10 @@ const CommunitySchema = new Schema({
type: Boolean,
default: true
},
allows_comments: {
type: Boolean,
default: true
},
/**
* 0: Main Community
* 1: Sub-Community
@ -20,11 +23,11 @@ const CommunitySchema = new Schema({
default: 0
},
parent: {
type: Number,
type: String,
default: null
},
admins: {
type: [String],
type: [Number],
default: undefined
},
created_at: {
@ -39,10 +42,6 @@ const CommunitySchema = new Schema({
type: Number,
default: 0
},
id: {
type: Number,
default: 0
},
has_shop_page: {
type: Number,
default: 0
@ -57,14 +56,12 @@ const CommunitySchema = new Schema({
default: undefined
},
community_id: String,
olive_community_id: String,
is_recommended: {
type: Number,
default: 0
},
browser_icon: String,
browser_thumbnail: String,
CTR_browser_header: String,
WiiU_browser_header: String,
app_data: String,
});
CommunitySchema.methods.upEmpathy = async function() {

60
src/models/content.js Normal file
View File

@ -0,0 +1,60 @@
const { Schema, model } = require('mongoose');
const ContentSchema = new Schema({
pid: Number,
followed_communities: {
type: [String],
default: [0]
},
followed_users: {
type: [Number],
default: [0]
},
following_users: {
type: [Number],
default: [0]
},
});
ContentSchema.methods.addToCommunities = async function(postID) {
const communities = this.get('followed_communities');
communities.addToSet(postID);
await this.save();
}
ContentSchema.methods.removeFromCommunities = async function(postID) {
const communities = this.get('followed_communities');
communities.pull(postID);
await this.save();
}
ContentSchema.methods.addToUsers = async function(postID) {
const users = this.get('followed_users');
users.addToSet(postID);
await this.save();
}
ContentSchema.methods.removeFromUsers = async function(postID) {
const users = this.get('followed_users');
users.pull(postID);
await this.save();
}
ContentSchema.methods.addToFollowers = async function(postID) {
const users = this.get('following_users');
users.addToSet(postID);
await this.save();
}
ContentSchema.methods.removeFromFollowers = async function(postID) {
const users = this.get('following_users');
users.pull(postID);
await this.save();
}
const CONTENT = model('CONTENT', ContentSchema);
module.exports = {
ContentSchema,
CONTENT
};

View File

@ -3,7 +3,7 @@ const moment = require("moment");
const snowflake = require('node-snowflake').Snowflake;
const user = new Schema({
pid: String,
pid: Number,
official: {
type: Boolean,
default: false
@ -35,16 +35,14 @@ const ConversationSchema = new Schema({
});
ConversationSchema.methods.newMessage = async function(message, fromPid) {
const users = this.get('users');
if(users[0].pid === fromPid) {
users[0].read = false;
users[1].read = true;
if(this.users[0].pid === fromPid) {
this.users[1].read = false;
this.markModified('users[1].read');
}
else {
users[0].read = true;
users[1].read = false;
this.users[0].read = false;
this.markModified('users[0].read');
}
this.set('users', users);
this.set('last_updated', moment(new Date()));
this.set('message_preview', message);
await this.save();
@ -52,9 +50,9 @@ ConversationSchema.methods.newMessage = async function(message, fromPid) {
ConversationSchema.methods.markAsRead = async function(pid) {
let users = this.get('users');
if(users[0].pid === pid.toString())
if(users[0].pid === pid)
users[0].read = true;
else if(users[1].pid === pid.toString())
else if(users[1].pid === pid)
users[1].read = true;
this.set('users', users)
this.markModified('users');

View File

@ -1,30 +1,16 @@
const { Schema, model } = require('mongoose');
const endpointSchema = new Schema({
has_error: Number,
version: Number,
endpoint: {
host: String,
api_host: String,
portal_host: String,
n3ds_host: String
},
guest: Boolean,
status: Number,
server_access_level: String,
topics: Boolean,
guest_access: Boolean,
host: String,
api_host: String,
portal_host: String,
n3ds_host: String
});
endpointSchema.methods.updateHosts = async function({host, api_host, portal_host, n3ds_host}) {
this.set('endpoint.host', host);
this.set('endpoint.api_host', api_host);
this.set('endpoint.portal_host', portal_host);
this.set('endpoint.n3ds_host', n3ds_host);
await this.save();
};
endpointSchema.methods.updateGuest = async function(mode) {
this.set('guest', mode);
await this.save();
}
const ENDPOINT = model('ENDPOINT', endpointSchema);
module.exports = {

View File

@ -0,0 +1,26 @@
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
};

View File

@ -1,52 +0,0 @@
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

@ -1,30 +1,28 @@
const { Schema, model } = require('mongoose');
const PostSchema = new Schema({
id: String,
title_id: String,
screen_name: String,
body: String,
app_data: String,
painting: String,
painting_uri: String,
screenshot: String,
url: String,
screenshot_length: Number,
search_key: {
type: [String],
default: undefined
},
topic_tag: {
type: [String],
type: String,
default: undefined
},
community_id: {
type: String,
default: undefined
},
country_id: Number,
created_at: Date,
feeling_id: Number,
id: String,
is_autopost: {
type: Number,
default: 0
@ -43,7 +41,12 @@ const PostSchema = new Schema({
},
empathy_count: {
type: Number,
default: 0
default: 0,
min: 0
},
country_id: {
type: Number,
default: 49
},
language_id: {
type: Number,
@ -51,14 +54,10 @@ const PostSchema = new Schema({
},
mii: String,
mii_face_url: String,
number: {
type: Number,
default: 1
},
pid: Number,
platform_id: Number,
region_id: Number,
parent_post: String,
parent: String,
reply_count: {
type: Number,
default: 0
@ -67,45 +66,54 @@ const PostSchema = new Schema({
type: Boolean,
default: false
},
parent: {
type: String,
default: null
},
message_to_pid: {
type: String,
default: null
}
},
removed: {
type: Boolean,
default: false
},
removed_reason: String,
removed_by: Number,
removed_at: Date,
yeahs: [Number]
});
PostSchema.methods.upEmpathy = async function() {
const empathy = this.get('empathy_count');
this.set('empathy_count', empathy + 1);
await this.save();
};
PostSchema.methods.downEmpathy = async function() {
const empathy = this.get('empathy_count');
this.set('empathy_count', empathy - 1);
await this.save();
};
PostSchema.methods.upReply = async function() {
const replyCount = this.get('reply_count');
this.set('reply_count', replyCount + 1);
if(replyCount + 1 < 0)
this.set('reply_count', 0);
else
this.set('reply_count', replyCount + 1);
await this.save();
};
PostSchema.methods.downReply = async function() {
const replyCount = this.get('reply_count');
this.set('reply_count', replyCount - 1);
if(replyCount - 1 < 0)
this.set('reply_count', 0);
else
this.set('reply_count', replyCount - 1);
await this.save();
};
PostSchema.methods.removePost = async function(reason, pid) {
this.set('removed', true);
this.set('removed_reason', reason);
this.set('removed_by', pid);
this.set('removed_at', new Date())
await this.save();
};
PostSchema.methods.unRemove = async function(reason) {
this.set('removed', false);
this.set('removed_reason', reason);
await this.save();
};
const POST = model('POST', PostSchema);
module.exports = {

33
src/models/report.js Normal file
View File

@ -0,0 +1,33 @@
const { Schema, model } = require('mongoose');
const ReportSchema = new Schema({
pid: Number,
reported_by: Number,
post_id: String,
reason: Number,
message: String,
created_at: {
type: Date,
default: new Date()
},
resolved: {
type: Boolean,
default: false
},
resolved_by: Number,
resolved_at: Date,
});
ReportSchema.methods.resolve = async function(pid) {
this.set('resolved', true);
this.set('resolved_by', pid);
this.set('resolved_at', new Date())
await this.save();
};
const REPORT = model('REPORT', ReportSchema);
module.exports = {
ReportSchema,
REPORT
};

95
src/models/settings.js Normal file
View File

@ -0,0 +1,95 @@
const { Schema, model } = require('mongoose');
const SettingsSchema = new Schema({
pid: Number,
screen_name: String,
account_status: {
type: Number,
default: 0
},
ban_lift_date: Date,
ban_reason: String,
profile_comment: {
type: String,
default: undefined
},
profile_comment_visibility: {
type: Boolean,
default: true
},
game_skill: {
type: Number,
default: 0
},
game_skill_visibility: {
type: Boolean,
default: true
},
birthday_visibility: {
type: Boolean,
default: false
},
relationship_visibility: {
type: Boolean,
default: false
},
country_visibility: {
type: Boolean,
default: false
},
profile_favorite_community_visibility: {
type: Boolean,
default: true
},
receive_notifications: {
type: Boolean,
default: true
}
});
SettingsSchema.methods.updateComment = async function(comment) {
this.set('profile_comment', comment);
await this.save();
};
SettingsSchema.methods.updateSkill = async function(skill) {
this.set('game_skill', skill);
await this.save();
};
SettingsSchema.methods.commentVisible = async function(active) {
this.set('profile_comment_visibility', active);
await this.save();
};
SettingsSchema.methods.skillVisible = async function(active) {
this.set('game_skill_visibility', active);
await this.save();
};
SettingsSchema.methods.birthdayVisible = async function(active) {
this.set('birthday_visibility', active);
await this.save();
};
SettingsSchema.methods.relationshipVisible = async function(active) {
this.set('relationship_visibility', active);
await this.save();
};
SettingsSchema.methods.countryVisible = async function(active) {
this.set('country_visibility', active);
await this.save();
};
SettingsSchema.methods.favCommunityVisible = async function(active) {
this.set('profile_favorite_community_visibility', active);
await this.save();
};
const SETTINGS = model('SETTINGS', SettingsSchema);
module.exports = {
SettingsSchema,
SETTINGS
};

View File

@ -1,278 +0,0 @@
const { Schema, model } = require('mongoose');
const notification = new Schema({
content: String,
link: String,
read: Boolean,
created_at: Date,
});
const UserSchema = new Schema({
pid: Number,
created_at: Date,
user_id: String,
pnid: String,
birthday: Date,
country: String,
pfp_uri: String,
mii: String,
mii_face_url: String,
/**
* Account Status
* 0 - Fine
* 1 - Limited from Posting
* 2 - Temporary Ban
* 3 - Forever Ban
*/
account_status: {
type: Number,
default: 0
},
ban_lift_date: Date,
ban_reason: String,
official: {
type: Boolean,
default: false
},
profile_comment: {
type: String,
default: undefined
},
game_skill: {
type: Number,
default: 0
},
game_skill_visibility: {
type: Boolean,
default: true
},
profile_comment_visibility: {
type: Boolean,
default: true
},
birthday_visibility: {
type: Boolean,
default: false
},
relationship_visibility: {
type: Boolean,
default: false
},
country_visibility: {
type: Boolean,
default: false
},
profile_favorite_community_visibility: {
type: Boolean,
default: true
},
notifications: {
type: Boolean,
default: false
},
likes: {
type: [String],
default: [0]
},
followed_communities: {
type: [String],
default: [0]
},
followed_users: {
type: [String],
default: [0]
},
following_users: {
type: [String],
default: [0]
},
followers: {
type: Number,
default: 0
},
following: {
type: Number,
default: 0
},
notification_list: {
type: [notification],
default: [{
content: 'This is your notifications! You\'ll see more stuff here soon!',
link: '/users/me',
read: false,
created_at: new Date(),
}]
}
});
UserSchema.methods.getAccountStatus = async function() {
return this.get('account_status');
};
UserSchema.methods.setAccountStatus = async function(accountStatus) {
this.set('account_status', accountStatus);
await this.save();
};
UserSchema.methods.getBanDate = async function() {
return this.get('ban_lift_date');
};
UserSchema.methods.setBanData = async function(banDate) {
this.set('ban_lift_date', banDate);
await this.save();
};
UserSchema.methods.getProfileComment = async function() {
return this.get('profile_comment');
};
UserSchema.methods.setProfileComment = async function(profileComment) {
this.set('profile_comment', profileComment);
await this.save();
};
UserSchema.methods.getGameSkill = async function() {
return this.get('game_skill');
};
UserSchema.methods.setGameSkill = async function(gameSkill) {
this.set('game_skill', gameSkill);
await this.save();
};
UserSchema.methods.getGameSkillVisibility = async function() {
return this.get('game_skill_visibility');
};
UserSchema.methods.setGameSkillVisibility = async function(gameSkillVisibility) {
this.set('game_skill_visibility', gameSkillVisibility);
await this.save();
};
UserSchema.methods.getProfileCommentVisibility = async function() {
return this.get('profile_comment_visibility');
};
UserSchema.methods.setProfileCommentVisibility = async function(profileCommentVisibility) {
this.set('profile_comment_visibility', profileCommentVisibility);
await this.save();
};
UserSchema.methods.getBirthdayVisibility = async function() {
return this.get('birthday_visibility');
};
UserSchema.methods.setBirthdayVisibility = async function(birthdayVisibility) {
this.set('birthday_visibility', birthdayVisibility);
await this.save();
};
UserSchema.methods.getRelationshipVisibility = async function() {
return this.get('relationship_visibility');
};
UserSchema.methods.setRelationshipVisibility = async function(accountStatus) {
this.set('relationship_visibility', accountStatus);
await this.save();
};
UserSchema.methods.getFavoriteCommunityVisibility = async function() {
return this.get('profile_favorite_community_visibility');
};
UserSchema.methods.setFavoriteCommunityVisibility = async function(favoriteCommunityVisibility) {
this.set('profile_favorite_community_visibility', favoriteCommunityVisibility);
await this.save();
};
UserSchema.methods.getCountryVisibility = async function() {
return this.get('country_visibility');
};
UserSchema.methods.setCountryVisibility = async function(countryVisibility) {
this.set('country_visibility', countryVisibility);
await this.save();
};
UserSchema.methods.addToLikes = async function(postID) {
const likes = this.get('likes');
likes.addToSet(postID);
await this.save();
}
UserSchema.methods.removeFromLike = async function(postID) {
const likes = this.get('likes');
likes.pull(postID);
await this.save();
}
UserSchema.methods.addToCommunities = async function(postID) {
const communities = this.get('followed_communities');
communities.addToSet(postID);
await this.upFollowing();
await this.save();
}
UserSchema.methods.removeFromCommunities = async function(postID) {
const communities = this.get('followed_communities');
communities.pull(postID);
await this.downFollowing();
await this.save();
}
UserSchema.methods.addToUsers = async function(postID) {
const users = this.get('followed_users');
users.addToSet(postID);
await this.upFollowing();
await this.save();
}
UserSchema.methods.removeFromUsers = async function(postID) {
const users = this.get('followed_users');
users.pull(postID);
await this.downFollowing();
await this.save();
}
UserSchema.methods.addToFollowers = async function(postID) {
const users = this.get('following_users');
users.addToSet(postID);
await this.upFollower();
await this.save();
}
UserSchema.methods.removeFromFollowers = async function(postID) {
const users = this.get('following_users');
users.pull(postID);
await this.downFollower();
await this.save();
}
UserSchema.methods.upFollower = async function() {
const followers = this.get('followers');
this.set('followers', followers + 1);
await this.save();
};
UserSchema.methods.downFollower = async function() {
const followers = this.get('followers');
if(followers > 0)
this.set('followers', followers - 1);
await this.save();
};
UserSchema.methods.upFollowing = async function() {
const following = this.get('following');
this.set('following', following + 1);
await this.save();
};
UserSchema.methods.downFollowing = async function() {
const following = this.get('following');
if(following > 0)
this.set('following', following - 1);
await this.save();
};
const USER = model('USER', UserSchema);
module.exports = {
UserSchema,
USER
};

View File

@ -1,8 +1,7 @@
process.title = 'Pretendo - Miiverse';
process.title = 'Pretendo - Juxt-Web';
const express = require('express');
const morgan = require('morgan');
const ejs = require('ejs');
const xmlparser = require('./middleware/xml-parser');
const cookieParser = require('cookie-parser');
const auth = require('./middleware/auth');
const database = require('./database');
@ -18,6 +17,8 @@ app.set('etag', false);
app.disable('x-powered-by');
app.set('view engine', 'ejs');
app.set('views', __dirname + '/webfiles');
app.set('trust proxy', 2)
app.get('/ip', (request, response) => response.send(request.ip))
// Create router
logger.info('Setting up Middleware');
@ -28,9 +29,8 @@ app.use(express.json());
app.use(express.urlencoded({
extended: true,
limit: '1mb',
parameterLimit: 100000
}));
app.use(xmlparser);
app.use(cookieParser());
app.use(auth);
@ -40,10 +40,15 @@ app.use(juxt_web);
// 404 handler
logger.info('Creating 404 status handler');
app.use((request, response) => {
//logger.warn(request.protocol + '://' + request.get('host') + request.originalUrl);
response.status(404);
response.send();
app.use((req, res) => {
logger.warn(req.protocol + '://' + req.get('host') + req.originalUrl);
res.render(req.directory + '/error.ejs', {
code: 404,
message: "Page not found",
cdnURL: config.CDN_domain,
lang: req.lang,
pid: req.pid
});
});
// non-404 error handler

View File

@ -4,40 +4,39 @@ const logger = require('../../logger');
const routes = require('./routes');
const router = express.Router();
const console = express.Router();
const admin = express.Router();
// Create subdomains
logger.info('[JUXT-WEB] Creating \'Web\' subdomain');
router.use(subdomain('juxt', console));
router.use(subdomain('juxt-beta', console));
router.use(subdomain('juxt-dev', console));
logger.info('[JUXT-WEB] Creating \'Wii U\' subdomain');
router.use(subdomain('portal.olv', console));
router.use(subdomain('portal-beta.olv', console));
router.use(subdomain('portal-dev.olv', console));
logger.info('[JUXT-WEB] Creating \'3DS\' subdomain');
router.use(subdomain('ctr.olv', console));
logger.info('[JUXT-WEB] Creating \'Admin\' subdomain');
router.use(subdomain('admin.olv', admin));
logger.info('[JUXT-WEB] Creating \'Web API\' subdomain');
router.use(subdomain('web_api.olv', admin));
router.use(subdomain('ctr-beta.olv', console));
router.use(subdomain('ctr-dev.olv', console));
// Setup routes
console.use('/titles/show', routes.PORTAL_SHOW);
console.use('/titles', routes.PORTAL_COMMUNITIES);
console.use('/communities', routes.PORTAL_COMMUNITIES);
console.use('/topics', routes.PORTAL_TOPICS);
console.use('/users', routes.PORTAL_USER);
console.use('/posts', routes.PORTAL_POST);
console.use('/activity-feed', routes.PORTAL_FEED);
console.use('/messages', routes.PORTAL_MESSAGES);
console.use('/feed', routes.PORTAL_FEED);
console.use('/friend_messages', routes.PORTAL_MESSAGES);
console.use('/news', routes.PORTAL_NEWS);
console.use('/', routes.PORTAL_WEB);
console.use('/login', routes.WEB_LOGIN);
console.use('/robots.txt', routes.ROBOTS);
console.use('/web', routes.PWA);
admin.use('/', routes.WEB_ADMIN);
admin.use('/v1/', routes.WEB_API);
console.use('/admin', routes.ADMIN);
module.exports = router;

View File

@ -0,0 +1,57 @@
const express = require('express');
const database = require('../../../../database');
const { POST } = require('../../../../models/post');
const util = require('../../../../util');
const moment = require('moment');
const config = require("../../../../../config.json");
const router = express.Router();
router.get('/', async function (req, res) {
if(!req.moderator)
return res.redirect('/login');
const reports = await database.getAllOpenReports();
const communityMap = await util.data.getCommunityHash();
const userContent = await database.getUserContent(req.pid);
const userMap = util.data.getUserHash();
const postIDs = reports.map(obj => obj.post_id);
const posts = await POST.aggregate([
{ $match: { id: { $in: postIDs } } },
{$addFields: {
"__order": { $indexOfArray: [ postIDs, "$id" ] }
}},
{ $sort: { "__order": 1 } },
{ $project: { index: 0, _id: 0 } }
]);
res.render(req.directory + '/reports.ejs', {
lang: req.lang,
moment: moment,
cdnURL: config.CDN_domain,
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator,
userMap,
communityMap,
userContent,
reports,
posts
});
});
router.delete('/:reportID', async function (req, res) {
let report = await database.getReportById(req.params.reportID);
if(!report) return res.sendStatus(402);
let post = await database.getPostByID(report.post_id);
if(!post) return res.sendStatus(404);
if(!req.moderator) return res.sendStatus(401);
await post.removePost(req.query.reason ? req.query.reason : 'Removed by moderator', req.pid);
await report.resolve(req.pid);
return res.sendStatus(200);
});
module.exports = router;

View File

@ -1,433 +0,0 @@
var express = require('express');
const database = require('../../../../database');
const logger = require('../../../../logger');
const util = require('../../../../util');
const config = require('../../../../../config.json');
const { COMMUNITY } = require('../../../../models/communities');
const { POST } = require('../../../../models/post');
var router = express.Router();
const moment = require('moment');
var multer = require('multer');
const snowflake = require('node-snowflake').Snowflake;
var storage = multer.memoryStorage();
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(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
res.send(await database.getCommunities(-1))
}
else
throw new Error('Invalid account ID or password');
});
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(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
res.send(await database.getCommunityByID(req.params.communityID))
}
else
throw new Error('Invalid account ID or password');
});
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(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');
}
let community = await database.getCommunityByID(req.params.communityID);
if(community !== null) {
community.delete().then(err => function () {
res.send(err);
});
res.sendStatus(200);
logger.audit('[' + user.user_id + ' - ' + user.pid + '] deleted community ' + community.name);
}
}
else
throw new Error('Invalid account ID or password');
});
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(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');
}
const community = await database.getCommunityByID(req.params.communityID);
const files = JSON.parse(JSON.stringify(req.files));
if(req.body.name && community.name !== req.body.name)
community.name = req.body.name;
if(req.body.description && community.description !== req.body.description)
community.description = req.body.description;
if(req.body.title_ids[0] !== community.title_ids[0] && req.body.title_ids[1] !== community.title_ids[1] && req.body.title_ids[2] !== community.title_ids[2]
&& req.body.title_ids[0] !== '' && req.body.title_ids[1] !== '' && req.body.title_ids[2] !== '') {
community.title_id = req.body.title_ids;
community.title_ids = req.body.title_ids;
}
if(req.body.icon && community.icon !== req.body.icon)
community.icon = req.body.icon;
if(req.files.browserIcon) {
community.browser_icon = `data:image/png;base64,${req.files.browserIcon[0].buffer.toString('base64')}`;
community.browser_thumbnail = `data:image/png;base64,${req.files.browserIcon[0].buffer.toString('base64')}`;
}
if(req.files.CTRbrowserHeader)
community.CTR_browser_header = `data:image/png;base64,${req.files.CTRbrowserHeader[0].buffer.toString('base64')}`;
if(req.files.WiiUbrowserHeader)
community.WiiU_browser_header = `data:image/png;base64,${req.files.WiiUbrowserHeader[0].buffer.toString('base64')}`;
if(req.body.is_recommended)
community.is_recommended = req.body.is_recommended;
if(req.body.has_shop_page)
community.has_shop_page = req.body.has_shop_page;
if(req.body.platform_id)
community.platform_id = req.body.platform_id;
community.save();
res.sendStatus(200);
util.data.refreshCache();
logger.audit('[' + user.user_id + ' - ' + user.pid + '] updated community ' + community.name);
}
else
throw new Error('Invalid account ID or password');
});
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(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');
}
JSON.parse(JSON.stringify(req.files));
let browserIcon, CTRHeader, WiiUHeader, thumb;
if(req.files.browserIcon) {
browserIcon = `data:image/png;base64,${req.files.browserIcon[0].buffer.toString('base64')}`;
thumb = `data:image/png;base64,${req.files.browserIcon[0].buffer.toString('base64')}`;
}
if(req.files.CTRbrowserHeader)
CTRHeader = `data:image/png;base64,${req.files.CTRbrowserHeader[0].buffer.toString('base64')}`;
if(req.files.WiiUbrowserHeader)
WiiUHeader = `data:image/png;base64,${req.files.WiiUbrowserHeader[0].buffer.toString('base64')}`;
const document = {
empathy_count: 0,
id: snowflake.nextId(),
has_shop_page: req.body.has_shop_page,
platform_id: req.body.platform_ID,
icon: req.body.icon,
created_at: moment(new Date()),
title_ids: req.body.title_ids,
title_id: req.body.title_ids,
community_id: snowflake.nextId(),
is_recommended: req.body.is_recommended,
name: req.body.name,
browser_icon: `data:image/png;base64,${browserIcon.toString('base64')}`,
CTR_browser_header: `data:image/png;base64,${CTRHeader.toString('base64')}`,
WiiU_browser_header: `data:image/png;base64,${WiiUHeader.toString('base64')}`,
browser_thumbnail: `data:image/png;base64,${thumb.toString('base64')}`,
description: req.body.description,
};
const newCommunity = new COMMUNITY(document);
newCommunity.save();
res.sendStatus(200);
logger.audit('[' + user.user_id + ' - ' + user.pid + '] created community ' + newCommunity.name);
}
else
throw new Error('Invalid account ID or password');
});
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(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');
}
const community = await database.getCommunityByID(req.params.communityID);
let browserIcon, CTRHeader, WiiUHeader, thumb;
if(req.files.browserIcon) {
browserIcon = `data:image/png;base64,${req.files.browserIcon[0].buffer.toString('base64')}`;
}
else {
browserIcon = community.browser_icon;
thumb = community.browser_thumbnail;
}
if(req.files.CTRbrowserHeader)
CTRHeader = `data:image/png;base64,${req.files.CTRbrowserHeader[0].buffer.toString('base64')}`;
else
CTRHeader = community.CTR_browser_header;
if(req.files.WiiUbrowserHeader)
WiiUHeader = `data:image/png;base64,${req.files.WiiUbrowserHeader[0].buffer.toString('base64')}`;
else
WiiUHeader = community.WiiU_browser_header;
JSON.parse(JSON.stringify(req.files));
const document = {
name: req.body.name,
description: req.body.description,
parent: req.body.parent,
type: req.body.type,
empathy_count: 0,
id: snowflake.nextId(),
has_shop_page: req.body.has_shop_page,
platform_id: req.body.platform_ID,
icon: req.body.icon,
created_at: moment(new Date()),
title_ids: req.body.title_ids,
title_id: req.body.title_ids,
community_id: snowflake.nextId(),
is_recommended: req.body.is_recommended,
browser_icon: browserIcon,
browser_thumbnail: thumb,
CTR_browser_header: CTRHeader,
WiiU_browser_header: WiiUHeader,
};
const newCommunity = new COMMUNITY(document);
newCommunity.save();
res.sendStatus(200);
logger.audit('[' + user.user_id + ' - ' + user.pid + '] created community ' + newCommunity.name);
}
else
throw new Error('Invalid account ID or password');
});
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(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
let endpoint = await database.getServerConfig();
endpoint.has_error = req.body.has_error;
endpoint.save();
res.send(endpoint);
}
else
throw new Error('Invalid account ID or password');
});
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(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
res.send(await database.getUsers(-1))
}
else
throw new Error('Invalid account ID or password');
});
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(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
let post = await database.getPostByID(req.query.postID);
let newPosts = await database.getUserPostsAfterTimestamp(post, 5);
let communityMap = await util.data.getCommunityHash();
if(newPosts.length > 0)
{
res.render('console/more_posts.ejs', {
communityMap: communityMap,
moment: moment,
user: user,
newPosts: newPosts,
account_server: config.account_server_domain.slice(8),
});
}
else
{
res.sendStatus(204);
}
}
else
throw new Error('Invalid account ID or password');
});
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(pnid.access_level !== 3)
throw new Error('Invalid credentials supplied');
res.send(await database.getUserByPID(req.params.userID))
}
else
throw new Error('Invalid account ID or password');
});
router.post('/users/:userID/update', upload.none(), async function(req, res) {
let parentUser = await database.getUserByPID(req.pid);
let user = await database.getUserByPID(req.params.userID);
if(user !== null)
{
if(config.authorized_PNIDs.indexOf(parentUser.pid) === -1)
throw new Error('Invalid credentials supplied');
user.account_status = req.body.account_status;
user.ban_reason = req.body.ban_reason;
user.ban_lift_date = moment(req.body.ban_date);
user.save();
res.sendStatus(200);
util.data.refreshCache();
logger.audit('[' + parentUser.user_id + ' - ' + parentUser.pid + '] banned ' + user.user_id + ' until ' + user.ban_lift_date + ' for ' + user.ban_reason);
}
else
throw new Error('Invalid account ID or password');
});
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(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');
}
let post = await database.getPostByID(req.params.postID);
if(post !== null) {
post.delete().then(err => function () {
res.send(err);
});
res.sendStatus(200);
logger.audit('[' + user.user_id + ' - ' + user.pid + '] deleted post by ' + post.screen_name);
}
else
res.sendStatus(404)
}
else
throw new Error('Invalid account ID or password');
});
router.get('/notifications', async function(req, res) {
await database.pushNewNotificationToAll('Testing a system wide notification and linking to a post', '/users/me')
res.sendStatus(200);
});
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(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');
}
let community = await database.getCommunityByID(req.body.olive_community_id);
let appData = "";
if (req.body.app_data) {
appData = req.body.app_data.replace(/\0/g, "").trim();
}
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);
}
let screenshot = "";
if (req.body.screenshot) {
screenshot = req.body.screenshot.replace(/\0/g, "").trim();
}
let miiFace;
switch (parseInt(req.body.emotion)) {
case 1:
miiFace = 'smile_open_mouth.png';
break;
case 2:
miiFace = 'wink_left.png';
break;
case 3:
miiFace = 'surprise_open_mouth.png';
break;
case 4:
miiFace = 'frustrated.png';
break;
case 5:
miiFace = 'sorrow.png';
break;
default:
miiFace = 'normal_face.png';
break;
}
const document = {
title_id: community.title_id[0],
community_id: community.community_id,
screen_name: user.user_id,
body: req.body.body,
app_data: appData,
painting: painting,
painting_uri: paintingURI,
screenshot: screenshot,
country_id: 49,
created_at: new Date(),
feeling_id: req.body.emotion,
id: snowflake.nextId(),
is_autopost: req.body.is_autopost,
is_spoiler: req.body.is_spoiler,
is_app_jumpable: req.body.is_app_jumpable,
language_id: req.body.language_id,
mii: user.mii,
mii_face_url: `http://mii.olv.pretendo.cc/mii/${user.pid}/${miiFace}`,
pid: req.body.pid,
platform_id: 0,
region_id: 2,
verified: user.official
};
let duplicatePost = await database.getDuplicatePosts(req.pid, document);
if(duplicatePost)
return res.redirect('/communities/' + community.community_id + '/new');
const newPost = new POST(document);
newPost.save();
res.sendStatus(200);
logger.audit(`[${user.pnid} - ${user.pid}] posed to the ${community.name} community.`);
}
else
throw new Error('Invalid account ID or password');
});
module.exports = router;

View File

@ -1,327 +0,0 @@
var express = require('express');
var xml = require('object-to-xml');
const database = require('../../../../database');
const logger = require('../../../../logger');
const util = require('../../../../util');
const config = require('../../../../../config.json');
const request = require("request");
var path = require('path');
var moment = require('moment');
var multer = require('multer');
var upload = multer({ dest: 'uploads/' });
var router = express.Router();
router.get('/', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
res.render('admin/admin_home.ejs', {
user: user,
account_server: config.account_server_domain.slice(8),
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/css/juxt.css', function (req, res) {
res.sendFile('css/juxt.css', {root: path.join(__dirname, '../../../../webfiles/admin/')});
});
router.get('/favicon.ico', function (req, res) {
res.sendFile('css/favicon.ico', {root: path.join(__dirname, '../../../../webfiles/console/')});
});
router.get('/icons/:image_id.png', async function (req, res) {
let community = await database.getCommunityByID(req.params.image_id.toString());
if(community !== null) {
if(community.browser_icon.indexOf('data:image/png;base64,') !== -1)
res.send(Buffer.from(community.browser_icon.replace('data:image/png;base64,',''), 'base64'));
else
res.send(Buffer.from(community.browser_icon, 'base64'));
}
else {
let user = await database.getUserByPID(req.params.image_id.toString());
if(user !== null)
if(user.pfp_uri.indexOf('data:image/png;base64,') !== -1)
res.send(Buffer.from(user.pfp_uri.replace('data:image/png;base64,',''), 'base64'));
else
res.send(Buffer.from(user.pfp_uri, 'base64'));
else
res.sendStatus(404);
}
});
router.get('/banner/:image_id.png', async function (req, res) {
let community = await database.getCommunityByID(req.params.image_id.toString());
if(community !== null)
if(community.WiiU_browser_header.indexOf('data:image/png;base64,') !== -1)
res.send(Buffer.from(community.WiiU_browser_header.replace('data:image/png;base64,',''), 'base64'));
else
res.send(Buffer.from(community.WiiU_browser_header, 'base64'));
else
res.sendStatus(404);
});
router.get('/discovery', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
res.render('admin/admin_discovery.ejs', {
user: user,
account_server: config.account_server_domain.slice(8),
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/communities', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
let communities = await database.getCommunities(500)
res.render('admin/admin_communities.ejs', {
user: user,
account_server: config.account_server_domain.slice(8),
communities: communities,
moment: moment,
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/audit', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
res.render('admin/admin_audit.ejs', {
user: user,
account_server: config.account_server_domain.slice(8),
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/announcements', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
let community = await database.getCommunityByID('announcements');
let newPosts = await database.getNewPostsByCommunity(community, 500);
let totalNumPosts = await database.getTotalPostsByCommunity(community);
res.render('admin/admin_announcements.ejs', {
community: community,
newPosts: newPosts,
totalNumPosts: totalNumPosts,
user: user,
account_server: config.account_server_domain.slice(8),
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/communities/new', upload.none(), async function (req, res) {
var communityID = req.query.CID;
let user = await database.getUserByPID(req.pid);
res.render('admin/admin_new_community.ejs', {
user: user,
account_server: config.account_server_domain.slice(8),
communityID: communityID,
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/communities/:communityID', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
let community = await database.getCommunityByID(req.params.communityID.toString());
let newPosts = await database.getNewPostsByCommunity(community, 500);
let totalNumPosts = await database.getTotalPostsByCommunity(community);
res.render('admin/admin_community.ejs', {
community: community,
newPosts: newPosts,
totalNumPosts: totalNumPosts,
user: user,
account_server: config.account_server_domain.slice(8),
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/communities/:communityID/edit', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
let community = await database.getCommunityByID(req.params.communityID.toString());
res.render('admin/admin_edit_community.ejs', {
user: user,
account_server: config.account_server_domain.slice(8),
community: community,
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/communities/:communityID/sub', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
let communities = await database.getSubCommunities(req.params.communityID.toString());
res.render('admin/admin_sub_communities.ejs', {
user: user,
account_server: config.account_server_domain.slice(8),
communities: communities,
moment: moment,
communityID: req.params.communityID.toString(),
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/communities/:communityID/sub/new', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
let community = await database.getCommunityByID(req.params.communityID.toString());
res.render('admin/admin_edit_sub_community.ejs', {
user: user,
account_server: config.account_server_domain.slice(8),
community: community,
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/users', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
let users = await database.getUsers(500);
res.render('admin/admin_users.ejs', {
user: user,
account_server: config.account_server_domain.slice(8),
users: users,
moment: moment,
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/users/:userID', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.params.userID);
let parentUser = await database.getUserByPID(req.pid)
if(user === null)
res.sendStatus(404);
let newPosts = await database.getNumberUserPostsByID(req.params.userID, 500);
let numPosts = await database.getTotalPostsByUserID(req.params.userID);
let communityMap = await util.data.getCommunityHash();
res.render('admin/admin_user.ejs', {
communityMap: communityMap,
moment: moment,
parentUser: parentUser,
user: user,
account_server: config.account_server_domain.slice(8),
newPosts: newPosts,
numPosts: numPosts,
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/users/:userID/edit', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.params.userID);
let parentUser = await database.getUserByPID(req.pid)
if(user === null)
res.sendStatus(404);
let newPosts = await database.getNumberUserPostsByID(req.params.userID, 50);
let numPosts = await database.getTotalPostsByUserID(req.params.userID);
res.render('admin/admin_edit_user.ejs', {
moment: moment,
parentUser: parentUser,
user: user,
account_server: config.account_server_domain.slice(8),
newPosts: newPosts,
numPosts: numPosts,
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/posts/new', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
let community = await database.getCommunityByID('announcements');
res.render('admin/admin_new_post.ejs', {
community: community,
user: user,
account_server: config.account_server_domain.slice(8),
mii_image_CDN: config.mii_image_CDN
});
});
router.get('/login', upload.none(), async function (req, res) {
if(req.cookies.token === undefined)
{
return res.render('admin/admin_login.ejs', {});
}
else
res.redirect('/');
});
router.get('/token', upload.none(), async function (req, res) {
let port;
let headers = {
'X-Nintendo-Client-ID': config["X-Nintendo-Client-ID"],
'X-Nintendo-Client-Secret': config["X-Nintendo-Client-Secret"],
'X-Nintendo-Title-ID': req.headers['x-nintendo-title-id'],
'authorization': req.headers['authorization'],
}
request.get({
url: 'https://' + config.account_server_domain + "/v1/api/provider/service_token/@me",
headers: headers
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
res.send(body);
}
else
{
res.statusCode = 400;
let response = {
error_code: 400,
message: body
};
res.send(response);
}
});
});
router.post('/login', upload.none(), async function (req, res) {
let user_id = req.body.user_id;
let user = await database.getUserByUsername(user_id);
let pnid = await database.getPNID(user.pid)
let password = req.body.password;
if(password !== null && pnid !== null) {
if(pnid.access_level !== 3) {
logger.audit('[' + user.user_id + ' - ' + user.pid + '] is not authorized to access the application');
res.statusCode = 403;
let response = {
error_code: 403,
message: 'You are not authorized to access this application'
};
return res.send(response);
}
let password_hash = await util.data.nintendoPasswordHash(password, user.pid);
await request.post({
url: 'https://' + config.account_server_domain + "/v1/api/oauth20/access_token/generate",
headers: {
'X-Nintendo-Client-ID': config["X-Nintendo-Client-ID"],
'X-Nintendo-Client-Secret': config["X-Nintendo-Client-Secret"],
'X-Nintendo-Title-ID': '0005001010040100'
},
form: {
user_id: user_id,
password_type: 'hash',
password: password_hash,
grant_type: 'password'
}
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
logger.audit('[' + user.user_id + ' - ' + user.pid + '] signed into the application');
return res.send(body);
}
else {
res.statusCode = 403;
let response = {
error_code: 403,
message: body
};
return res.send(response);
}
});
}
else {
res.statusCode = 403;
let response = {
error_code: 403,
message: 'Invalid account ID or passwordddddd'
};
return res.send(response);
}
});
module.exports = router;

View File

@ -1,22 +1,35 @@
var express = require('express');
var xml = require('object-to-xml');
const express = require('express');
const database = require('../../../../database');
const util = require('../../../../util');
const config = require('../../../../../config.json');
var multer = require('multer');
var moment = require('moment');
var upload = multer({ dest: 'uploads/' });
var router = express.Router();
const multer = require('multer');
const moment = require('moment');
const upload = multer({dest: 'uploads/'});
const router = express.Router();
const { POST } = require('../../../../models/post');
const { COMMUNITY } = require('../../../../models/communities');
router.get('/', async function (req, res) {
let popularCommunities = await database.getMostPopularCommunities(9);
let newCommunities = await database.getCommunities(6);
let newCommunities = await database.getNewCommunities(6);
let last24Hours = await calculateMostPopularCommunities();
let popularCommunities = await COMMUNITY.aggregate([
{ $match: { olive_community_id: { $in: last24Hours }, parent: null } },
{$addFields: {
index: { $indexOfArray: [ last24Hours, "$olive_community_id" ] }
}},
{ $sort: { index: 1 } },
{ $limit : 9 },
{ $project: { index: 0, _id: 0 } }
]);
res.render(req.directory + '/communities.ejs', {
cache: true,
popularCommunities: popularCommunities,
newCommunities: newCommunities,
cdnURL: config.CDN_domain,
lang: req.lang
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
});
@ -25,88 +38,120 @@ router.get('/all', async function (req, res) {
res.render(req.directory + '/all_communities.ejs', {
communities: communities,
cdnURL: config.CDN_domain,
lang: req.lang
});
});
router.get('/announcements', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let community = await database.getCommunityByID('announcements');
let communityMap = await util.data.getCommunityHash();
let newPosts = await database.getNumberNewCommunityPostsByID(community, config.post_limit);
let totalNumPosts = await database.getTotalPostsByCommunity(community);
res.render(req.directory + '/announcements.ejs', {
moment: moment,
community: community,
newPosts: newPosts,
communityMap: communityMap,
user: user,
totalNumPosts: totalNumPosts,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
});
router.get('/:communityID', async function (req, res) {
let user = await database.getUserByPID(req.pid);
if(req.params.communityID === 'announcements')
return res.redirect('/communities/announcements')
if(req.query.title_id) {
let community = await database.getCommunityByTitleID(req.query.title_id);
if(!community) return res.redirect('/404');
return res.redirect(`/titles/${community.olive_community_id}/new`);
}
res.redirect(`/titles/${req.params.communityID}/new`);
});
router.get('/:communityID/related', async function (req, res) {
let userSettings = await database.getUserSettings(req.pid);
let userContent = await database.getUserContent(req.pid);
if(!userContent || !userSettings)
return res.redirect('/404');
let community = await database.getCommunityByID(req.params.communityID.toString());
if(community === null && req.query.title_id)
community = await database.getCommunityByTitleID(req.query.title_id)
if(community === null)
return res.sendStatus(404);
if(!community) return res.render(req.directory + '/error.ejs', {code: 404, message: "Community not Found", pid: req.pid, lang: req.lang, cdnURL: config.CDN_domain });
let communityMap = await util.data.getCommunityHash();
let newPosts = await database.getNumberNewCommunityPostsByID(community, config.post_limit);
let totalNumPosts = await database.getTotalPostsByCommunity(community)
res.render(req.directory + '/community.ejs', {
// EJS variable and server-side variable
moment: moment,
community: community,
communityMap: communityMap,
newPosts: newPosts,
totalNumPosts: totalNumPosts,
user: user,
account_server: config.account_server_domain.slice(8),
let children = await database.getSubCommunities(community.olive_community_id);
if(!children)
return res.redirect(`/titles/${community.olive_community_id}/new`);
res.render(req.directory + '/sub_communities.ejs', {
selection: 2,
communityMap,
community,
children,
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
});
router.get('/:communityID/:type', async function (req, res) {
let user = await database.getUserByPID(req.pid);
if(req.params.communityID === 'announcements')
res.redirect('/communities/announcements')
let userSettings = await database.getUserSettings(req.pid);
let userContent = await database.getUserContent(req.pid);
if(!userContent || !userSettings)
return res.redirect('/404');
let community = await database.getCommunityByID(req.params.communityID.toString());
if(!community) return res.sendStatus(404);
if(!community) return res.render(req.directory + '/error.ejs', {code: 404, message: "Community not Found", pid: req.pid, lang: req.lang, cdnURL: config.CDN_domain });
let communityMap = await util.data.getCommunityHash();
let newPosts = await database.getNumberNewCommunityPostsByID(community, config.post_limit);
let totalNumPosts = await database.getTotalPostsByCommunity(community)
let children = await database.getSubCommunities(community.olive_community_id);
if(children.length === 0)
children = null;
let posts, type;
if(req.params.type === 'hot') {
posts = await database.getNumberPopularCommunityPostsByID(community, config.post_limit);
type = 1;
} else if(req.params.type === 'verified') {
posts = await database.getNumberVerifiedCommunityPostsByID(community, config.post_limit);
type = 2;
} else {
posts = await database.getNewPostsByCommunity(community, config.post_limit);
type = 0;
}
let numPosts = await database.getTotalPostsByCommunity(community)
let bundle = {
posts,
open: community.open,
numPosts,
communityMap,
userContent,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
link: `/titles/${req.params.communityID}/${req.params.type}/more?offset=${posts.length}&pjax=true`
}
if(req.query.pjax)
return res.render(req.directory + '/partials/posts_list.ejs', {
bundle,
moment,
lang: req.lang
});
res.render(req.directory + '/community.ejs', {
// EJS variable and server-side variable
moment: moment,
community: community,
communityMap: communityMap,
newPosts: newPosts,
totalNumPosts: totalNumPosts,
user: user,
posts: posts,
totalNumPosts: numPosts,
userSettings: userSettings,
userContent: userContent,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
children,
type,
bundle,
template: 'posts_list',
moderator: req.moderator
});
});
router.get('/:communityID/:type/loadPosts', async function (req, res) {
router.get('/:communityID/:type/more', async function (req, res) {
let offset = parseInt(req.query.offset);
let user = await database.getUserByPID(req.pid);
let userContent = await database.getUserContent(req.pid);
let communityMap = await util.data.getCommunityHash();
let posts;
let community = await database.getCommunityByID(req.params.communityID)
if(!community) return res.sendStatus(404);
if(!community) return res.redirect('/404');
if(!offset)
offset = 0;
switch (req.params.type) {
@ -120,43 +165,70 @@ router.get('/:communityID/:type/loadPosts', async function (req, res) {
posts = await database.getNewPostsByCommunity(community, config.post_limit, offset);
break;
}
let bundle = {
posts,
open: true,
numPosts: posts.length,
communityMap,
userContent,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
link: `/titles/${req.params.communityID}/${req.params.type}/more?offset=${offset + posts.length}&pjax=true`
}
if(posts.length > 0)
{
res.render(req.directory + '/more_posts.ejs', {
res.render(req.directory + '/partials/posts_list.ejs', {
communityMap: communityMap,
moment: moment,
database: database,
user: user,
newPosts: posts,
bundle,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
}
else
{
res.sendStatus(204);
}
});
router.post('/follow', upload.none(), async function (req, res) {
let community = await database.getCommunityByID(req.body.communityID);
let user = await database.getUserByPID(req.pid);
if(req.body.type === 'true' && user !== null && user.followed_communities.indexOf(community.community_id) === -1)
let community = await database.getCommunityByID(req.body.id);
let userContent = await database.getUserContent(req.pid);
if(userContent !== null && userContent.followed_communities.indexOf(community.olive_community_id) === -1)
{
community.upFollower();
user.addToCommunities(community.community_id);
res.sendStatus(200);
userContent.addToCommunities(community.olive_community_id);
res.send({ status: 200, id: community.olive_community_id, count: community.followers });
}
else if(req.body.type === 'false' && user !== null && user.followed_communities.indexOf(community.community_id) !== -1)
else if(userContent !== null && userContent.followed_communities.indexOf(community.olive_community_id) !== -1)
{
community.downFollower();
user.removeFromCommunities(community.community_id);
res.sendStatus(200);
userContent.removeFromCommunities(community.olive_community_id);
res.send({ status: 200, id: community.olive_community_id, count: community.followers });
}
else
res.sendStatus(423);
res.send({ status: 423, id: community.olive_community_id, count: community.followers });
});
async function calculateMostPopularCommunities() {
const now = new Date();
const last24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const posts = await POST.find({ created_at: { $gte: last24Hours }, message_to_pid: null }).lean();
const communityIds = {};
for (const post of posts) {
const communityId = post.community_id;
communityIds[communityId] = (communityIds[communityId] || 0) + 1;
}
return Object.entries(communityIds)
.sort((a, b) => b[1] - a[1])
.map((entry) => entry[0]);
}
module.exports = router;

View File

@ -1,54 +1,88 @@
var express = require('express');
var xml = require('object-to-xml');
const express = require('express');
const database = require('../../../../database');
const util = require('../../../../util');
const config = require('../../../../../config.json');
var moment = require('moment');
var router = express.Router();
const moment = require('moment');
const router = express.Router();
router.get('/', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let userContent = await database.getUserContent(req.pid);
let communityMap = await util.data.getCommunityHash();
let posts = await database.getNewsFeed(user, config.post_limit);
if(!userContent)
return res.redirect('/404');
let posts = await database.getNewsFeed(userContent, config.post_limit);
let bundle = {
posts,
open: true,
communityMap,
userContent,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
link: `/feed/more?offset=${posts.length}&pjax=true`
}
if(req.query.pjax)
return res.render(req.directory + '/partials/posts_list.ejs', {
bundle,
moment,
lang: req.lang
});
res.render(req.directory + '/feed.ejs', {
moment: moment,
user: user,
title: req.lang.global.activity_feed,
userContent: userContent,
posts: posts,
communityMap: communityMap,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
bundle,
template: 'posts_list',
moderator: req.moderator
});
});
router.get('/loadposts', async function (req, res) {
router.get('/more', async function (req, res) {
let offset = parseInt(req.query.offset);
let user = await database.getUserByPID(req.pid);
let userContent = await database.getUserContent(req.pid);
let communityMap = await util.data.getCommunityHash();
let posts;
if(offset !== null)
posts = await database.getNewsFeedOffset(user, config.post_limit, offset);
if(posts === undefined)
return res.sendStatus(204);
if(!offset) offset = 0;
posts = await database.getNewsFeedOffset(userContent, config.post_limit, offset);
let bundle = {
posts,
numPosts: posts.length,
open: true,
communityMap,
userContent,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
link: `/feed/more?offset=${offset + posts.length}&pjax=true`,
moderator: req.moderator
}
if(posts.length > 0)
{
res.render(req.directory + '/more_posts.ejs', {
res.render(req.directory + '/partials/posts_list.ejs', {
communityMap: communityMap,
moment: moment,
database: database,
user: user,
newPosts: posts,
bundle,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
}
else
{
res.sendStatus(204);
}
});
module.exports = router;

View File

@ -1,34 +1,35 @@
var express = require('express');
var xml = require('object-to-xml');
const express = require('express');
const database = require('../../../../database');
const util = require('../../../../util');
const config = require('../../../../../config.json');
const { POST } = require('../../../../models/post');
var moment = require('moment');
const moment = require('moment');
const {CONVERSATION} = require("../../../../models/conversation");
const crypto = require("crypto");
const snowflake = require('node-snowflake').Snowflake;
var router = express.Router();
const router = express.Router();
router.get('/', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let conversations = await database.getConversations(user.pid.toString());
let conversations = await database.getConversations(req.pid);
let usersMap = await util.data.getUserHash();
res.render(req.directory + '/messages.ejs', {
moment: moment,
user: user,
pid: req.pid,
conversations: conversations,
cdnURL: config.CDN_domain,
usersMap: usersMap,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
moderator: req.moderator
});
});
router.post('/new', async function (req, res, next) {
let conversation = await database.getConversationByID(req.body.conversationID);
let user = await database.getUserByPID(req.pid);
let user2 = await database.getUserByPID(req.body.message_to_pid);
if(req.body.conversationID === 0)
let conversation = await database.getConversationByID(req.body.community_id);
let user2 = await util.data.getUserDataFromPid(req.body.message_to_pid);
let postID = await generatePostUID(21);
let friends = await util.data.getFriends(user2.pid);
if(req.body.community_id === 0)
return res.sendStatus(404);
if(!conversation) {
if(!user || !user2)
@ -37,13 +38,13 @@ router.post('/new', async function (req, res, next) {
id: snowflake.nextId(),
users: [
{
pid: user.pid,
official: user.official,
pid: req.pid,
official: (req.user.accessLevel >= 2),
read: true
},
{
pid: user2.pid,
official: user2.official,
official: (user2.accessLevel >= 2),
read: false
},
]
@ -54,24 +55,78 @@ router.post('/new', async function (req, res, next) {
}
if(!conversation)
return res.sendStatus(404);
let 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,
if(!friends || friends.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 painting = "", paintingURI = "", screenshot = null;
if (req.body._post_type === 'painting' && req.body.painting) {
painting = req.body.painting.replace(/\0/g, "").trim();
paintingURI = await util.data.processPainting(painting, true);
await util.data.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.data.uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read');
}
let miiFace;
switch (parseInt(req.body.feeling_id)) {
case 1:
miiFace = 'smile_open_mouth.png';
break;
case 2:
miiFace = 'wink_left.png';
break;
case 3:
miiFace = 'surprise_open_mouth.png';
break;
case 4:
miiFace = 'frustrated.png';
break;
case 5:
miiFace = 'sorrow.png';
break;
default:
miiFace = 'normal_face.png';
break;
}
let body = req.body.body;
if(body)
body = req.body.body.replace(/[^A-Za-z\d\s-_!@#$%^&*(){}‛¨ƒºª«»“”„¿¡←→↑↓√§¶†‡¦–—⇒⇔¤¢€£¥™©®+×÷=±∞ˇ˘˙¸˛˜′″µ°¹²³♭♪•…¬¯‰¼½¾♡♥●◆■▲▼☆★♀♂,./?;:'"\\<>]/g, "");
if(body.length > 280)
body = body.substring(0,280);
const document = {
community_id: conversation.id,
message_to_pid: req.body.message_to_pid
screen_name: req.user.mii.name,
body: body,
painting: painting,
screenshot: screenshot ? `/screenshots/${req.pid}/${postID}.jpg`: "",
country_id: req.paramPackData ? req.paramPackData.country_id : 49,
created_at: new Date(),
feeling_id: req.body.feeling_id,
id: postID,
is_autopost: 0,
is_spoiler: (req.body.spoiler) ? 1 : 0,
is_app_jumpable: req.body.is_app_jumpable,
language_id: req.body.language_id,
mii: req.user.mii.data,
mii_face_url: `https://mii.olv.pretendo.cc/mii/${req.pid}/${miiFace}`,
pid: req.pid,
platform_id: req.paramPackData ? req.paramPackData.platform_id : 0,
region_id: req.paramPackData ? req.paramPackData.region_id : 2,
verified: (req.user.accessLevel >= 2),
message_to_pid: req.body.message_to_pid,
moderator: req.moderator
};
let duplicatePost = await database.getDuplicatePosts(req.pid, document);
if(duplicatePost && req.params.post_id)
return res.redirect('/posts/' + req.params.post_id);
const newPost = new POST(document);
newPost.save();
res.sendStatus(200);
res.redirect(`/friend_messages/${conversation.id}`);
let postPreviewText;
if(document.painting)
postPreviewText = 'sent a Drawing'
@ -79,29 +134,31 @@ router.post('/new', async function (req, res, next) {
postPreviewText = document.body.substring(0, 25) + '...';
else
postPreviewText = document.body;
await conversation.newMessage(postPreviewText, document.message_to_pid);
await conversation.newMessage(postPreviewText, user2.pid);
});
router.get('/new/:pid', async function (req, res, next) {
let user = await database.getUserByPID(req.pid);
let user2 = await database.getUserByPID(req.params.pid.toString());
if(!user || !user2)
let user = await util.data.getUserDataFromPid(req.pid);
let user2 = await util.data.getUserDataFromPid(req.params.pid);
let friends = await util.data.getFriends(user2.pid);
if(!req.user || !user2)
return res.sendStatus(422)
let conversation = await database.getConversationByUsers([user.pid, user2.pid]);
console.log(conversation);
let conversation = await database.getConversationByUsers([req.pid, user2.pid]);
if(conversation)
return res.redirect(`/messages/${conversation.id}`);
return res.redirect(`/friend_messages/${conversation.id}`);
if(!friends || friends.indexOf(req.pid) === -1)
return res.sendStatus(422);
let document = {
id: snowflake.nextId(),
users: [
{
pid: user.pid,
official: user.official,
pid: req.user.pid,
official: (req.user.accessLevel >= 2),
read: true
},
{
pid: user2.pid,
official: user2.official,
official: (user2.accessLevel >= 2),
read: false
},
]
@ -111,24 +168,24 @@ router.get('/new/:pid', async function (req, res, next) {
conversation = await database.getConversationByID(document.id);
if(!conversation)
return res.sendStatus(404);
let body = `${user.user_id} started a new chat!`;
let body = `${req.user.mii.name} started a new chat!`;
let newMessage = {
screen_name: user.user_id,
screen_name: req.user.mii.name,
body: body,
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,
id: await generatePostUID(21),
mii: req.user.mii.data,
mii_face_url: `https://mii.olv.pretendo.cc/mii/${req.pid}/normal_face.png`,
pid: req.pid,
verified: (req.user.accessLevel >= 2),
parent: null,
community_id: conversation.id,
message_to_pid: user2.pid
};
const newPost = new POST(newMessage);
newPost.save();
await conversation.newMessage(`${user.user_id} started a new chat!`, newMessage.message_to_pid);
res.redirect(`/messages/${conversation.id}`);
await conversation.newMessage(`${req.user.mii.name} started a new chat!`, user2.pid);
res.redirect(`/friend_messages/${conversation.id}`);
});
router.get('/:message_id', async function (req, res) {
@ -136,21 +193,32 @@ router.get('/:message_id', async function (req, res) {
if(!conversation) {
return res.sendStatus(404);
}
let user = await database.getUserByPID(req.pid);
let otherUserPid = conversation.users[0].pid.toString() === user.pid.toString() ? conversation.users[1].pid : conversation.users[0].pid;
let user2 = await database.getUserByPID(otherUserPid);
let messages = await database.getConversationMessages(conversation.id, 100, 0)
let user2 = conversation.users[0].pid === req.pid ? conversation.users[1] : conversation.users[0];
if(req.pid !== conversation.users[0].pid && req.pid !== conversation.users[1].pid)
res.redirect('/')
let messages = await database.getConversationMessages(conversation.id, 200, 0);
let userMap = await util.data.getUserHash();
res.render(req.directory + '/message_thread.ejs', {
moment: moment,
user: user,
user2: user2,
conversation: conversation,
messages: messages,
userMap: userMap,
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
await conversation.markAsRead(req.pid);
});
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 });
id = (inuse ? await generatePostUID() : id);
return id;
}
module.exports = router;

View File

@ -1,24 +1,67 @@
var express = require('express');
var xml = require('object-to-xml');
const express = require('express');
const database = require('../../../../database');
const util = require('../../../../util');
const config = require('../../../../../config.json');
var moment = require('moment');
var router = express.Router();
const util = require('../../../../util');
const moment = require('moment');
const router = express.Router();
router.get('/my_news', async function (req, res) {
let notifications = await database.getNotifications(req.pid, 25, 0);
let userMap = util.data.getUserHash();
let bundle = {
notifications,
userMap
}
if(req.query.pjax)
return res.render(req.directory + '/partials/notifications.ejs', {
bundle,
lang: req.lang,
moment
});
router.get('/', async function (req, res) {
let user = await database.getUserByPID(req.pid);
res.render(req.directory + '/notifications.ejs', {
moment: moment,
user: user,
moment,
selection: 0,
bundle,
cdnURL: config.CDN_domain,
lang: req.lang
lang: req.lang,
pid: req.pid,
template: 'notifications',
moderator: req.moderator
});
user.notification_list.filter(noti => noti.read === false).forEach(function(notification) {
notification.read = true;
notifications.filter(noti => noti.read === false).forEach(function(notification) {
notification.markRead();
});
});
router.get('/friend_requests', async function (req, res) {
let requests = (await util.data.getFriendRequests(req.pid)).reverse();
const now = new Date();
requests = requests.filter(request => new Date(request.expires * 1000) > new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000))
let userMap = util.data.getUserHash();
let bundle = {
requests: requests ? requests : [],
userMap
}
if(req.query.pjax)
return res.render(req.directory + '/partials/requests.ejs', {
bundle,
lang: req.lang,
moment
});
res.render(req.directory + '/notifications.ejs', {
moment,
selection: 1,
bundle,
cdnURL: config.CDN_domain,
lang: req.lang,
pid: req.pid,
template: 'requests',
moderator: req.moderator
});
user.markModified('notification_list');
user.save();
});
module.exports = router;

View File

@ -1,86 +1,208 @@
var express = require('express');
const express = require('express');
const database = require('../../../../database');
const util = require('../../../../util');
const config = require('../../../../../config.json');
const { POST } = require('../../../../models/post');
const rateLimit = require('../../../../middleware/ratelimit');
var multer = require('multer');
var moment = require('moment');
var upload = multer({ dest: 'uploads/' });
const snowflake = require('node-snowflake').Snowflake;
var router = express.Router();
const {POST} = require('../../../../models/post');
const multer = require('multer');
const moment = require('moment');
const rateLimit = require('express-rate-limit')
const {REPORT} = require("../../../../models/report");
const upload = multer({dest: 'uploads/'});
const crypto = require('crypto')
const router = express.Router();
router.post('/empathy', rateLimit, async function (req, res) {
const postLimit = rateLimit({
windowMs: 15 * 1000, // 30 seconds
max: 10, // Limit each IP to 1 request per `window`
standardHeaders: true,
legacyHeaders: true,
message: "New post limit reached. Try again in a minute",
handler: function (req, res) {
if (req.params.post_id)
res.redirect('/posts/' + req.params.post_id.toString());
else if (req.body.community_id)
res.redirect('/titles/' + req.body.community_id);
else {
res.render(req.directory + '/error.ejs', {
code: 429,
message: "Too many new posts have been created.",
cdnURL: config.CDN_domain,
lang: req.lang,
pid: req.pid
});
}
},
})
const yeahLimit = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 10, // Limit each IP to 60 requests per `window`
standardHeaders: true,
legacyHeaders: true,
})
router.post('/empathy', yeahLimit, async function (req, res) {
let post = await database.getPostByID(req.body.postID);
let user = await database.getUserByPID(req.pid);
if(!user)
return res.sendStatus(423);
if(req.body.type === 'up' && user.likes.indexOf(post.id) === -1 && user.id !== post.pid)
{
post.upEmpathy();
user.addToLikes(post.id)
res.sendStatus(200);
}
else if(req.body.type === 'down' && user.likes.indexOf(post.id) !== -1 && user.id !== post.pid)
{
post.downEmpathy();
user.removeFromLike(post.id);
res.sendStatus(200);
}
else
res.sendStatus(423);
if (!post)
return res.sendStatus(404);
if (post.yeahs.indexOf(req.pid) === -1) {
await POST.updateOne({
id: post.id,
yeahs: {
$ne: req.pid
}
},
{
$inc: {
empathy_count: 1
},
$push: {
yeahs: req.pid
}
});
res.send({status: 200, id: post.id, count: post.empathy_count + 1});
if (req.pid !== post.pid)
await util.data.newNotification({
pid: post.pid,
type: "yeah",
objectID: post.id,
userPID: req.pid,
link: `/posts/${post.id}`
});
} else if (post.yeahs.indexOf(req.pid) !== -1) {
await POST.updateOne({
id: post.id,
yeahs: {
$eq: req.pid
}
},
{
$inc: {
empathy_count: -1
},
$pull: {
yeahs: req.pid
}
});
res.send({status: 200, id: post.id, count: post.empathy_count - 1});
} else
res.send({status: 423, id: post.id, count: post.empathy_count});
});
router.post('/new', postLimit, upload.none(), async function (req, res) {
await newPost(req, res)
});
router.get('/:post_id', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let userSettings = await database.getUserSettings(req.pid);
let userContent = await database.getUserContent(req.pid);
let post = await database.getPostByID(req.params.post_id.toString());
if(post === null)
return res.sendStatus(404);
if (post === null) return res.redirect('/404');
if (post.parent) {
post = await database.getPostByID(post.parent);
if (post === null)
return res.sendStatus(404);
return res.redirect(`/posts/${post.id}`);
}
let community = await database.getCommunityByID(post.community_id);
let communityMap = await util.data.getCommunityHash();
let replies = await database.getPostReplies(req.params.post_id.toString(), 25)
res.render(req.directory + '/post.ejs', {
moment: moment,
user: user,
userSettings: userSettings,
userContent: userContent,
post: post,
replies: replies,
community: community,
communityMap: communityMap,
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
});
router.post('/:post_id/new', rateLimit, upload.none(), async function (req, res, next) {
let user = await database.getUserByPID(req.pid);
if(user.account_status !== 0 || req.body.olive_community_id === 'announcements') {
throw new Error('User not allowed to post')
router.delete('/:post_id', async function (req, res) {
let post = await database.getPostByID(req.params.post_id);
if(!post) return res.sendStatus(404);
if(req.pid !== post.pid && !req.moderator) return res.sendStatus(401);
if(req.moderator && req.pid !== post.pid)
await post.removePost(req.query.reason ? req.query.reason : 'Removed by moderator', req.pid);
else
await post.removePost('User requested removal', req.pid);
res.statusCode = 200;
if(post.parent)
res.send(`/posts/${post.parent}`);
else
res.send('/users/me');
});
router.post('/:post_id/new', postLimit, upload.none(), async function (req, res) {
await newPost(req, res);
});
router.post('/:post_id/report', upload.none(), async function (req, res) {
const { reason, message, post_id } = req.body;
const post = await database.getPostByID(post_id);
if(!reason || !post_id || !post)
return res.redirect('/404');
const reportDoc = {
pid: post.pid,
reported_by: req.pid,
post_id,
reason,
message,
created_at: new Date()
}
let parentPost = await database.getPostByID(req.params.post_id.toString());
if(!parentPost)
return res.sendStatus(403);
let community = await database.getCommunityByID(req.body.olive_community_id);
if(req.body.body === '' && req.body.painting === '' && req.body.screenshot === '') {
const reportObj = new REPORT(reportDoc);
await reportObj.save();
return res.redirect(`/posts/${post.id}`);
});
async function newPost(req, res) {
let userSettings = await database.getUserSettings(req.pid), parentPost = null, postID = await generatePostUID(21);
let community = await database.getCommunityByID(req.body.community_id);
if (!community || !userSettings || !req.user) {
res.status(403);
console.log('missing data')
return res.redirect(`/titles/show`);
}
if (req.params.post_id && (req.body.body === '' && req.body.painting === '' && req.body.screenshot === '')) {
res.status(422);
return res.redirect('/posts/' + req.params.post_id.toString());
}
let appData = "";
if (req.body.app_data) {
appData = req.body.app_data.replace(/\0/g, "").trim();
if (req.params.post_id) {
parentPost = await database.getPostByID(req.params.post_id.toString());
if (!parentPost)
return res.sendStatus(403);
}
let painting = "", paintingURI = "";
if (req.body.painting && req.body.painting !== 'eJztwTEBACAMA7DCNRlIQRbu4ZoEviTJTNvjZNUFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL55fYLL3w==') {
painting = req.body.painting.replace(/\0/g, "").trim();
if (!(community.admins && community.admins.indexOf(req.pid) !== -1 && userSettings.account_status === 0)
&& (community.type >= 2) && !(parentPost && community.allows_comments && community.open)) {
res.status(403);
return res.redirect(`/titles/${community.olive_community_id}/new`);
}
let painting = "", paintingURI = "", screenshot = null;
if (req.body._post_type === 'painting' && req.body.painting) {
if(req.body.bmp === 'true')
painting = await util.data.processPainting(req.body.painting.replace(/\0/g, "").trim(), false);
else
painting = req.body.painting;
paintingURI = await util.data.processPainting(painting, true);
await util.data.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read');
}
let screenshot = "";
if (req.body.screenshot) {
screenshot = req.body.screenshot.replace(/\0/g, "").trim();
await util.data.uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read');
}
let miiFace;
switch (parseInt(req.body.emotion)) {
switch (parseInt(req.body.feeling_id)) {
case 1:
miiFace = 'smile_open_mouth.png';
break;
@ -100,124 +222,65 @@ router.post('/:post_id/new', rateLimit, upload.none(), async function (req, res,
miiFace = 'normal_face.png';
break;
}
parentPost.reply_count = parentPost.reply_count + 1;
parentPost.save();
let body = req.body.body;
if (body)
body = req.body.body.replace(/[^A-Za-z\d\s-_!@#$%^&*(){}‛¨ƒºª«»“”„¿¡←→↑↓√§¶†‡¦–—⇒⇔¤¢€£¥™©®+×÷=±∞ˇ˘˙¸˛˜′″µ°¹²³♭♪•…¬¯‰¼½¾♡♥●◆■▲▼☆★♀♂,./?;:'"\\<>]/g, "");
if (body.length > 280)
body = body.substring(0, 280);
const document = {
title_id: community.title_id[0],
community_id: community.community_id,
screen_name: user.user_id,
body: req.body.body,
app_data: appData,
community_id: community.olive_community_id,
screen_name: userSettings.screen_name,
body: body,
painting: painting,
painting_uri: paintingURI,
screenshot: screenshot,
country_id: req.paramPackData.country_id,
screenshot: screenshot ? `/screenshots/${req.pid}/${postID}.jpg` : "",
country_id: req.paramPackData ? req.paramPackData.country_id : 49,
created_at: new Date(),
feeling_id: req.body.emotion,
id: snowflake.nextId(),
is_autopost: req.body.is_autopost,
feeling_id: req.body.feeling_id,
id: postID,
is_autopost: 0,
is_spoiler: (req.body.spoiler) ? 1 : 0,
is_app_jumpable: req.body.is_app_jumpable,
language_id: req.body.language_id,
mii: user.mii,
mii_face_url: `http://mii.olv.pretendo.cc/mii/${user.pid}/${miiFace}`,
mii: req.user.mii.data,
mii_face_url: `https://mii.olv.pretendo.cc/mii/${req.user.pid}/${miiFace}`,
pid: req.pid,
platform_id: req.paramPackData.platform_id,
region_id: req.paramPackData.region_id,
verified: user.official,
parent: parentPost.id
platform_id: req.paramPackData ? req.paramPackData.platform_id : 0,
region_id: req.paramPackData ? req.paramPackData.region_id : 2,
verified: req.moderator,
parent: parentPost ? parentPost.id : null,
moderator: req.moderator
};
let duplicatePost = await database.getDuplicatePosts(req.pid, document);
if(duplicatePost)
if (duplicatePost && req.params.post_id)
return res.redirect('/posts/' + req.params.post_id.toString());
if (document.body === '' && document.painting === '' && document.screenshot === '')
return res.redirect('/titles/' + community.olive_community_id + '/new');
const newPost = new POST(document);
newPost.save();
if(parentPost.pid !== user.pid)
await database.pushNewNotificationByPID(parentPost.pid, user.user_id + ' replied to your post!', '/posts/' + parentPost.id)
res.redirect('/posts/' + req.params.post_id.toString());
});
if (parentPost) {
parentPost.reply_count = parentPost.reply_count + 1;
parentPost.save();
}
if (parentPost && (parentPost.pid !== req.user.pid))
await util.data.newNotification({
pid: parentPost.pid,
type: "reply",
user: req.pid,
link: `/posts/${parentPost.id}`
});
if (parentPost)
res.redirect('/posts/' + req.params.post_id.toString());
else
res.redirect('/titles/' + community.olive_community_id + '/new');
}
router.post('/new', rateLimit, upload.none(), async function (req, res, next) {
let user = await database.getUserByPID(req.pid);
if(user.account_status !== 0) {
throw new Error('User not allowed to post')
}
let community = await database.getCommunityByID(req.body.olive_community_id);
if(community.community_id === 'announcements')
return res.sendStatus(403)
if(req.body.body === '' && req.body.painting === '' && req.body.screenshot === '') {
res.status(422);
return res.redirect('/communities/' + community.community_id + '/new');
}
let appData = "";
if (req.body.app_data) {
appData = req.body.app_data.replace(/\0/g, "").trim();
}
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, true);
}
let screenshot = "";
if (req.body.screenshot) {
screenshot = req.body.screenshot.replace(/\0/g, "").trim();
}
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});
id = (inuse ? await generatePostUID() : id);
return id;
}
let miiFace;
console.log(parseInt(req.body.emotion))
switch (parseInt(req.body.emotion)) {
case 1:
miiFace = 'smile_open_mouth.png';
break;
case 2:
miiFace = 'wink_left.png';
break;
case 3:
miiFace = 'surprise_open_mouth.png';
break;
case 4:
miiFace = 'frustrated.png';
break;
case 5:
miiFace = 'sorrow.png';
break;
default:
miiFace = 'normal_face.png';
break;
}
const document = {
title_id: community.title_id[0],
community_id: community.community_id,
screen_name: user.user_id,
body: req.body.body,
app_data: appData,
painting: painting,
painting_uri: paintingURI,
screenshot: screenshot,
country_id: req.paramPackData.country_id,
created_at: new Date(),
feeling_id: req.body.emotion,
id: snowflake.nextId(),
is_autopost: req.body.is_autopost,
is_spoiler: (req.body.spoiler) ? 1 : 0,
is_app_jumpable: req.body.is_app_jumpable,
language_id: req.body.language_id,
mii: user.mii,
mii_face_url: `http://mii.olv.pretendo.cc/mii/${user.pid}/${miiFace}`,
pid: req.pid,
platform_id: req.paramPackData.platform_id,
region_id: req.paramPackData.region_id,
verified: user.official
};
let duplicatePost = await database.getDuplicatePosts(req.pid, document);
if(duplicatePost)
return res.redirect('/communities/' + community.community_id + '/new');
const newPost = new POST(document);
newPost.save();
res.redirect('/communities/' + community.community_id + '/new');
});
module.exports = router;

View File

@ -1,67 +1,43 @@
var express = require('express');
var xml = require('object-to-xml');
const express = require('express');
const database = require('../../../../database');
const util = require('../../../../util');
const config = require('../../../../../config.json');
var moment = require('moment');
var router = express.Router();
const router = express.Router();
router.get('/', async function (req, res) {
try {
if(req.pid === 1000000000) {
res.render(req.directory + '/guest_notice.ejs', {
cdnURL: config.CDN_domain,
lang: req.lang,
});
}
else {
let user = await database.getUserByPID(req.pid);
if(user === null)
{
res.render(req.directory + '/first_run.ejs', {
cdnURL: config.CDN_domain,
lang: req.lang,
});
}
else {
if(moment(user.ban_lift_date).format('YYYY-MM-DD') <= moment().format('YYYY-MM-DD') && user.account_status !== 3) {
user.account_status = 0;
user.save()
}
/**
* Account Status
* 0 - Fine
* 1 - Limited from Posting
* 2 - Temporary Ban
* 3 - Forever Ban
*/
if(user.account_status !== 0)
{
res.render(req.directory + '/ban_notification.ejs', {
user: user,
moment: moment,
cdnURL: config.CDN_domain,
lang: req.lang,
});
}
else
{
let popularCommunities = await database.getMostPopularCommunities(9);
let newCommunities = await database.getNewCommunities(6);
res.render(req.directory + '/communities.ejs', {
popularCommunities: popularCommunities,
newCommunities: newCommunities,
cdnURL: config.CDN_domain,
lang: req.lang
});
}
}
}
if(req.pid === 1000000000) {
return res.render(req.directory + '/guest_notice.ejs', {
cdnURL: config.CDN_domain,
lang: req.lang,
moderator: req.moderator
});
}
catch (e) {
console.log(e)
let user = await database.getUserSettings(req.pid);
let content = await database.getUserContent(req.pid)
if(!user || !content) {
res.render(req.directory + '/first_run.ejs', {
cdnURL: config.CDN_domain,
lang: req.lang,
pid: req.pid,
moderator: req.moderator
});
}
if(req.query.topic_tag) {
res.redirect(`/topics?topic_tag=${req.query.topic_tag}`)
}
else if(req.query.pid) {
res.redirect(`/users/${req.query.pid}`)
}
else
res.redirect('/titles')
let usrMii = await database.getUserSettings(req.pid);
if(req.user.mii.name !== usrMii.screen_name) {
util.data.setName(req.pid, req.user.mii.name);
usrMii.screen_name = req.user.mii.name;
await usrMii.save();
}
});
@ -69,31 +45,24 @@ router.get('/first', async function (req, res) {
res.render(req.directory + '/first_run.ejs', {
cdnURL: config.CDN_domain,
lang: req.lang,
moderator: req.moderator
});
});
router.post('/newUser', async function (req, res) {
if(req.pid === null)
{
res.sendStatus(401);
}
return res.sendStatus(401);
let user = await database.getUserSettings(req.pid);
if(user)
return res.sendStatus(504);
await util.data.create_user(req.pid, req.body.experience, req.body.notifications);
if(await database.getUserSettings(req.pid) !== null)
res.sendStatus(200);
else
{
let user = await database.getUserByPID(req.pid);
if(user === null)
{
await util.data.create_user(req.pid, req.body.experience, req.body.notifications, req.body.region);
util.data.refreshCache();
if(await database.getUserByPID(req.pid) !== null)
res.sendStatus(200);
else
res.sendStatus(504);
}
else
{
res.sendStatus(504);
}
}
res.sendStatus(504);
});
module.exports = router;

View File

@ -0,0 +1,89 @@
const express = require('express');
const database = require('../../../../database');
const util = require('../../../../util');
const config = require('../../../../../config.json');
const moment = require('moment');
const { POST } = require('../../../../models/post');
const router = express.Router();
router.get('/', async function (req, res) {
let userContent = await database.getUserContent(req.pid);
let communityMap = await util.data.getCommunityHash();
let tag = req.query.topic_tag;
console.log(tag)
if(!userContent || !tag)
return res.redirect('/404');
let posts = await POST.find({ topic_tag: req.query.topic_tag }).sort({ created_at: -1}).limit(parseInt(req.query.limit));
let bundle = {
posts,
open: true,
communityMap,
userContent,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
link: `/topics/more?tag=${tag}&offset=${posts.length}&pjax=true`
}
if(req.query.pjax)
return res.render(req.directory + '/partials/posts_list.ejs', {
bundle,
moment,
lang: req.lang
});
res.render(req.directory + '/feed.ejs', {
moment: moment,
title: tag,
userContent: userContent,
posts: posts,
communityMap: communityMap,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
bundle,
template: 'posts_list',
moderator: req.moderator
});
});
router.get('/more', async function (req, res) {
let offset = req.query.offset ? parseInt(req.query.offset) : 0;
let userContent = await database.getUserContent(req.pid);
let communityMap = await util.data.getCommunityHash();
let tag = req.query.topic_tag;
if(!tag) return res.sendStatus(204);
let posts = await POST.find({ topic_tag: req.query.topic_tag }).sort({ created_at: -1}).limit(parseInt(req.query.limit));
let bundle = {
posts,
numPosts: posts.length,
open: true,
communityMap,
userContent,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
link: `/topics/more?tag=${tag}&offset=${posts.length}&pjax=true`
}
if(posts.length > 0) {
res.render(req.directory + '/partials/posts_list.ejs', {
communityMap: communityMap,
moment: moment,
database: database,
bundle,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
}
else
res.sendStatus(204);
});
module.exports = router;

View File

@ -1,221 +1,360 @@
var express = require('express');
var xml = require('object-to-xml');
const express = require('express');
const database = require('../../../../database');
const util = require('../../../../util');
const config = require('../../../../../config.json');
var multer = require('multer');
var moment = require('moment');
var upload = multer({ dest: 'uploads/' });
var router = express.Router();
const multer = require('multer');
const moment = require('moment');
const upload = multer({ dest: 'uploads/' });
const { POST } = require('../../../../models/post');
const {SETTINGS} = require("../../../../models/settings");
const router = express.Router();
router.get('/menu', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let user = await database.getUserSettings(req.pid);
res.render('ctr/user_menu.ejs', {
user: user,
});
});
router.get('/me', async function (req, res) {
let user = await database.getUserByPID(req.pid);
let newPosts = await database.getNumberUserPostsByID(req.pid, config.post_limit);
let numPosts = await database.getTotalPostsByUserID(req.pid);
router.get('/me', async function (req, res) { await userPage(req, res, req.pid) });
router.get('/me/settings', async function (req, res) {
let userSettings = await database.getUserSettings(req.pid);
let communityMap = await util.data.getCommunityHash();
res.render(req.directory + '/me_page.ejs', {
res.render(req.directory + '/settings.ejs', {
communityMap: communityMap,
moment: moment,
user: user,
newPosts: newPosts,
numPosts: numPosts,
userSettings: userSettings,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
});
router.post('/me', upload.none(), async function (req, res) {
let user = await database.getUserByPID(req.pid);
router.get('/me/:type', async function (req, res) { await userRelations(req, res, req.pid) });
user.country_visibility = !!req.body.country;
user.birthday_visibility = !!req.body.birthday;
user.game_skill_visibility = !!req.body.experience;
user.profile_comment_visibility = !!req.body.commentShow;
router.post('/me/settings', upload.none(), async function (req, res) {
let userSettings = await database.getUserSettings(req.pid);
userSettings.country_visibility = !!req.body.country;
userSettings.birthday_visibility = !!req.body.birthday;
userSettings.game_skill_visibility = !!req.body.experience;
userSettings.profile_comment_visibility = !!req.body.comment;
if (req.body.comment)
user.setProfileComment(req.body.comment);
userSettings.updateComment(req.body.comment);
else
user.setProfileComment('');
userSettings.updateComment('');
res.redirect('/users/me');
});
router.get('/show', async function (req, res) {
var userID = req.query.pid;
if(userID === 'me') {
res.sendStatus(504);
return;
res.redirect(`/users/${req.query.pid}`);
});
router.get('/:pid/more', async function (req, res) { await morePosts(req, res, req.params.pid) });
router.get('/:pid/yeahs/more', async function (req, res) { await moreYeahPosts(req, res, req.params.pid) });
router.get('/:pid/:type', async function (req, res) { await userRelations(req, res, req.params.pid) });
// TODO: Remove the need for a parameter to toggle the following state
router.post('/follow', upload.none(), async function (req, res) {
let userToFollowContent = await database.getUserContent(req.body.id);
let userContent = await database.getUserContent(req.pid);
if(userContent !== null && userContent.followed_users.indexOf(userToFollowContent.pid) === -1)
{
userToFollowContent.addToFollowers(userContent.pid);
userContent.addToUsers(userToFollowContent.pid);
res.send({ status: 200, id: userToFollowContent.pid, count: userToFollowContent.following_users.length - 1 });
let picked = await database.getNotification(userToFollowContent.pid, 2, userContent.pid);
//pid, type, reference_id, origin_pid, title, content
if(picked === null)
await util.data.newNotification({ pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` });
}
let parentUser = await database.getUserByPID(req.pid);
let user = await database.getUserByPID(userID);
if(user === null)
return res.sendStatus(404);
if(parentUser.pid === user.pid)
else if(userContent !== null && userContent.followed_users.indexOf(userToFollowContent.pid) !== -1)
{
userToFollowContent.removeFromFollowers(userContent.pid);
userContent.removeFromUsers(userToFollowContent.pid);
res.send({ status: 200, id: userToFollowContent.pid, count: userToFollowContent.following_users.length - 1 });
}
else
res.send({ status: 423, id: userToFollowContent.pid, count: userToFollowContent.following_users.length - 1 });
});
router.get('/:pid', async function (req, res) {
const userID = req.params.pid;
if(userID === 'me' || Number(userID) === req.pid)
return res.redirect('/users/me');
let newPosts = await database.getNumberUserPostsByID(user.pid, config.post_limit);
let numPosts = await database.getTotalPostsByUserID(user.pid);
await userPage(req, res, userID);
});
router.get('/:pid/:type', async function (req, res) {
const userID = req.params.pid;
if(userID === 'me' || Number(userID) === req.pid)
return res.redirect('/users/me');
await userRelations(req, res, userID);
});
async function userPage(req, res, userID) {
let pnid = userID === req.pid ? req.user : await util.data.getUserDataFromPid(userID).catch((e) => {
console.log(e.details);
});
let userContent = await database.getUserContent(userID);
if(isNaN(userID) || !pnid || !userContent)
return res.redirect('/404');
let userSettings = await database.getUserSettings(userID);
let posts = await database.getNumberUserPostsByID(userID, config.post_limit);
let numPosts = await database.getTotalPostsByUserID(userID);
let communityMap = await util.data.getCommunityHash();
let friends = await util.data.getFriends(userID);
let parentUserContent;
if(pnid.pid !== req.pid)
parentUserContent = await database.getUserContent(req.pid);
let bundle = {
posts,
open: true,
numPosts,
communityMap,
userContent: parentUserContent ? parentUserContent : userContent,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
link: `/users/${userID}/more?offset=${posts.length}&pjax=true`
}
if(req.query.pjax)
return res.render(req.directory + '/partials/posts_list.ejs', {
bundle,
lang: req.lang,
moment
});
let link = (pnid.pid === req.pid) ? '/users/me/' : `/users/${userID}/`;
res.render(req.directory + '/user_page.ejs', {
// EJS variable and server-side variable
communityMap: communityMap,
moment: moment,
user: user,
newPosts: newPosts,
numPosts: numPosts,
parentUser: parentUser,
template: 'posts_list',
selection: 0,
moment,
pnid,
numPosts,
userContent,
userSettings,
bundle,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
link,
friends,
parentUserContent,
moderator: req.moderator
});
});
}
router.get('/loadPosts', async function (req, res) {
let offset = parseInt(req.query.offset);
let pid;
if(req.query.pid)
pid = req.query.pid
else
pid = req.pid
let user = await database.getUserByPID(pid);
let newPosts = await database.getUserPostsOffset(pid, config.post_limit, offset);
let communityMap = await util.data.getCommunityHash();
if(newPosts.length > 0)
{
res.render(req.directory + '/more_posts.ejs', {
communityMap: communityMap,
moment: moment,
user: user,
newPosts: newPosts,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
async function userRelations(req, res, userID) {
let pnid = userID === req.pid ? req.user : await util.data.getUserDataFromPid(userID);
let userContent = await database.getUserContent(userID);
let link = (pnid.pid === req.pid) ? '/users/me/' : `/users/${userID}/`;
let userSettings = await database.getUserSettings(userID);
let numPosts = await database.getTotalPostsByUserID(userID);
let friends = await util.data.getFriends(userID);
let parentUserContent;
if(pnid.pid !== req.pid)
parentUserContent = await database.getUserContent(req.pid);
if(isNaN(userID) || !pnid)
return res.redirect('/404');
let followers, communities, communityMap, selection;
if(req.params.type === 'yeahs') {
let posts = await POST.find({ yeahs: req.pid, removed: false }).sort({created_at: -1});
/*let posts = await POST.aggregate([
{ $match: { id: { $in: likesArray } } },
{$addFields: {
"__order": { $indexOfArray: [ likesArray, "$id" ] }
}},
{ $sort: { "__order": 1 } },
{ $project: { index: 0, _id: 0 } },
{ $limit: config.post_limit }
]);*/
let communityMap = await util.data.getCommunityHash();
let bundle = {
posts,
open: true,
numPosts: posts.length,
communityMap,
userContent: parentUserContent ? parentUserContent : userContent,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
});
mii_image_CDN: config.mii_image_CDN,
link: `/users/${userID}/yeahs/more?offset=${posts.length}&pjax=true`
}
if(req.query.pjax)
return res.render(req.directory + '/partials/posts_list.ejs', {
bundle,
lang: req.lang,
moment
});
else
return res.render(req.directory + '/user_page.ejs', {
template: 'posts_list',
selection: 4,
moment,
pnid,
numPosts,
userContent,
userSettings,
bundle,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
link,
friends,
parentUserContent,
moderator: req.moderator
});
}
else
{
res.status(204)
res.send('<p class="no-posts-text">' + req.lang.global.no_posts + '</p>')
if(req.params.type === 'friends') {
followers = await SETTINGS.find({ pid: friends });
communities = [];
selection = 2;
}
else if(req.params.type === 'followers') {
followers = await database.getFollowingUsers(userContent);
communities = [];
selection = 3;
}
else {
followers = await database.getFollowedUsers(userContent);
communities = userContent.followed_communities;
communityMap = await util.data.getCommunityHash();
selection = 2;
}
});
router.get('/following', async function (req, res) {
let user = await database.getUserByPID(req.query.pid);
let followers = await database.getFollowedUsers(user);
let communities = user.followed_communities;
let communityMap = await util.data.getCommunityHash();
if(user.followed_users[0] === '0')
if(followers[0] === '0')
followers.splice(0, 0);
if(communities[0] === '0')
communities.splice(0, 1);
if(user.following > 0)
let bundle = {
followers: followers ? followers : [],
communities: communities,
communityMap: communityMap
}
if(req.query.pjax)
return res.render(req.directory + '/partials/following_list.ejs', {
bundle,
});
res.render(req.directory + '/user_page.ejs', {
template: 'following_list',
selection: selection,
moment,
pnid,
numPosts,
userContent,
userSettings,
bundle,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
link,
parentUserContent,
moderator: req.moderator
});
}
async function morePosts(req, res, userID) {
let offset = parseInt(req.query.offset);
let userContent = await database.getUserContent(req.pid);
let communityMap = await util.data.getCommunityHash();
if(!offset) offset = 0;
let posts = await database.getUserPostsOffset(userID, config.post_limit, offset);
let bundle = {
posts,
numPosts: posts.length,
open: true,
communityMap,
userContent,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
link: `/users/${userID}/more?offset=${offset + posts.length}&pjax=true`
}
if(posts.length > 0)
{
res.render(req.directory + '/following_list.ejs', {
moment: moment,
user: user,
followers: followers,
communities: communities,
res.render(req.directory + '/partials/posts_list.ejs', {
communityMap: communityMap,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
});
}
else
{
res.send('<p class="no-posts-text">' + req.lang.user_page.no_following + '</p>')
}
});
router.get('/followers', async function (req, res) {
let user = await database.getUserByPID(req.query.pid);
let followers = await database.getFollowingUsers(user);
let communities = [];
let userMap = await util.data.getUserHash();
if(followers[0] === '0')
followers.splice(0, 1);
if(user.followers > 0)
{
res.render(req.directory + '/following_list.ejs', {
moment: moment,
user: user,
followers: followers,
communities: communities,
userMap: userMap,
database: database,
bundle,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
}
else
{
res.send('<p class="no-posts-text">' + req.lang.user_page.no_followers + '</p>')
res.sendStatus(204);
}
async function moreYeahPosts(req, res, userID) {
let offset = parseInt(req.query.offset);
let parentUserContent = await database.getUserContent(userID);
let userContent = await database.getUserContent(req.pid);
let communityMap = await util.data.getCommunityHash();
if(!offset) offset = 0;
let likesArray = await userContent.likes.slice().reverse();
let posts = await POST.aggregate([
{ $match: { id: { $in: likesArray }, removed: false } },
{$addFields: {
"__order": { $indexOfArray: [ likesArray, "$id" ] }
}},
{ $sort: { "__order": 1 } },
{ $project: { index: 0, _id: 0 } },
{ $skip : offset },
{ $limit: config.post_limit }
]);
let bundle = {
posts: posts.reverse(),
numPosts: posts.length,
open: true,
communityMap,
userContent,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN,
link: `/users/${userID}/yeahs/more?offset=${offset + posts.length}&pjax=true`
}
});
router.get('/friends', async function (req, res) {
let user = await database.getUserByPID(req.query.pid);
let friends = null;
let userMap = await util.data.getUserHash();
if(friends)
if(posts.length > 0)
{
res.render(req.directory + '/following_list.ejs', {
res.render(req.directory + '/partials/posts_list.ejs', {
communityMap: communityMap,
moment: moment,
user: user,
friends: friends,
userMap: userMap,
database: database,
bundle,
account_server: config.account_server_domain.slice(8),
cdnURL: config.CDN_domain,
lang: req.lang,
mii_image_CDN: config.mii_image_CDN
mii_image_CDN: config.mii_image_CDN,
pid: req.pid,
moderator: req.moderator
});
}
else
{
res.send('<p class="no-posts-text">' + req.lang.user_page.no_friends + '</p>')
}
});
router.post('/follow', upload.none(), async function (req, res) {
let userToFollow = await database.getUserByPID(req.body.userID);
let user = await database.getUserByPID(req.pid);
if(req.body.type === 'true' && user !== null && user.followed_users.indexOf(userToFollow.pid) === -1)
{
userToFollow.addToFollowers(user.pid);
user.addToUsers(userToFollow.pid);
res.sendStatus(200);
let content = user.user_id + ' NEW_FOLLOWER';
var picked = userToFollow.notification_list.find(o => o.content === content);
if(picked === undefined)
await database.pushNewNotificationByPID(userToFollow.pid, content, '/users/show?pid=' + user.pid)
}
else if(req.body.type === 'false' && user !== null && user.followed_users.indexOf(userToFollow.pid) !== -1)
{
userToFollow.removeFromFollowers(user.pid);
user.removeFromUsers(userToFollow.pid);
res.sendStatus(200);
}
else
res.sendStatus(423);
});
res.sendStatus(204);
}
module.exports = router;

View File

@ -1,12 +1,11 @@
var express = require('express');
var router = express.Router();
var xml = require('object-to-xml');
const express = require('express');
const router = express.Router();
const database = require('../../../../database');
const util = require('../../../../util');
var path = require('path');
const { POST } = require('../../../../models/post');
const path = require('path');
router.get('/', function (req, res) {
res.redirect('/activity-feed')
res.redirect('/titles/show')
});
router.get('/css/:filename', function (req, res) {
@ -19,6 +18,11 @@ router.get('/js/:filename', function (req, res) {
res.sendFile('/js/' + req.params.filename, {root: path.join(__dirname, '../../../../webfiles/' + req.directory)});
});
router.get('/images/:filename', function (req, res) {
res.set("Content-Type", "image/png");
res.sendFile('/images/' + req.params.filename, {root: path.join(__dirname, '../../../../webfiles/' + req.directory)});
});
router.get('/fonts/:filename', function (req, res) {
res.set("Content-Type", "font/woff");
res.sendFile('/fonts/' + req.params.filename, {root: path.join(__dirname, '../../../../webfiles/' + req.directory)});
@ -26,20 +30,20 @@ router.get('/fonts/:filename', function (req, res) {
router.get('/favicon.ico', function (req, res) {
res.set("Content-Type", "image/x-icon");
res.sendFile('/css/favicon.ico', {root: path.join(__dirname, '../../../../webfiles/' + req.directory)});
res.sendFile('/images/favicon.ico', {root: path.join(__dirname, '../../../../webfiles/' + req.directory)});
});
router.get('/icons/:image_id.png', async function (req, res) {
res.set("Content-Type", "image/png");
let community = await database.getCommunityByID(req.params.image_id.toString());
if(community !== null) {
if(community !== null && community.browser_icon) {
if(community.browser_icon.indexOf('data:image/png;base64,') !== -1)
res.send(Buffer.from(community.browser_icon.replace('data:image/png;base64,',''), 'base64'));
else
res.send(Buffer.from(community.browser_icon, 'base64'));
}
else {
let user = await database.getUserByPID(req.params.image_id.toString());
let user = await database.getUserSettings(req.params.image_id.toString());
if(user !== null)
if(user.pfp_uri.indexOf('data:image/png;base64,') !== -1)
res.send(Buffer.from(user.pfp_uri.replace('data:image/png;base64,',''), 'base64'));
@ -75,7 +79,7 @@ router.get('/tip/:image_id.png', async function (req, res) {
router.get('/banner/:image_id.png', async function (req, res) {
res.set("Content-Type", "image/png");
let community = await database.getCommunityByID(req.params.image_id.toString());
if(community !== null)
if(community !== null && community.WiiU_browser_header !== undefined)
if(community.WiiU_browser_header.indexOf('data:image/png;base64,') !== -1)
res.send(Buffer.from(community.WiiU_browser_header.replace('data:image/png;base64,',''), 'base64'));
else
@ -109,18 +113,14 @@ 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);
let notifications = await database.getUnreadNotificationCount(req.pid);
let messagesCount = await database.getUnreadConversationCount(req.pid);
if(user.notification_list) {
res.send(
{
message_count: messagesCount,
notification_count: user.notification_list.filter(notification => notification.read === false).length,
}
)
}
res.send(
{
message_count: messagesCount,
notification_count: notifications,
}
)
});
router.get('/:post_id/oembed.json', async function (req, res) {
@ -135,11 +135,14 @@ router.get('/:post_id/oembed.json', async function (req, res) {
router.get('/downloadUserData.json', async function (req, res) {
res.set("Content-Type", "text/json");
let posts = await database.getUserPostsOffset(req.pid, 100000, 0);
let user = await database.getUserByPID(req.pid);
res.set('Content-Disposition', `attachment; filename="${req.pid}_user_data.json"`);
let posts = await POST.find({ pid: req.pid })
let userContent = await database.getUserSettings(req.pid);
let userSettings = await database.getUserContent(req.pid);
let doc = {
"user": user,
"content": posts,
"user_content": userContent,
"user_settings": userSettings,
"posts": posts,
}
res.send(doc)
});

View File

@ -7,9 +7,9 @@ module.exports = {
PORTAL_FEED: require('./console/feed'),
PORTAL_NEWS: require('./console/notifications'),
PORTAL_MESSAGES: require('./console/messages'),
WEB_ADMIN: require('./admin/home'),
WEB_API: require('./admin/api'),
PORTAL_TOPICS: require('./console/topics'),
WEB_LOGIN: require('./web/login'),
ROBOTS: require('./web/robots'),
PWA: require('./web/pwa'),
ADMIN: require('./admin/admin'),
};

View File

@ -1,78 +1,68 @@
var express = require('express');
var router = express.Router();
var parseString = require('xml2js').parseString;
const express = require('express');
const router = express.Router();
const parseString = require('xml2js').parseString;
const database = require('../../../../database');
const util = require('../../../../util');
var path = require('path');
const config = require("../../../../../config.json");
const request = require("request");
const logger = require("../../../../logger");
router.get('/', async function (req, res) {
res.render(req.directory + '/login.ejs');
res.render(req.directory + '/login.ejs', {toast: null, cdnURL: config.CDN_domain,});
});
router.post('/', async (req, res) => {
const { username, password } = req.body;
let user = await database.getUserByUsername(username);
if(!user) {
res.cookie('error', 'User not found.', { domain: '.pretendo.cc' });
return res.redirect('/account/login');
}
let password_hash = await util.data.nintendoPasswordHash(password, user.pid);
let auth, token;
await request.post({
url: `https://${config.account_server_domain}/v1/api/oauth20/access_token/generate`,
headers: {
'X-Nintendo-Client-ID': config["X-Nintendo-Client-ID"],
'X-Nintendo-Client-Secret': config["X-Nintendo-Client-Secret"],
'X-Nintendo-Title-ID': '0005001010040100'
},
form: {
user_id: username,
password_type: 'hash',
password: password_hash,
grant_type: 'password'
}
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
logger.audit('[' + user.user_id + ' - ' + user.pid + '] signed into the application');
parseString(body, async function (err, result) {
auth = result.OAuth20.access_token[0].token[0];
await request.get({
url: 'https://' + config.account_server_domain + "/v1/api/provider/service_token/@me",
headers: {
'X-Nintendo-Client-ID': config["X-Nintendo-Client-ID"],
'X-Nintendo-Client-Secret': config["X-Nintendo-Client-Secret"],
'X-Nintendo-Title-ID': '000500301001610A',
'authorization': `Bearer ${auth}`,
}
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
parseString(body, async function (err, result) {
token = result.service_token.token[0];
console.log(req.hostname);
let cookieDomain = (req.hostname === 'juxt.miiverse.cc') ? '.miiverse.cc' : '.pretendo.cc';
res.cookie('access_token', token, { domain : cookieDomain });
res.redirect('/activity-feed');
});
}
else
{
console.log(error);
}
});
});
}
else {
res.statusCode = 403;
let response = {
error_code: 403,
message: 'Invalid account ID or password'
};
return res.send(response);
const login = await util.data.login(username, password).catch((e) => {
console.log(e.details);
switch (e.details) {
case 'INVALID_ARGUMENT: User not found':
res.render(req.directory + '/login.ejs', {toast: 'Username was invalid.', cdnURL: config.CDN_domain,});
break;
case 'INVALID_ARGUMENT: Password is incorrect':
res.render(req.directory + '/login.ejs', {toast: 'Password was incorrect.', cdnURL: config.CDN_domain,});
break;
default:
res.render(req.directory + '/login.ejs', {toast: 'Invalid username or password.', cdnURL: config.CDN_domain,});
break;
}
});
if(!login) return;
const PNID = await util.data.getUserDataFromToken(login.accessToken);
if(!PNID)
return res.render(req.directory + '/login.ejs', {toast: 'Invalid username or password.', cdnURL: config.CDN_domain,});
const pid = PNID.pid;
let discovery = await database.getEndPoint(PNID.serverAccessLevel);
let message = '';
switch (discovery.status) {
case 3:
message = "Juxt is currently undergoing maintenance. Please try again later.";
break;
case 4:
message = "Juxt is currently closed. Thank you for your interest.";
break;
default:
message = "Juxt is currently unavailable. Please try again later.";
break;
}
if(discovery.status !== 0) {
return res.render(req.directory + '/error.ejs', {
code: 504,
message: message,
cdnURL: config.CDN_domain,
lang: req.lang,
pid: pid,
moderator: req.moderator
});
}
let cookieDomain = (req.hostname.indexOf('miiverse') !== -1) ? '.miiverse.cc' : '.pretendo.network';
let expiration = (req.hostname.indexOf('miiverse') !== -1) ? login.expiresIn * 60 * 60 * 24 : login.expiresIn * 60 * 60
res.cookie('access_token', login.accessToken, { domain : cookieDomain, maxAge: expiration });
res.cookie('refresh_token', login.refreshToken, { domain : cookieDomain });
res.redirect('/');
});

View File

@ -1,6 +1,6 @@
var express = require('express');
const express = require('express');
const path = require("path");
var router = express.Router();
const router = express.Router();
router.get('/icons/:filename', function (req, res) {
res.set("Content-Type", "image/png");

View File

@ -1,6 +1,6 @@
var express = require('express');
const express = require('express');
const path = require("path");
var router = express.Router();
const router = express.Router();
router.get('/', function (req, res) {
res.set("Content-Type", "text/css");

View File

@ -77,14 +77,14 @@
"swearing": "Post cannot contain explicit language."
},
"messages": {
"coming_soon": "Messages isn't ready yet. Check back soon!"
"coming_soon": "No Messages"
},
"setup": {
"welcome": "Welcome to Juxtaposition!",
"welcome_text": "Juxt is a gaming community that connects people from all over the world using Mii characters. Use Juxt to share your gaming experiences and meet people from around the world.",
"beta": "Beta Disclaimer",
"beta_text": {
"first": "You are about to try out the first public beta of Juxt. This means that a lot is still up in the air, and a lot can change at any time.",
"first": "Welcome to the Pretendo Network Public Beta! While a lot has changed from the first public beta, Juxt is still a work in progress and a lot can change at any time.",
"second": "This can and may include a total wipe of the database at the conclusion or during the beta period.",
"third": "The website, its software and all content found on it are provided on an “as is” and “as available” basis. The Pretendo Network does not give any warranties, whether express or implied, as to the suitability or usability of the website, its software or any of its content."
},

View File

@ -3,43 +3,70 @@ const NodeRSA = require('node-rsa');
const fs = require('fs-extra');
const database = require('./database');
const logger = require('./logger');
const grpc = require('nice-grpc');
const config = require('../config.json');
const { USER } = require('./models/user');
const { SETTINGS } = require('./models/settings');
const { CONTENT } = require('./models/content');
const { NOTIFICATION } = require('./models/notifications');
const { COMMUNITY } = require('./models/communities');
const { AccountDefinition } = require('pretendo-grpc/dist/account/account_service');
const { FriendsDefinition } = require('pretendo-grpc/dist/friends/friends_service');
const { APIDefinition } = require('pretendo-grpc/dist/api/api_service');
const translations = require('./translations')
var HashMap = require('hashmap');
let TGA = require('tga');
let pako = require('pako');
let PNG = require('pngjs').PNG;
var bmp = require("bmp-js");
const HashMap = require('hashmap');
const TGA = require('tga');
const pako = require('pako');
const PNG = require('pngjs').PNG;
const bmp = require("bmp-js");
const aws = require('aws-sdk');
const crc32 = require('crc/crc32');
let communityMap = new HashMap();
let userMap = new HashMap();
const { ip: friendsIP, port: friendsPort, api_key: friendsKey } = config.grpc.friends;
const friendsChannel = grpc.createChannel(`${friendsIP}:${friendsPort}`);
const friendsClient = grpc.createClient(FriendsDefinition, friendsChannel);
const { ip: apiIP, port: apiPort, api_key: apiKey } = config.grpc.account;
const apiChannel = grpc.createChannel(`${apiIP}:${apiPort}`);
const apiClient = grpc.createClient(APIDefinition, apiChannel);
const accountChannel = grpc.createChannel(`${apiIP}:${apiPort}`);
const accountClient = grpc.createClient(AccountDefinition, accountChannel);
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
});
nameCache();
function nameCache() {
database.connect().then(async e => {
let communities = await database.getCommunities();
let communities = await COMMUNITY.find();
if(communities !== null) {
for(let i = 0; i < communities.length; i++ ) {
if(communities[i].title_id !== null) {
for(let j = 0; j < communities[i].title_id.length; j++) {
communityMap.set(communities[i].title_id[j], communities[i].name);
communityMap.set(communities[i].title_id[j] + '-id', communities[i].community_id);
communityMap.set(communities[i].title_id[j] + '-id', communities[i].olive_community_id);
}
communityMap.set(communities[i].community_id, communities[i].name);
communityMap.set(communities[i].olive_community_id, communities[i].name);
}
}
logger.success('Created community index of ' + communities.length + ' communities');
}
logger.success('Created community index')
let users = await database.getUsers(1000);
let users = await database.getUsersSettings(-1);
if(users !== null) {
for(let i = 0; i < users.length; i++ ) {
if(users[i].pid !== null) {
userMap.set(users[i].pid.toString(), users[i].user_id);
userMap.set(users[i].pid, users[i].screen_name.replace(/[\u{0080}-\u{FFFF}]/gu,""));
}
}
logger.success('Created user index of ' + users.length + ' users')
}
logger.success('Created user index of ' + users.length + ' user(s)')
}).catch(error => {
logger.error(error);
@ -47,25 +74,26 @@ function nameCache() {
}
let methods = {
create_user: async function(pid, experience, notifications, region) {
const pnid = await database.getPNID(pid);
create_user: async function(pid, experience, notifications) {
const pnid = await this.getUserDataFromPid(pid);
if(!pnid)
return;
const newUsr = {
let newSettings = {
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,
screen_name: pnid.mii.name,
game_skill: experience,
notifications: notifications,
official: pnid.access_level === 3,
country: region,
};
const newUsrObj = new USER(newUsr);
await newUsrObj.save();
receive_notifications: notifications,
}
let newContent = {
pid: pid
}
const newSettingsObj = new SETTINGS(newSettings);
await newSettingsObj.save();
const newContentObj = new CONTENT(newContent);
await newContentObj.save();
this.setName(pid, pnid.mii.name);
},
decodeParamPack: function (paramPack) {
/* Decode base64 */
@ -79,80 +107,54 @@ let methods = {
}
return out;
},
processServiceToken: function(token) {
processServiceToken: function(encryptedToken) {
try
{
let B64token = Buffer.from(token, 'base64');
let B64token = Buffer.from(encryptedToken, 'base64');
let decryptedToken = this.decryptToken(B64token);
return decryptedToken.readUInt32LE(0x2);
const token = this.unpackToken(decryptedToken);
return token.pid;
}
catch(e)
{
//console.log(e)
console.log(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;
if (!config.aes_key) {
throw new Error('Service token AES key not found. Set config.aes_key');
}
const cryptoPath = `${__dirname}/certs/access`;
const iv = Buffer.alloc(16);
const key = Buffer.from(config.aes_key, 'hex');
const cryptoOptions = {
private_key: fs.readFileSync(`${cryptoPath}/private.pem`),
hmac_secret: config.account_server_secret
};
const expectedChecksum = token.readUint32BE();
const encryptedBody = token.subarray(4);
const privateKey = new NodeRSA(cryptoOptions.private_key, 'pkcs1-private-pem', {
environment: 'browser',
encryptionScheme: {
'hash': 'sha256',
}
});
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
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))
const decrypted = Buffer.concat([
decipher.update(encryptedBody),
decipher.final()
]);
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) {
console.log('Token signature did not match');
return null;
if (expectedChecksum !== crc32(decrypted)) {
throw new Error('Checksum did not match. Failed decrypt. Are you using the right key?');
}
return decryptedBody;
return decrypted;
},
unpackToken: function(token) {
return {
system_type: token.readUInt8(0x0),
token_type: token.readUInt8(0x1),
pid: token.readUInt32LE(0x2),
expire_time: token.readBigUInt64LE(0x6),
title_id: token.readBigUInt64LE(0xE),
access_level: token.readInt8(0x16)
};
},
processPainting: async function (painting, isTGA) {
if (isTGA) {
@ -163,14 +165,21 @@ let methods = {
} catch (err) {
console.error(err);
}
let tga = new TGA(Buffer.from(output));
let tga;
try {
tga = new TGA(Buffer.from(output));
}
catch (e) {
console.log(e)
return null;
}
let png = new PNG({
width: tga.width,
height: tga.height
});
png.data = tga.pixels;
let pngBuffer = PNG.sync.write(png);
return `data:image/png;base64,${pngBuffer.toString('base64')}`;
return PNG.sync.write(png);
//return `data:image/png;base64,${pngBuffer.toString('base64')}`;
}
else {
let paintingBuffer = Buffer.from(painting, 'base64');
@ -178,15 +187,12 @@ let methods = {
const tga = this.createBMPTgaBuffer(bitmap.width, bitmap.height, bitmap.data, false);
let output;
try
{
try {
output = pako.deflate(tga, {level: 6});
}
catch (err)
{
catch (err) {
console.error(err);
}
return new Buffer(output).toString('base64')
}
},
@ -210,6 +216,12 @@ let methods = {
refreshCache: function () {
nameCache();
},
setName: function (pid, name) {
if(!pid || !name)
return;
userMap.delete(pid);
userMap.set(pid, name);
},
resizeImage: function (file, width, height) {
sharp(file)
.resize({ height: height, width: width })
@ -247,10 +259,9 @@ let methods = {
return buffer;
},
processLanguage: function (header) {
if(!header)
processLanguage: function (paramPackData) {
if(!paramPackData)
return translations.EN;
let paramPackData = this.decodeParamPack(header);
switch (paramPackData.language_id) {
case '0':
return translations.JA
@ -279,6 +290,142 @@ let methods = {
default:
return translations.EN
}
},
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(notification) {
const now = new Date();
if(notification.type === 'follow') {
// { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` }
let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID })
if(existingNotification) {
existingNotification.lastUpdated = now;
existingNotification.read = false;
return await existingNotification.save();
}
const last10min = new Date(now.getTime() - 10 * 60 * 1000);
existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'follow', lastUpdated: { $gte: last10min } });
if(existingNotification) {
existingNotification.users.push({
user: notification.objectID,
timeStamp: now
});
existingNotification.lastUpdated = now;
existingNotification.link = notification.link;
existingNotification.objectID = notification.objectID;
existingNotification.read = false;
return await existingNotification.save();
}
else {
let newNotification = new NOTIFICATION({
pid: notification.pid,
type: notification.type,
users: [{
user: notification.objectID,
timestamp: now
}],
link: notification.link,
objectID: notification.objectID,
read: false,
lastUpdated: now
});
await newNotification.save();
}
}
/*else if(notification.type === 'yeah') {
// { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` }
let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID })
if(existingNotification) {
existingNotification.lastUpdated = new Date();
return await existingNotification.save();
}
existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'yeah' });
if(existingNotification) {
existingNotification.users.push({
user: notification.objectID,
timeStamp: new Date()
});
existingNotification.lastUpdated = new Date();
existingNotification.link = notification.link;
existingNotification.objectID = notification.objectID;
return await existingNotification.save();
}
else {
let newNotification = new NOTIFICATION({
pid: notification.pid,
type: notification.type,
users: [{
user: notification.objectID,
timestamp: new Date()
}],
link: notification.link,
objectID: notification.objectID,
read: false,
lastUpdated: new Date()
});
await newNotification.save();
}
}*/
},
getFriends: async function(pid) {
const pids = await friendsClient.getUserFriendPIDs({
pid: pid
}, {
metadata: grpc.Metadata({
'X-API-Key': friendsKey
})
});
return pids.pids;
},
getFriendRequests: async function(pid) {
const requests = await friendsClient.getUserFriendRequestsIncoming({
pid: pid
}, {
metadata: grpc.Metadata({
'X-API-Key': friendsKey
})
});
return requests.friendRequests;
},
login: async function(username, password) {
return await apiClient.login({
username: username,
password: password,
grantType: 'password'
}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey
})
});
},
getUserDataFromToken: async function(token) {
return apiClient.getUserData({}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey,
'X-Token': token
})
});
},
getUserDataFromPid: async function(pid) {
return accountClient.getUserData({
pid: pid
}, {
metadata: grpc.Metadata({
'X-API-Key': apiKey
})
});
},
getPid: async function(token) {
const user = await this.getUserDataFromToken(token);
return user.pid;
}
};
exports.data = methods;

View File

@ -1,164 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2><%= community.name %></h2>
<div class="community-page-info-container">
<table>
<tbody>
<tr>
<td>
<img class="community-page-info-icon" src="/icons/<%= community.community_id %>.png">
</td>
<td>
<h4 class="community-page-description"><%= community.description %></h4>
</td>
</tr>
</tbody>
</table>
<button type="button" style="margin-left: 20px; width: 760px; <%if(community.type === 1){%>display: none;<%}%>" onclick="window.location='/posts/new'">New Post</button>
<button type="button" style="margin-left: 20px; width: 760px" onclick="window.location='/communities/<%= community.community_id %>/edit'">Edit</button>
<div class="community-page-margin-line"></div>
<table class="community-page-table-wrapper">
<tbody>
<tr>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Followers</h4>
<h4 class="community-page-table-text"><%= community.followers %></h4>
</div>
</td>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Posts</h4>
<h4 class="community-page-table-text"><%=totalNumPosts%></h4>
</div>
</td>
<td>
<div class="community-page-shaded-info-container" style="width: 275px;">
<h4 class="community-page-table-label">Tags</h4>
<h4 class="community-page-table-text">N/A</h4>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="community-page-posts-wrapper">
<% newPosts.forEach(function(post) { %>
<div id="<%= post.id %>">
<div class="post-user-info-wrapper">
<%if(post.verified) {%>
<img class="community-page-post-user-icon verified" src="<%= mii_image_CDN %>/<%= post.pid %>/<% if(post.feeling_id === 1) {%>smile_open_mouth.png<%} else if(post.feeling_id === 2 ) {%>wink_left.png<%} else if(post.feeling_id === 3 ) {%>surprise_open_mouth.png<%} else if(post.feeling_id === 4 ) {%>frustrated.png<%} else if(post.feeling_id === 5 ) {%>sorrow.png<%} else {%>normal_face.png<%}%>" onclick="loadUserProfile(<%=post.pid%>)">
<span class="community-page-verified-user-badge community-page-verified" style=""></span>
<%} else {%>
<img class="community-page-post-user-icon" src="<%= mii_image_CDN %>/<%= post.pid %>/<% if(post.feeling_id === 1) {%>smile_open_mouth.png<%} else if(post.feeling_id === 2 ) {%>wink_left.png<%} else if(post.feeling_id === 3 ) {%>surprise_open_mouth.png<%} else if(post.feeling_id === 4 ) {%>frustrated.png<%} else if(post.feeling_id === 5 ) {%>sorrow.png<%} else {%>normal_face.png<%}%>" onclick="loadUserProfile(<%=post.pid%>)">
<span class="community-page-verified-user-badge community-page-verified" style="display: none;"></span>
<%}%>
<h2 class="community-page-post-username" onclick="loadUserProfile(<%=post.pid%>)"><%= post.screen_name %></h2>
<h4 class="community-page-post-time-stamp"><%= post.created_at %></h4>
<div class="community-page-post-yeah-button-wrapper">
<div class="community-page-post-yeah-button" onclick="deletePost(event, '<%= post.id %>')"></div>
</div>
<div id="yeah-<%= post.id %>" class="community-page-post-yeah-count"><%= post.empathy_count %> Yeahs</div>
</div>
<div class="community-page-post-wrapper">
<% if(post.body !== '' && post.painting === '' && post.screenshot === '' && !post.url) { %>
<h3><%= post.body %></h3>
<%} else { %>
<% if(post.screenshot !== '') { %>
<img class="community-page-post-screenshot" src="data:image/png;base64,<%= post.screenshot %>">
<%}%>
<% if(post.painting !== '') { %>
<img class="community-page-post-painting" src="<%= post.painting_uri %>">
<%}%>
<% if(post.url) { %>
<iframe width="760" height="427.5" src="<%= post.url %>" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<%}%>
<% if(post.body) { %>
<div class="community-page-post-text-overlay">
<h3><%= post.body %></h3>
</div>
<%}%>
<%}%>
</div>
</div>
<% }); %>
</div>
</div>
</div>
<script>
function deletePost(event, postID) {
if (event.shiftKey) {
var post = document.getElementById(postID);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
post.style.display = 'none';
}
else if (this.readyState === 4){
alert('Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
};
xhttp.open("POST", "/v1/posts/" + postID + '/delete', true);
xhttp.send();
} else {
if(confirm('Delete post?')) {
var post = document.getElementById(postID);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
post.style.display = 'none';
}
else if (this.readyState === 4){
alert('Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
};
xhttp.open("POST", "/v1/posts/" + postID + '/delete', true);
xhttp.send();
}
}
}
function loadUserPosts() {
var id = document.getElementsByClassName('post-user-info-wrapper')[document.getElementsByClassName('post-user-info-wrapper').length - 1].id
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
document.getElementsByClassName('community-page-posts-wrapper')[0].innerHTML += this.responseText;
}
else if(this.readyState === 4 && this.status === 204)
{
document.getElementById('load-more-posts-button').style.display = 'none';
}
else if (this.readyState === 4){
alert('Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
};
xhttp.open("GET", "/users/loadPosts" + '?postID=' + id, true);
xhttp.send();
}
</script>
</body>
</html>

View File

@ -1,30 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Audit Log</h2>
</div>
</div>
</body>
</html>

View File

@ -1,139 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
<style>
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 60%;
}
th {
cursor: pointer;
}
th, td {
text-align: left;
padding: 16px;
}
tr {
border: black;
border-width: 2px;
border-style: groove;
}
</style>
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Communities</h2>
<button onclick="location.assign('/communities/new')">New</button>
<input type="text" id="search" onkeyup="search()" placeholder="Search..">
<table id="community-list">
<tbody id="search-list">
<tbody>
<tr>
<th>Icon</th>
<th onClick="sortTable(0)">Name</th>
<th onClick="sortTable(1)">Created At</th>
<th onClick="sortTable(1)">Title ID's</th>
<th onClick="sortTable(2)">Followers</th>
</tr>
<% for(var i = 0; i < communities.length; i++) { %>
<tr id="<%= communities[i].community_id %>" onclick="location.assign('/communities/' + this.id)">
<td><img style="width: 80px " src="/icons/<%= communities[i].community_id %>.png"></td>
<td><a><%= communities[i].name %></a></td>
<td><%= moment(communities[i].created_at).fromNow() %></td>
<td><%= communities[i].title_ids %></td>
<td><%= communities[i].followers %></td>
</tr>
<%}%>
</tbody>
</table>
</div>
</div>
<script>
/**
* Removes row from table
* @param id
*/
function removeRow(id) {
var row = document.getElementById(id);
row.parentNode.removeChild(row);
}
/**
* Sorts table by selected column
* @param n
*/
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("community-list");
switching = true;
dir = "asc";
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
if (dir === "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
} else if (dir === "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount ++;
} else {
if (switchcount === 0 && dir === "asc") {
dir = "desc";
switching = true;
}
}
}
}
function search() {
var input, filter, table, tr, td, i, txtValue;
input = document.getElementById("search");
filter = input.value.toUpperCase();
table = document.getElementById("community-list");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[1];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
</script>
</body>
</html>

View File

@ -1,164 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2><%= community.name %></h2>
<div class="community-page-info-container">
<table>
<tbody>
<tr>
<td>
<img class="community-page-info-icon" src="/icons/<%= community.community_id %>.png">
</td>
<td>
<h4 class="community-page-description"><%= community.description %></h4>
</td>
</tr>
</tbody>
</table>
<button type="button" style="margin-left: 20px; width: 760px; <%if(community.type === 1){%>display: none;<%}%>" onclick="window.location='/communities/<%= community.community_id %>/sub'">Sub-Communities</button>
<button type="button" style="margin-left: 20px; width: 760px" onclick="window.location='/communities/<%= community.community_id %>/edit'">Edit</button>
<div class="community-page-margin-line"></div>
<table class="community-page-table-wrapper">
<tbody>
<tr>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Followers</h4>
<h4 class="community-page-table-text"><%= community.followers %></h4>
</div>
</td>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Posts</h4>
<h4 class="community-page-table-text"><%=totalNumPosts%></h4>
</div>
</td>
<td>
<div class="community-page-shaded-info-container" style="width: 275px;">
<h4 class="community-page-table-label">Tags</h4>
<h4 class="community-page-table-text">N/A</h4>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="community-page-posts-wrapper">
<% newPosts.forEach(function(post) { %>
<div id="<%= post.id %>">
<div class="post-user-info-wrapper">
<%if(post.verified) {%>
<img class="community-page-post-user-icon verified" src="<%= mii_image_CDN %>/<%= post.pid %>/<% if(post.feeling_id === 1) {%>smile_open_mouth.png<%} else if(post.feeling_id === 2 ) {%>wink_left.png<%} else if(post.feeling_id === 3 ) {%>surprise_open_mouth.png<%} else if(post.feeling_id === 4 ) {%>frustrated.png<%} else if(post.feeling_id === 5 ) {%>sorrow.png<%} else {%>normal_face.png<%}%>" onclick="loadUserProfile(<%=post.pid%>)">
<span class="community-page-verified-user-badge community-page-verified" style=""></span>
<%} else {%>
<img class="community-page-post-user-icon" src="<%= mii_image_CDN %>/<%= post.pid %>/<% if(post.feeling_id === 1) {%>smile_open_mouth.png<%} else if(post.feeling_id === 2 ) {%>wink_left.png<%} else if(post.feeling_id === 3 ) {%>surprise_open_mouth.png<%} else if(post.feeling_id === 4 ) {%>frustrated.png<%} else if(post.feeling_id === 5 ) {%>sorrow.png<%} else {%>normal_face.png<%}%>" onclick="loadUserProfile(<%=post.pid%>)">
<span class="community-page-verified-user-badge community-page-verified" style="display: none;"></span>
<%}%>
<h2 class="community-page-post-username" onclick="loadUserProfile(<%=post.pid%>)"><%= post.screen_name %></h2>
<h4 class="community-page-post-time-stamp"><%= post.created_at %></h4>
<div class="community-page-post-yeah-button-wrapper">
<div class="community-page-post-yeah-button" onclick="deletePost(event, '<%= post.id %>')"></div>
</div>
<div id="yeah-<%= post.id %>" class="community-page-post-yeah-count"><%= post.empathy_count %> Yeahs</div>
</div>
<div class="community-page-post-wrapper">
<% if(post.body !== '' && post.painting === '' && post.screenshot === '' && !post.url) { %>
<h3><%= post.body %></h3>
<%} else { %>
<% if(post.screenshot !== '') { %>
<img class="community-page-post-screenshot" src="data:image/png;base64,<%= post.screenshot %>">
<%}%>
<% if(post.painting !== '') { %>
<img class="community-page-post-painting" src="<%= post.painting_uri %>">
<%}%>
<% if(post.url) { %>
<iframe width="760" height="427.5" src="<%= post.url %>" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<%}%>
<% if(post.body) { %>
<div class="community-page-post-text-overlay">
<h3><%= post.body %></h3>
</div>
<%}%>
<%}%>
</div>
</div>
<% }); %>
</div>
</div>
</div>
<script>
function deletePost(event, postID) {
if (event.shiftKey) {
var post = document.getElementById(postID);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
post.style.display = 'none';
}
else if (this.readyState === 4){
alert('Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
};
xhttp.open("POST", "/v1/posts/" + postID + '/delete', true);
xhttp.send();
} else {
if(confirm('Delete post?')) {
var post = document.getElementById(postID);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
post.style.display = 'none';
}
else if (this.readyState === 4){
alert('Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
};
xhttp.open("POST", "/v1/posts/" + postID + '/delete', true);
xhttp.send();
}
}
}
function loadUserPosts() {
var id = document.getElementsByClassName('post-user-info-wrapper')[document.getElementsByClassName('post-user-info-wrapper').length - 1].id
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
document.getElementsByClassName('community-page-posts-wrapper')[0].innerHTML += this.responseText;
}
else if(this.readyState === 4 && this.status === 204)
{
document.getElementById('load-more-posts-button').style.display = 'none';
}
else if (this.readyState === 4){
alert('Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
};
xhttp.open("GET", "/users/loadPosts" + '?postID=' + id, true);
xhttp.send();
}
</script>
</body>
</html>

View File

@ -1,47 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Discovery Server Status</h2>
<form action="/v1/discovery/update" enctype="multipart/form-data" target="formSubmitFrame" method="post">
<select name="has_error">
<option value="0" selected>OPEN</option>
<option value="1">SYSTEM_UPDATE_REQUIRED</option>
<option value="2">SETUP_NOT_COMPLETE</option>
<option value="3">SERVICE_MAINTENANCE</option>
<option value="4">SERVICE_CLOSED</option>
<option value="5">PARENTAL_CONTROLS_ENABLED</option>
<option value="6">POSTING_LIMITED_PARENTAL_CONTROLS</option>
<option value="7">PNID_BANNED</option>
<option value="8">SERVER_ERROR</option>
</select>
<input type="submit" value="Submit">
<br>
<br>
<iframe name="formSubmitFrame"></iframe>
</form>
</div>
</div>
</body>
</html>

View File

@ -1,185 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Edit - <%= community.name %></title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Update <%=community.name%></h2>
<form action="/v1/communities/<%= community.community_id %>/update" enctype="multipart/form-data" target="formSubmitFrame" method="post">
<div class="form-section">
<div class="section-info">
<h2>Meta information</h2>
<p>Update the name, description, system icon, and title ID's of the community</p>
</div>
<div class="section-inputs">
<div class="input-div">
<label for="name">Community Name:</label>
<input type="text" id="name" name="name" value="<%=community.name%>">
</div>
<div class="input-div">
<label for="description">Description:</label>
<textarea type="text" id="description" name="description"><%=community.description%></textarea>
</div>
<div class="input-grid">
<div class="input-div">
<label for="title_ids">Title ID 1:</label>
<input type="text" id="title_ids" name="title_ids[]" value="<%=community.title_ids[0]%>">
</div>
<div class="input-div">
<label for="title_ids">Title ID 2:</label>
<input type="text" id="title_ids" name="title_ids[]" value="<%=community.title_ids[1]%>">
</div>
<div class="input-div">
<label for="title_ids">Title ID 3:</label>
<input type="text" id="title_ids" name="title_ids[]" value="<%=community.title_ids[2]%>">
</div>
<div class="input-div">
<label for="icon">System Icon (B64 TGA):</label>
<input type="text" id="icon" name="icon" value="<%=community.icon%>">
</div>
</div>
</div>
</div>
<div class="form-section">
<div class="section-info">
<h2>Browser Icons and Meta Data</h2>
<p>Update the browser icons for the Miiverse Applets, as well as setting the platform, and other settings</p>
</div>
<div class="section-inputs">
<div class="input-div choices-inputs do-input-cols">
<div>
Browser Icon (128px x 128px)
<input type="file" id="browserIcon" accept="image/png" name="browserIcon">
</div>
<div>
<img src="/icons/<%= community.community_id %>.png">
</div>
</div>
<hr>
<div class="input-div choices-inputs do-input-cols">
<div>
3DS Browser Banner (400px x 220px)
<input type="file" id="CTRbrowserHeader" accept="image/png" name="CTRbrowserHeader">
</div>
<div>
<img src="<%=community.CTR_browser_header%>">
</div>
</div>
<hr>
<div class="input-div choices-inputs">
Wii U Browser Banner (1498px x 328px)
<input type="file" id="WiiUbrowserHeader" accept="image/png" name="WiiUbrowserHeader">
<img src="<%=community.WiiU_browser_header%>">
</div>
<hr>
<div class="input-div choices-inputs">
Is Recommended?
<input type="radio" id="isRecomended" name="is_recommended" value="1" <%if(community.is_recommended === 1) {%> checked <%}%>>
<label for="isRecomended">True</label>
<input type="radio" id="isNotRecomended" name="is_recommended" value="0" <%if(community.is_recommended === 0) {%> checked <%}%>>
<label for="isNotRecomended">False</label>
</div>
<div class="input-div choices-inputs">
Has Shop Page?
<input type="radio" id="hasShopPage" name="has_shop_page" value="1" <%if(community.has_shop_page === 1) {%> checked <%}%>>
<label for="hasShopPage">True</label>
<input type="radio" id="noShopPage" name="has_shop_page" value="0" <%if(community.has_shop_page === 0) {%> checked <%}%>>
<label for="noShopPage">False</label>
</div>
<div class="input-div choices-inputs">
Platform
<input type="radio" id="WiiU" name="platform_id" value="0" <%if(community.platform_id === 0) {%> checked <%}%>>
<label for="platform_id">Wii U</label>
<input type="radio" id="3DS" name="platform_id" value="1" <%if(community.platform_id === 1) {%> checked <%}%>>
<label for="platform_id">3DS</label>
<input type="radio" id="Both" name="platform_id" value="2" <%if(community.platform_id === 2) {%> checked <%}%>>
<label for="platform_id">Both</label>
</div>
<div class="input-div choices-inputs">
Type
<input type="radio" id="main" name="type" value="0" <%if(community.type === 0) {%> checked <%}%>>
<label for="type">Main</label>
<input type="radio" id="sub" name="type" value="1" <%if(community.type === 1) {%> checked <%}%>>
<label for="type">Sub</label>
<input type="radio" id="admin" name="type" value="2" <%if(community.type === 2) {%> checked <%}%>>
<label for="type">Announcement</label>
</div>
<div class="input-div">
<label for="parent">Parent Community</label>
<input type="text" id="parent" name="parent" value="<%=community.parent%>">
</div>
</div>
</div>
<div class="form-section">
<div class="section-info">
<h2>Submit or Delete Community</h2>
<p></p>
</div>
<div class="section-inputs">
<div>
<button type="submit" class="btn">Submit</button>
<button type="button" class="btn" onclick="deleteCommunity()">Delete Community</button>
</div>
<iframe name="formSubmitFrame"></iframe>
</div>
</div>
</form>
</div>
</div>
<script>
function deleteCommunity() {
var confirm = prompt('Type the name of the community in to confirm you want to delete it.');
if (confirm === '<%=community.name%>') {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
alert('Community has been deleted');
window.location.replace("/communities");
}
else if (this.readyState === 4){
alert("Unable to delete community.")
}
};
xhttp.open("POST", "/v1/communities/<%=community.community_id%>/delete", true);
xhttp.send();
}
}
</script>
</body>
</html>

View File

@ -1,185 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Edit - <%= community.name %></title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>New <%=community.name%> Sub-Community</h2>
<form action="/v1/communities/<%=community.community_id%>/sub/new" enctype="multipart/form-data" target="formSubmitFrame" method="post">
<div class="form-section">
<div class="section-info">
<h2>Meta information</h2>
<p>Update the name, description, system icon, and title ID's of the community</p>
</div>
<div class="section-inputs">
<div class="input-div">
<label for="name">Community Name:</label>
<input type="text" id="name" name="name" value="<%=community.name%>">
</div>
<div class="input-div">
<label for="description">Description:</label>
<textarea type="text" id="description" name="description"><%=community.description%></textarea>
</div>
<div class="input-grid">
<div class="input-div">
<label for="title_ids">Title ID 1:</label>
<input type="text" id="title_ids" name="title_ids[]" value="<%=community.title_ids[0]%>">
</div>
<div class="input-div">
<label for="title_ids">Title ID 2:</label>
<input type="text" id="title_ids" name="title_ids[]" value="<%=community.title_ids[1]%>">
</div>
<div class="input-div">
<label for="title_ids">Title ID 3:</label>
<input type="text" id="title_ids" name="title_ids[]" value="<%=community.title_ids[2]%>">
</div>
<div class="input-div">
<label for="icon">System Icon (B64 TGA):</label>
<input type="text" id="icon" name="icon" value="<%=community.icon%>">
</div>
</div>
</div>
</div>
<div class="form-section">
<div class="section-info">
<h2>Browser Icons and Meta Data</h2>
<p>Update the browser icons for the Miiverse Applets, as well as setting the platform, and other settings</p>
</div>
<div class="section-inputs">
<div class="input-div choices-inputs do-input-cols">
<div>
Browser Icon (128px x 128px)
<input type="file" id="browserIcon" accept="image/png" name="browserIcon">
</div>
<div>
<img src="/icons/<%= community.community_id %>.png">
</div>
</div>
<hr>
<div class="input-div choices-inputs do-input-cols">
<div>
3DS Browser Banner (400px x 220px)
<input type="file" id="CTRbrowserHeader" accept="image/png" name="CTRbrowserHeader">
</div>
<div>
<img src="<%=community.CTR_browser_header%>">
</div>
</div>
<hr>
<div class="input-div choices-inputs">
Wii U Browser Banner (1498px x 328px)
<input type="file" id="WiiUbrowserHeader" accept="image/png" name="WiiUbrowserHeader">
<img src="<%=community.WiiU_browser_header%>">
</div>
<hr>
<div class="input-div choices-inputs">
Is Recommended?
<input type="radio" id="isRecomended" name="is_recommended" value="1" <%if(community.is_recommended === 1) {%> checked <%}%>>
<label for="isRecomended">True</label>
<input type="radio" id="isNotRecomended" name="is_recommended" value="0" <%if(community.is_recommended === 0) {%> checked <%}%>>
<label for="isNotRecomended">False</label>
</div>
<div class="input-div choices-inputs">
Has Shop Page?
<input type="radio" id="hasShopPage" name="has_shop_page" value="1" <%if(community.has_shop_page === 1) {%> checked <%}%>>
<label for="hasShopPage">True</label>
<input type="radio" id="noShopPage" name="has_shop_page" value="0" <%if(community.has_shop_page === 0) {%> checked <%}%>>
<label for="noShopPage">False</label>
</div>
<div class="input-div choices-inputs">
Platform
<input type="radio" id="WiiU" name="platform_id" value="0" <%if(community.platform_id === 0) {%> checked <%}%>>
<label for="platform_id">Wii U</label>
<input type="radio" id="3DS" name="platform_id" value="1" <%if(community.platform_id === 1) {%> checked <%}%>>
<label for="platform_id">3DS</label>
<input type="radio" id="Both" name="platform_id" value="2" <%if(community.platform_id === 2) {%> checked <%}%>>
<label for="platform_id">Both</label>
</div>
<div class="input-div choices-inputs">
Type
<input type="radio" id="main" name="type" value="0">
<label for="type">Main</label>
<input type="radio" id="sub" name="type" value="1" checked>
<label for="type">Sub</label>
<input type="radio" id="admin" name="type" value="2">
<label for="type">Announcement</label>
</div>
<div class="input-div">
<label for="parent">Parent Community</label>
<input type="text" id="parent" name="parent" value="<%=community.community_id%>">
</div>
</div>
</div>
<div class="form-section">
<div class="section-info">
<h2>Submit or Delete Community</h2>
<p></p>
</div>
<div class="section-inputs">
<div>
<button type="submit" class="btn">Submit</button>
<button type="button" class="btn" onclick="deleteCommunity()">Delete Community</button>
</div>
<iframe name="formSubmitFrame"></iframe>
</div>
</div>
</form>
</div>
</div>
<script>
function deleteCommunity() {
var confirm = prompt('Type the name of the community in to confirm you want to delete it.');
if (confirm === '<%=community.name%>') {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
alert('Community has been deleted');
window.location.replace("/communities");
}
else if (this.readyState === 4){
alert("Unable to delete community.")
}
};
xhttp.open("POST", "/v1/communities/<%=community.community_id%>/delete", true);
xhttp.send();
}
}
</script>
</body>
</html>

View File

@ -1,71 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Edit - <%= user.user_id %></title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= parentUser.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= parentUser.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Update <%= user.user_id %></h2>
<form action="/v1/users/<%= user.pid %>/update" enctype="multipart/form-data" target="formSubmitFrame" method="post">
<div class="form-section">
<div class="section-info">
<h2>Account Standing</h2>
</div>
<div class="section-inputs">
<div class="input-div choices-inputs">
Account Status
<input type="radio" id="account_status" name="account_status" value="0" <%if(user.account_status === 0) {%> checked <%}%>>
<label for="Fine">Fine</label>
<input type="radio" id="posting_limited" name="account_status" value="1" <%if(user.account_status === 1) {%> checked <%}%>>
<label for="posting_limited">Limited from Posting</label>
<input type="radio" id="temp_ban" name="account_status" value="2" <%if(user.account_status === 2) {%> checked <%}%>>
<label for="temp_ban">Temporary Ban</label>
<input type="radio" id="forever_ban" name="account_status" value="3" <%if(user.account_status === 3) {%> checked <%}%>>
<label for="forever_ban">Forever Ban</label>
</div>
<label for="ban_date">Banned Until:</label>
<input type="date" id="ban_date" name="ban_date" value="<%=moment(user.ban_lift_date).format('YYYY-MM-DD');%>">
<div class="input-div">
<label for="ban_reason">Ban reason:</label>
<textarea type="text" id="ban_reason" name="ban_reason"><%=user.ban_reason%></textarea>
</div>
</div>
</div>
<div class="form-section">
<div class="section-info">
<h2>Submit</h2>
<p></p>
</div>
<div class="section-inputs">
<div>
<button type="submit" class="btn">Submit</button>
</div>
<iframe name="formSubmitFrame"></iframe>
</div>
</div>
</form>
</div>
</div>
</body>
</html>

View File

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Home</h2>
<p>This is a temporary admin panel for Juxt that will likely be used throughout the beta, or at least until the main Pretendo Network Admin Panel is done lol.</p>
<p>If you can see this and your not a developer. Please tell Jemma how on earth you pulled it off, cause we kinda need this website to be secure :p</p>
</div>
</div>
</body>
</html>

View File

@ -1,105 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Login</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<div id="id01" class="modal">
<form class="modal-content animate" id="login" method="post" onsubmit="submitForm(); return false">
<div class="container">
<label for="user_id"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="user_id" autocomplete="username" required>
<label for="password"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" autocomplete="current-password" required>
<button>Login</button>
</div>
</form>
</div>
<script>
function submitForm() {
var xhr = new XMLHttpRequest();
xhr.open("POST", '/login');
var user_id = document.getElementsByName('user_id')[0].value;
var password = document.getElementsByName('password')[0].value;
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if(xhr.status === 200)
{
setCookie('auth', xmlToJSON.parseString(xhr.responseText).OAuth20[0].access_token[0].token[0]._text)
console.log(xmlToJSON.parseString(xhr.responseText).OAuth20[0].access_token[0].token[0]._text);
setServiceToken();
}
else
{
alert(JSON.parse(xhr.response).message);
}
}};
var data = "user_id=" + user_id + "&password=" + password;
xhr.send(data);
}
function setServiceToken() {
var xhr = new XMLHttpRequest();
xhr.open("GET", '/token');
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.setRequestHeader("X-Nintendo-Client-ID", "a2efa818a34fa16b8afbc8a74eba3eda");
xhr.setRequestHeader("X-Nintendo-Client-Secret", "c91cdb5658bd4954ade78533a339cf9a");
xhr.setRequestHeader("X-Nintendo-Title-ID", "000500301001610A");
xhr.setRequestHeader("Authorization", "Bearer " + getCookie("auth"));
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if(xhr.status === 200)
{
setCookie('token', xmlToJSON.parseString(xhr.responseText).service_token[0].token[0]._text)
console.log(xmlToJSON.parseString(xhr.responseText).service_token[0].token[0]._text);
location.assign('/');
}
else
{
console.log(xhr.response);
}
}};
xhr.send();
}
function setCookie(cname, cvalue) {
var date = new Date();
date.setTime(date.getTime() + (60 * 60 * 1000));
var expires = "expires="+ date.toUTCString();
let domain = document.location.host.indexOf('miiverse.cc') === -1 ? 'domain=pretendo.cc;' : 'domain=miiverse.cc;';
document.cookie = cname + "=" + cvalue + ";" + domain + expires + date.toGMTString();
}
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return null;
}
var xmlToJSON = function () { this.version = "1.3.4"; var e = { mergeCDATA: !0, grokAttr: !0, grokText: !0, normalize: !0, xmlns: !0, namespaceKey: "_ns", textKey: "_text", valueKey: "_value", attrKey: "_attr", cdataKey: "_cdata", attrsAsObject: !0, stripAttrPrefix: !0, stripElemPrefix: !0, childrenAsArray: !0 }, t = new RegExp(/(?!xmlns)^.*:/), r = new RegExp(/^\s+|\s+$/g); return this.grokType = function (e) { return /^\s*$/.test(e) ? null : /^(?:true|false)$/i.test(e) ? "true" === e.toLowerCase() : isFinite(e) ? parseFloat(e) : e }, this.parseString = function (e, t) { return this.parseXML(this.stringToXML(e), t) }, this.parseXML = function (a, n) { for (var s in n) e[s] = n[s]; var l = {}, i = 0, o = ""; if (e.xmlns && a.namespaceURI && (l[e.namespaceKey] = a.namespaceURI), a.attributes && a.attributes.length > 0) { var c = {}; for (i; i < a.attributes.length; i++) { var u = a.attributes.item(i); m = {}; var p = ""; p = e.stripAttrPrefix ? u.name.replace(t, "") : u.name, e.grokAttr ? m[e.valueKey] = this.grokType(u.value.replace(r, "")) : m[e.valueKey] = u.value.replace(r, ""), e.xmlns && u.namespaceURI && (m[e.namespaceKey] = u.namespaceURI), e.attrsAsObject ? c[p] = m : l[e.attrKey + p] = m } e.attrsAsObject && (l[e.attrKey] = c) } if (a.hasChildNodes()) for (var y, d, m, h = 0; h < a.childNodes.length; h++)4 === (y = a.childNodes.item(h)).nodeType ? e.mergeCDATA ? o += y.nodeValue : l.hasOwnProperty(e.cdataKey) ? (l[e.cdataKey].constructor !== Array && (l[e.cdataKey] = [l[e.cdataKey]]), l[e.cdataKey].push(y.nodeValue)) : e.childrenAsArray ? (l[e.cdataKey] = [], l[e.cdataKey].push(y.nodeValue)) : l[e.cdataKey] = y.nodeValue : 3 === y.nodeType ? o += y.nodeValue : 1 === y.nodeType && (0 === i && (l = {}), d = e.stripElemPrefix ? y.nodeName.replace(t, "") : y.nodeName, m = xmlToJSON.parseXML(y), l.hasOwnProperty(d) ? (l[d].constructor !== Array && (l[d] = [l[d]]), l[d].push(m)) : (e.childrenAsArray ? (l[d] = [], l[d].push(m)) : l[d] = m, i++)); else o || (e.childrenAsArray ? (l[e.textKey] = [], l[e.textKey].push(null)) : l[e.textKey] = null); if (o) if (e.grokText) { var x = this.grokType(o.replace(r, "")); null !== x && void 0 !== x && (l[e.textKey] = x) } else e.normalize ? l[e.textKey] = o.replace(r, "").replace(/\s+/g, " ") : l[e.textKey] = o.replace(r, ""); return l }, this.xmlToString = function (e) { try { return e.xml ? e.xml : (new XMLSerializer).serializeToString(e) } catch (e) { return null } }, this.stringToXML = function (e) { try { var t = null; return window.DOMParser ? t = (new DOMParser).parseFromString(e, "text/xml") : (t = new ActiveXObject("Microsoft.XMLDOM"), t.async = !1, t.loadXML(e), t) } catch (e) { return null } }, this }.call({}); "undefined" != typeof module && null !== module && module.exports ? module.exports = xmlToJSON : "function" == typeof define && define.amd && define(function () { return xmlToJSON });
</script>
</body>
</html>

View File

@ -1,158 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>New Community</h2>
<form action="/v1/communities/new" target="formSubmitFrame" enctype="multipart/form-data" method="post">
<div class="form-section">
<div class="section-info">
<h2>Meta information</h2>
<p>Update the name, description, system icon, and title ID's of the community</p>
</div>
<div class="section-inputs">
<div class="input-div">
<label for="name">Community Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="input-div">
<label for="description">Description:</label>
<textarea type="text" id="description" name="description" required></textarea>
</div>
<div class="input-grid">
<div class="input-div">
<label for="title_ids">Title ID 1:</label>
<input type="text" id="title_ids" name="title_ids[]" required>
</div>
<div class="input-div">
<label for="title_ids">Title ID 2:</label>
<input type="text" id="title_ids" name="title_ids[]">
</div>
<div class="input-div">
<label for="title_ids">Title ID 3:</label>
<input type="text" id="title_ids" name="title_ids[]">
</div>
<div class="input-div">
<label for="icon">System Icon (B64 TGA):</label>
<input type="text" id="icon" name="icon" required>
</div>
</div>
</div>
</div>
<div class="form-section">
<div class="section-info">
<h2>Browser Icons and Meta Data</h2>
<p>Update the browser icons for the Miiverse Applets, as well as setting the platform, and other settings</p>
</div>
<div class="section-inputs">
<div class="input-div choices-inputs do-input-cols">
<div>
Browser Icon (512px x 512px)
<input type="file" id="browserIcon" accept="image/png" name="browserIcon" required>
</div>
</div>
<hr>
<div class="input-div choices-inputs do-input-cols">
<div>
3DS Browser Banner
<input type="file" id="CTRbrowserHeader" accept="image/png" name="CTRbrowserHeader" required>
</div>
</div>
<hr>
<div class="input-div choices-inputs">
Wii U Browser Banner (1498px x 328px)
<input type="file" id="WiiUbrowserHeader" accept="image/png" name="WiiUbrowserHeader" required>
</div>
<hr>
<div class="input-div choices-inputs">
Is Recommended?
<input type="radio" id="isRecomended" name="is_recommended" value="1" required>
<label for="isRecomended">True</label>
<input type="radio" id="isNotRecomended" name="is_recommended" value="0" required>
<label for="isNotRecomended">False</label>
</div>
<div class="input-div choices-inputs">
Has Shop Page?
<input type="radio" id="hasShopPage" name="has_shop_page" value="1" required>
<label for="hasShopPage">True</label>
<input type="radio" id="noShopPage" name="has_shop_page" value="0" required>
<label for="noShopPage">False</label>
</div>
<div class="input-div choices-inputs">
Platform
<input type="radio" id="WiiU" name="platform_id" value="0" required>
<label for="platform_id">Wii U</label>
<input type="radio" id="3DS" name="platform_id" value="1" required>
<label for="platform_id">3DS</label>
<input type="radio" id="Both" name="platform_id" value="2" required>
<label for="platform_id">Both</label>
</div>
<div class="input-div choices-inputs">
Type
<input type="radio" id="main" name="type" value="0" checked>
<label for="type">Main</label>
<input type="radio" id="sub" name="type" value="1" <%if(communityID){%>checked<%}%>>
<label for="type">Sub</label>
<input type="radio" id="admin" name="type" value="2">
<label for="type">Announcement</label>
</div>
<div class="input-div">
<label for="parent">Parent Community</label>
<input type="text" id="parent" name="parent" value="<%=communityID%>">
</div>
</div>
</div>
<div class="form-section">
<div class="section-info">
<h2>Submit Community</h2>
<p></p>
</div>
<div class="section-inputs">
<div>
<button type="submit" class="btn">Submit</button>
</div>
<iframe name="formSubmitFrame"></iframe>
</div>
</div>
</form>
</div>
</div>
</body>
</html>

View File

@ -1,135 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Edit - <%= community.name %></title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>New Announcement</h2>
<form action="/v1/posts/new" class="post-emotion-radio" enctype="multipart/form-data" target="formSubmitFrame" method="post">
<table>
<tr>
<td>
<img class="post-user-icon" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<input type="hidden" id="olive_title_id" name="olive_title_id" value="<%= community.title_id[0] %>">
<input type="hidden" id="olive_community_id" name="olive_community_id" value="<%= community.community_id %>">
<input type="hidden" id="pid" name="pid" value="<%= user.pid %>">
<input type="hidden" id="_post_type" name="_post_type" value="body">
</td>
<td>
<div class="post-emotions-wrapper">
<label for="post-emotion-0">
<input type="radio" name="emotion" id="post-emotion-0" value="0" onclick="changeMiiImageReaction(this)" checked>
<span class="post-emotion-0"></span>
</label>
<label for="post-emotion-1">
<input type="radio" name="emotion" id="post-emotion-1" value="1" onclick="changeMiiImageReaction(this)">
<span class="post-emotion-1"></span>
</label>
<label for="post-emotion-2">
<input type="radio" name="emotion" id="post-emotion-2" value="2" onclick="changeMiiImageReaction(this)">
<span class="post-emotion-2"></span>
</label>
<label for="post-emotion-3">
<input type="radio" name="emotion" id="post-emotion-3" value="3" onclick="changeMiiImageReaction(this)">
<span class="post-emotion-3"></span>
</label>
<label for="post-emotion-4">
<input type="radio" name="emotion" id="post-emotion-4" value="4" onclick="changeMiiImageReaction(this)">
<span class="post-emotion-4"></span>
</label>
<label for="post-emotion-5">
<input type="radio" name="emotion" id="post-emotion-5" value="5" onclick="changeMiiImageReaction(this)">
<span class="post-emotion-5"></span>
</label>
</div>
<div class="post-emotions-arrow-left"></div>
</td>
<td>
<label class="checkbox-container checkbox-post">Spoilers
<input type="checkbox" id="spoiler" name="spoiler" value="true">
<span class="checkmark"></span>
</label>
<div class="post-screenshot-picker" onclick="loadScreenshots()">
<div class="post-screenshot-picker-icon"></div>
<!--<div class="post-screenshot-picker-dropdown">
<div><img src="" class="post-screenshot-preview" id="post-top-screen-preview" onclick="document.getElementById('screenshot-value').value = wiiuMainApplication.getScreenShot(true);"></div>
<div><img src="" class="post-screenshot-preview" id="post-bottom-screen-preview" onclick="document.getElementById('screenshot-value').value = wiiuMainApplication.getScreenShot(false);"></div>
<div onclick="document.getElementById('screenshot-value').value = ''; loadScreenshots()">No Screenshot</div>
<input type="hidden" id="screenshot-value" name="screenshot" value="">
</div>-->
</div>
</td>
</tr>
<tr>
<td rowspan="2" id="post-text-input">
<textarea class="post-textarea" id="comment" name="body" placeholder="Enter text here" rows="4" cols="50"></textarea>
</td>
<td rowspan="2" id="post-painting-input" style="display: none;">
<div class="post-memo">
<img class="post-memo-preview" id="memo" width="640px" height="240px" src="" style="display: none;" onclick="newPainting(false)">
<input type="hidden" id="memo-value" name="painting" value="">
</div>
</td>
<br>
</tr>
<tr style="height: 70px">
<td>
<input type="submit" value="Post" class="submit-button" style="margin-left: -124px;">
</td>
</tr>
</table>
</form>
<iframe name="formSubmitFrame"></iframe>
</div>
</div>
<script>
function changeMiiImageReaction(element) {
if(element.checked) {
var pfp = document.getElementsByClassName('post-user-icon')[0];
var newPfp;
switch (element.value) {
case '1':
newPfp = 'smile_open_mouth.png'
break;
case '2':
newPfp = 'wink_left.png'
break;
case '3':
newPfp = 'surprise_open_mouth.png'
break;
case '4':
newPfp = 'frustrated.png'
break;
case '5':
newPfp = 'sorrow.png'
break;
default:
newPfp = 'normal_face.png'
break;
}
pfp.src = pfp.src.substring(0, pfp.src.lastIndexOf('/') + 1) + newPfp;
}
}
</script>
</body>
</html>

View File

@ -1,139 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
<style>
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 60%;
}
th {
cursor: pointer;
}
th, td {
text-align: left;
padding: 16px;
}
tr {
border: black;
border-width: 2px;
border-style: groove;
}
</style>
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Communities</h2>
<button onclick="location.assign('/communities/<%=communityID%>/sub/new')">New</button>
<input type="text" id="search" onkeyup="search()" placeholder="Search..">
<table id="community-list">
<tbody id="search-list">
<tbody>
<tr>
<th>Icon</th>
<th onClick="sortTable(0)">Name</th>
<th onClick="sortTable(1)">Created At</th>
<th onClick="sortTable(1)">Title ID's</th>
<th onClick="sortTable(2)">Followers</th>
</tr>
<% for(var i = 0; i < communities.length; i++) { %>
<tr id="<%= communities[i].community_id %>" onclick="location.assign('/communities/' + this.id)">
<td><img style="width: 80px " src="/icons/<%= communities[i].community_id %>.png"></td>
<td><a><%= communities[i].name %></a></td>
<td><%= moment(communities[i].created_at).fromNow() %></td>
<td><%= communities[i].title_ids %></td>
<td><%= communities[i].followers %></td>
</tr>
<%}%>
</tbody>
</table>
</div>
</div>
<script>
/**
* Removes row from table
* @param id
*/
function removeRow(id) {
var row = document.getElementById(id);
row.parentNode.removeChild(row);
}
/**
* Sorts table by selected column
* @param n
*/
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("community-list");
switching = true;
dir = "asc";
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
if (dir === "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
} else if (dir === "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount ++;
} else {
if (switchcount === 0 && dir === "asc") {
dir = "desc";
switching = true;
}
}
}
}
function search() {
var input, filter, table, tr, td, i, txtValue;
input = document.getElementById("search");
filter = input.value.toUpperCase();
table = document.getElementById("community-list");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[1];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
</script>
</body>
</html>

View File

@ -1,168 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= parentUser.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= parentUser.pfp_uri %>">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<div class="community-page-info-container">
<img class="community-page-info-icon" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<h2 class="community-page-title"><%= user.user_id %></h2>
<h4 class="community-page-description"><%= user.profile_comment %></h4>
<button type="button" style="margin-left: 20px; width: 760px" onclick="window.location='/users/<%= user.pid %>/edit'">Edit</button>
<div class="community-page-margin-line"></div>
<table class="community-page-table-wrapper">
<tbody>
<tr>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Country</h4>
<h4 class="community-page-table-text"><%=user.country%></h4>
</div>
</td>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Birthday</h4>
<h4 class="community-page-table-text">N/A</h4>
</div>
</td>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Game experience</h4>
<h4 class="community-page-table-text">
<%if(user.game_skill === 0) {%>
Beginner
<%} else if(user.game_skill === 1) {%>
Intermediate
<%} else if(user.game_skill === 2) {%>
Expert
<%} else {%>
N/A
<%}%>
</h4>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="community-page-posts-wrapper">
<% if(numPosts === 0) {%>
<p>No Posts</p>
<%} else { %>
<% newPosts.forEach(function(post) { %>
<div id="<%= post.id %>">
<div class="post-user-info-wrapper" id="<%= post.id %>">
<%if(post.verified) {%>
<img class="community-page-post-user-icon verified" src="<%= mii_image_CDN %>/<%= post.pid %>/<% if(post.feeling_id === 1) {%>smile_open_mouth.png<%} else if(post.feeling_id === 2 ) {%>wink_left.png<%} else if(post.feeling_id === 3 ) {%>surprise_open_mouth.png<%} else if(post.feeling_id === 4 ) {%>frustrated.png<%} else if(post.feeling_id === 5 ) {%>sorrow.png<%} else {%>normal_face.png<%}%>" onclick="loadUserProfile(<%=post.pid%>)">
<span class="community-page-verified-user-badge community-page-verified" style=""></span>
<%} else {%>
<img class="community-page-post-user-icon" src="<%= mii_image_CDN %>/<%= post.pid %>/<% if(post.feeling_id === 1) {%>smile_open_mouth.png<%} else if(post.feeling_id === 2 ) {%>wink_left.png<%} else if(post.feeling_id === 3 ) {%>surprise_open_mouth.png<%} else if(post.feeling_id === 4 ) {%>frustrated.png<%} else if(post.feeling_id === 5 ) {%>sorrow.png<%} else {%>normal_face.png<%}%>" onclick="loadUserProfile(<%=post.pid%>)">
<span class="community-page-verified-user-badge community-page-verified" style="display: none;"></span>
<%}%>
<h2 class="community-page-post-username" onclick="loadUserProfile(<%=post.pid%>)"><%= post.screen_name %></h2>
<h4 class="community-page-post-time-stamp"><%= post.created_at %></h4>
<div class="community-page-post-yeah-button-wrapper">
<div class="community-page-post-yeah-button" onclick="deletePost(event, '<%= post.id %>')"></div>
</div>
<div id="yeah-<%= post.id %>" class="community-page-post-yeah-count"><%= post.empathy_count %> Yeahs</div>
</div>
<div class="community-page-post-wrapper">
<% if(post.body !== '' && post.painting === '' && post.screenshot === '' && !post.url) { %>
<h3><%= post.body %></h3>
<%} else { %>
<% if(post.screenshot !== '') { %>
<img class="community-page-post-screenshot" src="data:image/png;base64,<%= post.screenshot %>">
<%}%>
<% if(post.painting !== '') { %>
<img class="community-page-post-painting" src="<%= post.painting_uri %>">
<%}%>
<% if(post.url) { %>
<iframe width="760" height="427.5" src="<%= post.url %>" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<%}%>
<% if(post.body) { %>
<div class="community-page-post-text-overlay">
<h3><%= post.body %></h3>
</div>
<%}%>
<%}%>
</div>
</div>
<% }); %>
<button id="load-more-posts-button" onclick="loadUserPosts()">Load More Posts</button>
<%}%>
</div>
</div>
</div>
<script>
function deletePost(event, postID) {
if (event.shiftKey) {
var post = document.getElementById(postID);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
post.style.display = 'none';
}
else if (this.readyState === 4){
alert('Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
};
xhttp.open("POST", "/v1/posts/" + postID + '/delete', true);
xhttp.send();
} else {
if(confirm('Delete post?')) {
var post = document.getElementById(postID);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
post.style.display = 'none';
}
else if (this.readyState === 4){
alert('Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
};
xhttp.open("POST", "/v1/posts/" + postID + '/delete', true);
xhttp.send();
}
}
}
function loadUserPosts() {
var id = document.getElementsByClassName('post-user-info-wrapper')[document.getElementsByClassName('post-user-info-wrapper').length - 1].id
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
document.getElementsByClassName('community-page-posts-wrapper')[0].innerHTML += this.responseText;
}
else if(this.readyState === 4 && this.status === 204)
{
document.getElementById('load-more-posts-button').style.display = 'none';
}
else if (this.readyState === 4){
alert('Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing');
}
};
xhttp.open("GET", "/v1/users/loadPosts" + '?postID=' + id, true);
xhttp.send();
}
</script>
</body>
</html>

View File

@ -1,127 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Juxt Admin Panel</title>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
<style>
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 60%;
}
th {
cursor: pointer;
}
th, td {
text-align: left;
padding: 16px;
}
tr {
border: black;
border-width: 2px;
border-style: groove;
}
</style>
</head>
<body>
<h2 style="display: inline-block; margin-left: 20px">Juxt Admin Panel - <%= user.user_id %></h2> <img style="width: 57px; display: inline-block; position: absolute; right: 8px;" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<div class="row">
<div class="left" style="background-color:#bbb;max-width: 10%;">
<ul id="myMenu">
<li><a href="/">Home</a></li>
<li><a href="/communities">Communities</a></li>
<li><a href="/audit">Audit Log</a></li>
<li><a href="/announcements">Announcements</a></li>
<li><a href="/users">Users</a></li>
<li><a href="/discovery">Discovery</a></li>
</ul>
</div>
<div class="right" style="background-color:#ddd;">
<h2>Communities</h2>
<input type="text" id="mySearch" onkeyup="myFunction()" placeholder="Search..">
<table id="adddrop"><tbody id="search-list">
<tbody>
<tr>
<th>Icon</th>
<th onClick="sortTable(0)">Name</th>
<th onClick="sortTable(1)">Created At</th>
<th onClick="sortTable(1)">Account Status</th>
<th onClick="sortTable(2)">Followers</th>
</tr>
<% for(var i = 0; i < users.length; i++) { %>
<tr id="<%= users[i].pid %>" onclick="location.assign('/users/' + this.id)">
<td><img style="width: 80px " src="<%= mii_image_CDN %>/<%= users[i].pid %>/normal_face.png"></td>
<td><a><%= users[i].user_id %> (<%= users[i].pnid %>)</a></td>
<td><%= moment(users[i].created_at).fromNow() %></td>
<td><%= users[i].account_status %></td>
<td><%= users[i].followers %></td>
</tr>
<%}%>
</tbody>
</table>
</div>
</div>
<script>
/**
* Sorts table by selected column
* @param n
*/
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("adddrop");
switching = true;
dir = "asc";
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
if (dir === "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
} else if (dir === "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount ++;
} else {
if (switchcount === 0 && dir === "asc") {
dir = "desc";
switching = true;
}
}
}
}
function myFunction() {
var input, filter, ul, tr, a, i;
input = document.getElementById("mySearch");
filter = input.value.toUpperCase();
ul = document.getElementById("search-list");
tr = ul.getElementsByTagName("tr");
for (i = 1; i < tr.length; i++) {
a = tr[i].getElementsByTagName("a")[0];
if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,60 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3DS Testing</title>
<script src="/js/pjax.js"></script>
<script src="/js/juxt.js"></script>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<%- include('partials/head', { title: lang.all_communities.text }); %>
<body>
<div id="main">
<div class="top-screen">
<div class="header-description">
<p>
Check out the communities for the games that you play or games that you're curious about!
</p>
</div>
<div id="headers-communities-icon" class="header-icon"></div>
<h2 class="header-title">Communities</h2>
</div>
<div class="bottom-screen">
<div class="search-container">
<div id="search-icon"></div>
<input id="search-bar" type="text" placeholder="Search communities..." name="search" onclick="searchCommunities(this)" readonly>
</div>
<div class="communities-wrapper" id="popular-communities">
<h1 class="communities-header">All Communities</h1>
<table>
<tbody>
<div id="body">
<header id="header">
<h1 id="page-title" class=""><%= lang.all_communities.text %></h1>
</header>
<div class="body-content">
<div class="communities-list">
<ul class="list-content-with-icon-column" id="community-new-content">
<% for(var i = 0; i < communities.length; i++) {%>
<% if(i === communities.length - 1) { %>
<tr>
<td>
<div class="community-list-wrapper bottom" data-pjax="/communities/<%= communities[i].community_id %>/new">
<img class="community-list-icon" src="<%= communities[i].browser_thumbnail %>">
<h2 class="community-list-title"><%= communities[i].name %></h2>
<h4><%= communities[i].followers %> followers</h4>
<% var communityID = communities[i].olive_community_id; %>
<li id="<%=communityID%>">
<a href="/titles/<%=communityID%>/new" data-pjax="#body" class="scroll to-community-button">
<span class="icon-container"><img src="http://mii.olv.pretendo.cc/icons/<%=communityID%>/64.png" class="icon" alt=""></span>
<div class="body">
<div class="body-content">
<span class="community-name title"><%= communities[i].name %></span>
<br>
<span class="text"><%= communities[i].followers %> <%= lang.community.followers %></span>
</div>
</td>
</tr>
<% } else { %>
<tr>
<td>
<div class="community-list-wrapper" data-pjax="/communities/<%= communities[i].community_id %>/new">
<img class="community-list-icon" src="<%= communities[i].browser_thumbnail %>">
<h2 class="community-list-title"><%= communities[i].name %></h2>
<h4><%= communities[i].followers %> followers</h4>
</div>
</td>
</tr>
<% }} %>
</tbody>
</table>
</div>
<div class="toolbar-padding"></div>
</div>
</a>
</li>
<% } %>
</ul>
</div>
</div>
<body onload="onStart()"></body>
</body>
</html>

View File

@ -1,92 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3DS Testing</title>
<script src="/js/pjax.js"></script>
<script src="/js/juxt.js"></script>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<%- include('partials/head', { title: lang.global.communities }); %>
<body>
<div id="main">
<div class="top-screen">
<div class="header-description">
<p>
Check out the communities for the games that you play or games that you're curious about!
</p>
</div>
<div id="headers-communities-icon" class="header-icon"></div>
<h2 class="header-title">Communities</h2>
</div>
<div class="bottom-screen">
<div class="search-container">
<div id="search-icon"></div>
<input id="search-bar" type="text" placeholder="Search communities..." name="search" onclick="searchCommunities(this)" readonly>
</div>
<div class="communities-wrapper" id="popular-communities">
<h1 class="communities-header">Popular Places</h1>
<div data-pjax="/communities/all">
<button class="outlined-button" style="position: absolute;margin-left: 222px;margin-top: -28px;"><p>Show All</p></button>
<div id="body">
<header id="header" class="buttons">
<h1 id="page-title" class=""><%= lang.global.communities %></h1>
<a id="header-communities-button" class="right" href="/titles/all" data-pjax="#body"><%= lang.all_communities.text %></a>
<!--<a id="header-communities-button" class="left" href="#" data-pjax="#body" onclick="testOffline();">Test Offline Post</a>-->
</header>
<div class="body-content">
<div class="communities-list">
<div class="headline">
<h2><%= lang.all_communities.popular_places %></h2>
</div>
<table>
<tbody>
<ul class="list-content-with-icon-column" id="community-new-content">
<% for(var i = 0; i < popularCommunities.length; i++) {%>
<% if(i === popularCommunities.length - 1) { %>
<tr>
<td>
<div class="community-list-wrapper bottom" data-pjax="/communities/<%= popularCommunities[i].community_id %>/new">
<img class="community-list-icon" src="<%= cdnURL %>/icons/<%= popularCommunities[i].community_id %>.png">
<h2 class="community-list-title"><%= popularCommunities[i].name %></h2>
<h4><%= popularCommunities[i].followers %> followers</h4>
</div>
</td>
</tr>
<% } else { %>
<tr>
<td>
<div class="community-list-wrapper" data-pjax="/communities/<%= popularCommunities[i].community_id %>/new">
<img class="community-list-icon" src="<%= cdnURL %>/icons/<%= popularCommunities[i].community_id %>.png">
<h2 class="community-list-title"><%= popularCommunities[i].name %></h2>
<h4><%= popularCommunities[i].followers %> followers</h4>
</div>
</td>
</tr>
<% }} %>
</tbody>
</table>
<% var communityID = popularCommunities[i].olive_community_id; %>
<li id="<%=communityID%>">
<a href="/titles/<%=communityID%>/new" data-pjax="#body" class="scroll to-community-button">
<span class="icon-container"><img src="http://mii.olv.pretendo.cc/icons/<%=communityID%>/64.png" class="icon" alt=""></span>
<div class="body">
<div class="body-content">
<span class="community-name title"><%= popularCommunities[i].name %></span>
<br>
<span class="text"><%= popularCommunities[i].followers %> <%= lang.community.followers %></span>
</div>
</div>
</a>
</li>
<% } %>
</ul>
<div class="headline headline-green">
<h2><%= lang.all_communities.new_communities %></h2>
</div>
<ul class="list-content-with-icon-column" id="community-top-content">
<% for(var j = 0; j < newCommunities.length; j++) {%>
<% var communityID = newCommunities[j].olive_community_id; %>
<li id="<%=communityID%>">
<a href="/titles/<%=communityID%>/new" data-pjax="#body" class="scroll to-community-button">
<span class="icon-container"><img src="http://mii.olv.pretendo.cc/icons/<%=communityID%>/64.png" class="icon" alt=""></span>
<div class="body">
<div class="body-content">
<span class="community-name title"><%= newCommunities[j].name %></span>
<br>
<span class="text"><%= newCommunities[j].followers %> <%= lang.community.followers %></span>
</div>
</div>
</a>
</li>
<% } %>
</ul>
</div>
<div class="communities-wrapper" id="new-communities">
<h1 class="communities-header">New Communities</h1>
<table>
<tbody>
<% for(var i = 0; i < newCommunities.length; i++) {%>
<% if(i === newCommunities.length - 1) { %>
<tr>
<td>
<div class="community-list-wrapper bottom" data-pjax="/communities/<%= newCommunities[i].community_id %>/new">
<img class="community-list-icon" src="<%= newCommunities[i].browser_thumbnail %>">
<h2 class="community-list-title"><%= newCommunities[i].name %></h2>
<h4><%= newCommunities[i].followers %> followers</h4>
</div>
</td>
</tr>
<% } else { %>
<tr>
<td>
<div class="community-list-wrapper" data-pjax="/communities/<%= newCommunities[i].community_id %>/new">
<img class="community-list-icon" src="<%= newCommunities[i].browser_thumbnail %>">
<h2 class="community-list-title"><%= newCommunities[i].name %></h2>
<h4><%= newCommunities[i].followers %> followers</h4>
</div>
</td>
</tr>
<% }} %>
</tbody>
</table>
</div>
<div class="toolbar-padding"></div>
</div>
</div>
<body onload="onStart()"></body>
</body>
<body onload="stopLoading();">
</html>

View File

@ -1,167 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3DS Testing</title>
<script src="/js/pjax.js"></script>
<script src="/js/juxt.js"></script>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<%- include('partials/head', { title: community.name }); %>
<body>
<div id="main">
<div class="top-screen" <%if(community.CTR_browser_header) {%>style="background: url('<%=community.CTR_browser_header%>')"<%}%>>
<div class="header-description">
<p>
<% community.description %>
</p>
</div>
<div id="" class="header-icon"><img class="community-page-info-icon" src="<%= cdnURL %>/icons/<%= community.community_id %>.png"></div>
<h2 class="header-title"><%= community.name %></h2>
</div>
<div class="bottom-screen">
<div id="windowOverlay" class="overlay" style="margin-bottom: 0">
<div class="overlay-content" style="position: fixed">
<div id="post-window" class="post-wrapper">
<!--Start of Main Post-->
<form class="post-emotion-radio" action="/posts/new" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>
<div class="post-emotions-wrapper">
<label for="post-emotion-0">
<input type="radio" name="emotion" id="post-emotion-0" value="0" checked onclick="selectEmotion(this)">
<span class="selected-post-emotion-0"></span>
</label>
<label for="post-emotion-1">
<input type="radio" name="emotion" id="post-emotion-1" value="1" onclick="selectEmotion(this)">
<span class="post-emotion-1"></span>
</label>
<label for="post-emotion-2">
<input type="radio" name="emotion" id="post-emotion-2" value="2" onclick="selectEmotion(this)">
<span class="post-emotion-2"></span>
</label>
<label for="post-emotion-3">
<input type="radio" name="emotion" id="post-emotion-3" value="3" onclick="selectEmotion(this)">
<span class="post-emotion-3"></span>
</label>
<label for="post-emotion-4">
<input type="radio" name="emotion" id="post-emotion-4" value="4" onclick="selectEmotion(this)">
<span class="post-emotion-4"></span>
</label>
<label for="post-emotion-5">
<input type="radio" name="emotion" id="post-emotion-5" value="5" onclick="selectEmotion(this)">
<span class="post-emotion-5"></span>
</label>
</div>
<input type="hidden" id="olive_title_id" name="olive_title_id" value="<%= community.title_id[0] %>">
<input type="hidden" id="olive_community_id" name="olive_community_id" value="<%= community.community_id %>">
<input type="hidden" id="_post_type" name="_post_type" value="body">
</td>
<td>
<div class="post-screenshot-picker" onclick="loadScreenshots()">
<div class="post-screenshot-picker-icon"></div>
<div class="post-screenshot-picker-dropdown">
<div><img src="" class="post-screenshot-preview" id="post-top-screen-preview" onclick="document.getElementById('screenshot-value').value = cave.capture_getUpperImageLeftDetail();"></div>
<div><img src="" class="post-screenshot-preview" id="post-bottom-screen-preview" onclick="document.getElementById('screenshot-value').value = cave.capture_getLowerImage();"></div>
<div onclick="document.getElementById('screenshot-value').value = ''; loadScreenshots()">No Screenshot</div>
<input type="hidden" id="screenshot-value" name="screenshot" value="">
</div>
</div>
</td>
<td>
</td>
</tr>
<tr>
<td>
<div class="post-type-button-text selected" onclick="swapPostType(0)"></div>
</td>
<td rowspan="2" id="post-text-input">
<textarea class="post-textarea" id="comment" name="body" placeholder="Tap here to make a post." rows="4" cols="50" onchange="if(cave.checkWord(this.value) === -2) { this.value = ''; alert('Post cannot contain explicit language');}"></textarea>
</td>
<td rowspan="2" id="post-painting-input" style="display: none;">
<div class="post-memo">
<img class="post-memo-preview" id="memo" width="170px" height="77px" src="" style="display: none;" onclick="newPainting(false)">
<input type="hidden" id="memo-value" name="painting" value="">
</div>
</td>
<br>
</tr>
<tr>
<td>
<div class="post-type-button-painting" onclick="swapPostType(1);newPainting(false)"></div>
</td>
</tr>
<tr>
<td colspan="2">
<label class="checkbox-container checkbox-post">Spoilers
<input type="checkbox" id="spoiler" name="spoiler" value="true" onclick="cave.snd_playSe('SE_OLV_OK');">
<span class="checkmark"></span>
</label>
</td>
</tr>
<tr style="height: 70px">
<td class="post-close-button-wrapper" colspan="2">
<div class="post-close-button" onclick="hideNewPostScreen();cave.memo_clear();">
<p style="margin-top: -2px;">X <b>Close</b></p>
</div>
</td>
<td>
<input type="submit" value="Post" class="submit-button" onclick="cave.snd_playSe('SE_OLV_OK');">
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
<div id="community-related-wrapper">
<div class="related-button-text">
Related
</div>
</div>
<%if(user.followed_communities.indexOf(community.id) !== -1){ %>
<div class="community-page-follow-button selected" id="<%= community.community_id %>" onclick="followCommunity(this)">
<p class="community-page-follow-button-text" style="color: #FFFFFF">Following</p>
</div>
<%} else {%>
<div class="community-page-follow-button" id="<%= community.community_id %>" onclick="followCommunity(this)" <%if(user.pid === 1000000000) {%> style="display: none" <%}%>>
<p class="community-page-follow-button-text">Follow</p>
</div>
<div id="body">
<header id="header" style="background: url('http://mii.olv.pretendo.cc/headers/<%= community.parent ? community.parent : community.olive_community_id %>/3DS.png')">
<h1 id="page-title" class="community">
<span>
<span class="icon-container">
<img src="http://mii.olv.pretendo.cc/icons/<%= community.parent ? community.parent : community.olive_community_id %>/64.png" class="icon">
</span>
<span class="community-name">
<%= community.name %>
</span>
<span class="text">
<span>
<span class="sprite posts"></span>
<%= bundle.numPosts %>
</span>
<span>|
<span class="sprite followers"></span>
<span id="followers"><%= community.followers %></span>
</span>
</span>
</span>
</span>
</h1>
<% if(((community.open && community.type < 2) || (community.admins && community.admins.indexOf(pid) !== -1)) && userSettings.pid !== 1000000000 && userSettings.account_status === 0) {%>
<a id="header-post-button" class="header-button left" href="#"
data-sound="SE_WAVE_SELECT_TAB" data-module-hide="community-post-list"
data-module-show="add-post-page" data-header="false" data-screenshot="true"
data-message="<%= lang.new_post.post_to %> <%= community.name %>">Post + </a>
<%}%>
<button class="community-page-new-post-button" onclick="showNewPostScreen()">New Post</button>
<div class="community-page-posts-wrapper">
<table class="community-page-posts-header">
<tbody>
<tr>
<td>
<h4 id="recent-tab" onclick="loadPosts(0)" class="community-page-posts-header-tab active">Recent posts</h4>
</td>
<td>
<h4 id="popular-tab" onclick="loadPosts(1)" class="community-page-posts-header-tab">Popular posts</h4>
</td>
<td>
<h4 id="verified-tab" onclick="loadPosts(2)" class="community-page-posts-header-tab">Verified posts</h4>
</td>
</tr>
</tbody>
</table>
<div id="wrapper">
<% if(totalNumPosts === 0) {%>
<p class="no-posts-text">No Posts</p>
<%} else { %>
<button id="load-more-posts-button" data-offset="20" onclick="loadCommunityPosts(this)"><%= lang.global.more %></button>
<% newPosts.forEach(function(post) { %>
<%- include('post_template', { post: post, mii_image_CDN: mii_image_CDN, lang: lang, reply: false }); %>
<% }); %>
<%}%>
</div>
<%if(children){%>
<a id="header-communities-button" class="right" href="/titles/<%= community.olive_community_id %>/related" data-pjax="#body">Related Communities</a>
<%}%>
<%if(community.open){%>
<button type="button" class="submit follow yeah-button <%if(children) {%>suggested<%}%> <%if(userContent.followed_communities.indexOf(community.olive_community_id) !== -1){ %>selected<%}%>" onclick="follow(this)" data-sound="SE_WAVE_CHECKBOX_UNCHECK" data-url="/titles/follow" data-community-id="<%= community.olive_community_id %>">
<span class="sprite yeah"></span>
</button>
<%}%>
</header>
<div class="body-content tab2-content" id="community-post-list">
<div class="community-info info-content with-header-banner">
</div>
<menu class="tab-header">
<li id="tab-header-post" class="tab-button <%if(type === 0){ %>selected<%}%>">
<a href="/titles/<%= community.olive_community_id %>/new" data-sound="SE_WAVE_SELECT_TAB"><span class="new-post"><%= lang.community.recent %></span></a>
</li>
<li id="tab-header-hot-post" class="tab-button <%if(type === 1){ %>selected<%}%>"><a href="/titles/<%= community.olive_community_id %>/hot" data-sound="SE_WAVE_SELECT_TAB"><span><%= lang.community.popular %></span></a></li>
</menu>
<div class="tab-body post-list">
<%- include('partials/' + template, { bundle }); %>
</div>
<div class="toolbar-padding"></div>
</div>
<body onload="onStart()"></body>
<%- include('partials/new_post', { pid, lang, id: community.olive_community_id, name: community.name, url: '/posts/new', show: 'community-post-list', message_pid: '' }); %>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

1
src/webfiles/ctr/css/juxt.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<%- include('partials/head', { title: 'Error: ' + code }); %>
<body>
<div id="body">
<header id="header" class="">
<h1 id="page-title" class="">Error <%= code %>: <%= message %></h1>
</header>
<div class="body-content tab2-content" id="community-post-list">
<p>Whoops! Looks like we couldn't find the page you're looking for.</p>
<p>Double-check your link or try again later</p>
</div>
</div>
</body>
</html>

View File

@ -1,39 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3DS Testing</title>
<script src="/js/pjax.js"></script>
<script src="/js/juxt.js"></script>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<%- include('partials/head', { title: title }); %>
<body>
<div id="main">
<div class="top-screen">
<div class="header-description">
<p>
Here you can view posts and more from the users you follow.
</p>
<div id="body">
<header id="header" class="">
<h1 id="page-title" class=""><%= title %></h1>
</header>
<div class="body-content tab2-content" id="community-post-list">
<div class="tab-body post-list">
<%- include('partials/' + template, { bundle }); %>
</div>
<div id="headers-activity-feed-icon" class="header-icon"></div>
<h2 class="header-title"><%= lang.global.activity_feed %></h2>
</div>
<div class="bottom-screen">
<div class="community-page-posts-wrapper">
<div id="wrapper">
<% if(posts.length === 0) {%>
<p class="no-posts-text"><%= lang.activity_feed.empty %></p>
<%} else { %>
<button id="load-more-posts-button" data-offset="20" onclick="loadFeedPosts(this)"><%= lang.global.more %></button>
<% posts.forEach(function(post) { %>
<%- include('post_template', { post: post, mii_image_CDN: mii_image_CDN, lang: lang, reply: false }); %>
<% }); %>
<%}%>
</div>
</div>
<div class="toolbar-padding"></div>
</div>
<body onload="onStart()"></body>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
src/webfiles/ctr/js/juxt.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,57 +1,84 @@
/* pjax CTR
Written By: Jemma
*/
var hist = [window.location];
var Pjax = {
elements: null,
selectors: null,
href: null,
history: [],
events: {
PjaxRequest: document.createEvent('Event'),
PjaxLoaded: document.createEvent('Event'),
PjaxDone: document.createEvent('Event')
},
init: function(init) {
this.elements = init.elements;
this.selectors = init.selectors;
this.href = document.location.href;
window.pjax = {
loadUrl: function(url) {
cave.transition_begin();
this.events.PjaxRequest.initEvent('PjaxRequest', true, true);
this.events.PjaxLoaded.initEvent('PjaxLoaded', true, true);
this.events.PjaxDone.initEvent('PjaxDone', true, true);
return this;
},
refresh: function() {
var els = document.querySelectorAll(this.elements);
if (!els) return;
console.log(this.elements);
console.log(els);
for (var i = 0; i < els.length; i++) {
els[i].addEventListener("click", function (e) { pageWrapper(e, this) });
}
},
loadUrl: function (url, push) {
if(!this.elements || !this.selectors) return;
document.dispatchEvent(Pjax.events.PjaxRequest);
this.get(url, this.parseDom);
if(!push && Pjax.href.indexOf(url) === -1)
Pjax.history.push(Pjax.href);
console.log(url)
},
get: function(url, callback) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
hist.push(url);
document.getElementById("main").innerHTML = this.responseText;
cave.transition_end();
onPageLoad();
}
else if(this.readyState === 4) {
cave.error_callFreeErrorViewer(5983000 + this.status, 'Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing ' + this.readyState);
cave.transition_end();
if(this.readyState === 4) {
document.dispatchEvent(Pjax.events.PjaxLoaded);
this.responseURL = url;
return callback(this);
}
};
xhttp.open("GET", url, true);
xhttp.send();
},
parseDom: function(data) {
var response = data.responseText;
if(response && data.status === 200) {
var html = document.implementation.createHTMLDocument('');
html.documentElement.innerHTML = response;
for(var i = 0; i < Pjax.selectors.length; i++) {
var newElement = html.querySelector(Pjax.selectors[i]);
var oldElement = document.querySelector(Pjax.selectors[i]);
if(!newElement || !oldElement) continue;
oldElement.outerHTML = newElement.outerHTML;
}
console.log(data);
Pjax.refresh();
Pjax.href = data.responseURL;
document.dispatchEvent(Pjax.events.PjaxDone);
}
},
canGoBack: function() {
return this.history.length >= 1;
},
back: function() {
if(!this.canGoBack())
return;
cave.transition_begin();
hist.pop();
var url = hist[hist.length - 1]
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
document.getElementById("main").innerHTML = this.responseText;
cave.transition_end();
onPageLoad();
}
else if(this.readyState === 4) {
cave.error_callFreeErrorViewer(5983000 + this.status, 'Error: "' + this.statusText + '"\nPlease send code to Jemma on Discord with what you were doing ' + this.readyState);
cave.transition_end();
}
};
xhttp.open("GET", url, true);
xhttp.send();
},
canGoBack: function () {
//alert(hist.length);
if(hist.length <= 1) {
cave.toolbar_setButtonType(0);
return false;
}
else {
cave.toolbar_setButtonType(1);
return true;
}
var url = this.history.pop();
this.loadUrl(url, true);
}
};
}
function pageWrapper(e, element) {
e.preventDefault();
Pjax.loadUrl(element.href);
return false;
}

1
src/webfiles/ctr/js/pjax.min.js vendored Normal file
View File

@ -0,0 +1 @@
var Pjax={elements:null,selectors:null,href:null,history:[],events:{PjaxRequest:document.createEvent("Event"),PjaxLoaded:document.createEvent("Event"),PjaxDone:document.createEvent("Event")},init:function(init){this.elements=init.elements;this.selectors=init.selectors;this.href=document.location.href;this.events.PjaxRequest.initEvent("PjaxRequest",true,true);this.events.PjaxLoaded.initEvent("PjaxLoaded",true,true);this.events.PjaxDone.initEvent("PjaxDone",true,true);return this},refresh:function(){var els=document.querySelectorAll(this.elements);if(!els)return;console.log(this.elements);console.log(els);for(var i=0;i<els.length;i++){els[i].addEventListener("click",function(e){pageWrapper(e,this)})}},loadUrl:function(url,push){if(!this.elements||!this.selectors)return;document.dispatchEvent(Pjax.events.PjaxRequest);this.get(url,this.parseDom);if(!push&&Pjax.href.indexOf(url)===-1)Pjax.history.push(Pjax.href);console.log(url)},get:function(url,callback){var xhttp=new XMLHttpRequest;xhttp.onreadystatechange=function(){if(this.readyState===4){document.dispatchEvent(Pjax.events.PjaxLoaded);this.responseURL=url;return callback(this)}};xhttp.open("GET",url,true);xhttp.send()},parseDom:function(data){var response=data.responseText;if(response&&data.status===200){var html=document.implementation.createHTMLDocument("");html.documentElement.innerHTML=response;for(var i=0;i<Pjax.selectors.length;i++){var newElement=html.querySelector(Pjax.selectors[i]);var oldElement=document.querySelector(Pjax.selectors[i]);if(!newElement||!oldElement)continue;oldElement.outerHTML=newElement.outerHTML}console.log(data);Pjax.refresh();Pjax.href=data.responseURL;document.dispatchEvent(Pjax.events.PjaxDone)}},canGoBack:function(){return this.history.length>=1},back:function(){if(!this.canGoBack())return;var url=this.history.pop();this.loadUrl(url,true)}};function pageWrapper(e,element){e.preventDefault();Pjax.loadUrl(element.href);return false}

View File

@ -1,76 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3DS Testing</title>
<script src="/js/pjax.js"></script>
<script src="/js/juxt.js"></script>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<body>
<div id="main">
<div class="top-screen">
<div class="header-description">
<p>
</p>
</div>
<div id="" class="header-icon"><img class="community-page-info-icon" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png"></div>
<h2 class="header-title"><%= user.user_id %></h2>
</div>
<div class="bottom-screen">
<%if(user.profile_comment_visibility && user.profile_comment){%>
<div class="communities-wrapper" id="popular-communities" style="margin-top: 10px">
<p class="communities-header"><%=user.profile_comment%></p>
</div>
<%}%>
<table class="community-page-table-wrapper">
<tbody>
<tr>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Country</h4>
<h4 class="community-page-table-text"><%if(user.country_visibility){%><%=user.country%><%}else {%>Private<%}%></h4>
</div>
</td>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Birthday</h4>
<h4 class="community-page-table-text"><%if(user.birthday_visibility){%><%=moment.utc(user.birthday).format("MMM Do")%><%}else {%>Private<%}%></h4>
</div>
</td>
</tr>
<tr>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Game experience</h4>
<h4 class="community-page-table-text">
<%if(user.game_skill === 0) {%>
Beginner
<%} else if(user.game_skill === 1) {%>
Intermediate
<%} else if(user.game_skill === 2) {%>
Expert
<%} else {%>
N/A
<%}%>
</h4>
</div>
</td>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Yeahs</h4>
<h4 class="community-page-table-text">N/A</h4>
</div>
</td>
</tr>
</tbody>
</table>
<div class="communities-wrapper" id="popular-communities">
<h1 class="communities-header">User Posts</h1>
</div>
<div class="toolbar-padding"></div>
</div>
<body onload="onStart()"></body>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<%- include('partials/head', { title: lang.global.messages }); %>
<body>
<div id="body">
<header id="header" class="buttons">
<h1 id="page-title" class=""><%= userMap.get(user2.pid) %></h1>
<a id="header-post-button" class="header-button left" href="#"
data-sound="SE_WAVE_SELECT_TAB" data-module-hide="message-page"
data-module-show="add-post-page" data-header="false" data-screenshot="true"
data-message="Message to <%= userMap.get(user2.pid) %>">Post + </a>
</header>
<div class="body-content message-post-list" id="message-page">
<% messages.forEach(function(message) { %>
<div id="message-<%= message.id %>" class="post scroll <%if(message.pid === pid) {%>my-post<%} else {%>other-post<%}%>">
<a href="/users/show?pid=<%= message.pid %>" data-pjax="#body" class="scroll-focus mii-icon-container">
<img src="<%= message.mii_face_url.replace('https:', 'http:') %>" class="mii-icon">
</a>
<header>
<span class="timestamp"><%= moment(message.created_at).fromNow()%></span>
</header>
<div class="post-body">
<%if(message.screenshot) {%>
<img class="message-viewer-bubble-sent-screenshot" src="http://mii.olv.pretendo.cc<%= message.screenshot %>">
<%}%>
<%if(message.painting) {%>
<img class="message-viewer-bubble-sent-memo" src="http://mii.olv.pretendo.cc/paintings/<%=message.pid%>/<%=message.id%>.png">
<%}else {%>
<p class="post-content"><%= message.body %></p>
<%}%>
</div>
</div>
<%});%>
</div>
<%- include('partials/new_post', { pid, lang, id: conversation.id, name: userMap.get(user2.pid), url: '/friend_messages/new', show: 'message-page', message_pid: user2.pid }); %>
<img src="" onerror="cave.toolbar_setActiveButton(4);setTimeout(function() { window.scrollTo(0, 500000); }, 1000)">
</div>
</body>
</html>

View File

@ -0,0 +1,33 @@
<ul class="list-content-with-icon-column arrow-list" id="news-list-content">
<% if(conversations.length === 0) {%>
<p class="no-posts-text"><%= lang.messages.coming_soon %></p>
<%} else { %>
<% conversations.forEach(function(conversation) { %>
<%
// get the PID of the opposite user
var userObj, me;
if(conversation.users[0].pid === pid) {
userObj = conversation.users[1];
me = conversation.users[0];
}
else if(conversation.users[1].pid === pid) {
userObj = conversation.users[0];
me = conversation.users[1];
}
%>
<li>
<a href="/users/<%= userObj.pid %>" data-pjax="#body" class="icon-container notify <%if(userObj.official) {%> verified<%}%>">
<img src="http://mii.olv.pretendo.cc/mii/<%= userObj.pid %>/normal_face.png" class="icon">
</a>
<a href="/friend_messages/<%= conversation.id %>" data-pjax="#body" class="arrow-button"></a>
<div class="body message">
<p class="">
<span class="nick-name"><%= usersMap.get(userObj.pid) %></span>
<span class="id-name">@<%= usersMap.get(userObj.pid) %></span>
<span> <%= conversation.message_preview %></span>
<span class="timestamp"> <%= moment(conversation.last_updated).fromNow() %></span>
</p>
</div>
</li>
<% });} %>
</ul>

View File

@ -1,58 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3DS Testing</title>
<script src="/js/pjax.js"></script>
<script src="/js/juxt.js"></script>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<%- include('partials/head', { title: 'Notifications and Messages' }); %>
<body>
<div id="main">
<div class="top-screen">
<div class="header-description">
<p>
Here you can view notifications. You'll get notifications when someone follows you, or replies to your post.
</p>
<div id="body">
<header id="header" class="">
<h1 id="page-title" class="">Notifications and Messages</h1>
</header>
<div class="body-content tab2-content" id="news-page">
<menu class="tab-header no-margin">
<li id="tab-header-my-news" class="tab-button <%if(selection === 0){ %>selected<%}%>" data-show-post-button="1">
<a href="/news/my_news" data-pjax-replace="1" data-sound="SE_WAVE_SELECT_TAB">
<span class="new-post">Updates</span>
</a>
</li>
<li id="tab-header-friend-request" class="tab-button <%if(selection === 1){ %>selected<%}%>">
<a href="/friend_messages" data-pjax-cache-container="#body" data-pjax-replace="1" data-sound="SE_WAVE_SELECT_TAB">
<span><%= lang.global.messages %></span>
</a>
</li>
</menu>
<div class="tab-body">
<%- include('partials/' + template, { bundle }); %>
</div>
<div id="headers-news-icon" class="header-icon"></div>
<h2 class="header-title">Notifications</h2>
</div>
<div class="bottom-screen">
<div id="messages-list-wrapper">
<table cellspacing="0">
<tbody id="messages-list">
<% if(user.notification_list.length === 0) {%>
<p class="no-posts-text"><%= lang.notifications.none %></p>
<%} else { %>
<% for(let i = user.notification_list.length - 1; i >= 0; i--) { %>
<% var content; if(user.notification_list[i].content.indexOf('NEW_FOLLOWER') !== -1) content = user.notification_list[i].content.replace('NEW_FOLLOWER', lang.notifications.new_follower); else content = user.notification_list[i].content %>
<% if(i === user.notification_list.length - 1) { %>
<tr class="message-wrapper" data-pjax="<%=user.notification_list[i].link%>">
<td class="messages-unread-badge-wrapper top">
<%if(!user.notification_list[i].read) {%><div class="unread-badge"></div><%}%>
</td>
<td class="messages-list-wrapper top">
<h3 class="notifications-list-content"><%= content %><span style="color: rgba(113,141,148,1)"> - <%= moment(user.notification_list[i].created_at).fromNow() %></span></h3>
</td>
</tr>
<%} else {%>
<tr class="message-wrapper" data-pjax="<%=user.notification_list[i].link%>">
<td class="messages-unread-badge-wrapper">
<%if(!user.notification_list[i].read) {%><div class="unread-badge"></div><%}%>
</td>
<td class="messages-list-wrapper">
<h3 class="notifications-list-content"><%= content %><span style="color: rgba(113,141,148,1)"> - <%= moment(user.notification_list[i].created_at).fromNow() %></span></h3>
</td>
</tr>
<%}%>
<%}%>
<%}%>
</tbody>
</table>
</div>
<div class="toolbar-padding"></div>
</div>
<body onload="onStart()"></body>
</div>
</body>
</html>

View File

@ -0,0 +1,27 @@
<div class="communities-list">
<ul class="list-content-with-icon-column" id="community-new-content">
<% bundle.followers.forEach(function(user) { %>
<%if(user.pid === 0) return %>
<li id="<%= user.pid %>">
<span class="icon-container"><img src="http://mii.olv.pretendo.cc/mii/<%= user.pid %>/normal_face.png" class="icon" alt=""></span>
<a href="/users/show?pid=<%= user.pid %>" data-pjax="#body" class="scroll to-community-button full"></a>
<div class="body">
<div class="body-content">
<span class="community-name title"><%= user.screen_name %></span>
</div>
</div>
</li>
<% }); %>
<% bundle.communities.forEach(function(community) { %>
<li id="<%= community %>">
<span class="icon-container"><img src="http://mii.olv.pretendo.cc/icons/<%= community %>/128.png" class="icon" alt=""></span>
<a href="/titles/<%= community %>/new" data-pjax="#body" class="scroll to-community-button full"></a>
<div class="body">
<div class="body-content">
<span class="community-name title"><%= bundle.communityMap.get(community) %></span>
</div>
</div>
</li>
<% }); %>
</ul>
</div>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="/css/juxt.min.css">
<script src="/js/debug.js"></script>
<script src="/js/pjax.min.js"></script>
<script src="/js/juxt.min.js"></script>
<title><%= title %></title>
</head>

View File

@ -0,0 +1,86 @@
<div id="add-post-page" class="add-post-page official-user-post">
<header id="header">
<h1 id="page-title" class=""><%= lang.new_post.post_to %> <%= name %></h1>
</header>
<form method="post" action="<%= url %>" id="posts-form" data-is-own-title="1" data-is-identified="1">
<input type="hidden" name="community_id" value="<%= id %>"/>
<input type="hidden" name="bmp" value="true"/>
<div class="add-post-page-content">
<div class="feeling-selector expression">
<img src="http://mii.olv.pretendo.cc/mii/<%= pid %>/normal_face.png" id="mii-face" class="icon">
<ul class="buttons">
<li><input type="radio" name="feeling_id" value="0" class="feeling-button-normal"
data-mii-face-url="http://mii.olv.pretendo.cc/mii/<%= pid %>/normal_face.png" checked
data-sound="SE_WAVE_MII_FACE_00">
</li>
<li>
<input type="radio" name="feeling_id" value="1" class="feeling-button-happy"
data-mii-face-url="http://mii.olv.pretendo.cc/mii/<%= pid %>/smile_open_mouth.png"
data-sound="SE_WAVE_MII_FACE_01">
</li>
<li>
<input type="radio" name="feeling_id" value="2" class="feeling-button-like"
data-mii-face-url="http://mii.olv.pretendo.cc/mii/<%= pid %>/wink_left.png"
data-sound="SE_WAVE_MII_FACE_02">
</li>
<li>
<input type="radio" name="feeling_id" value="3" class="feeling-button-surprised"
data-mii-face-url="http://mii.olv.pretendo.cc/mii/<%= pid %>/surprise_open_mouth.png"
data-sound="SE_WAVE_MII_FACE_03">
</li>
<li>
<input type="radio" name="feeling_id" value="4" class="feeling-button-frustrated"
data-mii-face-url="http://mii.olv.pretendo.cc/mii/<%= pid %>/frustrated.png"
data-sound="SE_WAVE_MII_FACE_04">
</li>
<li>
<input type="radio" name="feeling_id" value="5" class="feeling-button-puzzled"
data-mii-face-url="http://mii.olv.pretendo.cc/mii/<%= pid %>/sorrow.png"
data-sound="SE_WAVE_MII_FACE_05">
</li>
</ul>
</div>
<div class="image-selector dropdown">
<button id="screenshot-button" type="button" data-toggle="dropdown" class="dropdown-toggle" data-sound="SE_WAVE_BALLOON_OPEN"
onclick="window.alert(cave.capture_getLowerImage())">
<img class="preview-image sprite" src="">
</button>
<input id="screenshot-value" type="hidden" name="screenshot" value="">
</div>
<div class="textarea-container textarea-with-menu active-text">
<menu class="textarea-menu">
<li class="textarea-menu-text">
<span class="sprite text-input selected" id="text-sprite"></span>
<input type="radio" name="_post_type" value="body" checked data-sound=""
onclick="newText()">
<textarea name="body" id="post-text" class="textarea-text selected" value="" maxlength="280"
placeholder="Share your thoughts in a post to a community or to your followers."></textarea>
</li>
<li class="textarea-menu-memo">
<span class="sprite memo" id="memo-sprite"></span>
<input type="radio" name="_post_type" value="painting" data-sound=""
onclick="newPainting(false)">
<div class="textarea-memo" id="post-memo" data-sound="" onclick="newPainting(false)">
<img id="memo" class="textarea-memo-preview" src="">
<input id="memo-value" type="hidden" name="painting">
</div>
</li>
</menu>
</div>
<div class="checkbox-container spoiler-button">
<span>
<label for="spoiler">Spoiler</label>
<input type="checkbox" id="spoiler" name="spoiler" value="true">
</span>
</div>
</div>
<input id="message_to_pid" type="hidden" name="message_to_pid"
<% if(message_pid) { %>value="<%= message_pid %><% } %>">
<input id="close-modal-button" type="button" class="olv-modal-close-button fixed-bottom-button left"
value="Cancel" data-sound="SE_WAVE_CANCEL"
data-module-show="<%= show %>" data-module-hide="add-post-page"
data-header="true">
<input type="submit" id="submit" class="post-button fixed-bottom-button" value="Post"
onclick="wiiuBrowser.lockUserOperation(true);">
</form>
</div>

View File

@ -0,0 +1,37 @@
<ul class="list-content-with-icon-column arrow-list" id="news-list-content">
<%if(!bundle.notifications) {%><li><p><%= lang.notifications.none %></p></li><%}%>
<%if(bundle.notifications){%>
<% for(var notification of bundle.notifications) {%>
<% if(notification.type === 'follow') {%>
<li>
<a href="/users/<%= notification.objectID %>" data-pjax="#body" class="icon-container notify">
<img src="http://mii.olv.pretendo.cc/mii/<%= notification.objectID %>/normal_face.png" class="icon">
</a>
<div class="body">
<% if(notification.users.length === 1) {%>
<p class=""><span class="nick-name"><%= bundle.userMap.get(Number(notification.objectID)) %></span>
<a href="<%= notification.link %>" class="link"> <%= lang.notifications.new_follower %></a>
<span class="timestamp"> <%= moment(notification.lastUpdated).fromNow() %></span>
</p>
<%} else if(notification.users.length === 2) {%>
<p class="">
<span class="nick-name"><%= bundle.userMap.get(Number(notification.objectID)) %></span>
<span>and <span class="nick-name"><%= bundle.userMap.get(notification.users[0].user) %></span></span>
<a href="<%= notification.link %>" class="link"> <%= lang.notifications.new_follower %></a>
<span class="timestamp"> <%= moment(notification.lastUpdated).fromNow() %></span>
</p>
<%} else if(notification.users.length > 2) {%>
<p class="">
<span class="nick-name"><%= bundle.userMap.get(Number(notification.objectID)) %></span>,
<span class="nick-name"><%= bundle.userMap.get(notification.users[0].user) %></span>,
<span>and <span class="nick-name"><%= notification.users.length - 2%> other(s)</span></span>
<a href="<%= notification.link %>" class="link"> <%= lang.notifications.new_follower %></a>
<span class="timestamp"> <%= moment(notification.lastUpdated).fromNow() %></span>
</p>
<%}%>
</div>
</li>
<%}%>
<%}%>
<%}%>
</ul>

View File

@ -0,0 +1,72 @@
<div id="post-<%= post.id %>" class="post <%if(reply) {%>reply<%}%> <%if(post.is_spoiler) {%>spoiler<%}%>">
<a href="/users/show?pid=<%= post.pid %>" class="mii-icon-container" data-pjax="#body">
<img src="<%= post.mii_face_url.replace('https:', 'http:') %>" class="mii-icon">
</a>
<div class="post-body-content">
<div class="post-body <%if(userContent.likes && userContent.likes.indexOf(post.id) !== -1){ %> yeah <%}%>" id="<%= post.id %>">
<header>
<span class="screen-name"><%= post.screen_name %></span>
<span class="timestamp">- <%= moment(post.created_at).fromNow() %></span>
<%if(post.topic_tag) {%>
<a href="/topics?topic_tag=<%=post.topic_tag%>" data-pjax="#body">
<span>
<span class="sprite tag"></span>
<span class="tags"><%=post.topic_tag%></span>
</span>
</a>
<%}%>
</header>
<%if(!reply) {%>
<a href="/titles/<%= post.community_id %>" class="community-banner" data-pjax="#body">
<span class="title-icon-container" data-pjax="#body">
<img src="http://mii.olv.pretendo.cc/icons/<%= post.community_id %>/32.png" class="title-icon">
</span>
<span class="community-name"><%= communityMap.get(post.community_id) %></span>
</a>
<%}%>
<%if(post.is_spoiler) {%>
<div class="spoiler-wrapper" id="spoiler-<%= post.id %>">
<button data-post-id="<%= post.id %>">Show Spoiler</button>
</div>
<%}%>
<div class="post-content" <%if(!reply) {%>data-href="/posts/<%= post.id %>"<%}%>>
<% if(post.body !== '') { %>
<p class="post-content-text"><%= post.body %></p>
<%}%>
<% if(post.screenshot !== '') { %>
<img class="post-screenshot" src="http://mii.olv.pretendo.cc<%= post.screenshot %>" onclick="alert(this.src)">
<%}%>
<% if(post.painting !== '') { %>
<img class="post-memo" src="http://mii.olv.pretendo.cc/paintings/<%=post.pid%>/<%=post.id%>.png">
<%}%>
<% if(post.url) { %>
<iframe width="760" height="427.5" src="<%= post.url.replace('watch?v=','embed/') %>" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<%}%>
</div>
<div class="post-buttons">
<button type="button" class="submit yeah-button <%if(post.yeahs && post.yeahs.indexOf(userContent.pid) !== -1){ %> selected <%}%>" data-post="<%= post.id %>">
<span class="sprite yeah"></span>
</button>
<a <%if(!locals.yeah) {%>href="/posts/<%= post.id %>"<%}%> class="to-permalink-button" data-pjax="#body">
<%if(!locals.yeah) {%>
<span class="sprite feeling" id="count-<%= post.id %>"><%=post.empathy_count %></span>
<%if(!reply) {%>
<span class="sprite reply"><%=post.reply_count %></span>
<%}%>
<%}%>
</a>
</div>
</div>
</div>
<%if(locals.yeah && post.yeahs.length > 0) {%>
<h6 class="yeah-text"><span class="feeling" id="count-<%= post.id %>"><%=post.empathy_count %></span> people gave this post a yeah.</h6>
<div class="yeah-list">
<% var length = post.yeahs.length > 10 ? 10 : post.yeahs.length%>
<%for(var i = 0; i < length; i++) {%>
<a href="/users/<%= yeah %>" class="mii-icon-container" data-pjax="#body">
<img src="http://mii.olv.pretendo.cc/mii/<%= post.yeahs[i] %>/normal_face.png" class="mii-icon">
</a>
<%}%>
</div>
<%}%>
</div>

View File

@ -0,0 +1,11 @@
<%if(!bundle.open) {%><div class="headline"><h2>This community is closed to new posts.</h2></div><%}%>
<% if(bundle.numPosts === 0) {%>
<p class="no-posts-text"><%= lang.global.no_posts %></p>
<%} else { %>
<% bundle.posts.forEach(function(post) { %>
<%- include('post_template', { post: post, mii_image_CDN: bundle.mii_image_CDN, lang: bundle.lang, userContent: bundle.userContent, communityMap: bundle.communityMap, reply: false }); %>
<% }); %>
<div class="button-wrapper center">
<button type="button" class="load-more" data-href="<%= bundle.link %>"><%= bundle.lang.global.more %></button>
</div>
<%}%>

View File

@ -0,0 +1,20 @@
<ul class="list-content-with-icon-column arrow-list" id="news-list-content">
<%if(!bundle.requests) {%><li><p>No Friend Requests</p></li><%}%>
<%if(bundle.requests){%>
<% for(var request of bundle.requests) {%>
<%if(bundle.userMap.get(request.sender)) {%>
<li>
<a href="/users/<%= request.sender %>" data-pjax="#body" class="icon-container notify">
<img src="http://mii.olv.pretendo.cc/mii/<%= request.sender %>/normal_face.png" class="icon">
</a>
<div class="body">
<p class=""><span class="nick-name"><%= bundle.userMap.get(request.sender) %></span>
<span><%=request.message%></span>
<span class="timestamp"> <%= moment(request.sent * 1000).fromNow() %></span>
</p>
</div>
</li>
<%}%>
<%}%>
<%}%>
</ul>

View File

@ -1,37 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3DS Testing</title>
<script src="/js/pjax.js"></script>
<script src="/js/juxt.js"></script>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<%- include('partials/head', { title: lang.global.activity_feed }); %>
<body>
<div id="main">
<div class="top-screen">
<div class="header-description">
<p>
</p>
<div id="body">
<header id="header" class="buttons">
<h1 id="page-title" class=""><%= post.screen_name %></h1>
<% if(((community.allows_comments && community.open) || (community.admins && community.admins.indexOf(pid) !== -1)) && userSettings.pid !== 1000000000 && userSettings.account_status === 0) {%>
<a id="header-post-button" class="header-button left" href="#"
data-sound="SE_WAVE_SELECT_TAB" data-module-hide="post"
data-module-show="add-post-page" data-header="false" data-screenshot="true"
data-message="Reply to <%= post.screen_name %>">Reply +</a>
<%}%>
</header>
<div class="body-content tab2-content" id="post">
<div class="post-wrapper parent">
<%- include('partials/post_template', { post: post, mii_image_CDN: mii_image_CDN, lang: lang, reply: false, pid: pid, yeah: true }); %>
</div>
<div id="" class="header-icon"><img class="community-page-info-icon" src="<%= user.pfp_uri %>"></div>
<h2 class="header-title">Post</h2>
</div>
<div class="bottom-screen">
<div class="communities-wrapper" id="popular-communities" style="margin-top: 15px">
<%- include('post_template', { post: post, mii_image_CDN: mii_image_CDN, lang: lang, reply: false }); %>
</div>
<span class="replies-line"></span>
<% replies.forEach(function(post) { %>
<div class="communities-wrapper" id="popular-communities" style="margin-top: 15px">
<%- include('post_template', { post: post, mii_image_CDN: mii_image_CDN, lang: lang, reply: true }); %>
</div>
<span class="replies-line"></span>
<%- include('partials/post_template', { post: post, mii_image_CDN: mii_image_CDN, lang: lang, reply: true, pid: pid }); %>
<% }); %>
<div class="toolbar-padding"></div>
</div>
<% if(((community.allows_comments && community.open) || (community.admins && community.admins.indexOf(pid) !== -1)) && userSettings.pid !== 1000000000 && userSettings.account_status === 0) {%>
<%- include('partials/new_post', { pid, lang, id: post.community_id, name: post.screen_name, url: `/posts/${post.id}/new`, show: 'post', message_pid: '' }); %>
<%}%>
</div>
<body onload="onStart()"></body>
</body>
</html>

View File

@ -1,49 +0,0 @@
<div class="posts-wrapper" id="<%= post.id %>">
<div class="post-user-info-wrapper" id="<%= post.id %>">
<div data-pjax="/users/show?pid=<%= post.pid %>">
<%if(post.verified) {%>
<img class="community-page-post-user-icon verified" src="<%= mii_image_CDN %>/<%= post.pid %>/<% if(post.feeling_id === 1) {%>smile_open_mouth.png<%} else if(post.feeling_id === 2 ) {%>wink_left.png<%} else if(post.feeling_id === 3 ) {%>surprise_open_mouth.png<%} else if(post.feeling_id === 4 ) {%>frustrated.png<%} else if(post.feeling_id === 5 ) {%>sorrow.png<%} else {%>normal_face.png<%}%>" data-pjax="/users/show?pid=<%= post.pid %>">
<span class="community-page-verified-user-badge community-page-verified" style="" data-pjax="/users/show?pid=<%= post.pid %>"></span>
<%} else {%>
<img class="community-page-post-user-icon" src="<%= mii_image_CDN %>/<%= post.pid %>/<% if(post.feeling_id === 1) {%>smile_open_mouth.png<%} else if(post.feeling_id === 2 ) {%>wink_left.png<%} else if(post.feeling_id === 3 ) {%>surprise_open_mouth.png<%} else if(post.feeling_id === 4 ) {%>frustrated.png<%} else if(post.feeling_id === 5 ) {%>sorrow.png<%} else {%>normal_face.png<%}%>" data-pjax="/users/show?pid=<%= post.pid %>">
<span class="community-page-verified-user-badge community-page-verified" style="display: none;" data-pjax="/users/show?pid=<%= post.pid %>"></span>
<%}%>
<h2 class="community-page-post-username" data-pjax="/users/show?pid=<%= post.pid %>"><%= post.screen_name %></h2>
<h4 class="community-page-post-time-stamp"><%= moment(post.created_at).fromNow() %> - <a onclick="cave.snd_playSe('SE_OLV_OK');pjax.loadUrl('/communities/<%= post.community_id %>/new')"><%= communityMap.get(post.community_id) %></a></h4>
</div>
<div class="community-page-post-yeah-button-wrapper <%if(user.likes.indexOf(post.id) !== -1){ %> selected <%}%>">
<div class="community-page-post-yeah-button" onclick="<%if(user.pid !== 1000000000) {%>yeah(this.parentNode, '<%= post.id %>')<%}%>"></div>
</div>
<div id="yeah-<%= post.id %>" class="community-page-post-yeah-count"><%= post.empathy_count %> <%= lang.global.yeahs %></div>
</div>
<div class="spoiler-overlay" <% if(post.is_spoiler === 0) {%>style="display: none"<%}%>>
<button onclick="cave.snd_playSe('SE_OLV_OK'); this.parentElement.style.display = 'none'; document.getElementById('post-content-<%= post.id%>').style.display = 'block'">Click to Show Spoiler</button>
</div>
<div class="community-page-post-wrapper" id="post-content-<%= post.id%>" <%if(!reply){%>onclick="cave.snd_playSe('SE_OLV_OK'); pjax.loadUrl('/posts/<%= post.id%>')"<%}%> <% if(post.is_spoiler === 1) {%>style="display: none"<%}%>>
<% if(post.body !== '' && post.painting === '' && post.screenshot === '' && !post.url) { %>
<h3><%= post.body %></h3>
<%} else { %>
<% if(post.screenshot !== '') { %>
<img id="<%= post.id %>" class="community-page-post-screenshot" src="data:image/png;base64,<%= post.screenshot %>">
<%}%>
<% if(post.painting !== '') { %>
<img id="<%= post.id%>" class="community-page-post-painting" src="<%= post.painting_uri %>">
<%}%>
<% if(post.url) { %>
<h3 style="font-weight: bolder; color: #4F279B">Video Playback is not yet supported on the 3DS</h3>
<%}%>
<% if(post.body) { %>
<div class="community-page-post-text-overlay">
<h3><%= post.body %></h3>
</div>
<%}%>
<%}%>
</div>
<%if(post.reply_count > 0) {%>
<div class="reply-banner">
<div></div>
<h2><%= post.reply_count %> replies</h2>
</div>
<%}%>
</div>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<%- include('partials/head', { title: 'Whoops!' }); %>
<body>
<div id="body">
<header id="header" class="">
<h1 id="page-title" class="">User Settings</h1>
</header>
<div class="body-content tab2-content" id="community-post-list">
<p>Howdy! We're not quite done here yet.</p>
<p>Check back soon for updates!</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<%- include('partials/head', { title: lang.all_communities.text }); %>
<body>
<div id="body">
<header id="header">
<h1 id="page-title" class=""><%= community.name %> Related Communities</h1>
</header>
<div class="body-content">
<div class="communities-list">
<ul class="list-content-with-icon-column" id="community-new-content">
<% for(var i = 0; i < children.length; i++) {%>
<% var communityID = children[i].olive_community_id; %>
<li id="<%=communityID%>">
<a href="/titles/<%=communityID%>/new" data-pjax="#body" class="scroll to-community-button">
<span class="icon-container"><img src="http://mii.olv.pretendo.cc/icons/<%=communityID%>/64.png" class="icon" alt=""></span>
<div class="body">
<div class="body-content">
<span class="community-name title"><%= children[i].name %></span>
<br>
<span class="text"><%= children[i].followers %> <%= lang.community.followers %></span>
</div>
</div>
</a>
</li>
<% } %>
</ul>
</div>
</div>
</body>
</html>

View File

@ -1,53 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3DS Testing</title>
<script src="/js/pjax.js"></script>
<script src="/js/juxt.js"></script>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<%- include('partials/head', { title: 'Whoops!' }); %>
<body>
<div id="main">
<div class="top-screen">
<div class="header-description">
<p>
Here you can change your profile and settings. You can also search for other users here.
</p>
</div>
<div id="" class="header-icon"><img class="community-page-info-icon" src="<%= user.pfp_uri %>"></div>
<h2 class="header-title">User Menu</h2>
</div>
<div class="bottom-screen">
<div class="communities-wrapper" id="popular-communities" style="margin-top: 15px">
<table>
<tbody>
<tr>
<td>
<button class="user-menu-button" data-pjax="/users/me">Profile</button>
</td>
</tr>
<tr>
<td>
<button class="user-menu-button" data-pjax="/users/me">Search</button>
</td>
</tr>
<tr>
<td>
<button class="user-menu-button" data-pjax="/communities/announcements">Juxt Announcements</button>
</td>
</tr>
<tr>
<td>
<button class="user-menu-button" data-pjax="/users/me">Settings/Other</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="toolbar-padding"></div>
<div id="body">
<header id="header" class="">
<h1 id="page-title" class="">User Menu</h1>
</header>
<div class="body-content tab2-content" id="community-post-list">
<p>Howdy! We're not quite done here yet.</p>
<p>Check back soon for updates!</p>
</div>
</div>
<body onload="onStart()"></body>
</body>
</html>

View File

@ -1,76 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3DS Testing</title>
<script src="/js/pjax.js"></script>
<script src="/js/juxt.js"></script>
<link rel="stylesheet" type="text/css" href="/css/juxt.css">
</head>
<%- include('partials/head', { title: pnid.mii.name }); %>
<body>
<div id="main">
<div class="top-screen">
<div class="header-description">
<p>
</p>
</div>
<div id="" class="header-icon"><img class="community-page-info-icon" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png"></div>
<h2 class="header-title"><%= user.user_id %></h2>
</div>
<div class="bottom-screen">
<%if(user.profile_comment_visibility && user.profile_comment){%>
<div class="communities-wrapper" id="popular-communities" style="margin-top: 10px">
<p class="communities-header"><%=user.profile_comment%></p>
</div>
<div id="body">
<header id="header" class="buttons">
<h1 id="page-title" class="community">
<span>
<span class="icon-container">
<img src="http://mii.olv.pretendo.cc/mii/<%=pnid.pid%>/normal_face.png" class="icon">
</span>
<span class="community-name">
<%= pnid.mii.name %> - @<%= pnid.username %>
</span>
<span class="text">
<span>
<span class="sprite posts"></span>
<%=numPosts%>
</span>
<span>|
<span class="sprite followers"></span>
<span id="followers"><%= userContent.following_users.length - 1 %></span>
</span>
</span>
</span>
</span>
</h1>
<%if(pnid.pid === pid) {%>
<a id="header-communities-button" class="header-button left" href="/users/me/settings" data-pjax="#body">Settings</a>
<%}%>
<table class="community-page-table-wrapper">
<tbody>
<tr>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Country</h4>
<h4 class="community-page-table-text"><%if(user.country_visibility){%><%=user.country%><%}else {%>Private<%}%></h4>
</div>
</td>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Birthday</h4>
<h4 class="community-page-table-text"><%if(user.birthday_visibility){%><%=moment.utc(user.birthday).format("MMM Do")%><%}else {%>Private<%}%></h4>
</div>
</td>
</tr>
<tr>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Game experience</h4>
<h4 class="community-page-table-text">
<%if(user.game_skill === 0) {%>
Beginner
<%} else if(user.game_skill === 1) {%>
Intermediate
<%} else if(user.game_skill === 2) {%>
Expert
<%} else {%>
N/A
<%}%>
</h4>
</div>
</td>
<td>
<div class="community-page-shaded-info-container">
<h4 class="community-page-table-label">Yeahs</h4>
<h4 class="community-page-table-text">N/A</h4>
</div>
</td>
</tr>
</tbody>
</table>
<div class="communities-wrapper" id="popular-communities">
<h1 class="communities-header">User Posts</h1>
<%if(pnid.pid !== pid) {%>
<button type="button" class="submit follow yeah-button <%if(parentUserContent.followed_users.indexOf(userContent.pid) !== -1){ %>selected<%}%>" onclick="follow(this)" data-sound="SE_WAVE_CHECKBOX_UNCHECK" data-url="/users/follow" data-community-id="<%=pnid.pid%>">
<span class="sprite yeah"></span>
</button>
<%}%>
</header>
<div class="body-content tab2-content" id="community-post-list">
<menu class="tab-header user-page no-margin">
<li id="tab-header-post" class="tab-button <%if(selection === 0){ %>selected<%}%>">
<a href="<%= link %>" data-sound="SE_WAVE_SELECT_TAB">
<span class="new-post"><%= lang.user_page.posts %></span>
</a>
</li>
<li id="tab-header-friends" class="tab-button <%if(selection === 1){ %>selected<%}%>">
<a href="<%= link %>friends" data-sound="SE_WAVE_SELECT_TAB">
<span><%= lang.user_page.friends %></span>
</a>
</li>
<li id="tab-header-following" class="tab-button <%if(selection === 2){ %>selected<%}%>">
<a href="<%= link %>following" data-sound="SE_WAVE_SELECT_TAB">
<span><%= lang.user_page.following %></span>
</a>
</li>
<br>
<li id="tab-header-followers" class="tab-button <%if(pnid.pid !== pid) {%>double<%}%> <%if(selection === 3){ %>selected<%}%>">
<a href="<%= link %>followers" data-sound="SE_WAVE_SELECT_TAB">
<span><%= lang.user_page.followers %></span>
</a>
</li>
<li id="tab-header-yeahs" class="tab-button <%if(pnid.pid !== pid) {%>double<%}%> <%if(selection === 4){ %>selected<%}%>">
<a href="<%= link %>yeahs" data-sound="SE_WAVE_SELECT_TAB">
<span><%= lang.global.yeahs %></span>
</a>
</li>
<%if(pnid.pid === pid) {%>
<li id="tab-header-requests" class="tab-button">
<a href="/news/friend_requests" data-sound="SE_WAVE_SELECT_TAB">
<span>Requests</span>
</a>
</li>
<%}%>
</menu>
<div class="tab-body post-list">
<%- include('partials/' + template, { bundle }); %>
</div>
<div class="toolbar-padding"></div>
</div>
<body onload="onStart()"></body>
</div>
</body>
</html>

View File

@ -1,42 +1,32 @@
<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>
</head>
<!DOCTYPE html>
<html lang="en">
<%- include('partials/head', { title: lang.all_communities.text }); %>
<body>
<%- include('nav_bar', { selection: 2 }); %>
<div id="main">
<h1 class="page-header"><%= lang.all_communities.text %></h1>
<div class="search-container">
<form>
<div id="search-icon"></div>
<input id="search-bar" onchange="searchCommunities()" onkeyup="searchCommunities()" type="text" placeholder="<%= lang.all_communities.search %>" name="search">
</form>
</div>
<div id="community-list" class="communities-wrapper">
<table style="padding-top: 30px">
<tbody>
<tr>
<%- include('partials/nav_bar', { selection: 2, pid: pid }); %>
<div id="body">
<header id="header">
<h1 id="page-title" class=""><%= lang.all_communities.text %></h1>
</header>
<div class="body-content">
<div class="communities-list">
<ul class="list-content-with-icon-column" id="community-new-content">
<% for(var i = 0; i < communities.length; i++) {%>
<td>
<div class="community-list-wrapper" data-pjax="/communities/<%= communities[i].community_id %>/new">
<img class="community-list-icon" src="<%= cdnURL %>/icons/<%= communities[i].community_id %>.png">
<h2 class="community-list-title"><%= communities[i].name %></h2>
<h4 class="community-list-followers" <%if(communities[i].name.length < 16) {%> style="margin-top: 45px" <%}%>><%= communities[i].followers %> followers</h4>
<% var communityID = communities[i].olive_community_id; %>
<li id="<%=communityID%>">
<span class="icon-container"><img src="https://mii.olv.pretendo.cc/icons/<%=communityID%>/128.png" class="icon" alt=""></span>
<a href="/titles/<%=communityID%>/new" data-pjax="#body" class="scroll to-community-button"></a>
<div class="body">
<div class="body-content">
<span class="community-name title"><%= communities[i].name %></span>
<span class="text"><%= communities[i].followers %> <%= lang.community.followers %></span>
</div>
</div>
</td>
<% if( (i + 1) % 3 === 0) { %>
</tr>
<tr>
</li>
<% } %>
<% } %>
</tr>
</tbody>
</table>
</ul>
</div>
</div>
<img src="" onerror="wiiuBrowser.showLoadingIcon(!1);window.scroll(0, 0);">
</div>
<img src="" onerror="wiiuBrowser.showLoadingIcon(!1);">
</body>
<body onload="wiiuBrowser.endStartUp();"></body>
</html>

View File

@ -1,46 +0,0 @@
<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>
<meta content="<%= community.name %> - Juxtaposition Community" property="og:title" />
<meta content="<%= community.followers%> Followers" property="og:site_name">
<meta content="<%= community.description %>" property="og:description" />
<meta content="https://portal.olv.pretendo.cc/communities/<%= community.community_id %>/new" property="og:url" />
<meta content="https://portal.cdn.pretendo.cc/icons/<%= community.community_id %>.png" property="og:image" />
<meta content="#1F8A42" data-react-helmet="true" name="theme-color" />
</head>
<body>
<%- include('nav_bar', { selection: 2 }); %>
<div id="main">
<div id="overlay-filter">
<div class="community-page-header" style="background-image: url('<%= cdnURL %>/banner/<%= community.community_id %>.png')"></div>
<%- include('scroll_wrapper', { text: community.name, lang: lang }); %>
<div class="community-page-info-container" style="height: 120px;">
<img class="community-page-info-icon" src="<%= cdnURL %>/icons/<%= community.community_id %>.png">
<a class="community-page-follow-button-text" style="display: none" id="<%=community.community_id%>"></a>
<h2 class="community-page-title"><%= community.name %></h2>
<h4 class="community-page-description"><%= community.description %></h4>
</div>
<div>
</div>
<div class="community-page-post-box" style="margin-top: 50px;">
<div id="wrapper">
<% if(totalNumPosts === 0) {%>
<p class="no-posts-text"><%= lang.global.no_posts %></p>
<%} else { %>
<button id="load-more-posts-button" data-offset="20" onclick="loadCommunityPosts(this, true)"><%= lang.global.more %></button>
<% newPosts.forEach(function(post) { %>
<%- include('post_template', { post: post, mii_image_CDN: mii_image_CDN, lang: lang, reply: false }); %>
<% }); %>
<%}%>
</div>
</div>
<br>
<img src="" onerror="wiiuBrowser.showLoadingIcon(!1);window.scroll(0, 0);">
</div>
</div>
<br>
<body onload="stopLoading(); wiiuBrowser.showLoadingIcon(!1);wiiuBrowser.lockUserOperation(false);">
</body>
</html>

View File

@ -1,75 +1,54 @@
<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>
</head>
<!DOCTYPE html>
<html lang="en">
<%- include('partials/head', { title: lang.global.communities }); %>
<body>
<%- include('nav_bar', { selection: 2 }); %>
<div id="main">
<h1 class="page-header"><%= lang.global.communities %></h1>
<div id="admin-announcements" class="communities-wrapper announcements-banner" data-pjax="/communities/announcements">
<p class="announcements-banner-text"><%= lang.all_communities.ann_string %></p>
</div>
<div id="popular" class="communities-wrapper">
<table>
<tbody>
<tr>
<td>
<h1 class="communities-header"><%= lang.all_communities.popular_places %></h1>
</td>
<td>
<div data-pjax="/communities/all">
<button class="outlined-button" style=" margin-top: -25px; right: 50px; position: absolute;"><%= lang.all_communities.show_all %></button>
</div>
</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<%- include('partials/nav_bar', { selection: 2, pid: pid }); %>
<div id="body">
<header id="header">
<h1 id="page-title" class=""><%= lang.global.communities %></h1>
<a id="header-communities-button" href="/titles/all" data-pjax="#body"><%= lang.all_communities.text %></a>
</header>
<div class="body-content">
<div class="communities-list">
<div class="headline">
<h2><%= lang.all_communities.popular_places %></h2>
</div>
<ul class="list-content-with-icon-column" id="community-new-content">
<% for(var i = 0; i < popularCommunities.length; i++) {%>
<td>
<div class="community-list-wrapper" data-pjax="/communities/<%= popularCommunities[i].community_id %>/new">
<img class="community-list-icon" src="<%= cdnURL %>/icons/<%= popularCommunities[i].community_id %>.png">
<h2 class="community-list-title"><%= popularCommunities[i].name %></h2>
<h4 class="community-list-followers" <%if(popularCommunities[i].name.length < 16) {%> style="margin-top: 45px" <%}%>><%= popularCommunities[i].followers %> followers</h4>
<% var popCommunityID = popularCommunities[i].olive_community_id; %>
<li id="<%=popCommunityID%>">
<span class="icon-container"><img src="https://mii.olv.pretendo.cc/icons/<%=popCommunityID%>/128.png" class="icon" alt=""></span>
<a href="/titles/<%=popCommunityID%>/new" data-pjax="#body" class="scroll to-community-button"></a>
<div class="body">
<div class="body-content">
<span class="community-name title"><%= popularCommunities[i].name %></span>
<span class="text"><%= popularCommunities[i].followers %> <%= lang.community.followers %></span>
</div>
</div>
</td>
<% if( (i + 1) % 3 === 0) { %>
</tr>
<tr>
</li>
<% } %>
<% } %>
</tr>
</tbody>
</table>
</div>
<div id="new" class="communities-wrapper">
<h1 class="communities-header"><%= lang.all_communities.new_communities %></h1>
<table>
<tbody>
<tr>
</ul>
<div class="headline headline-green">
<h2><%= lang.all_communities.new_communities %></h2>
</div>
<ul class="list-content-with-icon-column" id="community-top-content">
<% for(var j = 0; j < newCommunities.length; j++) {%>
<td>
<div class="community-list-wrapper" data-pjax="/communities/<%= newCommunities[j].community_id %>/new">
<img class="community-list-icon" src="<%= cdnURL %>/icons/<%= newCommunities[j].community_id %>.png">
<h2 class="community-list-title"><%= newCommunities[j].name %></h2>
<h4 class="community-list-followers" <%if(newCommunities[j].name.length < 16) {%> style="margin-top: 45px" <%}%>><%= newCommunities[j].followers %> followers</h4>
<% var communityID = newCommunities[j].olive_community_id; %>
<li id="<%=communityID%>">
<span class="icon-container"><img src="https://mii.olv.pretendo.cc/icons/<%=communityID%>/128.png" class="icon" alt=""></span>
<a href="/titles/<%=communityID%>/new" data-pjax="#body" class="scroll to-community-button"></a>
<div class="body">
<div class="body-content">
<span class="community-name title"><%= newCommunities[j].name %></span>
<span class="text"><%= newCommunities[j].followers %> <%= lang.community.followers %></span>
</div>
</div>
</td>
<% if( (j + 1) % 3 === 0) { %>
</tr>
<tr>
</li>
<% } %>
<% } %>
</tr>
</tbody>
</table>
</ul>
</div>
</div>
<img src="" onerror="wiiuBrowser.showLoadingIcon(!1);window.scroll(0, 0);">
</div>
<body onload="stopLoading();">
</body>
<body onload="stopLoading();">
</html>

View File

@ -1,169 +1,53 @@
<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>
<meta content="<%= community.name %> - Juxtaposition Community" property="og:title" />
<meta content="<%= community.followers%> Followers" property="og:site_name">
<meta content="<%= community.description %>" property="og:description" />
<meta content="https://portal.olv.pretendo.cc/communities/<%= community.community_id %>/new" property="og:url" />
<meta content="https://portal.cdn.pretendo.cc/icons/<%= community.community_id %>.png" property="og:image" />
<meta content="#1F8A42" data-react-helmet="true" name="theme-color" />
</head>
<!DOCTYPE html>
<html lang="en">
<%- include('partials/head', { title: community.name }); %>
<body>
<%- include('nav_bar', { selection: 2 }); %>
<div id="main">
<div id="windowOverlay" class="overlay">
<div class="overlay-content">
<div id="post-window" class="post-wrapper">
<!--Start of Main Post-->
<form class="post-emotion-radio" action="/posts/new" method="post" enctype="multipart/form-data">
<table>
<tr>
<td colspan="2">
<h1 class="communities-header" style="padding-left: 50px; padding-top: 10px"><%= lang.new_post.post_to %> <%= community.name %></h1>
</td>
</tr>
<tr>
<td>
<img class="post-user-icon" src="<%= mii_image_CDN %>/<%= user.pid %>/normal_face.png">
<input type="hidden" id="olive_title_id" name="olive_title_id" value="<%= community.title_id[0] %>">
<input type="hidden" id="olive_community_id" name="olive_community_id" value="<%= community.community_id %>">
<input type="hidden" id="_post_type" name="_post_type" value="body">
</td>
<td>
<div class="post-emotions-wrapper">
<label for="post-emotion-0">
<input type="radio" name="emotion" id="post-emotion-0" value="0" onclick="changeMiiImageReaction(this)" checked>
<span class="post-emotion-0"></span>
</label>
<label for="post-emotion-1">
<input type="radio" name="emotion" id="post-emotion-1" value="1" onclick="changeMiiImageReaction(this)">
<span class="post-emotion-1"></span>
</label>
<label for="post-emotion-2">
<input type="radio" name="emotion" id="post-emotion-2" value="2" onclick="changeMiiImageReaction(this)">
<span class="post-emotion-2"></span>
</label>
<label for="post-emotion-3">
<input type="radio" name="emotion" id="post-emotion-3" value="3" onclick="changeMiiImageReaction(this)">
<span class="post-emotion-3"></span>
</label>
<label for="post-emotion-4">
<input type="radio" name="emotion" id="post-emotion-4" value="4" onclick="changeMiiImageReaction(this)">
<span class="post-emotion-4"></span>
</label>
<label for="post-emotion-5">
<input type="radio" name="emotion" id="post-emotion-5" value="5" onclick="changeMiiImageReaction(this)">
<span class="post-emotion-5"></span>
</label>
</div>
<div class="post-emotions-arrow-left"></div>
</td>
<td>
<div class="post-screenshot-picker" onclick="loadScreenshots()">
<div class="post-screenshot-picker-icon"></div>
<div class="post-screenshot-picker-dropdown">
<div><img src="" class="post-screenshot-preview" id="post-top-screen-preview" onclick="selectScreenshot(1)"></div>
<div><img src="" class="post-screenshot-preview" id="post-bottom-screen-preview" onclick="selectScreenshot(2)"></div>
<div onclick="selectScreenshot(0)">No Screenshot</div>
<input type="hidden" id="screenshot-value" name="screenshot" value="">
</div>
</div>
</td>
</tr>
<tr>
<td>
<div class="post-type-button-text selected" onclick="swapPostType(0)"></div>
</td>
<td rowspan="2" id="post-text-input">
<textarea class="post-textarea" id="comment" name="body" maxlength="280" placeholder="<%= lang.new_post.text_hint %>" rows="4" cols="50" onchange="if(wiiuFilter.checkWord(this.value) === -2) { this.value = ''; alert('<%= lang.new_post.swearing %>');}"></textarea>
</td>
<td rowspan="2" id="post-painting-input" style="display: none;">
<div class="post-memo">
<img class="post-memo-preview" id="memo" width="640px" height="240px" src="" style="display: none;" onclick="newPainting(false)">
<input type="hidden" id="memo-value" name="painting" value="">
</div>
</td>
<br>
</tr>
<tr>
<td>
<div class="post-type-button-painting" onclick="swapPostType(1);newPainting(false)"></div>
</td>
</tr>
<tr>
<td colspan="2">
<label class="checkbox-container checkbox-post">Spoilers
<input type="checkbox" id="spoiler" name="spoiler" value="true" onclick="wiiuSound.playSoundByName('SE_WAVE_MENU', 1);">
<span class="checkmark"></span>
</label>
</td>
</tr>
<tr style="height: 70px">
<td class="post-close-button-wrapper" colspan="2">
<div class="post-close-button" onclick="toggleOverlay();wiiuMemo.reset();">X <b><%= lang.global.close %></b></div>
</td>
<td>
<input type="submit" value="Post" class="submit-button" style="margin-left: -124px;" onclick="wiiuBrowser.lockUserOperation(true);wiiuSound.playSoundByName('SE_WAVE_MENU', 1);wiiuSound.playSoundByName('BGM_OLV_MAIN_LOOP_NOWAIT', 3);">
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
<div id="overlay-filter">
<div class="community-page-header" style="background-image: url('<%= cdnURL %>/banner/<%= community.community_id %>.png')"></div>
<%- include('scroll_wrapper', { text: community.name, lang: lang }); %>
<%if(user.pid !== 1000000000 && user.account_status === 0) {%>
<div id="community-new-post-wrapper" onclick="showNewPostScreen()">
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 30 30" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
</div>
<%- include('partials/nav_bar', { selection: 2, pid: pid }); %>
<div id="body">
<header id="header">
<% if(((community.open && community.type < 2) || (community.admins && community.admins.indexOf(pid) !== -1)) && userSettings.pid !== 1000000000 && userSettings.account_status === 0) {%>
<a id="header-post-button" class="header-button" href="#" data-sound="SE_WAVE_SELECT_TAB" data-module-hide="community-post-list" data-module-show="add-post-page" data-header="false" data-menu="false">Post</a>
<%}%>
<div class="community-page-info-container" style="height: 170px;">
<div class="community-page-follow-button-wrapper <%if(user.followed_communities.indexOf(community.community_id) !== -1){ %>selected<%}%>" <%if(user.pid !== 1000000000) {%>onclick="followCommunity()"<%}%>>
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 25 25" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart" fill="#716e74" stroke="#716e74"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
<p class="community-page-follow-button-text" id="<%= community.community_id %>"><%= community.followers %> <%= lang.community.followers %></p>
</div>
<img class="community-page-info-icon" src="<%= cdnURL %>/icons/<%= community.community_id %>.png">
<h2 class="community-page-title"><%= community.name %></h2>
<h4 class="community-page-description"><%= community.description %></h4>
<%if(children){%>
<a id="header-communities-button" href="related" data-pjax="#body">Related Communities</a>
<%}%>
</header>
<div class="body-content tab2-content" id="community-post-list">
<div class="header-banner-container">
<img src="https://mii.olv.pretendo.cc/headers/<%= community.parent ? community.parent : community.olive_community_id %>/WiiU.png" class="header-banner with-top-button"></div>
<div class="community-info info-content with-header-banner">
<span class="icon-container"><img src="https://mii.olv.pretendo.cc/icons/<%= community.parent ? community.parent : community.olive_community_id %>/128.png" class="icon"></span>
<%if(community.open){%>
<a href="#" class="favorite-button button <%if(userContent.followed_communities.indexOf(community.olive_community_id) !== -1){ %>checked<%}%>" onclick="follow(this)" data-sound="SE_WAVE_CHECKBOX_UNCHECK" data-url="/titles/follow" data-community-id="<%= community.olive_community_id %>"></a>
<%}%>
<span class="title"><%= community.name %></span>
<span class="text">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="23" fill="#ffffff" viewBox="0 0 256 200"><rect width="256" height="256" fill="none"></rect><polygon points="128 160 96 160 96 128 192 32 224 64 128 160" fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="24"></polygon><line x1="164" y1="60" x2="196" y2="92" fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="24"></line><path d="M216,128.6V208a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V48a8,8,0,0,1,8-8h79.4" fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="24"></path></svg>
<%= bundle.numPosts %>
</span>
<span>|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="23" fill="#ffffff" viewBox="0 0 256 200"><rect width="256" height="256" fill="none"></rect><line x1="204" y1="136" x2="244" y2="136" fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="24"></line><line x1="224" y1="116" x2="224" y2="156" fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="24"></line><circle cx="108" cy="100" r="60" fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="24"></circle><path d="M22.2,200a112,112,0,0,1,171.6,0" fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="24"></path></svg>
<span id="followers"><%= community.followers %></span>
</span>
</span>
</div>
<div>
<table class="community-page-posts-header switcher" style="-webkit-transform: translate(-35%, -35%);">
<tbody>
<tr>
<td>
<h4 id="recent-tab" onclick="loadPosts(0)" class="community-page-posts-header-tab active"><%= lang.community.recent %></h4>
</td>
<td>
<h4 id="popular-tab" onclick="loadPosts(1)" class="community-page-posts-header-tab"><%= lang.community.popular %></h4>
</td>
<td>
<h4 id="verified-tab" onclick="loadPosts(2)" class="community-page-posts-header-tab"><%= lang.community.verified %></h4>
</td>
</tr>
</tbody>
</table>
<menu class="tab-header">
<li id="tab-header-post" class="tab-button <%if(type === 0){ %>selected<%}%>">
<a href="/titles/<%= community.olive_community_id %>/new" data-sound="SE_WAVE_SELECT_TAB"><span class="new-post"><%= lang.community.recent %></span></a>
</li>
<li id="tab-header-hot-post" class="tab-button <%if(type === 1){ %>selected<%}%>"><a href="/titles/<%= community.olive_community_id %>/hot" data-sound="SE_WAVE_SELECT_TAB"><span><%= lang.community.popular %></span></a></li>
</menu>
<div id="new-post-button-container" class="none">
<a href="#" class="button" data-offset="10"><%= lang.global.more %></a>
<div id="new-post"></div>
</div>
<div class="community-page-post-box" style="margin-top: 120px;">
<div id="wrapper">
<% if(totalNumPosts === 0) {%>
<p class="no-posts-text"><%= lang.global.no_posts %></p>
<%} else { %>
<button id="load-more-posts-button" data-offset="20" onclick="loadCommunityPosts(this)"><%= lang.global.more %></button>
<% newPosts.forEach(function(post) { %>
<%- include('post_template', { post: post, mii_image_CDN: mii_image_CDN, lang: lang, reply: false }); %>
<% }); %>
<%}%>
</div>
<div class="tab-body post-list">
<%- include('partials/' + template, { bundle }); %>
</div>
<br>
<img src="" onerror="wiiuBrowser.showLoadingIcon(!1);window.scroll(0, 0);">
</div>
<%- include('partials/new_post', { pid, lang, id: community.olive_community_id, name: community.name, url: '/posts/new', show: 'community-post-list', message_pid: '' }); %>
</div>
<br>
<body onload="stopLoading(); wiiuBrowser.showLoadingIcon(!1);wiiuBrowser.lockUserOperation(false);">
</body>
</html>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 651 KiB

Some files were not shown because too many files have changed in this diff Show More