Merge branch 'GDColon-master'
70
.github/workflows/analyse.yml
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '42 17 * * 4'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
13
.gitignore
vendored
|
@ -1,7 +1,9 @@
|
|||
# Ew
|
||||
extra
|
||||
|
||||
# Package manager lockfiles
|
||||
package-lock.json
|
||||
misc/secretStuff.json
|
||||
yarn.lock
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
@ -41,9 +43,6 @@ build/Release
|
|||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
|
@ -61,6 +60,8 @@ typings/
|
|||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
# Editors
|
||||
.idea
|
||||
.vscode
|
166
README.md
|
@ -1,3 +1,9 @@
|
|||
hi, this is colon from the future.
|
||||
|
||||
what the FUCK was wrong with me back then???? seriously this is some of the worst code i've ever seen
|
||||
|
||||
welp, here's the readme. but you've been warned,,,
|
||||
|
||||
|
||||
# GDBrowser
|
||||
|
||||
|
@ -32,43 +38,33 @@ If you want to disable rate limits, ip forwarding, etc you can do so by modifyin
|
|||
Hold up, wait a minute... private servers are an official feature now!
|
||||
|
||||
|
||||
If you would like to add your GDPS to GDBrowser, simply reach out to me on [Twitter](https://twitter.com/TheRealGDColon) and I'll be happy to add it (provided the server is relatively large and active)
|
||||
If you would like to add your GDPS to GDBrowser, [fill out this quick form](https://forms.gle/kncuRqyKykQX42QD7) and I'll be happy to add it (provided the server is relatively large and active)
|
||||
|
||||
|
||||
If you 100% insist on adding a private server to your own magical little fork, you can do so by adding it to **servers.json**. Simply add a new object to the array with the following information:
|
||||
|
||||
| identifier | description |
|
||||
|:----------------:|:-----------------------------:|
|
||||
| `name` | The display name of the server |
|
||||
| `link` | The server's website URL (unrelated to the actual endpoint) |
|
||||
| `author` | The creator(s) of the server |
|
||||
| `authorLink` | The URL to open when clicking on the creator's name |
|
||||
| `id` | An ID for the server, also used as the subdomain (e.g. `something` would become `something.gdbrowser.com`) |
|
||||
| `endpoint` | The actual endpoint to ~~spam~~ send requests to (e.g. `http://boomlings.com/database/` - make sure it ends with a slash!) |
|
||||
|
||||
|
||||
|
||||
**name:** The display name of the server
|
||||
|
||||
**link:** The server's website URL (unrelated to the actual endpoint)
|
||||
|
||||
**author:** The creator(s) of the server
|
||||
|
||||
**authorLink:** The URL to open when clicking on the creator's name
|
||||
|
||||
**id:** An ID for the server, also used as the subdomain (e.g. `something` would become `something.gdbrowser.com`)
|
||||
|
||||
**endpoint:** The actual endpoint to ~~spam~~ send requests to (e.g. `http://boomlings.com/database/` - make sure it ends with a slash!)
|
||||
|
||||
|
||||
----
|
||||
There's also a few optional values for fine-tuning. I'll add more over time
|
||||
|
||||
[string] **timestampSuffix:** A string to append at the end of timestamps. Vanilla GD uses " ago"
|
||||
|
||||
[string] **demonList:** The URL of the server's Demon List API, if it has one (e.g. `http://pointercrate.com/` - make sure it ends with a slash!)
|
||||
|
||||
[bool] **downloadsDisabled:** (Greys out all forms of downloading on the frontend (daily, weekly, analysis, etc). I love you too RobTop <3
|
||||
|
||||
[bool] **onePointNine:** Makes a bunch of fancy changes to better fit 1.9 servers. (removes orbs/diamonds, hides some pointless buttons, etc)
|
||||
|
||||
[bool] **weeklyLeaderboard:** Enables the lost but not forgotten Weekly Leaderboard, for servers that still milk it
|
||||
|
||||
[object] **substitutions:** A list of parameter substitutions, because some servers rename/obfuscate them. (e.g. `{ "levelID": "oiuyhxp4w9I" }`)
|
||||
|
||||
[object] **overrides:** A list of endpoint substitutions, because some servers use renamed or older versions. (e.g. `{ "getGJLevels21": "dorabaeChooseLevel42" }`)
|
||||
| identifier | description | type |
|
||||
|:----------------:|:-----------------------------:|:----:|
|
||||
| `timestampSuffix` | A string to append at the end of timestamps. Vanilla GD uses " ago" | string |
|
||||
| `demonList` | The URL of the server's Demon List API, if it has one (e.g. `http://pointercrate.com/` - make sure it ends with a slash!) | string |
|
||||
| `disabled` | An array of menu buttons to "disable" (mappacks, gauntlets, daily, weekly, etc). They appear greyed out but are still clickable. | array |
|
||||
| `pinned` | "Pins" the server to the top of the GDPS list. It appears above all unpinned servers and is not placed in alphabetical order. | bool |
|
||||
| `onePointNine` | Makes a bunch of fancy changes to better fit 1.9 servers. (removes orbs/diamonds, hides some pointless buttons, etc) | bool |
|
||||
| `weeklyLeaderboard` | Enables the lost but not forgotten Weekly Leaderboard, for servers that still milk it | bool |
|
||||
| `substitutions` | A list of parameter substitutions, because some servers rename/obfuscate them. (e.g. `{ "levelID": "oiuyhxp4w9I" }`) | object |
|
||||
| `overrides` | A list of endpoint substitutions, because some servers use renamed or older versions. (e.g. `{ "getGJLevels21": "dorabaeChooseLevel42" }`) | object |
|
||||
|
||||
|
||||
|
||||
|
@ -94,9 +90,6 @@ This is where all the backend stuff happens! Yipee!
|
|||
|
||||
They're all fairly similar. Fetch something, parse the response, and serve it in a crisp and non-intimidating JSON. This is probably what you came for.
|
||||
|
||||
|
||||
|
||||
The odd one out is icon.js, which is for generating GD icons. The code here is horrendous, so apologies in advance. Improvements to it would be greatly appreciated! (and i will love you forever)
|
||||
|
||||
|
||||
|
||||
|
@ -136,30 +129,6 @@ XOR.js encrypts/decrypts stuff like GD passwords
|
|||
|
||||
The HTML files! Nothing too fancy, since it can all be seen directly from gdbrowser. Note that profile.html and level.html (and some parts of home.html) have [[VARIABLES]] (name, id, etc) replaced by the server when they're sent.
|
||||
|
||||
|
||||
|
||||
comingsoon.html was used while the site was still in development, I just left it in there as a nice little throwback
|
||||
|
||||
|
||||
|
||||
## Icons
|
||||
|
||||
It's GJ_Gamesheet02 but split into a much more intimidating cluster of a million files. These icons are put together and colored in the monstrosity that is icon.js
|
||||
|
||||
|
||||
|
||||
parseIconPlist.js reads GJ_GameSheet02-uhd.plist and magically transforms it into gameSheet.json. Props to 101arrowz for helping make this
|
||||
|
||||
|
||||
colors.json is a list of the player colors in GD. Fairly straight forward
|
||||
|
||||
|
||||
forms.json is a list of the different icon forms, their ingame filenames, and their index in responses from the GD servers
|
||||
|
||||
|
||||
offsets.json is a bunch of hardcoded offsets. Desperate times call for desperate measures
|
||||
|
||||
|
||||
|
||||
## Misc
|
||||
|
||||
|
@ -170,81 +139,32 @@ Inevitable misc folder
|
|||
**Level Analysis Stuff (in a separate folder)**
|
||||
|
||||
|
||||
|
||||
blocks.json - The object IDs in the different 'families' of blocks
|
||||
|
||||
|
||||
|
||||
colorProperties.json - Color channel cheatsheet
|
||||
|
||||
|
||||
|
||||
initialProperties.json - Level settings cheatsheet
|
||||
|
||||
|
||||
|
||||
objectProperties.json - Object property cheatsheet. Low budget version of [AlFas' one](https://github.com/AlFasGD/GDAPI/blob/master/GDAPI/GDAPI/Enumerations/GeometryDash/ObjectProperty.cs)
|
||||
|
||||
|
||||
|
||||
objects.json - IDs for portals, orbs, triggers, and misc stuff
|
||||
| name | description |
|
||||
|:----:|:-----------:|
|
||||
| `blocks.json` | The object IDs in the different 'families' of blocks |
|
||||
| `colorProperties.json` | Color channel cheatsheet |
|
||||
| `initialProperties.json` | Level settings cheatsheet |
|
||||
| `objectProperties.json` | Object property cheatsheet. Low budget version of [AlFas' one](https://github.com/AlFasGD/GDAPI/blob/master/GDAPI/GDAPI/Enumerations/GeometryDash/ObjectProperty.cs) |
|
||||
| `objects.json` | IDs for portals, orbs, triggers, and misc stuff |
|
||||
|
||||
|
||||
|
||||
**Everything Else**
|
||||
|
||||
|
||||
|
||||
achievements.json - List of all GD/meltdown/subzero/etc achievements. `parseAchievementPlist.js` automatically creates this file
|
||||
|
||||
|
||||
|
||||
achievementTypes.json - An object containing different categories of achievements (stars, shards, vault, etc) and how to identify them
|
||||
|
||||
|
||||
|
||||
colors.json - List of icon colors in RGB format
|
||||
|
||||
|
||||
|
||||
credits.json - Credits! (shown on the homepage)
|
||||
|
||||
|
||||
|
||||
dragscroll.js - Used on several pages for drag scrolling
|
||||
|
||||
|
||||
|
||||
music.json - An array of the official GD tracks (name, artist)
|
||||
|
||||
|
||||
|
||||
parseAchievementPlist.js - A script that reads GD's achievement .plist files and converts it into achievements.json
|
||||
|
||||
|
||||
|
||||
sampleIcons.json - A pool of icons, one of which will randomly appear when visiting the icon kit. Syntax is [Name, ID, Col1, Col2, Glow],
|
||||
|
||||
|
||||
|
||||
secretStuff.json - GJP goes here, needed for level leaderboards. Not included in the repo for obvious reasons
|
||||
|
||||
|
||||
|
||||
settings.js - Tweak small settings here, mainly for local use or GDPS'es
|
||||
|
||||
|
||||
|
||||
shops.js - A hardcoded list of all the shop icons in GD
|
||||
|
||||
|
||||
|
||||
sizecheck.js - Excecuted on most pages. Used for the 'page isn't wide enough' message, back button, and a few other things
|
||||
|
||||
|
||||
| name | description |
|
||||
|:----:|:-----------:|
|
||||
| `achievements.json` | List of all GD/meltdown/subzero/etc achievements. `parseAchievementPlist.js` automatically creates this file |
|
||||
| `achievementTypes.json` | An object containing different categories of achievements (stars, shards, vault, etc) and how to identify them |
|
||||
| `credits.json` | Credits! (shown on the homepage) |
|
||||
| `dragscroll.js` | Used on several pages for drag scrolling |
|
||||
| `global.js` | Excecuted on most pages. Used for the 'page isn't wide enough' message, back button, icons, and a few other things |
|
||||
| `music.json` | An array of the official GD tracks (name, artist) |
|
||||
| `sampleIcons.json` | A pool of icons, one of which will randomly appear when visiting the icon kit. Syntax is [Name, ID, Col1, Col2, Glow] |
|
||||
| `secretStuff.json` | GJP goes here, needed for level leaderboards. Not included in the repo for obvious reasons |
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
happy gdbrowsing and god bless.
|
||||
happy gdbrowsing and god bless.
|
||||
|
|
|
@ -6,6 +6,14 @@ const properties = require('../misc/analysis/objectProperties.json')
|
|||
const ids = require('../misc/analysis/objects.json')
|
||||
|
||||
module.exports = async (app, req, res, level) => {
|
||||
|
||||
if (!level) {
|
||||
level = {
|
||||
name: (req.body.name || "Unnamed").slice(0, 64),
|
||||
data: (req.body.data || "")
|
||||
}
|
||||
}
|
||||
|
||||
let unencrypted = level.data.startsWith('kS') // some gdps'es don't encrypt level data
|
||||
let levelString = unencrypted ? level.data : Buffer.from(level.data, 'base64')
|
||||
|
||||
|
@ -16,7 +24,7 @@ module.exports = async (app, req, res, level) => {
|
|||
return res.send(response_data);
|
||||
} else {
|
||||
zlib.unzip(levelString, (err, buffer) => {
|
||||
if (err) { return res.send("-2"); }
|
||||
if (err) { return res.status(500).send("-2"); }
|
||||
|
||||
const raw_data = buffer.toString();
|
||||
const response_data = analyze_level(level, raw_data);
|
||||
|
@ -56,6 +64,7 @@ function analyze_level(level, rawData) {
|
|||
let miscCounts = {}
|
||||
let triggerGroups = []
|
||||
let highDetail = 0
|
||||
let alphaTriggers = []
|
||||
|
||||
let misc_objects = {};
|
||||
let block_ids = {};
|
||||
|
@ -145,9 +154,19 @@ function analyze_level(level, rawData) {
|
|||
last = Math.max(last, obj.x);
|
||||
}
|
||||
|
||||
if (obj.trigger == "Alpha") { // invisible triggers
|
||||
alphaTriggers.push(obj)
|
||||
}
|
||||
|
||||
data[i] = obj;
|
||||
}
|
||||
|
||||
let invisTriggers = []
|
||||
alphaTriggers.forEach(tr => {
|
||||
if (tr.x < 500 && !tr.touchTriggered && !tr.spawnTriggered && tr.opacity == 0 && tr.duration == 0
|
||||
&& alphaTriggers.filter(x => x.targetGroupID == tr.targetGroupID).length == 1) invisTriggers.push(Number(tr.targetGroupID))
|
||||
})
|
||||
|
||||
response.level = {
|
||||
name: level.name, id: level.id, author: level.author, playerID: level.playerID, accountID: level.accountID, large: level.large
|
||||
}
|
||||
|
@ -157,7 +176,7 @@ function analyze_level(level, rawData) {
|
|||
response.settings = {}
|
||||
|
||||
response.portals = level_portals.sort(function (a, b) {return parseInt(a.x) - parseInt(b.x)}).map(x => x.portal + " " + Math.floor(x.x / (Math.max(last, 529.0) + 340.0) * 100) + "%").join(", ")
|
||||
response.coins = level_coins.sort(function (a, b) {return parseInt(a.x) - parseInt(b.x)}).map(x => x.coin + " " + Math.floor(x.x / (Math.max(last, 529.0) + 340.0) * 100) + "%").join(", ")
|
||||
response.coins = level_coins.sort(function (a, b) {return parseInt(a.x) - parseInt(b.x)}).map(x => Math.floor(x.x / (Math.max(last, 529.0) + 340.0) * 100))
|
||||
response.coinsVerified = level.verifiedCoins
|
||||
|
||||
response.orbs = orb_array
|
||||
|
@ -177,7 +196,11 @@ function analyze_level(level, rawData) {
|
|||
})
|
||||
|
||||
response.triggerGroups = sortObj(response.triggerGroups)
|
||||
response.triggerGroups.total = Object.keys(response.triggerGroups).length
|
||||
let triggerKeys = Object.keys(response.triggerGroups).map(x => Number(x.slice(6)))
|
||||
response.triggerGroups.total = triggerKeys.length
|
||||
|
||||
// find alpha group with the most objects
|
||||
response.invisibleGroup = triggerKeys.find(x => invisTriggers.includes(x))
|
||||
|
||||
response.text = level_text.sort(function (a, b) {return parseInt(a.x) - parseInt(b.x)}).map(x => [Buffer.from(x.message, 'base64').toString(), Math.round(x.x / last * 99) + "%"])
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
const Player = require('../classes/Player.js')
|
||||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.offline) return res.send("-1")
|
||||
if (req.offline) return res.sendError()
|
||||
|
||||
let count = +req.query.count || 10
|
||||
if (count > 1000) count = 1000
|
||||
|
@ -20,14 +22,14 @@ module.exports = async (app, req, res) => {
|
|||
|
||||
req.gdRequest(path, req.gdParams(params), function(err, resp, body) {
|
||||
|
||||
if (err || body == '-1' || !body) return res.send("-1")
|
||||
if (err) return res.sendError()
|
||||
|
||||
comments = body.split('|')
|
||||
comments = comments.map(x => x.split(':'))
|
||||
comments = comments.map(x => x.map(x => app.parseResponse(x, "~")))
|
||||
if (req.query.type == "profile") comments.filter(x => x[0][2])
|
||||
else comments = comments.filter(x => x[1] && x[1][1])
|
||||
if (!comments.length) return res.send("-1")
|
||||
else comments = comments.filter(x => x[0] && x[0][2])
|
||||
if (!comments.length) return res.status(204).send([])
|
||||
|
||||
let pages = body.split('#')[1].split(":")
|
||||
let lastPage = +Math.ceil(+pages[0] / +pages[2]);
|
||||
|
@ -52,20 +54,15 @@ module.exports = async (app, req, res) => {
|
|||
}
|
||||
|
||||
if (req.query.type != "profile") {
|
||||
comment.username = y[1] || "Unknown"
|
||||
let commentUser = new Player(y)
|
||||
Object.keys(commentUser).forEach(k => {
|
||||
comment[k] = commentUser[k]
|
||||
})
|
||||
comment.levelID = x[1] || req.params.id
|
||||
comment.playerID = x[3]
|
||||
comment.accountID = y[16]
|
||||
comment.playerID = x[3] || 0
|
||||
comment.color = (comment.playerID == "16" ? "50,255,255" : x[12] || "255,255,255")
|
||||
if (x[10] > 0) comment.percent = +x[10]
|
||||
comment.moderator = +x[11] || 0
|
||||
comment.icon = {
|
||||
form: ['icon', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider'][+y[14]],
|
||||
icon: +y[9] || 1,
|
||||
col1: +y[10],
|
||||
col2: +y[11],
|
||||
glow: +y[15] > 1
|
||||
}
|
||||
app.userCache(req.id, comment.accountID, comment.playerID, comment.username)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,10 +4,14 @@ const Level = require('../classes/Level.js')
|
|||
|
||||
module.exports = async (app, req, res, api, ID, analyze) => {
|
||||
|
||||
function rejectLevel() {
|
||||
if (!api) return res.redirect('search/' + req.params.id)
|
||||
else return res.sendError()
|
||||
}
|
||||
|
||||
if (req.offline) {
|
||||
if (!api && levelID < 0) return res.redirect('/')
|
||||
if (!api) return res.redirect('search/' + req.params.id)
|
||||
else return res.send("-1")
|
||||
return rejectLevel()
|
||||
}
|
||||
|
||||
let levelID = ID || req.params.id
|
||||
|
@ -17,17 +21,17 @@ module.exports = async (app, req, res, api, ID, analyze) => {
|
|||
|
||||
req.gdRequest('downloadGJLevel22', { levelID }, function (err, resp, body) {
|
||||
|
||||
if (err || !body || body == '-1' || body.startsWith("<")) {
|
||||
if (analyze && api && req.server.downloadsDisabled) return res.send("-3")
|
||||
if (err) {
|
||||
if (analyze && api && req.server.downloadsDisabled) return res.status(403).send("-3")
|
||||
else if (!api && levelID < 0) return res.redirect(`/?daily=${levelID * -1}`)
|
||||
else if (!api) return res.redirect('search/' + req.params.id)
|
||||
else return res.send("-1")
|
||||
else return rejectLevel()
|
||||
}
|
||||
|
||||
let authorData = body.split("#")[3] // daily/weekly only, most likely
|
||||
|
||||
let levelInfo = app.parseResponse(body)
|
||||
let level = new Level(levelInfo, req.server, true)
|
||||
if (!level.id) return rejectLevel()
|
||||
|
||||
let foundID = app.accountCache[req.id][Object.keys(app.accountCache[req.id]).find(x => app.accountCache[req.id][x][1] == level.playerID)]
|
||||
if (foundID) foundID = foundID.filter(x => x != level.playerID)
|
||||
|
@ -39,7 +43,7 @@ module.exports = async (app, req, res, api, ID, analyze) => {
|
|||
if (err2 && (foundID || authorData)) {
|
||||
let authorInfo = foundID || authorData.split(":")
|
||||
level.author = authorInfo[1] || "-"
|
||||
level.accountID = authorInfo[0].includes(",") ? "0" : authorInfo[0]
|
||||
level.accountID = authorInfo[0] && authorInfo[0].includes(",") ? "0" : authorInfo[0]
|
||||
}
|
||||
|
||||
else if (!err && b2 != '-1') {
|
||||
|
@ -60,7 +64,7 @@ module.exports = async (app, req, res, api, ID, analyze) => {
|
|||
level = level.getSongInfo(app.parseResponse(songRes, '~|~'))
|
||||
level.extraString = levelInfo[36]
|
||||
level.data = levelInfo[4]
|
||||
if (req.isGDPS) level.gdps = (req.onePointNine ? "1.9/" : "") + req.endpoint
|
||||
if (req.isGDPS) level.gdps = (req.onePointNine ? "1.9/" : "") + req.server.id
|
||||
|
||||
if (analyze) return app.run.analyze(app, req, res, level)
|
||||
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
let cache = {}
|
||||
let gauntletNames = ["Fire", "Ice", "Poison", "Shadow", "Lava", "Bonus", "Chaos", "Demon", "Time", "Crystal", "Magic", "Spike", "Monster", "Doom", "Death"]
|
||||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.offline) return res.send("-1")
|
||||
if (req.offline) return res.sendError()
|
||||
|
||||
let cached = cache[req.id]
|
||||
if (app.config.cacheGauntlets && cached && cached.data && cached.indexed + 2000000 > Date.now()) return res.send(cached.data) // half hour cache
|
||||
|
||||
req.gdRequest('getGJGauntlets21', {}, function (err, resp, body) {
|
||||
|
||||
if (err || !body || body == '-1' || body.startsWith("<")) return res.send("-1")
|
||||
if (err) return res.sendError()
|
||||
let gauntlets = body.split('#')[0].split('|').map(x => app.parseResponse(x)).filter(x => x[3])
|
||||
let gauntletList = gauntlets.map(x => ({ id: +x[1], levels: x[3].split(",") }))
|
||||
let gauntletList = gauntlets.map(x => ({ id: +x[1], name: gauntletNames[+x[1] - 1] || "Unknown", levels: x[3].split(",") }))
|
||||
|
||||
if (app.config.cacheGauntlets) cache[req.id] = {data: gauntletList, indexed: Date.now()}
|
||||
res.send(gauntletList)
|
||||
|
|
389
api/icon.js
|
@ -1,389 +0,0 @@
|
|||
// this file is a potential candidate for worst code on github
|
||||
// i advise you to turn back now
|
||||
// seriously, it's not too late
|
||||
|
||||
const Jimp = require('jimp');
|
||||
const fs = require('fs');
|
||||
const icons = require('../icons/gameSheet.json');
|
||||
const colors = require('../icons/colors.json');
|
||||
const forms = require('../icons/forms.json')
|
||||
const offsets = require('../icons/offsets.json');
|
||||
|
||||
let hexRegex = /^[A-Fa-f0-9]{6}$/
|
||||
function hexConvert(hex) { hex = hex.replace('#', ''); return {r: '0x' + hex[0] + hex[1] | 0, g: '0x' + hex[2] + hex[3] | 0, b: '0x' + hex[4] + hex[5] | 0}; }
|
||||
function recolor(img, col) {
|
||||
return img.scan(0, 0, img.bitmap.width, img.bitmap.height, function (x, y, idx) {
|
||||
if (img.bitmap.data.slice(idx, idx+3).every(function(val) {return val >= 20 && val <= 255})) { // If it's not "black, i.e. we want to recolor it"
|
||||
this.bitmap.data[idx] = colors[col].r / (255 / this.bitmap.data[idx]);
|
||||
this.bitmap.data[idx + 1] = colors[col].g / (255 / this.bitmap.data[idx + 1]);
|
||||
this.bitmap.data[idx + 2] = colors[col].b / (255 / this.bitmap.data[idx + 2]);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Caveat of genFileName is that if there are any falsey values in the arguments they are ignored.
|
||||
This is usually a good thing though - avoid issues by not putting something like 0 instead of '0' */
|
||||
function genFileName(...args) { return args.filter(function(val) {return val}).join('_') +'_001.png' }
|
||||
function fromIcons(filename) { return `./icons/${filename}` }
|
||||
let cache = {};
|
||||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
function buildIcon(account=[], usercode) {
|
||||
|
||||
let { form, ind } = forms[req.query.form] || {};
|
||||
form = form || 'player';
|
||||
ind = ind || 21;
|
||||
|
||||
let iconID = req.query.icon || account[ind] || 1;
|
||||
let col1 = req.query.col1 || account[10] || 0;
|
||||
let col2 = req.query.col2 || account[11] || 3;
|
||||
let colG = req.query.colG || req.query.colg
|
||||
let colW = req.query.colW || req.query.colw || req.query.col3
|
||||
let outline = req.query.glow || account[28] || "0";
|
||||
|
||||
let topless = form == "bird" && req.query.topless
|
||||
let drawLegs = !(req.query.noLegs > 0)
|
||||
let autoSize = req.query.size == "auto"
|
||||
let sizeParam = autoSize || (req.query.size && !isNaN(req.query.size))
|
||||
if (outline == "0" || outline == "false") outline = false;
|
||||
|
||||
if (iconID && iconID.toString().length == 1) iconID = "0" + iconID;
|
||||
|
||||
function genImageName(...args) { return genFileName(form, iconID, ...args) }
|
||||
|
||||
let icon, glow, extra;
|
||||
function setBaseIcons() {
|
||||
icon = genImageName(isSpecial && '01');
|
||||
glow = genImageName(isSpecial && '01', '2');
|
||||
extra = genImageName(isSpecial && '01', 'extra');
|
||||
}
|
||||
let isSpecial = ['robot', 'spider'].includes(form);
|
||||
setBaseIcons();
|
||||
|
||||
if (!fs.existsSync(fromIcons(icon)) || (isSpecial && !fs.existsSync(fromIcons(genImageName('02'))))) {
|
||||
iconID = '01';
|
||||
setBaseIcons();
|
||||
}
|
||||
|
||||
let ex = fromIcons(extra)
|
||||
let hasExtra = fs.existsSync(ex)
|
||||
|
||||
let cols = [col1, col2, colG, colW]
|
||||
cols.forEach(col => {
|
||||
if (!col) return
|
||||
col = col.toString()
|
||||
if (col.match(hexRegex)) colors[col.toLowerCase()] = hexConvert(col)
|
||||
})
|
||||
|
||||
if (!colors[col1] || isNaN(colors[col1].r)) col1 = colors[+col1] ? +col1 : 0
|
||||
if (!colors[col2] || isNaN(colors[col2].r)) col2 = colors[+col2] ? +col2 : 3
|
||||
if (!colors[colG] || isNaN(colors[colG].r)) colG = colors[+colG] ? +colG : null
|
||||
if (!colors[colW] || isNaN(colors[colW].r)) colW = colors[+colW] ? +colW : null
|
||||
if (colW && (!hasExtra || colW == 12)) colW = null
|
||||
|
||||
if (col1 == 15 || col1 === "000000") outline = true;
|
||||
|
||||
let iconCode = `${req.query.form == "cursed" ? "cursed" : form}${topless ? "top" : ""}-${iconID}-${col1}-${col2}-${colG || "x"}-${colW || "x"}-${outline ? 1 : 0}`
|
||||
|
||||
if (!sizeParam && (!isSpecial || drawLegs) && cache[iconCode]) return res.end(cache[iconCode].value)
|
||||
|
||||
let useExtra = false
|
||||
let originalOffset = icons[icon].spriteOffset;
|
||||
let minusOrigOffset = function(x, y) { return x - originalOffset[y] }
|
||||
let offset = icons[glow].spriteOffset.map(minusOrigOffset);
|
||||
let robotLeg1, robotLeg2, robotLeg3, robotLeg3b, robotLeg2b, robotLeg1b, robotLeg1c;
|
||||
let robotOffset1, robotOffset2, robotOffset3, robotOffset1b, robotOffset2b, robotOffset3b;
|
||||
let robotGlow1, robotGlow2, robotGlow3, glowOffset
|
||||
let ufoTop, ufoOffset, ufoCoords, ufoSprite
|
||||
let extrabit, offset2, size2;
|
||||
|
||||
if (isSpecial) {
|
||||
const legs = [1,2,3].map(function(val) {return genImageName(`0${val+1}`)});
|
||||
const glows = [1,2,3].map(function(val) {return genImageName(`0${val+1}`, '2')});
|
||||
robotOffset1 = icons[legs[0]].spriteOffset.map(minusOrigOffset).concat(icons[legs[0]].spriteSize);
|
||||
robotOffset2 = icons[legs[1]].spriteOffset.map(minusOrigOffset).concat(icons[legs[1]].spriteSize);
|
||||
robotOffset3 = icons[legs[2]].spriteOffset.map(minusOrigOffset).concat(icons[legs[2]].spriteSize);
|
||||
|
||||
robotOffset1b = icons[glows[0]].spriteOffset.map(minusOrigOffset).concat(icons[glows[0]].spriteSize);
|
||||
robotOffset2b = icons[glows[1]].spriteOffset.map(minusOrigOffset).concat(icons[glows[1]].spriteSize);
|
||||
robotOffset3b = icons[glows[2]].spriteOffset.map(minusOrigOffset).concat(icons[glows[2]].spriteSize);
|
||||
|
||||
robotLeg1 = new Jimp(fromIcons(legs[0])); robotGlow1 = new Jimp(fromIcons(glows[0]))
|
||||
robotLeg2 = new Jimp(fromIcons(legs[1])); robotGlow2 = new Jimp(fromIcons(glows[1]))
|
||||
robotLeg3 = new Jimp(fromIcons(legs[2])); robotGlow3 = new Jimp(fromIcons(glows[2]))
|
||||
|
||||
glowOffset = offsets[form][+iconID] || []
|
||||
}
|
||||
|
||||
Jimp.read(fromIcons(glow)).then(async function (image) {
|
||||
|
||||
let size = [image.bitmap.width, image.bitmap.height]
|
||||
let glow = recolor(image, col2)
|
||||
let imgOff = isSpecial ? 100 : 0
|
||||
|
||||
let eb = fromIcons(extra)
|
||||
if (fs.existsSync(eb)) {
|
||||
extrabit = icons[extra]
|
||||
offset2 = extrabit.spriteOffset.map(minusOrigOffset);
|
||||
size2 = extrabit.spriteSize;
|
||||
extra = new Jimp(eb);
|
||||
if (colW) await Jimp.read(eb).then(e => { extra = recolor(e, colW) })
|
||||
useExtra = true
|
||||
}
|
||||
|
||||
Jimp.read(fromIcons(icon)).then(async function (ic) {
|
||||
|
||||
let iconSize = [ic.bitmap.width, ic.bitmap.height]
|
||||
recolor(ic, col1)
|
||||
ic.composite(glow, (iconSize[0] / 2) - (size[0] / 2) + offset[0], (iconSize[1] / 2) - (size[1] / 2) - offset[1], { mode: Jimp.BLEND_DESTINATION_OVER })
|
||||
|
||||
if (form == "bird" && !topless) {
|
||||
ufoTop = genImageName('3')
|
||||
ufoOffset = icons[ufoTop].spriteOffset.map(minusOrigOffset).concat(icons[ufoTop].spriteSize);
|
||||
ufoCoords = [imgOff + (iconSize[0] / 2) - (ufoOffset[2] / 2) + ufoOffset[0], (iconSize[1] / 2) - (ufoOffset[3] / 2) - ufoOffset[1] + 300 - iconSize[1]]
|
||||
ufoSprite = fromIcons(ufoTop)
|
||||
ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_BOTTOM)
|
||||
// Only add dome if there's no glow, otherwise the dome will be outlined as well
|
||||
if (!outline) ic.composite(await Jimp.read(ufoSprite), ufoCoords[0], ufoCoords[1], {mode: Jimp.BLEND_DESTINATION_OVER})
|
||||
}
|
||||
|
||||
if (drawLegs && (form == "robot" || req.query.form == "cursed")) {
|
||||
|
||||
ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
|
||||
ic.contain(iconSize[0] + 200, 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
|
||||
|
||||
await Jimp.read(new Jimp(robotGlow1)).then(rob => {
|
||||
rob.rotate(-45)
|
||||
robotGlow1 = recolor(rob, col2)
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotGlow2)).then(rob => {
|
||||
rob.rotate(45)
|
||||
robotGlow2 = recolor(rob, col2)
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotGlow3)).then(rob => {
|
||||
robotGlow3 = recolor(rob, col2)
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotLeg1)).then(rob => {
|
||||
rob.rotate(-45)
|
||||
recolor(rob, col1)
|
||||
rob.composite(robotGlow1, (robotOffset1[2] - robotOffset1b[2]) + (glowOffset[0] || 1), ((robotOffset1[3] - robotOffset1b[3]) / 2) + (glowOffset[1] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
|
||||
robotLeg1 = rob
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotLeg2)).then(rob => {
|
||||
rob.rotate(45)
|
||||
recolor(rob, col1)
|
||||
rob.composite(robotGlow2, ((robotOffset2[2] - robotOffset2b[2]) / 4) + (glowOffset[4] || 0), ((robotOffset2[3] - robotOffset2b[3]) / 2) + (glowOffset[5] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
|
||||
robotLeg2 = rob
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotLeg2)).then(rob => {
|
||||
robotLeg2b = rob.color([{ apply: 'darken', params: [20] }]).rotate(-5)
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotLeg3)).then(rob => {
|
||||
recolor(rob, col1)
|
||||
rob.composite(robotGlow3, ((robotOffset3[2] - robotOffset3b[2]) / 2) + (glowOffset[2] || 0), ((robotOffset3[3] - robotOffset3b[3]) / 2) + (glowOffset[3] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
|
||||
robotLeg3 = rob
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotLeg3)).then(rob => {
|
||||
robotLeg3b = rob.color([{ apply: 'darken', params: [10] }])
|
||||
})
|
||||
|
||||
ic.composite(robotLeg2b, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 31, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 73)
|
||||
ic.composite(robotLeg3b, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + robotOffset3[0] + 20, (iconSize[1] / 2) - (robotOffset3[3]) - robotOffset3[1] + 78)
|
||||
ic.composite(robotLeg2, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 20, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 73)
|
||||
ic.composite(robotLeg3, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + robotOffset3[0] + 40, (iconSize[1] / 2) - (robotOffset3[3]) - robotOffset3[1] + 78)
|
||||
ic.composite(robotLeg1, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] - 20, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 50)
|
||||
|
||||
}
|
||||
|
||||
else if (drawLegs && form == "spider") {
|
||||
|
||||
let spiderBody;
|
||||
ic.contain(iconSize[0], 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
|
||||
ic.contain(iconSize[0] + 200, 300, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_TOP)
|
||||
|
||||
if (iconID == "07") {
|
||||
robotOffset2[2] -= 10
|
||||
robotOffset2[1] += 12
|
||||
robotOffset1b[3] -= 105
|
||||
robotOffset2b[3] -= 150
|
||||
robotOffset2b[2] -= 60
|
||||
}
|
||||
|
||||
if (iconID == "16") {
|
||||
robotOffset1b[3] -= 100
|
||||
robotOffset2b[3] -= 200
|
||||
robotOffset2b[2] -= 30
|
||||
}
|
||||
|
||||
await Jimp.read(new Jimp(robotGlow1)).then(rob => {
|
||||
if (robotGlow1.bitmap.width < 10) robotGlow1.opacity(0)
|
||||
else robotGlow1 = recolor(rob, col2)
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotGlow2)).then(rob => {
|
||||
robotGlow2 = recolor(rob, col2)
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotGlow3)).then(rob => {
|
||||
robotGlow3 = recolor(rob, col2)
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotLeg1)).then(rob => {
|
||||
recolor(rob, col1)
|
||||
rob.composite(robotGlow1, ((robotOffset1[2] - robotOffset1b[2]) / 2) + (glowOffset[2] || 0), ((robotOffset1[3] - robotOffset1b[3]) / 4) + (glowOffset[3] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
|
||||
robotLeg1 = rob
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotLeg2)).then(rob => {
|
||||
recolor(rob, col1)
|
||||
rob.composite(robotGlow2, ((robotOffset2[2] - robotOffset2b[2]) / 6) + (glowOffset[0] || 0), ((robotOffset2[3] - robotOffset2b[3]) / 6) + (glowOffset[1] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
|
||||
rob.rotate(-40)
|
||||
robotLeg2 = rob
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotLeg1)).then(rob => {
|
||||
robotLeg1b = rob.color([{ apply: 'darken', params: [20] }])
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotLeg1b)).then(rob => {
|
||||
robotLeg1c = rob.mirror(true, false)
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(robotLeg3)).then(rob => {
|
||||
recolor(rob, col1)
|
||||
rob.composite(robotGlow3, ((robotOffset3[2] - robotOffset3b[2]) / 2) + (glowOffset[4] || 0), ((robotOffset3[3] - robotOffset3b[3]) / 2) + (glowOffset[5] || 0), { mode: Jimp.BLEND_DESTINATION_OVER })
|
||||
robotLeg3 = rob
|
||||
})
|
||||
|
||||
await Jimp.read(new Jimp(ic)).then(rob => {
|
||||
spiderBody = rob
|
||||
})
|
||||
|
||||
ic.composite(robotLeg3, 100 + (iconSize[0] / 2) - (robotOffset3[2]) + (robotOffset3[0]), (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 77)
|
||||
ic.composite(robotLeg1b, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 35, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70)
|
||||
ic.composite(robotLeg1c, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 75, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70)
|
||||
// ^ BELOW
|
||||
ic.composite(spiderBody, 0, 0)
|
||||
// v ABOVE
|
||||
ic.composite(robotLeg2, 100 + (iconSize[0] / 2) - (robotOffset2[2]) + robotOffset2[0] - 60, (iconSize[1] / 2) - (robotOffset2[3]) - robotOffset2[1] + 75)
|
||||
ic.composite(robotLeg1, 100 + (iconSize[0] / 2) - (robotOffset1[2]) + robotOffset1[0] + 7, (iconSize[1] / 2) - (robotOffset1[3]) - robotOffset1[1] + 70)
|
||||
}
|
||||
|
||||
// every now and then jimp does a fucky wucky uwu and this line errors. seems to be an issue with the lib itself :v
|
||||
try { if (useExtra) ic.composite(extra, imgOff + (iconSize[0] / 2) - (size2[0] / 2) + offset2[0], (iconSize[1] / 2) - (size2[1] / 2) - offset2[1] + (form == "bird" && !req.query.topless ? 300 - iconSize[1] : 0)) }
|
||||
catch(e) {}
|
||||
|
||||
let finalSize = [ic.bitmap.width, ic.bitmap.height]
|
||||
|
||||
function finish(img) {
|
||||
img.autocrop(0.01, false)
|
||||
if (form == "swing") img.resize(120, 111)
|
||||
if (img.bitmap.height == 300) ic.autocrop(1, false)
|
||||
if (sizeParam) {
|
||||
let thicc = img.bitmap.width > img.bitmap.height
|
||||
let imgSize = req.query.size == "auto" ? (thicc ? img.bitmap.width : img.bitmap.height) : Math.round(req.query.size)
|
||||
if (imgSize < 32) imgSize = 32
|
||||
if (imgSize > 512) imgSize = 512
|
||||
if (thicc) img.contain(img.bitmap.width, img.bitmap.width, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
|
||||
else img.contain(img.bitmap.height, img.bitmap.height, Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
|
||||
img.resize(imgSize, Jimp.AUTO)
|
||||
}
|
||||
img.getBuffer(Jimp.AUTO, (err, buffer) => {
|
||||
if (!sizeParam && drawLegs) {
|
||||
cache[iconCode] = { value: buffer, timeoutID: setTimeout(function() {delete cache[iconCode]}, 10000000) } // 3 hour cache
|
||||
if (usercode) cache[usercode] = { value: buffer, timeoutID: setTimeout(function() {delete cache[usercode]}, 300000) } // 5 min cache for player icons
|
||||
}
|
||||
return res.end(buffer, 'base64')
|
||||
})
|
||||
}
|
||||
|
||||
if (!outline) return finish(ic)
|
||||
|
||||
else {
|
||||
|
||||
ic.getBuffer(Jimp.AUTO, function (err, buff) {
|
||||
|
||||
const Canvas = require('canvas')
|
||||
, Image = Canvas.Image
|
||||
, canvas = Canvas.createCanvas(finalSize[0] + 10, finalSize[1] + 10)
|
||||
, ctx = canvas.getContext('2d');
|
||||
|
||||
if (!colG) colG = (col2 == 15 || col2 == "000000" ? col1 : col2)
|
||||
if (colG == 15 || colG == "000000") colG = 12
|
||||
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
var dArr = [-1, -1, 0, -1, 1, -1, -1, 0, 1, 0, -1, 1, 0, 1, 1, 1], // offset array
|
||||
s = 2, i = 0, x = canvas.width / 2 - finalSize[0] / 2, y = canvas.height / 2 - finalSize[1] / 2;
|
||||
|
||||
for (; i < dArr.length; i += 2) ctx.drawImage(img, x + dArr[i] * s, y + dArr[i + 1] * s);
|
||||
|
||||
ctx.globalCompositeOperation = "source-in";
|
||||
ctx.fillStyle = `rgba(${colors[colG].r}, ${colors[colG].g}, ${colors[colG].b}, 1})`;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
|
||||
// Add UFO top last so it doesn't get glow'd
|
||||
if (form == "bird" && !topless) {
|
||||
const dome = new Image()
|
||||
dome.src = ufoSprite
|
||||
ctx.drawImage(dome, ufoCoords[0]+5, ufoCoords[1]+5)
|
||||
}
|
||||
|
||||
ctx.drawImage(img, x, y)
|
||||
|
||||
}
|
||||
|
||||
img.onerror = err => { throw err }
|
||||
img.src = buff
|
||||
|
||||
Jimp.read(canvas.toBuffer()).then(b => {
|
||||
return finish(b)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
let username = req.params.text
|
||||
let userCode;
|
||||
res.contentType('image/png');
|
||||
|
||||
if (req.offline || req.query.hasOwnProperty("noUser") || req.query.hasOwnProperty("nouser") || username == "icon") return buildIcon()
|
||||
|
||||
else if (app.config.cachePlayerIcons && !Object.keys(req.query).filter(x => !["form", "forceGD"].includes(x)).length) {
|
||||
userCode = `${req.id}u-${username.toLowerCase()}-${forms[req.query.form] ? req.query.form : 'cube'}`
|
||||
if (cache[userCode]) return res.end(cache[userCode].value)
|
||||
}
|
||||
|
||||
let accountMode = !req.query.hasOwnProperty("player") && Number(req.params.id)
|
||||
let foundID = app.userCache(req.id, username)
|
||||
let skipRequest = accountMode || foundID
|
||||
let forceGD = req.query.hasOwnProperty("forceGD")
|
||||
|
||||
// skip request by causing fake error lmao
|
||||
req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : req.gdParams({ str: username, forceGD }, !forceGD), function (err1, res1, body1) {
|
||||
|
||||
let result = foundID ? foundID[0] : (accountMode || err1 || !body1 || body1 == "-1" || body1.startsWith("<")) ? username : app.parseResponse(body1)[16];
|
||||
|
||||
req.gdRequest('getGJUserInfo20', req.gdParams({ targetAccountID: result, forceGD }, !forceGD), function (err2, res2, body2) {
|
||||
|
||||
if (err2 || !body2 || body2 == '-1' || body2.startsWith("<")) return buildIcon();
|
||||
let iconData = app.parseResponse(body2)
|
||||
if (!foundID && !forceGD) app.userCache(req.id, iconData[16], iconData[2], iconData[1])
|
||||
return buildIcon(iconData, userCode);
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
}
|
|
@ -1,33 +1,36 @@
|
|||
const {GoogleSpreadsheet} = require('google-spreadsheet');
|
||||
const sheet = new GoogleSpreadsheet('1ADIJvAkL0XHGBDhO7PP9aQOuK3mPIKB2cVPbshuBBHc'); // accurate leaderboard spreadsheet
|
||||
|
||||
let indexes = ["stars", "coins", "demons", "diamonds"]
|
||||
|
||||
let forms = ['cube', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider']
|
||||
let lastIndex = [{"stars": 0, "coins": 0, "demons": 0}, {"stars": 0, "coins": 0, "demons": 0}]
|
||||
let caches = [{"stars": null, "coins": null, "demons": null}, {"stars": null, "coins": null, "demons": null}, {"stars": null, "coins": null, "demons": null}] // 0 for JSON, 1 for mods, 2 for GD
|
||||
let lastIndex = [{"stars": 0, "coins": 0, "demons": 0}, {"stars": 0, "coins": 0, "demons": 0, "diamonds": 0}]
|
||||
let caches = [{"stars": null, "coins": null, "demons": null, "diamonds": null}, {"stars": null, "coins": null, "demons": null, "diamonds": null}, {"stars": null, "coins": null, "demons": null, "diamonds": null}] // 0 for JSON, 1 for mods, 2 for GD
|
||||
|
||||
module.exports = async (app, req, res, post) => {
|
||||
|
||||
if (req.isGDPS) return res.send(req.server.weeklyLeaderboard ? "-3" : "-2")
|
||||
if (!app.sheetsKey) return res.send([])
|
||||
// Accurate leaderboard returns 418 because private servers do not use.
|
||||
if (req.isGDPS) return res.status(418).send("-2")
|
||||
if (!app.sheetsKey) return res.status(500).send([])
|
||||
let gdMode = post || req.query.hasOwnProperty("gd")
|
||||
let modMode = !gdMode && req.query.hasOwnProperty("mod")
|
||||
let cache = caches[gdMode ? 2 : modMode ? 1 : 0]
|
||||
|
||||
let type = req.query.type ? req.query.type.toLowerCase() : 'stars'
|
||||
if (type == "usercoins") type = "coins"
|
||||
if (!["stars", "coins", "demons"].includes(type)) type = "stars"
|
||||
if (!indexes.includes(type)) type = "stars"
|
||||
if (lastIndex[modMode ? 1 : 0][type] + 600000 > Date.now() && cache[type]) return res.send(gdMode ? cache[type] : JSON.parse(cache[type])) // 10 min cache
|
||||
|
||||
sheet.useApiKey(app.sheetsKey)
|
||||
sheet.loadInfo().then(async () => {
|
||||
let tab = sheet.sheetsById[1555821000]
|
||||
await tab.loadCells('A2:F2')
|
||||
await tab.loadCells('A2:H2')
|
||||
|
||||
let cellIndex = type == "demons" ? 2 : type == "coins" ? 1 : 0
|
||||
if (modMode) cellIndex += 3
|
||||
let cellIndex = indexes.findIndex(x => type == x)
|
||||
if (modMode) cellIndex += indexes.length
|
||||
|
||||
let cell = tab.getCell(1, cellIndex).value
|
||||
if (cell.startsWith("GoogleSpreadsheetFormulaError")) return res.send("-1")
|
||||
if (!cell || typeof cell != "string" || cell.startsWith("GoogleSpreadsheetFormulaError")) { console.log("Spreadsheet Error:"); console.log(cell); return res.sendError() }
|
||||
let leaderboard = JSON.parse(cell.replace(/~( |$)/g, ""))
|
||||
|
||||
let gdFormatting = ""
|
||||
|
|
|
@ -2,10 +2,13 @@ const request = require('request')
|
|||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
// Accurate leaderboard returns 418 because Private servers do not use.
|
||||
if (req.isGDPS) return res.status(418).send("0")
|
||||
|
||||
request.post('http://robtopgames.com/Boomlings/get_scores.php', {
|
||||
form : { secret: app.config.params.secret || "Wmfd2893gb7", name: "Player" } }, function(err, resp, body) {
|
||||
|
||||
if (err || !body || body == 0) return res.send("0")
|
||||
if (err || !body || body == 0) return res.status(500).send("0")
|
||||
|
||||
let info = body.split(" ").filter(x => x.includes(";"))
|
||||
let users = []
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
const colors = require('../../iconkit/sacredtexts/colors.json');
|
||||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.offline) return res.send("-1")
|
||||
if (req.offline) return res.sendError()
|
||||
|
||||
let amount = 100;
|
||||
let count = req.query.count ? parseInt(req.query.count) : null
|
||||
|
@ -18,9 +20,9 @@ module.exports = async (app, req, res) => {
|
|||
|
||||
req.gdRequest('getGJLevelScores211', params, function(err, resp, body) {
|
||||
|
||||
if (err || body == -1 || !body) return res.send("-1")
|
||||
if (err) return res.status(500).send({error: true, lastWorked: app.timeSince(req.id)})
|
||||
scores = body.split('|').map(x => app.parseResponse(x)).filter(x => x[1])
|
||||
if (!scores.length) return res.send("-1")
|
||||
if (!scores.length) return res.status(500).send([])
|
||||
else app.trackSuccess(req.id)
|
||||
|
||||
scores.forEach(x => {
|
||||
|
@ -37,7 +39,9 @@ module.exports = async (app, req, res) => {
|
|||
icon: +x[9],
|
||||
col1: +x[10],
|
||||
col2: +x[11],
|
||||
glow: +x[15] > 1
|
||||
glow: +x[15] > 1,
|
||||
col1RGB: colors[x[10]] || colors["0"],
|
||||
col2RGB: colors[x[11]] || colors["3"]
|
||||
}
|
||||
keys.forEach(k => delete x[k])
|
||||
app.userCache(req.id, x.accountID, x.playerID, x.username)
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
const Player = require('../../classes/Player.js')
|
||||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.offline) return res.send("-1")
|
||||
if (req.offline) return res.sendError()
|
||||
|
||||
let amount = 100;
|
||||
let count = req.query.count ? parseInt(req.query.count) : null
|
||||
if (count && count > 0) {
|
||||
if (count > 5000) amount = 5000
|
||||
if (count > 10000) amount = 10000
|
||||
else amount = count;
|
||||
}
|
||||
|
||||
|
@ -13,35 +15,19 @@ module.exports = async (app, req, res) => {
|
|||
|
||||
if (["creators", "creator", "cp"].some(x => req.query.hasOwnProperty(x) || req.query.type == x)) params.type = "creators"
|
||||
else if (["week", "weekly"].some(x => req.query.hasOwnProperty(x) || req.query.type == x)) params.type = "week"
|
||||
else if (["global", "relative"].some(x => req.query.hasOwnProperty(x) || req.query.type == x)) {
|
||||
params.type = "relative"
|
||||
params.accountID = req.query.accountID
|
||||
}
|
||||
|
||||
req.gdRequest('getGJScores20', params, function(err, resp, body) {
|
||||
|
||||
if (err || body == '-1' || !body) return res.send("-1")
|
||||
if (err) return res.sendError()
|
||||
scores = body.split('|').map(x => app.parseResponse(x)).filter(x => x[1])
|
||||
if (!scores.length) return res.send("-1")
|
||||
if (!scores.length) return res.sendError()
|
||||
|
||||
scores.forEach(x => {
|
||||
let keys = Object.keys(x)
|
||||
x.rank = +x[6]
|
||||
x.username = x[1]
|
||||
x.playerID = x[2]
|
||||
x.accountID = x[16]
|
||||
x.stars = +x[3]
|
||||
x.demons = +x[4]
|
||||
x.cp = +x[8]
|
||||
x.coins = +x[13]
|
||||
x.usercoins = +x[17]
|
||||
x.diamonds = +x[46]
|
||||
x.icon = {
|
||||
form: ['icon', 'ship', 'ball', 'ufo', 'wave', 'robot', 'spider'][+x[14]],
|
||||
icon: +x[9] || 1,
|
||||
col1: +x[10],
|
||||
col2: +x[11],
|
||||
glow: +x[15] > 1
|
||||
}
|
||||
keys.forEach(k => delete x[k])
|
||||
app.userCache(req.id, x.accountID, x.playerID, x.username)
|
||||
})
|
||||
return res.send(scores)
|
||||
})
|
||||
scores = scores.map(x => new Player(x))
|
||||
scores.forEach(x => app.userCache(req.id, x.accountID, x.playerID, x.username))
|
||||
return res.send(scores.slice(0, amount))
|
||||
})
|
||||
}
|
19
api/level.js
|
@ -4,28 +4,24 @@ const Level = require('../classes/Level.js')
|
|||
|
||||
module.exports = async (app, req, res, api, analyze) => {
|
||||
|
||||
if (req.offline) {
|
||||
function rejectLevel() {
|
||||
if (!api) return res.redirect('search/' + req.params.id)
|
||||
else return res.send("-1")
|
||||
else return res.sendError()
|
||||
}
|
||||
|
||||
if (req.offline) return rejectLevel()
|
||||
|
||||
let levelID = req.params.id
|
||||
if (levelID == "daily") return app.run.download(app, req, res, api, 'daily', analyze)
|
||||
else if (levelID == "weekly") return app.run.download(app, req, res, api, 'weekly', analyze)
|
||||
else if (levelID.match(/[^0-9]/)) {
|
||||
if (!api) return res.redirect('search/' + req.params.id)
|
||||
else return res.send("-1")
|
||||
}
|
||||
else if (levelID.match(/[^0-9]/)) return rejectLevel()
|
||||
else levelID = levelID.replace(/[^0-9]/g, "")
|
||||
|
||||
if (analyze || req.query.hasOwnProperty("download")) return app.run.download(app, req, res, api, levelID, analyze)
|
||||
|
||||
req.gdRequest('getGJLevels21', { str: levelID, type: 0 }, function (err, resp, body) {
|
||||
|
||||
if (err || !body || body == '-1' || body.startsWith("<") || body.startsWith("##")) {
|
||||
if (!api) return res.redirect('search/' + req.params.id)
|
||||
else return res.send("-1")
|
||||
}
|
||||
if (err || body.startsWith("##")) return rejectLevel()
|
||||
|
||||
let preRes = body.split('#')[0].split('|', 10)
|
||||
let author = body.split('#')[1].split('|')[0].split(':')
|
||||
|
@ -34,8 +30,9 @@ module.exports = async (app, req, res, api, analyze) => {
|
|||
|
||||
let levelInfo = app.parseResponse(preRes.find(x => x.startsWith(`1:${levelID}`)) || preRes[0])
|
||||
let level = new Level(levelInfo, req.server, false, author).getSongInfo(song)
|
||||
if (!level.id) return rejectLevel()
|
||||
|
||||
if (req.isGDPS) level.gdps = (req.onePointNine ? "1.9/" : "") + req.endpoint
|
||||
if (req.isGDPS) level.gdps = (req.onePointNine ? "1.9/" : "") + req.server.id
|
||||
if (level.author != "-") app.userCache(req.id, level.accountID, level.playerID, level.author)
|
||||
|
||||
function sendLevel() {
|
||||
|
|
|
@ -3,7 +3,7 @@ let cache = {}
|
|||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.offline) return res.send("-1")
|
||||
if (req.offline) return res.sendError()
|
||||
|
||||
let cached = cache[req.id]
|
||||
if (app.config.cacheMapPacks && cached && cached.data && cached.indexed + 5000000 > Date.now()) return res.send(cached.data) // 1.5 hour cache
|
||||
|
@ -13,7 +13,7 @@ module.exports = async (app, req, res) => {
|
|||
function mapPackLoop() {
|
||||
req.gdRequest('getGJMapPacks21', params, function (err, resp, body) {
|
||||
|
||||
if (err || !body || body == '-1' || body.startsWith("<")) return res.send("-1")
|
||||
if (err) return res.sendError()
|
||||
|
||||
let newPacks = body.split('#')[0].split('|').map(x => app.parseResponse(x)).filter(x => x[2])
|
||||
packs = packs.concat(newPacks)
|
||||
|
@ -26,8 +26,8 @@ module.exports = async (app, req, res) => {
|
|||
|
||||
let mappacks = packs.map(x => ({ // "packs.map()" laugh now please
|
||||
id: +x[1],
|
||||
levels: x[3].split(","),
|
||||
name: x[2],
|
||||
levels: x[3].split(","),
|
||||
stars: +x[4],
|
||||
coins: +x[5],
|
||||
difficulty: difficulties[+x[6]] || "unrated",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.method !== 'POST') return res.status(405).send("Method not allowed.")
|
||||
|
||||
if (!req.body.accountID) return res.status(400).send("No account ID provided!")
|
||||
if (!req.body.password) return res.status(400).send("No password provided!")
|
||||
|
||||
|
@ -11,11 +13,11 @@ module.exports = async (app, req, res) => {
|
|||
|
||||
req.gdRequest('getGJUserInfo20', params, function (err, resp, body) {
|
||||
|
||||
if (err || body == -1 || body == -2 || !body) return res.status(400).send(`Error counting messages! Messages get blocked a lot so try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
if (err) return res.status(400).send(`Error counting messages! Messages get blocked a lot so try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
else app.trackSuccess(req.id)
|
||||
let count = app.parseResponse(body)[38]
|
||||
if (!count) return res.status(400).send("Error fetching unread messages!")
|
||||
else res.status(200).send(count)
|
||||
else res.send(count)
|
||||
})
|
||||
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
module.exports = async (app, req, res, api) => {
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.method !== 'POST') return res.status(405).send("Method not allowed.")
|
||||
|
||||
if (!req.body.accountID) return res.status(400).send("No account ID provided!")
|
||||
if (!req.body.password) return res.status(400).send("No password provided!")
|
||||
|
@ -15,7 +17,7 @@ module.exports = async (app, req, res, api) => {
|
|||
req.gdRequest('deleteGJMessages20', params, function (err, resp, body) {
|
||||
|
||||
if (body != 1) return res.status(400).send(`The Geometry Dash servers refused to delete the message! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
else res.status(200).send(`${deleted == 1 ? "1 message" : `${deleted} messages`} deleted!`)
|
||||
else res.send(`${deleted == 1 ? "1 message" : `${deleted} messages`} deleted!`)
|
||||
app.trackSuccess(req.id)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
module.exports = async (app, req, res, api) => {
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.method !== 'POST') return res.status(405).send("Method not allowed.")
|
||||
|
||||
if (!req.body.accountID) return res.status(400).send("No account ID provided!")
|
||||
if (!req.body.password) return res.status(400).send("No password provided!")
|
||||
|
@ -11,7 +13,7 @@ module.exports = async (app, req, res, api) => {
|
|||
|
||||
req.gdRequest('downloadGJMessage20', params, function (err, resp, body) {
|
||||
|
||||
if (err || body == -1 || !body) return res.status(400).send(`Error fetching message! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
if (err) return res.status(400).send(`Error fetching message! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
else app.trackSuccess(req.id)
|
||||
|
||||
let x = app.parseResponse(body)
|
||||
|
@ -29,7 +31,7 @@ module.exports = async (app, req, res, api) => {
|
|||
msg.browserColor = true
|
||||
}
|
||||
|
||||
return res.status(200).send(msg)
|
||||
return res.send(msg)
|
||||
})
|
||||
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
module.exports = async (app, req, res, api) => {
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.method !== 'POST') return res.status(405).send("Method not allowed.")
|
||||
|
||||
if (req.body.count) return app.run.countMessages(app, req, res)
|
||||
if (!req.body.accountID) return res.status(400).send("No account ID provided!")
|
||||
|
@ -13,7 +15,7 @@ module.exports = async (app, req, res, api) => {
|
|||
|
||||
req.gdRequest('getGJMessages20', params, function (err, resp, body) {
|
||||
|
||||
if (err || body == -1 || body == -2 || !body) return res.status(400).send(`Error fetching messages! Messages get blocked a lot so try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
if (err) return res.status(400).send(`Error fetching messages! Messages get blocked a lot so try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
else app.trackSuccess(req.id)
|
||||
|
||||
let messages = body.split("|").map(msg => app.parseResponse(msg))
|
||||
|
@ -37,7 +39,7 @@ module.exports = async (app, req, res, api) => {
|
|||
app.userCache(req.id, msg.accountID, msg.playerID, msg.author)
|
||||
messageArray.push(msg)
|
||||
})
|
||||
return res.status(200).send(messageArray)
|
||||
return res.send(messageArray)
|
||||
})
|
||||
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
module.exports = async (app, req, res, api) => {
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.method !== 'POST') return res.status(405).send("Method not allowed.")
|
||||
|
||||
if (!req.body.targetID) return res.status(400).send("No target ID provided!")
|
||||
if (!req.body.message) return res.status(400).send("No message provided!")
|
||||
|
@ -17,7 +19,7 @@ module.exports = async (app, req, res, api) => {
|
|||
|
||||
req.gdRequest('uploadGJMessage20', params, function (err, resp, body) {
|
||||
if (body != 1) return res.status(400).send(`The Geometry Dash servers refused to send the message! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
else res.status(200).send('Message sent!')
|
||||
else res.send('Message sent!')
|
||||
app.trackSuccess(req.id)
|
||||
})
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ function sha1(data) { return crypto.createHash("sha1").update(data, "binary").di
|
|||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.method !== 'POST') return res.status(405).send("Method not allowed.")
|
||||
|
||||
if (!req.body.ID) return res.status(400).send("No ID provided!")
|
||||
if (!req.body.accountID) return res.status(400).send("No account ID provided!")
|
||||
if (!req.body.password) return res.status(400).send("No password provided!")
|
||||
|
@ -30,9 +32,8 @@ module.exports = async (app, req, res) => {
|
|||
params.chk = chk
|
||||
|
||||
req.gdRequest('likeGJItem211', params, function (err, resp, body) {
|
||||
if (err) return res.status(400).send("The Geometry Dash servers returned an error! Perhaps they're down for maintenance")
|
||||
if (!body || body == -1) return res.status(400).send(`The Geometry Dash servers rejected your vote! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
if (err) return res.status(400).send(`The Geometry Dash servers rejected your vote! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
else app.trackSuccess(req.id)
|
||||
res.status(200).send((params.like == 1 ? 'Successfully liked!' : 'Successfully disliked!') + " (this will only take effect if this is your first time doing so)")
|
||||
res.send((params.like == 1 ? 'Successfully liked!' : 'Successfully disliked!') + " (this will only take effect if this is your first time doing so)")
|
||||
})
|
||||
}
|
|
@ -11,6 +11,8 @@ function getTime(time) {
|
|||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.method !== 'POST') return res.status(405).send("Method not allowed.")
|
||||
|
||||
if (!req.body.comment) return res.status(400).send("No comment provided!")
|
||||
if (!req.body.username) return res.status(400).send("No username provided!")
|
||||
if (!req.body.levelID) return res.status(400).send("No level ID provided!")
|
||||
|
@ -38,14 +40,13 @@ module.exports = async (app, req, res) => {
|
|||
params.chk = chk
|
||||
|
||||
req.gdRequest('uploadGJComment21', params, function (err, resp, body) {
|
||||
if (err) return res.status(400).send("The Geometry Dash servers returned an error! Perhaps they're down for maintenance")
|
||||
if (!body || body == -1) return res.status(400).send(`The Geometry Dash servers rejected your comment! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
if (err) return res.status(400).send(`The Geometry Dash servers rejected your comment! Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
if (body.startsWith("temp")) {
|
||||
let banStuff = body.split("_")
|
||||
return res.status(400).send(`You have been banned from commenting for ${(parseInt(banStuff[1]) / 86400).toFixed(0)} days. Reason: ${banStuff[2] || "None"}`)
|
||||
}
|
||||
|
||||
res.status(200).send(`Comment posted to level ${params.levelID} with ID ${body}`)
|
||||
res.send(`Comment posted to level ${params.levelID} with ID ${body}`)
|
||||
app.trackSuccess(req.id)
|
||||
rateLimit[req.body.username] = Date.now();
|
||||
setTimeout(() => {delete rateLimit[req.body.username]; }, cooldown);
|
||||
|
|
|
@ -3,6 +3,8 @@ function sha1(data) { return crypto.createHash("sha1").update(data, "binary").di
|
|||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.method !== 'POST') return res.status(405).send("Method not allowed.")
|
||||
|
||||
if (!req.body.comment) return res.status(400).send("No comment provided!")
|
||||
if (!req.body.username) return res.status(400).send("No username provided!")
|
||||
if (!req.body.accountID) return res.status(400).send("No account ID provided!")
|
||||
|
@ -23,13 +25,12 @@ module.exports = async (app, req, res) => {
|
|||
params.chk = chk
|
||||
|
||||
req.gdRequest('uploadGJAccComment20', params, function (err, resp, body) {
|
||||
if (err) return res.status(400).send("The Geometry Dash servers returned an error! Perhaps they're down for maintenance")
|
||||
else if (!body || body == -1) return res.status(400).send(`The Geometry Dash servers rejected your profile post! Try again later, or make sure your username and password are entered correctly. Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
if (body.startsWith("temp")) {
|
||||
if (err) return res.status(400).send(`The Geometry Dash servers rejected your profile post! Try again later, or make sure your username and password are entered correctly. Try again later, or make sure your username and password are entered correctly. Last worked: ${app.timeSince(req.id)} ago.`)
|
||||
else if (body.startsWith("temp")) {
|
||||
let banStuff = body.split("_")
|
||||
return res.status(400).send(`You have been banned from commenting for ${(parseInt(banStuff[1]) / 86400).toFixed(0)} days. Reason: ${banStuff[2] || "None"}`)
|
||||
}
|
||||
else app.trackSuccess(req.id)
|
||||
res.status(200).send(`Comment posted to ${params.userName} with ID ${body}`)
|
||||
res.send(`Comment posted to ${params.userName} with ID ${body}`)
|
||||
})
|
||||
}
|
|
@ -1,23 +1,29 @@
|
|||
const fs = require('fs')
|
||||
const Player = require('../classes/Player.js')
|
||||
|
||||
module.exports = async (app, req, res, api, getLevels) => {
|
||||
|
||||
if (req.offline) {
|
||||
if (!api) return res.redirect('/search/' + req.params.id)
|
||||
else return res.send("-1")
|
||||
else return res.sendError()
|
||||
}
|
||||
|
||||
let username = getLevels || req.params.id
|
||||
let probablyID
|
||||
if (username.endsWith(".") && req.isGDPS) {
|
||||
username = username.slice(0, -1)
|
||||
probablyID = Number(username)
|
||||
}
|
||||
let accountMode = !req.query.hasOwnProperty("player") && Number(req.params.id)
|
||||
let foundID = app.userCache(req.id, username)
|
||||
let skipRequest = accountMode || foundID
|
||||
let skipRequest = accountMode || foundID || probablyID
|
||||
let searchResult;
|
||||
|
||||
// if you're searching by account id, an intentional error is caused to skip the first request to the gd servers. see i pulled a sneaky on ya. (fuck callbacks man)
|
||||
req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : { str: username, page: 0 }, function (err1, res1, b1) {
|
||||
|
||||
req.gdRequest(skipRequest ? "" : 'getGJUsers20', skipRequest ? {} : { str: username, page: 0 }, function (err1, res1, b1) {
|
||||
|
||||
if (foundID) searchResult = foundID[0]
|
||||
else if (accountMode || err1 || b1 == '-1' || b1.startsWith("<") || !b1) searchResult = req.params.id
|
||||
else if (accountMode || err1 || b1 == '-1' || b1.startsWith("<") || !b1) searchResult = probablyID ? username : req.params.id
|
||||
else if (!req.isGDPS) searchResult = app.parseResponse(b1.split("|")[0])[16]
|
||||
else { // GDPS's return multiple users, GD no longer does this
|
||||
let userResults = b1.split("|").map(x => app.parseResponse(x))
|
||||
|
@ -35,43 +41,14 @@ module.exports = async (app, req, res, api, getLevels) => {
|
|||
let account = app.parseResponse(body || "")
|
||||
let dumbGDPSError = req.isGDPS && (!account[16] || account[1].toLowerCase() == "undefined")
|
||||
|
||||
if (err2 || body == '-1' || !body || dumbGDPSError) {
|
||||
if (err2 || dumbGDPSError) {
|
||||
if (!api) return res.redirect('/search/' + req.params.id)
|
||||
else return res.send("-1")
|
||||
else return res.sendError()
|
||||
}
|
||||
|
||||
if (!foundID) app.userCache(req.id, account[16], account[2], account[1])
|
||||
|
||||
let userData = {
|
||||
username: account[1] || "[MISSINGNO.]",
|
||||
playerID: account[2],
|
||||
accountID: account[16],
|
||||
rank: +account[30],
|
||||
stars: +account[3],
|
||||
diamonds: +account[46],
|
||||
coins: +account[13],
|
||||
userCoins: +account[17],
|
||||
demons: +account[4],
|
||||
cp: +account[8],
|
||||
friendRequests: account[19] == "0",
|
||||
messages: account[18] == "0" ? "all" : account[18] == "1" ? "friends" : "off",
|
||||
commentHistory: account[50] == "0" ? "all" : account[50] == "1" ? "friends" : "off",
|
||||
moderator: +account[49],
|
||||
youtube: account[20] || null,
|
||||
twitter: account[44] || null,
|
||||
twitch: account[45] || null,
|
||||
icon: +account[21],
|
||||
ship: +account[22],
|
||||
ball: +account[23],
|
||||
ufo: +account[24],
|
||||
wave: +account[25],
|
||||
robot: +account[26],
|
||||
spider: +account[43],
|
||||
col1: +account[10],
|
||||
col2: +account[11],
|
||||
deathEffect: +account[48] || 1,
|
||||
glow: account[28] == "1",
|
||||
}
|
||||
let userData = new Player(account)
|
||||
|
||||
if (api) return res.send(userData)
|
||||
|
||||
|
|
|
@ -2,21 +2,20 @@ const request = require('request')
|
|||
const music = require('../misc/music.json')
|
||||
const Level = require('../classes/Level.js')
|
||||
let demonList = {}
|
||||
// list: [], lastUpdated: 0
|
||||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
if (req.offline) return res.send("-1")
|
||||
if (req.offline) return res.status(500).send(req.query.hasOwnProperty("err") ? "err" : "-1")
|
||||
|
||||
let demonMode = req.query.hasOwnProperty("demonlist") || req.query.hasOwnProperty("demonList") || req.query.type == "demonlist" || req.query.type == "demonList"
|
||||
if (demonMode) {
|
||||
if (!req.server.demonList) return res.send('-1')
|
||||
if (!req.server.demonList) return res.sendError(400)
|
||||
let dList = demonList[req.id]
|
||||
if (!dList || !dList.list.length || dList.lastUpdated + 600000 < Date.now()) { // 10 minute cache
|
||||
return request.get(req.server.demonList + 'api/v2/demons/listed/?limit=100', function (err1, resp1, list1) {
|
||||
if (err1) return res.send("-1")
|
||||
if (err1) return res.sendError()
|
||||
else return request.get(req.server.demonList + 'api/v2/demons/listed/?limit=100&after=100', function (err2, resp2, list2) {
|
||||
if (err2) return res.send("-1")
|
||||
if (err2) return res.sendError()
|
||||
demonList[req.id] = {list: JSON.parse(list1).concat(JSON.parse(list2)).map(x => String(x.level_id)), lastUpdated: Date.now()}
|
||||
return app.run.search(app, req, res)
|
||||
})
|
||||
|
@ -68,6 +67,8 @@ module.exports = async (app, req, res) => {
|
|||
case 'starred': filters.type = 11; break;
|
||||
case 'halloffame': filters.type = 16; break;
|
||||
case 'hof': filters.type = 16; break;
|
||||
case 'gdw': filters.type = 17; break;
|
||||
case 'gdworld': filters.type = 17; break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +86,9 @@ module.exports = async (app, req, res) => {
|
|||
filters.type = 10
|
||||
filters.str = demonMode ? demonList[req.id].list : filters.str.split(",")
|
||||
listSize = filters.str.length
|
||||
filters.str = filters.str.slice(filters.page*amount, filters.page*amount + amount).join()
|
||||
filters.str = filters.str.slice(filters.page*amount, filters.page*amount + amount)
|
||||
if (!filters.str.length) return res.sendError(400)
|
||||
filters.str = filters.str.map(x => String(Number(x) + (+req.query.l || 0))).join()
|
||||
filters.page = 0
|
||||
}
|
||||
|
||||
|
@ -94,7 +97,7 @@ module.exports = async (app, req, res) => {
|
|||
|
||||
req.gdRequest('getGJLevels21', req.gdParams(filters), function(err, resp, body) {
|
||||
|
||||
if (err || !body || body == '-1' || body.startsWith("<")) return res.send("-1")
|
||||
if (err) return res.sendError()
|
||||
let splitBody = body.split('#')
|
||||
let preRes = splitBody[0].split('|')
|
||||
let authorList = {}
|
||||
|
@ -116,6 +119,7 @@ module.exports = async (app, req, res) => {
|
|||
let songSearch = songs.find(y => y['~1'] == x[35]) || []
|
||||
|
||||
let level = new Level(x, req.server).getSongInfo(songSearch)
|
||||
if (!level.id) return
|
||||
level.author = authorList[x[6]] ? authorList[x[6]][0] : "-";
|
||||
level.accountID = authorList[x[6]] ? authorList[x[6]][1] : "0";
|
||||
|
||||
|
@ -124,7 +128,7 @@ module.exports = async (app, req, res) => {
|
|||
level.demonPosition = demonList[req.id].list.indexOf(level.id) + 1
|
||||
}
|
||||
|
||||
if (req.isGDPS) level.gdps = (req.onePointNine ? "1.9/" : "") + req.endpoint
|
||||
if (req.isGDPS) level.gdps = (req.onePointNine ? "1.9/" : "") + req.server.id
|
||||
if (level.author != "-" && app.config.cacheAccountIDs) app.userCache(req.id, level.accountID, level.playerID, level.author)
|
||||
|
||||
//this is broken if you're not on page 0, blame robtop
|
||||
|
|
54
api/song.js
|
@ -1,56 +1,10 @@
|
|||
const request = require('request')
|
||||
|
||||
module.exports = async (app, req, res) => {
|
||||
|
||||
// temporary solution until song api is re-enabled
|
||||
|
||||
if (req.offline) return res.send('-1')
|
||||
if (req.offline) return res.sendError()
|
||||
|
||||
let songID = req.params.song
|
||||
req.gdRequest('getGJSongInfo', {songID: songID}, function(err, resp, body) {
|
||||
if (err || !body || body.startsWith("<")) return res.send('-1')
|
||||
else if (body < 0) return res.send(false)
|
||||
request.get('https://www.newgrounds.com/audio/listen/' + songID, function(err2, resp2, song) {
|
||||
console.log(resp2.statusCode)
|
||||
return res.send(resp2.statusCode == 200)
|
||||
})
|
||||
if (err) return res.sendError(400)
|
||||
return res.send(!body.startsWith("-") && body.length > 10)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// RobTop disabled his song checking page soo.... //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
/* let info = {error: true, exists: false, artist: { name: "", scouted: false, whitelisted: false }, song: { name: "", externalUse: false, allowed: false } }
|
||||
|
||||
if (req.offline) return res.send(info)
|
||||
|
||||
let songID = req.params.song
|
||||
let testError = false
|
||||
|
||||
request.post('http://boomlings.com/database/testSong.php?songID=' + songID, req.gdParams(), function(err, resp, body) {
|
||||
if (err || !body || body == '-1' || body.startsWith("<")) return res.send(info)
|
||||
else if (!body.includes("<br>")) testError = true
|
||||
|
||||
req.gdRequest('getGJSongInfo', {songID: songID}, function(err2, resp, songAllowed) {
|
||||
if (err2 || !songAllowed || songAllowed < 0 || body.startsWith("<")) return res.send(info)
|
||||
|
||||
info.song.allowed = songAllowed.length > 15
|
||||
if (testError) return res.send(info)
|
||||
|
||||
let artistInfo = body.split(/<\/?br>/)
|
||||
info.artist.name = artistInfo[0].split(": ")[1]
|
||||
info.exists = info.artist.name.length > 0
|
||||
info.artist.scouted = artistInfo[2].split("is NOT").length == 1
|
||||
info.artist.whitelisted = artistInfo[1].split("is NOT").length == 1
|
||||
info.song.name = artistInfo[4].split(": ")[1]
|
||||
info.song.externalUse = artistInfo[5].split("API NOT").length == 1
|
||||
|
||||
delete info.error
|
||||
res.send(info)
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
} */
|
||||
}
|
BIN
assets/bluecoin.png
Normal file
After Width: | Height: | Size: 7 KiB |
BIN
assets/colU.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
|
@ -35,7 +35,9 @@ body {
|
|||
.rankNumber {
|
||||
font-size: 5vh;
|
||||
text-align: center;
|
||||
text-shadow: -0.2vh -0.2vh 0vh #000, 0.2vh -0.2vh 0vh #000, -0.2vh 0.2vh 0vh #000, 0.2vh 0.2vh 0vh #000;
|
||||
-webkit-text-stroke-width: 0.2vh;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 0.3vh 0.3vh 0vh rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.username {
|
||||
|
@ -47,12 +49,16 @@ body {
|
|||
|
||||
.level {
|
||||
font-size: 3.8vh;
|
||||
text-shadow: -0.18vh -0.18vh 0vh #000, 0.18vh -0.18vh 0vh #000, -0.18vh 0.18vh 0vh #000, 0.18vh 0.18vh 0vh #000;
|
||||
-webkit-text-stroke-width: 0.15vh;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 0.2vh 0.2vh 0vh rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.score {
|
||||
font-size: 2.5vh;
|
||||
text-shadow: -0.15vh -0.15vh 0vh #000, 0.15vh -0.15vh 0vh #000, -0.15vh 0.15vh 0vh #000, 0.15vh 0.15vh 0vh #000;
|
||||
-webkit-text-stroke-width: 0.1vh;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 0.15vh 0.15vh 0vh rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.powerup {
|
||||
|
@ -92,8 +98,6 @@ h1 {
|
|||
font-size: 8vh;
|
||||
font-family: Kaine;
|
||||
color: #ffc800;
|
||||
letter-spacing: .02em;
|
||||
text-shadow: -0.25vh -0.25vh 0vh #000, 0.25vh -0.25vh 0vh #000, -0.25vh 0.25vh 0vh #000, 0.25vh 0.25vh 0vh #000;
|
||||
}
|
||||
|
||||
h2 {
|
||||
|
@ -102,9 +106,11 @@ h2 {
|
|||
font-size: 7.5vh;
|
||||
font-family: Pusab, Arial;
|
||||
color: rgb(255, 200, 0);
|
||||
letter-spacing: 0.02em;
|
||||
text-align: center;
|
||||
text-shadow: -0.275vh -0.275vh 0vh #000, 0.275vh -0.275vh 0vh #000, -0.275vh 0.275vh 0vh #000, 0.275vh 0.275vh 0vh #000, 0.5vh 0.5vh 0vh rgba(0,0,0,0.4);
|
||||
letter-spacing: -0.01em;
|
||||
-webkit-text-stroke-width: 0.265vh;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 0.35vh 0.35vh 0.1vh rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.gdButton {
|
||||
|
|
|
@ -78,11 +78,22 @@ img, .noSelect {
|
|||
|
||||
.smaller {
|
||||
font-size: 5vh;
|
||||
text-shadow: -0.2vh -0.2vh 0vh #000, 0.2vh -0.2vh 0vh #000, -0.2vh 0.2vh 0vh #000, 0.2vh 0.2vh 0vh #000, 0.3vh 0.4vh 0vh rgba(0,0,0,0.4);
|
||||
-webkit-text-stroke-width: 0.20vh;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 0.35vh 0.35vh 0vh rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.slightlySmaller {
|
||||
font-size: 6vh;
|
||||
-webkit-text-stroke-width: 0.25vh;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 0.35vh 0.35vh 0vh rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.biggerShadow {
|
||||
-webkit-text-stroke-width: 0.3vh;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 0.5vh 0.5vh 0vh rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.bigger {
|
||||
|
@ -154,43 +165,50 @@ p {
|
|||
word-break: break-word;
|
||||
}
|
||||
|
||||
h1 {
|
||||
h1, h2, h3 {
|
||||
font-family: Pusab, Arial;
|
||||
font-weight: normal;
|
||||
margin: 0% 0%;
|
||||
font-size: 6vh;
|
||||
font-family: Pusab, Arial;
|
||||
color: white;
|
||||
letter-spacing: 0.02em;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-shadow: -0.275vh -0.275vh 0vh #000, 0.275vh -0.275vh 0vh #000, -0.275vh 0.275vh 0vh #000, 0.275vh 0.275vh 0vh #000, 0.5vh 0.6vh 0vh rgba(0,0,0,0.4);
|
||||
letter-spacing: -0.01em;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-text-stroke-color: black;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
font-size: 6vh;
|
||||
line-height: 100%;
|
||||
white-space: nowrap;
|
||||
-webkit-text-stroke-width: 0.25vh;
|
||||
text-shadow: 0.375vh 0.375vh 0vh rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: normal;
|
||||
margin: 0 0;
|
||||
font-size: 8vh;
|
||||
font-family: Pusab, Arial;
|
||||
color: rgb(255, 200, 0);
|
||||
letter-spacing: 0.02em;
|
||||
text-shadow: -0.275vh -0.275vh 0vh #000, 0.275vh -0.275vh 0vh #000, -0.275vh 0.275vh 0vh #000, 0.275vh 0.275vh 0vh #000, 0.5vh 0.5vh 0vh rgba(0,0,0,0.4);
|
||||
-webkit-text-stroke-width: 0.265vh;
|
||||
background: -webkit-linear-gradient(#FDA00C, #FFE348);
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-filter: drop-shadow(0.25vh 0.25vh rgba(0, 0, 0, 0.25));
|
||||
filter: drop-shadow(0.25vh 0.25vh rgba(0, 0, 0, 0.25));
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
h3, input[type=text], input[type=password], input[type=number], .h3Size {
|
||||
font-weight: normal;
|
||||
margin: 0 0;
|
||||
font-size: 3.5vh;
|
||||
font-family: Pusab, Arial;
|
||||
color: white;
|
||||
letter-spacing: 0.02em;
|
||||
text-shadow: -0.15vh -0.15vh 0vh #000, 0.15vh -0.15vh 0vh #000, -0.15vh 0.15vh 0vh #000, 0.15vh 0.15vh 0vh #000;
|
||||
-webkit-text-stroke-width: 0.14vh;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 0.2vh 0.2vh 0.02vh rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
-webkit-text-stroke-width: inherit;
|
||||
-webkit-text-stroke-color: inherit;
|
||||
text-shadow: inherit;
|
||||
}
|
||||
|
||||
hr {
|
||||
|
@ -207,6 +225,7 @@ input[type=text], input[type=password], input[type=number] {
|
|||
margin-top: 1%;
|
||||
width: 65%;
|
||||
font-size: 5vh;
|
||||
font-family: Pusab, Arial;
|
||||
}
|
||||
|
||||
input[type=checkbox], .changeDaWorld {
|
||||
|
@ -323,14 +342,16 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
}
|
||||
|
||||
.brownBox {
|
||||
border: 2.5vh solid transparent;
|
||||
border-width: 2.5vh;
|
||||
border-style: solid;
|
||||
border-radius: 3vh;
|
||||
background-color: #995533;
|
||||
border-image: url('../../assets/brownbox.png') 10% round;
|
||||
}
|
||||
|
||||
.blueBox {
|
||||
border: 2.5vh solid transparent;
|
||||
border-width: 2.5vh;
|
||||
border-style: solid;
|
||||
border-radius: 3vh;
|
||||
background-color: #334499;
|
||||
border-image: url('../../assets/bluebox.png') 10% round;
|
||||
|
@ -339,7 +360,8 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
.fancybox {
|
||||
width: 69vh;
|
||||
padding: 1vh 3vh;
|
||||
border: 3.5vh solid transparent;
|
||||
border-width: 3.5vh;
|
||||
border-style: solid;
|
||||
border-radius: 3vh;
|
||||
background-color: #001931;
|
||||
border-image: url('../../assets/fancybox.png') 10% stretch;
|
||||
|
@ -351,14 +373,18 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
}
|
||||
|
||||
.epicbox {
|
||||
border: 3.5vh solid transparent;
|
||||
|
||||
border-width: 3.5vh;
|
||||
border-style: solid;
|
||||
border-image: url('../../assets/epicbox.png') 20% stretch;
|
||||
border-image-slice: 85 77;
|
||||
border-image-width: 9.5vh;
|
||||
}
|
||||
|
||||
.leaderboardBox {
|
||||
border: 3.5vh solid transparent;
|
||||
|
||||
border-width: 3.5vh;
|
||||
border-style: solid;
|
||||
border-image: url('../../assets/leaderboardbox.png') 20% stretch;
|
||||
border-image-slice: 85 77;
|
||||
border-image-width: 9.5vh;
|
||||
|
@ -389,7 +415,11 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
}
|
||||
|
||||
.sideSpaceD {
|
||||
margin-left: 25%;
|
||||
margin-right: 25%;
|
||||
}
|
||||
|
||||
.rightSpace {
|
||||
margin-right: 2%;
|
||||
}
|
||||
|
||||
.diffDiv {
|
||||
|
@ -439,8 +469,9 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
|
||||
#filterStuff div h1, .smallerer {
|
||||
font-size: 4vh;
|
||||
text-shadow: -0.2vh -0.2vh 0vh #000, 0.2vh -0.2vh 0vh #000, -0.2vh 0.2vh 0vh #000, 0.2vh 0.2vh 0vh #000, 0.3vh 0.4vh 0vh rgba(0,0,0,0.4);
|
||||
}
|
||||
-webkit-text-stroke-width: 0.18vh;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 0.3vh 0.3vh 0vh rgba(0, 0, 0, 0.3);}
|
||||
|
||||
.sideButton {
|
||||
margin-bottom: 30%;
|
||||
|
@ -460,7 +491,7 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.gdButton:active {
|
||||
.gdButton:active, .gdButton:focus-visible, a:focus-visible .gdButton {
|
||||
animation: bounceButton 0.25s ease-in-out forwards;
|
||||
}
|
||||
|
||||
|
@ -486,13 +517,20 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
margin-bottom: 15%;
|
||||
}
|
||||
|
||||
.menuButtonList td {
|
||||
padding: 1.2vh 2vh;
|
||||
}
|
||||
|
||||
.menuButtonList td a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
width: 24vh;
|
||||
margin: 1.2vh 2vh;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menuButton:active {
|
||||
.menuButton:active, .menuButton:focus-visible, a:focus-visible .menuButton {
|
||||
animation: bounceButton 0.25s ease-in-out forwards;
|
||||
}
|
||||
|
||||
|
@ -654,7 +692,6 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
text-align: left;
|
||||
padding-top: 1.5vh;
|
||||
padding-left: 1.5vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.compact {
|
||||
|
@ -664,11 +701,19 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
transform: scale(0.8) translate(-13%, -12.5%)
|
||||
}
|
||||
|
||||
|
||||
.comment h2, .smallGold {
|
||||
background: -webkit-linear-gradient(#e28000, #ffee44);
|
||||
background-clip: text;
|
||||
width: fit-content;
|
||||
vertical-align: top;
|
||||
font-size: 3.7vh;
|
||||
text-shadow: -0.15vh -0.15vh 0vh #000, 0.15vh -0.15vh 0vh #000, -0.15vh 0.15vh 0vh #000, 0.15vh 0.15vh 0vh #000, 0.3vh 0.3vh 0vh rgba(0,0,0,0.4);
|
||||
-webkit-text-stroke-width: 0.14vh;
|
||||
-webkit-text-stroke-color: black;
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-filter: drop-shadow(0.2vh 0.2vh rgba(0, 0, 0, 0.25));
|
||||
filter: drop-shadow(0.2vh 0.2vh rgba(0, 0, 0, 0.25));
|
||||
}
|
||||
|
||||
.commentText {
|
||||
|
@ -699,10 +744,9 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
|
||||
.commentPercent {
|
||||
vertical-align: top;
|
||||
margin: 0 0 0 1vh;
|
||||
margin: 0 0 0 1.5vh;
|
||||
font-size: 2vh;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
transform: translateY(40%);
|
||||
}
|
||||
|
||||
.commentLikes {
|
||||
|
@ -716,8 +760,54 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
transform: translateY(-19%)
|
||||
}
|
||||
|
||||
.commentIcon {
|
||||
margin-right: 1.25%;
|
||||
width: 3.5%;
|
||||
}
|
||||
|
||||
.commentIcon img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.commentRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.creditsIcon {
|
||||
height: 30%;
|
||||
margin-bottom: 7%;
|
||||
}
|
||||
|
||||
.creditsIcon img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.specialThanksIcon img {
|
||||
width: 42%;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.leaderboardSlot {
|
||||
height: 25%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
padding-left: 0%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.leaderboardName {
|
||||
margin-right: 2.5%;
|
||||
}
|
||||
|
||||
.leaderboardStars {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.weeklyStuff {
|
||||
|
@ -725,10 +815,38 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
}
|
||||
|
||||
.ranking {
|
||||
transform:scale(0.82) translate(-20.7vh, -20vh);
|
||||
position: absolute;
|
||||
height: 10%;
|
||||
width: 12.5%;
|
||||
display: flex;
|
||||
padding-left: 5%;
|
||||
padding-right: 2%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 88%;
|
||||
width: 10%
|
||||
}
|
||||
|
||||
.ranking h2 {
|
||||
width: 100%;
|
||||
margin-top: 2%;
|
||||
}
|
||||
|
||||
.leaderboardIcon {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.leaderboardIcon img {
|
||||
height: 80%;
|
||||
max-width: 150%;
|
||||
}
|
||||
|
||||
.leaderboardSide {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
margin-top: 0.5%
|
||||
}
|
||||
|
||||
#collectibles, .leaderboardStats {
|
||||
|
@ -742,12 +860,16 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
|||
}
|
||||
|
||||
.leaderboardStats {
|
||||
display: flex;
|
||||
margin-top: 2%;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.leaderboardStats img {
|
||||
transform: translate(-20%, -7%);
|
||||
width: 4.3%;
|
||||
margin-left: 1%;
|
||||
margin-right: 2.5%;
|
||||
}
|
||||
|
||||
#boomling {
|
||||
|
@ -1305,4 +1427,18 @@ cp { color: #ff00ff }
|
|||
100% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable all transitions and animations if the user has reduced motion enabled in their sytem settings
|
||||
* Also disabled on devices that may be slow to render new frames for performance optimization
|
||||
*/
|
||||
@media screen and
|
||||
(prefers-reduced-motion: reduce),
|
||||
(update: slow) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.001ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.001ms !important;
|
||||
}
|
||||
}
|
|
@ -28,21 +28,25 @@ h1 {
|
|||
font-size: 60px;
|
||||
font-family: Pusab;
|
||||
color: white;
|
||||
letter-spacing: 0.02em;
|
||||
letter-spacing: -0.01em;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-shadow: -2.2px -2.2px 0px #000, 2.2px -2.2px 0px #000, -2.2px 2.2px 0px #000, 2.2px 2.2px 0px #000, 5px 5px 0px rgba(0,0,0,0.4);
|
||||
-webkit-text-stroke-width: 2.2px;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 3px 3px 0.5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
h2 {
|
||||
h2, select, input[type=number] {
|
||||
font-weight: normal;
|
||||
margin: 0 0;
|
||||
font-size: 35px;
|
||||
font-family: Pusab;
|
||||
width: fit-content;
|
||||
color: white;
|
||||
letter-spacing: 0.02em;
|
||||
text-shadow: -1.5px -1.5px 0px #000, 1.5px -1.5px 0px #000, -1.5px 1.5px 0px #000, 1.5px 1.5px 0px #000;
|
||||
letter-spacing: -0.01em;
|
||||
-webkit-text-stroke-width: 1.6px;
|
||||
-webkit-text-stroke-color: black;
|
||||
text-shadow: 2px 2px 0px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
|
@ -85,13 +89,6 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
outline: none;
|
||||
}
|
||||
|
||||
input:not([type='checkbox']), select {
|
||||
padding: 7 7 7 7;
|
||||
font-size: 14px;
|
||||
width: 15%;
|
||||
border-style: 1px inset black;
|
||||
}
|
||||
|
||||
input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, input:-webkit-autofill:active {
|
||||
box-shadow: 0 0 0px 1000px #764F1A inset !important;
|
||||
-webkit-box-shadow: 0 0 0px 1000px #764F1A inset !important;
|
||||
|
@ -112,6 +109,18 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
font-family: "Roboto";
|
||||
}
|
||||
|
||||
#iconbox {
|
||||
display: flex;
|
||||
max-height: 175px;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#result {
|
||||
object-fit: contain;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#textbox {
|
||||
text-transform: uppercase;
|
||||
font-size: 22px;
|
||||
|
@ -168,12 +177,6 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.squareIcon {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
padding: 5px 5px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#url {
|
||||
width: 500px;
|
||||
max-width: 90%;
|
||||
|
@ -234,7 +237,7 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
|
||||
#generate {
|
||||
font-family: "Roboto";
|
||||
border: transparent;
|
||||
border: rgba(0, 0, 0, 0);
|
||||
background-color: #88FF33;
|
||||
box-shadow: 2px 2px 3px #66AA22;
|
||||
border-radius: 10px;
|
||||
|
@ -255,7 +258,7 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
|
||||
.miniButton {
|
||||
font-family: "Roboto";
|
||||
border: transparent;
|
||||
border: rgba(0, 0, 0, 0);
|
||||
background-color: #88FF33;
|
||||
box-shadow: 1.5px 1.5px 2px #66AA22;
|
||||
border-radius: 10px;
|
||||
|
@ -278,25 +281,50 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
width: 800px;
|
||||
overflow-x: hidden;
|
||||
display: block;
|
||||
padding-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
margin: 0 auto 0 auto;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.iconContainer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
padding: 0px 25px;
|
||||
}
|
||||
|
||||
.iconContainer button {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.iconContainer button img {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.iconKit button, .colTypes button {
|
||||
margin: 0 0 0 0;
|
||||
padding: 0 0 0 0;
|
||||
border: 5px solid transparent;
|
||||
padding: 0px 0px;
|
||||
border: 5px solid rgba(0, 0, 0, 0); /* need this for border image to work */
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.iconSelected {
|
||||
border-image: url('../../assets/select.png') 10 stretch !important;
|
||||
}
|
||||
|
||||
.iconTabButton, .glowToggle, .copyForm {
|
||||
.iconTabButton, .glowToggle, .copyForm, .miscFormButton {
|
||||
margin: 0 5 0 5;
|
||||
transition: transform .1s ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.iconTabButton:focus-visible, .glowToggle:focus-visible, .copyForm:focus-visible, .menuButton:focus-visible, .postButton:focus-visible, .miscFormButton:focus-visible {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.colorPicker {
|
||||
|
@ -332,12 +360,13 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
margin: 18 10 0 10 !important;
|
||||
}
|
||||
|
||||
.iconTabButton:active, .glowToggle:active, .copyForm:active {
|
||||
.iconTabButton:active, .glowToggle:active, .copyForm:active, .miscFormButton:active {
|
||||
transform:scale(1.05)
|
||||
}
|
||||
|
||||
#colors {
|
||||
width: 1000px;
|
||||
max-width: 90%;
|
||||
overflow-x: auto;
|
||||
padding: 10 10 10 10;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
|
@ -345,7 +374,7 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
|
||||
#colors::-webkit-scrollbar, #iconKitParent::-webkit-scrollbar {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
height: 10px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
|
@ -354,30 +383,35 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
}
|
||||
|
||||
#iconKitParent {
|
||||
min-height: 90px;
|
||||
max-height: 210px;
|
||||
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
#iconKitParent div {
|
||||
margin-top: -5;
|
||||
}
|
||||
|
||||
#colors div {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.iconColor div {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#gdfloor {
|
||||
margin-bottom: 15px;
|
||||
margin-top: 0px;
|
||||
margin-top: 1px;
|
||||
border: 0;
|
||||
height: 3px;
|
||||
width: 80%;
|
||||
background-image: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
|
||||
position: relative;
|
||||
z-index: -10;
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
margin: 0 5 0 5;
|
||||
transition: transform .15s ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.menuButton:hover {
|
||||
|
@ -390,21 +424,23 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
|
||||
#iconprogressbar {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
height: 12px;
|
||||
height: 7px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
#iconloading {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
height: 12px;
|
||||
height: 7px;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.iconScroll::-webkit-scrollbar {
|
||||
body::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
background: transparent;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.iconHover {
|
||||
.iconHover, .iconButton:focus-visible, .iconColor:focus-visible {
|
||||
transform: scale(1.075);
|
||||
background-color: rgb(255, 255, 255, 0.2);
|
||||
border-radius: 10px;
|
||||
|
@ -421,7 +457,7 @@ input:focus, select:focus, textarea:focus, button:focus {
|
|||
}
|
||||
|
||||
.brownBox {
|
||||
border: 17px solid transparent;
|
||||
border: 17px solid rgba(0, 0, 0, 0);
|
||||
border-radius: 25px;
|
||||
background-color: #995533;
|
||||
border-image: url('../../assets/brownbox.png') 10% round;
|
||||
|
@ -442,7 +478,7 @@ input[type=text] {
|
|||
color: white;
|
||||
letter-spacing: 0.02em;
|
||||
text-shadow: -0.2vh -0.2vh 0vh #000, 0.2vh -0.2vh 0vh #000, -0.2vh 0.2vh 0vh #000, 0.2vh 0.2vh 0vh #000;
|
||||
border: 0 solid transparent;
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
white-space: nowrap;
|
||||
|
@ -454,6 +490,22 @@ input[type=text]::placeholder {
|
|||
text-shadow: -0.15vh -0.15vh 0vh #000, 0.15vh -0.15vh 0vh #000, -0.15vh 0.15vh 0vh #000, 0.15vh 0.15vh 0vh #000;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
border: none;
|
||||
border-radius: 7px;
|
||||
height: 50px;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
white-space: nowrap;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
display: none;
|
||||
}
|
||||
|
@ -474,10 +526,107 @@ input[type=checkbox]:checked + label.gdcheckbox {
|
|||
background-image: url(../../assets/check-on.png);
|
||||
}
|
||||
|
||||
#settingList {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#settingList div {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#settingList .gdCheckbox {
|
||||
margin: 0px 0px 5px 0px;
|
||||
}
|
||||
|
||||
.animationSelector {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.animationSelector select {
|
||||
border-radius: 7px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
width: 300px;
|
||||
height: 45px;
|
||||
font-size: 32px;
|
||||
padding-left: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.animationSelector option {
|
||||
color: black;
|
||||
font-size: 20px;
|
||||
font-family: aller, helvetica, arial;
|
||||
letter-spacing: unset;
|
||||
-webkit-text-stroke-width: unset;
|
||||
-webkit-text-stroke-color: unset;
|
||||
text-shadow: unset;
|
||||
}
|
||||
|
||||
.animationSelector h2 {
|
||||
width: unset
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
appearance: none !important;
|
||||
height: 20px;
|
||||
min-height: 20px;
|
||||
border: 1px solid;
|
||||
cursor: pointer;
|
||||
border-image: url(../../assets/slider_track.png);
|
||||
border-image-slice: 21 22 22 21;
|
||||
border-image-width: 30px 30px 30px 30px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
appearance: none !important;
|
||||
height: 45px;
|
||||
width: 45px;
|
||||
background-image: url("../../assets/slider_thumb.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb:active { background-image: url("../../assets/slider_thumb_active.png"); }
|
||||
|
||||
#extraInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
#extraInfo div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: left;
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#extraInfo p {
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
margin: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
.bounce {
|
||||
animation: boxAnimator 0.25s;
|
||||
}
|
||||
|
||||
.greyedOut {
|
||||
filter: grayscale(100%) brightness(0.7);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gold {
|
||||
color: rgb(255, 200, 0);
|
||||
}
|
||||
|
@ -494,6 +643,10 @@ input[type=checkbox]:checked + label.gdcheckbox {
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1100px) {
|
||||
.hideIfSmall { display: none !important; }
|
||||
}
|
||||
|
||||
.spin {
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
-moz-animation: spin 2s linear infinite;
|
||||
|
|
BIN
assets/deatheffects/18.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
assets/deatheffects/19.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
assets/deatheffects/20.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
assets/expanded-off.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
assets/expanded-on.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/gauntlets/unknown.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
assets/gd_circle.png
Normal file
After Width: | Height: | Size: 175 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 201 KiB |
Before Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 121 KiB |
Before Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 725 B |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 186 KiB |
Before Width: | Height: | Size: 213 KiB |
Before Width: | Height: | Size: 131 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 175 KiB |
Before Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 161 KiB |
Before Width: | Height: | Size: 130 KiB |
BIN
assets/gdw_circle.png
Normal file
After Width: | Height: | Size: 138 KiB |
BIN
assets/gdw_transparent.png
Normal file
After Width: | Height: | Size: 243 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 36 KiB |
BIN
assets/iconkitbuttons/btn-reveal.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
assets/iconkitbuttons/copied.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
assets/iconkitbuttons/copy.png
Normal file
After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
BIN
assets/iconkitbuttons/jetpack_off.png
Normal file
After Width: | Height: | Size: 9 KiB |
BIN
assets/iconkitbuttons/jetpack_on.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/iconkitbuttons/psd.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
assets/iconkitbuttons/spoilers.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
assets/iconkitbuttons/spoilers_on.png
Normal file
After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 9 KiB |
BIN
assets/loading_old.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/objects/browncoin.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
assets/objects/triggers/AdvRandom.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/objects/triggers/CameraEdge.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/objects/triggers/CameraGuide.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/objects/triggers/CameraRotate.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/objects/triggers/Pause.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/objects/triggers/Resume.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/objects/triggers/Scale.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/objects/triggers/Song.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/objects/triggers/StopJump.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
assets/objects/triggers/TimeWarp.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
assets/plus_blue.png
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 7.3 KiB |