add ability for user to override skin and cape without sysop intervention

This commit is contained in:
xFuney
2021-01-09 13:28:39 +00:00
parent 5ad48f10e8
commit 8f1aec6d42
14 changed files with 735 additions and 4 deletions

241
index.js
View File

@@ -6,6 +6,10 @@
const path = require('path');
const fs = require('fs');
const request = require('sync-request');
const fetch = require('node-fetch');
var session = require('express-session');
const fileUpload = require('express-fileupload');
var sizeOf = require('image-size');
function downloadFileSync(url) {
return require('child_process')
@@ -371,6 +375,23 @@ function get_cape(uname) {
}
}
// oauth_to_uuid
// Purpose: Uses mc-oauth.net to translate an OAuth code into a UUID.
// Returns: Nothing - this is a callback based function.
function oAuthToUUID(token, cb) {
return fetch('https://mc-oauth.net/api/api?token', {
method: "GET",
headers: {
"token": token
}
}).then(response => {
return response.json();
}).then(json => {
cb(json);
});
}
// rm_dir
// Purpose: removes all files in a directory.
// Returns: nothing.
@@ -417,16 +438,232 @@ rm_dir(Cape_CacheLocation);
console.log('[CACHE] Cape cache wipe completed.')
// Initialise express.
const express = require('express')
const express = require('express');
const { O_TRUNC } = require('constants');
const { resourceUsage } = require('process');
const app = express()
const port = 80
const port = process.env.NODE_PORT || 6481
// Initialise session cookie middleware.
app.set('trust proxy', 1) // trust first proxy
app.use(session({
secret: process.env.COOKIE_SECRET,
resave: false,
saveUninitialized: true,
cookie: { secure: false, maxAge: 7200000 }
}))
// Initialise static folders.
app.use(express.static('static'))
app.use('/MinecraftResources', express.static('resources'))
// Initialise middleware.
app.use(express.urlencoded());
app.use(fileUpload());
// Initialise routers.
// POST /authenticate
// Parameters:
// code: OAuth code from MC-OAuth.
// Returns:
// 301 redirect to appropriate page.
app.post('/authenticate', (req,res) => {
oAuthToUUID(req.body.code, (response) => {
if (response.status !== "fail") {
// add to session
req.session.username = response.username
req.session.uuid = response.uuid
// redirect
res.redirect('/skin_landing')
} else {
// throw back to auth page.
res.redirect('/auth.html')
}
})
})
// GET /end_session
// No parameters.
// Returns:
// redirect to home page with user session expired.
app.get('/end_session', (req,res) => {
req.session.regenerate(function(err) {
// will have a new session here
res.redirect('/')
})
})
// GET /skin_landing
// No parameters.
// Returns:
// landing page
app.get('/skin_landing', (req,res) => {
if (req.session.username !== undefined) {
let temp = fs.readFileSync(path.join(__dirname, 'templates', 'landing.html')).toString();
temp = temp.replace('%%USERNAME%%', req.session.username)
temp = temp.replace('%%USERNAME%%', req.session.username)
temp = temp.replace('%%USERNAME%%', req.session.username)
temp = temp.replace('%%USERNAME%%', req.session.username)
temp = temp.replace('%%USERNAME%%', req.session.username)
temp = temp.replace('%%USERNAME%%', req.session.username)
res.send(temp)
} else {
res.redirect('/')
}
})
// GET /skin_change
// No parameters
// Returns:
// skin changing page
app.get('/skin_change', (req,res) => {
if (req.session.username !== undefined) {
let temp = fs.readFileSync(path.join(__dirname, 'templates', 'skin_change.html')).toString();
temp = temp.replace('%%USERNAME%%', req.session.username)
res.send(temp)
} else {
res.redirect('/')
}
})
// POST /upload_skin
// No parameters.
// Returns:
// success or fail page
app.post('/upload_skin', (req,res) => {
if (req.session.username == undefined) {
// Don't even look at it.
return res.redirect('/')
}
if (!req.files || Object.keys(req.files).length === 0) {
return res.redirect('/skin_change');
}
if (req.files.upload.mimetype != "image/png") {
return res.redirect('/skin_change');
}
// The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file
let sampleFile = req.files.upload;
// Use the mv() method to place the file somewhere on your server
sampleFile.mv(path.join(Skin_OverrideLocation, req.session.username + '.png'), function(err) {
if (err) {
return res.redirect('/failure');
}
var dimensions = sizeOf(path.join(Skin_OverrideLocation, req.session.username + '.png'));
if (dimensions.width == "64" && dimensions.height == "32") {
// classic steve skin
res.redirect('/success');
} else if (dimensions.width == "64" && dimensions.height == "64") {
// non-classic skins (alex or something)
res.redirect('/success')
} else {
// user must be trying to test us.
fs.unlinkSync(path.join(Skin_OverrideLocation, req.session.username + '.png'))
res.sendFile(path.join(__dirname, 'templates', 'size_too_big.html'))
}
});
})
// GET /cape_change
// No parameters
// Returns:
// cape changing page
app.get('/cape_change', (req,res) => {
if (req.session.username !== undefined) {
let temp = fs.readFileSync(path.join(__dirname, 'templates', 'cape_change.html')).toString();
temp = temp.replace('%%USERNAME%%', req.session.username)
res.send(temp)
} else {
res.redirect('/')
}
})
// POST /upload_cape
// No parameters.
// Returns:
// success or fail page
app.post('/upload_cape', (req,res) => {
if (req.session.username == undefined) {
// Don't even look at it.
return res.redirect('/')
}
if (!req.files || Object.keys(req.files).length === 0) {
return res.redirect('/cape_change');
}
if (req.files.upload.mimetype != "image/png") {
return res.redirect('/cape_change');
}
// The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file
let sampleFile = req.files.upload;
// Use the mv() method to place the file somewhere on your server
sampleFile.mv(path.join(Cape_OverrideLocation, req.session.username + '.png'), function(err) {
if (err) {
return res.redirect('/failure');
}
var dimensions = sizeOf(path.join(Cape_OverrideLocation, req.session.username + '.png'));
if (dimensions.width == "64" && dimensions.height == "32") {
// cape, GOOD
res.redirect('/success');
} else {
// user must be trying to test us.
fs.unlinkSync(path.join(Cape_OverrideLocation, req.session.username + '.png'))
res.sendFile(path.join(__dirname, 'templates', 'size_too_big.html'))
}
});
})
// GET /success
// No parameters.
// Returns:
// success page
app.get('/success', (req,res) => {
if (req.session.username !== undefined) {
let temp = fs.readFileSync(path.join(__dirname, 'templates', 'success.html')).toString();
temp = temp.replace('%%USERNAME%%', req.session.username)
res.send(temp)
} else {
res.redirect('/')
}
})
// GET /failure
// No parameters.
// Returns:
// failure page
app.get('/failure', (req,res) => {
if (req.session.username !== undefined) {
let temp = fs.readFileSync(path.join(__dirname, 'templates', 'failure.html')).toString();
temp = temp.replace('%%USERNAME%%', req.session.username)
res.send(temp)
} else {
res.redirect('/')
}
})
// GET /MinecraftSkins/:username
// Paramters:
// username: username of character to get skin for

95
package-lock.json generated
View File

@@ -76,6 +76,14 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"busboy": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz",
"integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==",
"requires": {
"dicer": "0.3.0"
}
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@@ -156,6 +164,14 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"dicer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz",
"integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==",
"requires": {
"streamsearch": "0.1.2"
}
},
"download-file-sync": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/download-file-sync/-/download-file-sync-1.0.4.tgz",
@@ -218,6 +234,41 @@
"vary": "~1.1.2"
}
},
"express-fileupload": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.2.0.tgz",
"integrity": "sha512-oe4WpKcSppXnl5peornawWUa6tKmIc1/kJxMNRGJR1A0v4zyLL6VsFR6wZ8P2a4Iq3aGx8xae3Vlr+MOMQhFPw==",
"requires": {
"busboy": "^0.3.1"
}
},
"express-session": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz",
"integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==",
"requires": {
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.0.2",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.0",
"uid-safe": "~2.1.5"
},
"dependencies": {
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
},
"safe-buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
}
}
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -296,6 +347,14 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"image-size": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.9.3.tgz",
"integrity": "sha512-5SakFa79uhUVSjKeQE30GVzzLJ0QNzB53+I+/VD1vIesD6GP6uatWIlgU0uisFNLt1u0d6kBydp7yfk+lLJhLQ==",
"requires": {
"queue": "6.0.1"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
@@ -354,6 +413,11 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -362,6 +426,11 @@
"ee-first": "1.1.1"
}
},
"on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
},
"parse-cache-control": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
@@ -404,6 +473,19 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"queue": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.1.tgz",
"integrity": "sha512-AJBQabRCCNr9ANq8v77RJEv73DPbn55cdTb+Giq4X0AVnNVZvMHlYp7XlQiN+1npCZj1DuSmaA2hYVUUDgxFDg==",
"requires": {
"inherits": "~2.0.3"
}
},
"random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -492,6 +574,11 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -562,6 +649,14 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"requires": {
"random-bytes": "~1.0.0"
}
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

View File

@@ -11,6 +11,10 @@
"dependencies": {
"download-file-sync": "^1.0.4",
"express": "^4.17.1",
"express-fileupload": "^1.2.0",
"express-session": "^1.17.1",
"image-size": "^0.9.3",
"node-fetch": "^2.6.1",
"sync-request": "^6.1.0"
}
}

40
static/auth.html Normal file
View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Action MC Skins</title>
<link rel="stylesheet" type="text/css" href="/style-dark.css">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body class="body-dark">
<div class="title-frame center">
<h1>ActionMC Skins</h1>
</div>
<div class="main-frame center">
<h3>To authenticate, join this server (works with MC 1.7.x to latest):</h3>
<h2>srv.mc-oauth.net</h2>
<h3>It will give you a six digit code, enter that in the input box below.</h3>
</div>
<div class="main-frame center">
<h3>Authenticate</h3>
<form action="/authenticate" method="POST">
<input name="code" placeholder="Code (e.g. 007007)">
<br />
<br />
<button type="submit">Submit Code</button>
<h4>Codes generated by MC-OAuth expire within 5 minutes of their creation!</h4>
</form>
</div>
<div class="hidden-frame center">
<p>Powered by Theta, a custom skin server solution by Virtue.</p>
</div>
</body>
</html>

View File

@@ -1,2 +1,43 @@
<h1>Theta Skin Server</h1>
<p>Your website goes here!</p>
<!DOCTYPE html>
<html>
<head>
<title>Action MC Skins</title>
<link rel="stylesheet" type="text/css" href="/style-dark.css">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body class="body-dark">
<div class="title-frame center">
<h1>ActionMC Skins</h1>
</div>
<div class="main-frame center">
<h3>This page lets you change your skin on our skins server.</h3>
<h3>Click the button below to start the authentication procedure.</h3>
<button onmouseup="window.location.href = '/auth.html';">Authenticate</button>
<h3>Currently, changing your skin is only available to people with premium Minecraft accounts.</h3>
<h4>This is subject to change.</h4>
</div>
<div class="main-frame center">
<h3>What is a "Premium" Minecraft account?</h3>
<h4>A premium Minecraft account is an account that you have PAID for e.g. bought from Mojang/Microsoft.</h4>
</div>
<div class="main-frame center">
<h3>Other Links</h3>
<h4><a href="/skin-previewer.html">Skin Previewer</a></h4>
</div>
<div class="hidden-frame center">
<p>Powered by Theta, a custom skin server solution by Virtue.</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,2 @@
<h1>Theta Skin Server</h1>
<p>Your website goes here!</p>

76
static/style-dark.css Normal file
View File

@@ -0,0 +1,76 @@
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
background-image: url("/tile.png");
background-repeat: repeat;
}
.body-dark {
background-image: url("/tile_dark.png");
background-repeat: repeat;
}
.alert-frame {
padding-top: 2px;
padding-bottom: 2px;
text-align:center;
background-color: #800080;
color: white;
margin: 20px;
width: 90%;
height: 90%;
margin-left: auto;
margin-right: auto;
}
.invis {
display: none;
}
.title-frame {
padding-top: 10px;
padding-bottom: 10px;
text-align:center;
background-color: #3a3a3a;
color: white;
margin: 50px;
width: 90%;
height: 90%;
margin-left: auto;
margin-right: auto;
}
.main-frame {
padding-top: 2px;
padding-bottom: 2px;
text-align:center;
background-color: #4a4a4a;
color: white;
margin: 50px;
width: 90%;
height: 90%;
margin-left: auto;
margin-right: auto;
}
.server-frame {
padding-top: 2px;
padding-bottom: 2px;
text-align:center;
background-color: #606060;
color: white;
margin: 50px;
width: 90%;
height: 90%;
margin-left: auto;
margin-right: auto;
}
.hidden-frame {
text-align:center;
color: white;
margin: 50px;
width: 90%;
height: 90%;
margin-left: auto;
margin-right: auto;
}

BIN
static/tile_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Action MC Skins</title>
<link rel="stylesheet" type="text/css" href="/style-dark.css">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<style>
button {
padding: 10px;
padding-left: 20px;
padding-right: 20px;
text-align:center;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body class="body-dark">
<div class="title-frame center">
<h1>ActionMC Skins</h1>
</div>
<div class="main-frame center">
<h3>Cape Change</h3>
<h2>Please upload the cape you wish to put on your character:</h2>
<form action="/upload_cape" method="POST" enctype="multipart/form-data">
<input type="file" name="upload" accept="image/png" />
<br />
<button type="submit">Submit</button>
</form>
<h3>Maximum file size is 16KB (16384 bytes). Skin must be a PNG file.</h3>
<h4></h4>
</div>
<div class="hidden-frame center">
<p>Powered by Theta, a custom skin server solution by Virtue.</p>
</div>
</body>
</html>

35
templates/failure.html Normal file
View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Action MC Skins</title>
<link rel="stylesheet" type="text/css" href="/style-dark.css">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<style>
button {
padding: 10px;
padding-left: 20px;
padding-right: 20px;
text-align:center;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body class="body-dark">
<div class="title-frame center">
<h1>ActionMC Skins</h1>
</div>
<div class="main-frame center">
<h2>An error occured.</h2>
<h3>The server encountered an error trying to handle your request.</h3>
<button onmousedown="window.location.href='/skin_landing';">Try Again</button>
<h4></h4>
</div>
<div class="hidden-frame center">
<p>Powered by Theta, a custom skin server solution by Virtue.</p>
</div>
</body>
</html>

49
templates/landing.html Normal file
View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<title>Action MC Skins</title>
<link rel="stylesheet" type="text/css" href="/style-dark.css">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<style>
button {
padding: 10px;
padding-left: 20px;
padding-right: 20px;
text-align:center;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body class="body-dark">
<div class="title-frame center">
<h1>ActionMC Skins</h1>
</div>
<div class="main-frame center">
<h3>Hello, %%USERNAME%%.</h3>
<h2>What do you wish to change?</h2>
<button onmouseup="window.location.href='/skin_change';">Skin</button>
<button onmouseup="window.location.href='/cape_change';">Cape</button>
<br />
<br />
<button onmouseup="window.location.href='/end_session';">Logout</button>
<h4></h4>
</div>
<div class="main-frame center">
<h3>These are your current skin and cape textures:</h3>
<table style="width:100%;">
<tr>
<th><h3>Skin</h3><a href="/MinecraftSkins/%%USERNAME%%.png"><img src="MinecraftSkins/%%USERNAME%%.png" /></a></th>
<th><h3>Cape</h3><a href="/MinecraftCloaks/%%USERNAME%%.png"><img src="/MinecraftCloaks/%%USERNAME%%.png" /></a></th>
</tr>
</table>
<h4>Keep in mind these are the skins we either have cached from Mojang for you or the skin you set using our service.</h4>
</div>
<div class="hidden-frame center">
<p>Powered by Theta, a custom skin server solution by Virtue.</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<title>Action MC Skins</title>
<link rel="stylesheet" type="text/css" href="/style-dark.css">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<style>
button {
padding: 10px;
padding-left: 20px;
padding-right: 20px;
text-align:center;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body class="body-dark">
<div class="title-frame center">
<h1>ActionMC Skins</h1>
</div>
<div class="main-frame center">
<h2>Image size too big!</h2>
<h3>Skins must be either 64x32 or 64x64 pixels.</h3>
<h3>Capes can only be 64x32 pixels.</h3>
<button onmousedown="window.location.href='/skin_landing';">Try Again</button>
<h4>The file you have uploaded was removed from the server.</h4>
<h4></h4>
</div>
<div class="hidden-frame center">
<p>Powered by Theta, a custom skin server solution by Virtue.</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Action MC Skins</title>
<link rel="stylesheet" type="text/css" href="/style-dark.css">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<style>
button {
padding: 10px;
padding-left: 20px;
padding-right: 20px;
text-align:center;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body class="body-dark">
<div class="title-frame center">
<h1>ActionMC Skins</h1>
</div>
<div class="main-frame center">
<h3>Skin Change</h3>
<h2>Please upload the skin you wish to put on your character:</h2>
<form action="/upload_skin" method="POST" enctype="multipart/form-data">
<input type="file" name="upload" accept="image/png" />
<br />
<button type="submit">Submit</button>
</form>
<h3>Maximum file size is 16KB (16384 bytes). Skin must be a PNG file.</h3>
<h4></h4>
</div>
<div class="hidden-frame center">
<p>Powered by Theta, a custom skin server solution by Virtue.</p>
</div>
</body>
</html>

35
templates/success.html Normal file
View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Action MC Skins</title>
<link rel="stylesheet" type="text/css" href="/style-dark.css">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<style>
button {
padding: 10px;
padding-left: 20px;
padding-right: 20px;
text-align:center;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body class="body-dark">
<div class="title-frame center">
<h1>ActionMC Skins</h1>
</div>
<div class="main-frame center">
<h2>Skin Successfully Changed!</h2>
<h3>You will now see your new skin in-game.</h3>
<button onmousedown="window.location.href='/skin_landing';">Go Back</button>
<h4></h4>
</div>
<div class="hidden-frame center">
<p>Powered by Theta, a custom skin server solution by Virtue.</p>
</div>
</body>
</html>