2019-10-15 23:42:47 -03:00
const express = require ( 'express' ) ;
const fs = require ( "fs" )
2019-11-07 01:07:31 -03:00
const compression = require ( 'compression' ) ;
2020-10-28 22:25:13 -03:00
const timeout = require ( 'connect-timeout' )
2020-09-10 10:02:40 -03:00
const rateLimit = require ( "express-rate-limit" ) ;
const app = express ( ) ;
2020-11-07 21:20:44 -03:00
2020-09-10 10:02:40 -03:00
app . offline = false // set to true to go into "offline" mode (in case of ip ban from rob)
2020-11-07 21:20:44 -03:00
app . config = require ( './settings' ) // tweak settings in this file if you're using a GDPS
2020-11-01 17:29:32 -03:00
app . endpoint = app . config . endpoint // default is boomlings.com/database/
2020-11-07 21:20:44 -03:00
app . accountCache = { } // account IDs are cached here to shave off requests to getgjusers
2021-01-11 18:00:21 -03:00
app . lastSuccess = null // timestamp of the last time a gjp request was accepted my the servers
2020-11-07 21:20:44 -03:00
let rlMessage = "Rate limited ¯\\_(ツ)_/¯<br><br>Please do not spam my servers with a crazy amount of requests. It slows things down on my end and stresses RobTop's servers just as much." +
" If you really want to send a zillion requests for whatever reason, please download the GDBrowser repository locally - or even just send the request directly to the GD servers.<br><br>" +
"This kind of spam usually leads to GDBrowser getting IP banned by RobTop, and every time that happens I have to start making the rate limit even stricter. Please don't be the reason for that.<br><br>" +
"(also, keep in mind that most endpoints have a ?count parameter that let you fetch a LOT more stuff in just one request)"
2020-09-10 10:02:40 -03:00
const RL = rateLimit ( {
2020-11-01 17:29:32 -03:00
windowMs : app . config . rateLimiting ? 5 * 60 * 1000 : 0 ,
max : app . config . rateLimiting ? 100 : 0 , // max requests per 5 minutes
2020-11-07 21:20:44 -03:00
message : rlMessage ,
keyGenerator : function ( req ) { return req . headers [ 'x-real-ip' ] || req . headers [ 'x-forwarded-for' ] } ,
2020-09-10 10:02:40 -03:00
skip : function ( req ) { return ( ( req . url . includes ( "api/level" ) && ! req . query . hasOwnProperty ( "download" ) ) ? true : false ) }
} )
2019-11-07 01:07:31 -03:00
2020-11-07 21:20:44 -03:00
const RL2 = rateLimit ( {
windowMs : app . config . rateLimiting ? 2 * 60 * 1000 : 0 ,
max : app . config . rateLimiting ? 200 : 0 , // max requests per 1 minute
message : rlMessage ,
keyGenerator : function ( req ) { return req . headers [ 'x-real-ip' ] || req . headers [ 'x-forwarded-for' ] }
} )
2019-10-15 23:42:47 -03:00
let api = true ;
2021-01-04 12:21:58 -03:00
let gdIcons = fs . readdirSync ( './assets/previewicons' )
2020-09-24 23:07:53 -03:00
let sampleIcons = require ( './misc/sampleIcons.json' )
2021-01-11 18:00:21 -03:00
let achievements = require ( './misc/achievements.json' )
let achievementTypes = require ( './misc/achievementTypes.json' )
2021-01-14 14:02:38 -03:00
let shopIcons = require ( './misc/shops.json' )
2020-11-26 18:04:42 -03:00
let colorList = require ( './icons/colors.json' )
let forms = { "player" : "cube" , "bird" : "ufo" , "dart" : "wave" }
2021-01-04 12:21:58 -03:00
let assetPage = fs . readFileSync ( './html/assets.html' , 'utf8' )
2020-11-26 18:04:42 -03:00
let whiteIcons = fs . readdirSync ( './icons' ) . filter ( x => x . endsWith ( "extra_001.png" ) ) . map ( function ( x ) { let xh = x . split ( "_" ) ; return [ xh [ 1 ] == "ball" ? "ball" : forms [ xh [ 0 ] ] || xh [ 0 ] , + xh [ xh [ 1 ] == "ball" ? 2 : 1 ] ] } )
2020-11-27 00:45:11 -03:00
let colorOrder = [ 0 , 1 , 2 , 3 , 16 , 4 , 5 , 6 , 13 , 7 , 8 , 9 , 29 , 10 , 14 , 11 , 12 , 17 , 18 , 15 , 27 , 32 , 28 , 38 , 20 , 33 , 21 , 34 , 22 , 39 , 23 , 35 , 24 , 36 , 25 , 37 , 30 , 26 , 31 , 19 , 40 , 41 ]
2020-09-24 23:07:53 -03:00
2019-10-21 12:20:25 -03:00
app . use ( compression ( ) ) ;
2019-10-15 23:42:47 -03:00
app . use ( express . json ( ) ) ;
app . use ( express . urlencoded ( { extended : true } ) ) ;
2020-11-01 17:29:32 -03:00
app . use ( timeout ( '20s' ) ) ;
2019-10-15 23:42:47 -03:00
app . set ( 'json spaces' , 2 )
2020-11-01 17:29:32 -03:00
app . use ( function ( req , res , next ) {
2021-01-17 02:05:06 -03:00
req . gdParams = function ( obj = { } , substitute = true ) {
2020-11-01 17:29:32 -03:00
Object . keys ( app . config . params ) . forEach ( x => { if ( ! obj [ x ] ) obj [ x ] = app . config . params [ x ] } )
let ip = req . headers [ 'x-real-ip' ] || req . headers [ 'x-forwarded-for' ]
2021-01-14 20:18:19 -03:00
let params = { form : obj , headers : app . config . ipForwarding && ip ? { 'x-forwarded-for' : ip , 'x-real-ip' : ip } : { } }
2021-01-17 02:05:06 -03:00
if ( substitute ) { // GDPS substitutions in settings.js
for ( let sub in app . config . substitutions ) {
if ( params . form [ sub ] ) { params . form [ app . config . substitutions [ sub ] ] = params . form [ sub ] ; delete params . form [ sub ] }
}
2021-01-14 20:18:19 -03:00
}
return params
2020-11-01 17:29:32 -03:00
}
next ( )
} )
2020-10-28 22:25:13 -03:00
let directories = [ "" ]
fs . readdirSync ( './api' ) . filter ( x => ! x . includes ( "." ) ) . forEach ( x => directories . push ( x ) )
2019-12-25 18:20:32 -03:00
2021-01-11 18:00:21 -03:00
app . trackSuccess = function ( ) {
// made this a function in case i wanna do more stuff in the future
app . lastSuccess = Date . now ( )
}
app . timeSince = function ( time = app . lastSuccess ) {
if ( ! time ) return "[unknown]"
let secsPassed = Math . floor ( ( Date . now ( ) - time ) / 1000 )
let minsPassed = Math . floor ( secsPassed / 60 )
secsPassed -= 60 * minsPassed ;
return ` ${ minsPassed } m ${ secsPassed } s `
}
2021-01-15 12:29:46 -03:00
app . isGDPS = app . endpoint != "http://boomlings.com/database/"
2021-01-17 02:05:06 -03:00
app . GDPSName = ( app . isGDPS ? app . endpoint . split ( "/" ) [ 2 ] : "" )
2021-01-15 12:29:46 -03:00
2019-12-25 18:20:32 -03:00
app . run = { }
directories . forEach ( d => {
fs . readdirSync ( './api/' + d ) . forEach ( x => { if ( x . includes ( '.' ) ) app . run [ x . split ( '.' ) [ 0 ] ] = require ( './api/' + d + "/" + x ) } )
2019-10-16 19:47:53 -03:00
} )
2019-10-15 23:42:47 -03:00
2019-12-22 00:52:09 -03:00
try {
const secrets = require ( "./misc/secretStuff.json" )
app . id = secrets . id
app . gjp = secrets . gjp
2020-04-24 15:26:29 -04:00
app . sheetsKey = secrets . sheetsKey
2020-02-20 22:09:40 -03:00
if ( app . id == "account id goes here" || app . gjp == "account gjp goes here" ) console . warn ( "Warning: No account ID and/or GJP has been provided in secretStuff.json! These are required for level leaderboards to work." )
2020-04-24 15:26:29 -04:00
if ( app . sheetsKey . startsWith ( "google sheets api key" ) ) app . sheetsKey = undefined
2019-12-22 00:52:09 -03:00
}
2019-10-15 23:42:47 -03:00
2019-12-25 18:20:32 -03:00
catch ( e ) {
2019-12-22 00:52:09 -03:00
app . id = 0
app . gjp = 0
2020-02-20 22:09:40 -03:00
console . warn ( "Warning: secretStuff.json has not been created! These are required for level leaderboards to work." )
2019-10-15 23:42:47 -03:00
}
app . parseResponse = function ( responseBody , splitter ) {
2019-10-23 18:48:01 -03:00
if ( ! responseBody || responseBody == "-1" ) return { } ;
2021-01-15 12:29:46 -03:00
if ( responseBody . startsWith ( "\nWarning:" ) ) responseBody = responseBody . split ( "\n" ) . slice ( 2 ) . join ( "\n" ) . trim ( ) // GDPS'es are wild
if ( responseBody . startsWith ( "<br />" ) ) responseBody = responseBody . split ( "<br />" ) . slice ( 2 ) . join ( "<br />" ) . trim ( ) // Seriously screw this
2019-10-15 23:42:47 -03:00
let response = responseBody . split ( '#' ) [ 0 ] . split ( splitter || ':' ) ;
let res = { } ;
for ( let i = 0 ; i < response . length ; i += 2 ) {
res [ response [ i ] ] = response [ i + 1 ] }
2021-01-15 12:29:46 -03:00
return res
}
2019-10-15 23:42:47 -03:00
2019-10-16 19:47:53 -03:00
//xss bad
2019-10-25 18:22:29 -03:00
app . clean = function ( text ) { if ( ! text || typeof text != "string" ) return text ; else return text . replace ( /&/g , "&" ) . replace ( /</g , "<" ) . replace ( />/g , ">" ) . replace ( /=/g , "=" ) . replace ( /"/g , """ ) . replace ( /'/g , "'" ) }
2019-10-15 23:42:47 -03:00
2019-12-16 10:48:05 -03:00
// ASSETS
app . use ( '/assets' , express . static ( _ _dirname + '/assets' , { maxAge : "7d" } ) ) ;
2021-01-04 12:21:58 -03:00
app . use ( '/assets/css' , express . static ( _ _dirname + '/assets/css' ) ) ; // override maxAge
app . get ( "/sizecheck.js" , function ( req , res ) { res . sendFile ( _ _dirname + "/misc/sizecheck.js" ) } )
app . get ( "/dragscroll.js" , function ( req , res ) { res . sendFile ( _ _dirname + "/misc/dragscroll.js" ) } )
app . get ( "/assets/:dir*?" , function ( req , res ) {
let main = ( req . params . dir || "" ) . toLowerCase ( )
let dir = main + ( req . params [ 0 ] || "" ) . toLowerCase ( )
if ( dir . includes ( '.' ) || ! req . path . endsWith ( "/" ) ) {
if ( ! req . params [ 0 ] ) main = ""
2021-01-11 18:00:21 -03:00
if ( req . params . dir == "deatheffects" || req . params . dir == "trails" ) return res . sendFile ( _ _dirname + "/assets/deatheffects/0.png" )
2021-01-04 12:21:58 -03:00
return res . send ( ` <p style="font-size: 20px; font-family: aller, helvetica, arial">Looks like this file doesn't exist ¯ \\ _(ツ)_/¯<br><a href='/assets/ ${ main } '>View directory listing for <b>/assets/ ${ main } </b></a></p> ` )
}
let path = ` ./assets/ ${ dir } `
let files = [ ]
if ( fs . existsSync ( path ) ) { files = fs . readdirSync ( path ) }
assetPage = fs . readFileSync ( './html/assets.html' , 'utf8' )
// remember to remove this
let assetData = JSON . stringify ( { files : files . filter ( x => x . includes ( '.' ) ) , directories : files . filter ( x => ! x . includes ( '.' ) ) } )
res . send ( assetPage . replace ( '{NAME}' , dir || "assets" ) . replace ( '{DATA}' , assetData ) )
} )
2019-10-15 23:42:47 -03:00
2019-12-16 10:48:05 -03:00
// POST REQUESTS
2019-10-15 23:42:47 -03:00
2020-09-10 10:02:40 -03:00
app . post ( "/like" , RL , function ( req , res ) { app . run . like ( app , req , res ) } )
app . post ( "/postComment" , RL , function ( req , res ) { app . run . postComment ( app , req , res ) } )
app . post ( "/postProfileComment" , RL , function ( req , res ) { app . run . postProfileComment ( app , req , res ) } )
2019-12-25 18:20:32 -03:00
2020-09-10 10:02:40 -03:00
app . post ( "/messages" , RL , async function ( req , res ) { app . run . getMessages ( app , req , res ) } )
app . post ( "/messages/:id" , RL , async function ( req , res ) { app . run . fetchMessage ( app , req , res ) } )
app . post ( "/deleteMessage" , RL , function ( req , res ) { app . run . deleteMessage ( app , req , res ) } )
app . post ( "/sendMessage" , RL , function ( req , res ) { app . run . sendMessage ( app , req , res ) } )
2019-10-15 23:42:47 -03:00
2020-10-28 22:25:13 -03:00
app . post ( "/accurateLeaderboard" , function ( req , res ) { app . run . accurate ( app , req , res , true ) } )
2020-10-02 15:33:24 -03:00
2019-10-15 23:42:47 -03:00
2019-12-16 10:48:05 -03:00
// HTML
2019-10-15 23:42:47 -03:00
2020-09-10 10:02:40 -03:00
app . get ( "/" , function ( req , res ) {
if ( app . offline && ! req . query . hasOwnProperty ( "home" ) ) res . sendFile ( _ _dirname + "/html/offline.html" )
else res . sendFile ( _ _dirname + "/html/home.html" )
} )
2021-01-11 18:00:21 -03:00
app . get ( "/achievements" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/achievements.html" ) } )
2019-12-16 10:48:05 -03:00
app . get ( "/analyze/:id" , async function ( req , res ) { res . sendFile ( _ _dirname + "/html/analyze.html" ) } )
app . get ( "/api" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/api.html" ) } )
2020-11-16 21:28:24 -03:00
app . get ( "/boomlings" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/boomlings.html" ) } )
2019-12-16 10:48:05 -03:00
app . get ( "/comments/:id" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/comments.html" ) } )
2020-10-28 22:25:13 -03:00
app . get ( "/demon/:id" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/demon.html" ) } )
2019-12-16 10:48:05 -03:00
app . get ( "/gauntlets" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/gauntlets.html" ) } )
2019-12-16 17:01:46 -03:00
app . get ( "/iconkit" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/iconkit.html" ) } )
2019-12-16 10:48:05 -03:00
app . get ( "/leaderboard" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/leaderboard.html" ) } )
app . get ( "/leaderboard/:text" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/levelboard.html" ) } )
app . get ( "/mappacks" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/mappacks.html" ) } )
2019-12-29 20:59:29 -03:00
app . get ( "/messages" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/messages.html" ) } )
2019-12-16 10:48:05 -03:00
app . get ( "/search" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/filters.html" ) } )
app . get ( "/search/:text" , function ( req , res ) { res . sendFile ( _ _dirname + "/html/search.html" ) } )
2019-10-15 23:42:47 -03:00
2019-12-16 10:48:05 -03:00
// API
2019-10-21 00:33:01 -03:00
2020-09-10 10:02:40 -03:00
app . get ( "/api/analyze/:id" , RL , async function ( req , res ) { app . run . level ( app , req , res , api , true ) } )
2020-11-16 21:28:24 -03:00
app . get ( "/api/boomlings" , function ( req , res ) { app . run . boomlings ( app , req , res ) } )
2020-11-07 21:20:44 -03:00
app . get ( "/api/comments/:id" , RL2 , function ( req , res ) { app . run . comments ( app , req , res ) } )
2019-12-16 10:48:05 -03:00
app . get ( "/api/credits" , function ( req , res ) { res . send ( require ( './misc/credits.json' ) ) } )
2021-01-15 12:29:46 -03:00
app . get ( "/api/gauntlets" , async function ( req , res ) { app . run . gauntlets ( app , req , res ) } )
2020-10-28 22:25:13 -03:00
app . get ( "/api/leaderboard" , function ( req , res ) { app . run [ req . query . hasOwnProperty ( "accurate" ) ? "accurate" : "scores" ] ( app , req , res ) } )
2020-11-07 21:20:44 -03:00
app . get ( "/api/leaderboardLevel/:id" , RL2 , function ( req , res ) { app . run . leaderboardLevel ( app , req , res ) } )
2020-09-10 10:02:40 -03:00
app . get ( "/api/level/:id" , RL , async function ( req , res ) { app . run . level ( app , req , res , api ) } )
2021-01-15 12:29:46 -03:00
app . get ( "/api/mappacks" , async function ( req , res ) { app . run . mappacks ( app , req , res ) } )
2020-11-07 21:20:44 -03:00
app . get ( "/api/profile/:id" , RL2 , function ( req , res ) { app . run . profile ( app , req , res , api ) } )
app . get ( "/api/search/:text" , RL2 , function ( req , res ) { app . run . search ( app , req , res ) } )
2021-01-05 13:03:04 -03:00
app . get ( "/api/song/:song" , function ( req , res ) { app . run . song ( app , req , res ) } )
2019-12-16 10:48:05 -03:00
2019-10-15 23:42:47 -03:00
2019-12-16 10:48:05 -03:00
// REDIRECTS
2019-10-15 23:42:47 -03:00
2019-12-16 10:48:05 -03:00
app . get ( "/icon" , function ( req , res ) { res . redirect ( '/iconkit' ) } )
app . get ( "/iconkit/:text" , function ( req , res ) { res . redirect ( '/icon/' + req . params . text ) } )
2020-10-13 11:25:07 -03:00
app . get ( "/obj/:text" , function ( req , res ) { res . redirect ( '/obj/' + req . params . text ) } )
2019-12-16 10:48:05 -03:00
app . get ( "/leaderboards/:id" , function ( req , res ) { res . redirect ( '/leaderboard/' + req . params . id ) } )
2020-09-29 22:42:18 -03:00
app . get ( "/profile/:id" , function ( req , res ) { res . redirect ( '/u/' + req . params . id ) } )
app . get ( "/p/:id" , function ( req , res ) { res . redirect ( '/u/' + req . params . id ) } )
2019-12-16 10:48:05 -03:00
app . get ( "/l/:id" , function ( req , res ) { res . redirect ( '/leaderboard/' + req . params . id ) } )
app . get ( "/a/:id" , function ( req , res ) { res . redirect ( '/analyze/' + req . params . id ) } )
app . get ( "/c/:id" , function ( req , res ) { res . redirect ( '/comments/' + req . params . id ) } )
2020-10-28 22:25:13 -03:00
app . get ( "/d/:id" , function ( req , res ) { res . redirect ( '/demon/' + req . params . id ) } )
2019-10-15 23:42:47 -03:00
2020-09-24 23:07:53 -03:00
// API AND HTML
2020-09-29 22:42:18 -03:00
app . get ( "/u/:id" , function ( req , res ) { app . run . profile ( app , req , res ) } )
2020-09-24 23:07:53 -03:00
app . get ( "/:id" , function ( req , res ) { app . run . level ( app , req , res ) } )
2019-12-16 10:48:05 -03:00
// MISC
2019-10-15 23:42:47 -03:00
2019-12-25 18:20:32 -03:00
app . get ( "/icon/:text" , function ( req , res ) { app . run . icon ( app , req , res ) } )
2021-01-14 14:02:38 -03:00
app . get ( "/api/achievements" , function ( req , res ) { res . send ( { achievements , types : achievementTypes , shopIcons , colors : colorList } ) } )
2020-09-24 23:07:53 -03:00
app . get ( '/api/icons' , function ( req , res ) {
let sample = [ JSON . stringify ( sampleIcons [ Math . floor ( Math . random ( ) * sampleIcons . length ) ] . slice ( 1 ) ) ]
2020-11-26 18:04:42 -03:00
res . send ( { icons : gdIcons , colors : colorList , colorOrder , whiteIcons , sample } ) ;
2020-09-24 23:07:53 -03:00
} ) ;
2019-10-15 23:42:47 -03:00
app . get ( '*' , function ( req , res ) {
2019-12-16 10:48:05 -03:00
if ( req . path . startsWith ( '/api' ) ) res . send ( '-1' )
2019-12-15 21:11:35 -03:00
else res . redirect ( '/search/404%20' )
2019-10-15 23:42:47 -03:00
} ) ;
2020-11-01 17:29:32 -03:00
app . use ( function ( err , req , res , next ) {
if ( err && err . message == "Response timeout" ) res . status ( 500 ) . send ( 'Internal server error! (Timed out)' )
} )
2021-01-04 12:21:58 -03:00
app . listen ( app . config . port , ( ) => console . log ( ` Site online! (port ${ app . config . port } ) ` ) )