Merge branch 'GDColon-master'

This commit is contained in:
Justin Pridgen 2022-07-08 22:25:31 -04:00
commit 39e26aa90b
4716 changed files with 116890 additions and 3328 deletions

70
.github/workflows/analyse.yml vendored Normal file
View 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
View file

@ -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
View file

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

View file

@ -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) + "%"])

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = ""

View file

@ -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 = []

View file

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

View file

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

View file

@ -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() {

View file

@ -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",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)")
})
}

View file

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

View file

@ -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}`)
})
}

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

BIN
assets/colU.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/deatheffects/19.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/deatheffects/20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
assets/expanded-off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
assets/expanded-on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
assets/gd_circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

BIN
assets/gdw_circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
assets/gdw_transparent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 9 KiB

BIN
assets/loading_old.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
assets/plus_blue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

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