├── .github └── workflows │ └── main.yml ├── .gitignore ├── .whitesource ├── LICENSE ├── README.md ├── example.env ├── index.js ├── package-lock.json ├── package.json └── src ├── classes ├── App.js └── Router.js ├── database ├── mongo.js ├── redis.js └── setup.sql ├── public ├── css │ ├── bootstrap.min.css │ ├── fontawesome-min.css │ └── style.css ├── js │ ├── bots.js │ ├── home.js │ ├── rooms.js │ ├── statistics.js │ └── utils.js └── static │ └── moving-bg.png ├── routes └── api.js ├── util ├── Logger.js ├── connect.js ├── cron.js └── stats.js └── views ├── Components ├── Footer.ejs ├── Head.ejs ├── Navbar.ejs └── Noscript.ejs ├── bots.ejs ├── index.ejs ├── rooms.ejs └── scheduled.ejs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Production Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: executing remote ssh commands using password 13 | uses: appleboy/ssh-action@master 14 | with: 15 | host: ${{ secrets.IP }} 16 | username: ${{ secrets.USER }} 17 | password: ${{ secrets.PRIVATE_KEY }} 18 | port: ${{ secrets.PORT }} 19 | script: | 20 | nvm use 14 21 | cd /opt/dogetracker 22 | git fetch --all 23 | git reset --hard origin/master 24 | npm install --save 25 | pm2 restart dogehouse-stats 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff" 8 | }, 9 | "issueSettings": { 10 | "minSeverityLevel": "LOW" 11 | } 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | DogeGarden logo 4 |

5 |

6 | An open-source website for viewing, managing and exploring statistics and historical data from DogeHouse 🐶 7 |

8 |

9 | 10 | discord - users online 11 | 12 |

13 | 14 |

15 | Website 16 | · 17 | Tracker 18 | · 19 | Discord 20 | · 21 | Documentation 22 |

23 | 24 | --- 25 | 26 | ## Installation 27 | 28 | - You need a MySQL Server to run the /src/database/setup.sql database setup. 29 | 30 | 1. git clone https://github.com/dogegarden/dogetracker 31 | 2. Install [NodeJS](https://nodejs.org/en/) and [NPM](https://www.npmjs.com/). 32 | 3. Install depends: `npm install --save` 33 | 4. Fill out example.env 34 | 5. Run index.js 35 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | MYSQL_HOST=localhost 2 | MYSQL_PORT=3306 3 | MYSQL_USER=root 4 | MYSQL_PASS= 5 | MYSQL_DB=dogestats 6 | HOST_URL=https://stats.dogegarden.net 7 | 8 | EXPRESS_PORT=7000 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const App = new (require('./src/classes/App')); 2 | const Logger = require('./src/util/Logger'); 3 | const Cron = require('./src/util/cron'); 4 | 5 | (async function () { 6 | await App.registerRoutes(); 7 | await App.listen(() => { 8 | Logger.info(`Express started on on port ${process.env.EXPRESS_PORT}`); 9 | }, true); 10 | Cron.setCron(); 11 | // Cron.autoRunMYSQL(); 12 | })() 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dogegarden-stats", 3 | "version": "1.0.6", 4 | "description": "An open-source site to provide data and statistics about dogehouse.tv.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "axios": "^0.21.1", 14 | "chalk": "^4.1.0", 15 | "dateformat": "^4.5.1", 16 | "ejs": "^3.1.6", 17 | "express": "^4.17.1", 18 | "fs": "0.0.1-security", 19 | "ioredis": "^4.24.2", 20 | "monk": "^7.3.3", 21 | "mysql": "^2.18.1", 22 | "node-cron": "^3.0.0", 23 | "util": "^0.12.3" 24 | }, 25 | "devDependencies": { 26 | "body-parser": "^1.19.0", 27 | "cookie-parser": "^1.4.5", 28 | "cors": "^2.8.5", 29 | "dotenv": "^8.2.0", 30 | "nodemon": "^2.0.7", 31 | "path": "^0.12.7" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/classes/App.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const cors = require('cors') 3 | const cookieParser = require('cookie-parser') 4 | const bodyParser = require('body-parser') 5 | const path = require('path') 6 | const Router = require('./Router'); 7 | const Logger = require('../util/Logger') 8 | const fs = require('fs').promises; 9 | const axios = require('axios') 10 | require('dotenv').config() 11 | 12 | 13 | class App { 14 | server; 15 | constructor() { 16 | this.app = express() 17 | this.server = require('http').createServer(this.app) 18 | this.app.engine('e', require('ejs').renderFile) 19 | this.app.set('view engine', 'ejs') 20 | this.app.set('views', path.join(__dirname, '..', 'views')) 21 | this.app.use(cors({ 22 | origin: "*" 23 | })); 24 | this.app.use('/public', express.static(path.join(__dirname, '..', 'public'))); 25 | this.app.use(cookieParser()); 26 | this.app.use(express.json()) 27 | this.app.use(express.urlencoded({ extended: true })) 28 | } 29 | /** 30 | * @param {express.Request} req 31 | * @param {express.Response} res 32 | * @param {function()} next 33 | */ 34 | 35 | async registerRoutes() { 36 | 37 | const filePath = path.join(__dirname, '..', 'routes'); 38 | const files = await fs.readdir(filePath); 39 | for await (const file of files) { 40 | if (file.endsWith('.js')) { 41 | const router = require(path.join(filePath, file)); 42 | if (router.prototype instanceof Router) { 43 | const instance = new router(this); 44 | Logger.route(`Route File ${instance.path} running.`); 45 | if (instance.auth) { 46 | this.app.use(instance.path, this.Authentication, instance.createRoute()); 47 | } else { 48 | this.app.use(instance.path, instance.createRoute()); 49 | } 50 | } 51 | } 52 | } 53 | 54 | // routes below... 55 | 56 | let packageConf = require('../../package.json') 57 | 58 | this.app.get('/', function (req, res) { 59 | res.render('index.ejs', { 60 | path: req.path, 61 | version: packageConf.version 62 | }) 63 | }) 64 | 65 | // this.app.get('/bots', function(req, res) { 66 | // res.render('bots.ejs', { 67 | // path: req.path, 68 | // }) 69 | // }) 70 | 71 | this.app.get('/rooms', async function (req, res) { 72 | try { 73 | const rooms = await axios.get('https://api.dogegarden.net/v1/popularRooms'); 74 | console.log(rooms.data.rooms.rooms) 75 | res.render('rooms.ejs', { 76 | path: req.path, 77 | rooms: rooms.data.rooms.rooms 78 | }) 79 | } catch (err) { 80 | console.log(err) 81 | } 82 | }) 83 | 84 | // this.app.get('/scheduled', async function(req, res) { 85 | // try { 86 | // const rooms = await axios.get('https://api.dogegarden.net/v1/scheduledRooms'); 87 | // console.log(rooms.data.rooms) 88 | // res.render('scheduled.ejs', { 89 | // path: req.path, 90 | // rooms: rooms.data.rooms 91 | // }) 92 | // } catch(err) { 93 | // console.log(err) 94 | // } 95 | // }) 96 | 97 | // this.app.get('/statistics', async function(req, res) { 98 | // res.render('statistics.ejs', { 99 | // path: req.path 100 | // }) 101 | // }) 102 | 103 | // this.app.get('/bots', function(req, res) { 104 | // res.render('bots.ejs', { 105 | // path: req.path, 106 | // }) 107 | // }) 108 | 109 | } // end listener 110 | 111 | 112 | 113 | async listen(fn) { 114 | this.server.listen(process.env.EXPRESS_PORT, fn); 115 | } 116 | 117 | } 118 | 119 | module.exports = App; -------------------------------------------------------------------------------- /src/classes/Router.js: -------------------------------------------------------------------------------- 1 | const Route = require('express').Router; 2 | class Router { 3 | /** 4 | * 5 | * @param {import('./App')} client 6 | * @param {string} path 7 | * @param {boolean} auth 8 | */ 9 | constructor(client, path, auth = false) { 10 | this.client = client; 11 | this.path = path; 12 | this.auth = auth; 13 | this.router = Route(); 14 | } 15 | createRoute() { 16 | return this.router; 17 | } 18 | } 19 | module.exports = Router; 20 | -------------------------------------------------------------------------------- /src/database/mongo.js: -------------------------------------------------------------------------------- 1 | const monk = require('monk'); 2 | const Logger = require('../util/Logger') 3 | require('dotenv').config() 4 | const db = monk(process.env.MONGO_URL); 5 | Logger.mongo('Connected to mongodb.') 6 | 7 | class Calls { 8 | static async getUser(id) { 9 | const collection = db.get('users') 10 | return (await collection.findOne({ user_id: id })) 11 | } 12 | 13 | static async addRoom(room) { 14 | const collection = db.get('rooms') 15 | return (await collection.insert({ 16 | room_id: room.id, 17 | room_name: room.name 18 | })) 19 | } 20 | 21 | static async getRoom(id) { 22 | const collection = db.get('rooms') 23 | return (await collection.findOne({ 24 | room_id: room.id, 25 | })) 26 | } 27 | 28 | 29 | 30 | // static async addUsersToRoom(room_id, users) { 31 | // const collection = db.get('rooms') 32 | // return (await collection.findOne({ 33 | // room_id: room.id, 34 | // })) 35 | // } 36 | } 37 | 38 | module.exports = Calls; -------------------------------------------------------------------------------- /src/database/redis.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Redis = require('ioredis'); 4 | const Logger = require('../util/Logger') 5 | // const axios = require('axios') 6 | require('dotenv').config() 7 | 8 | Logger.reddis('Connected to reddis.') 9 | 10 | const redisOptions = { 11 | host: process.env.REDIS_HOST, 12 | port: process.env.REDIS_PORT, 13 | password: process.env.REDIS_PASS, 14 | db: 0, 15 | connectionName: `dogegarden-stats`, 16 | }; 17 | 18 | function createClient(prefix, options = {}) { 19 | const allOptions = { 20 | ...redisOptions, 21 | ...options, 22 | 23 | keyPrefix: `dogestats:${prefix}:` 24 | }; 25 | 26 | return new Redis(allOptions); 27 | } 28 | 29 | // const redisDemo = async () => { 30 | // const clientTest = createClient(`test1`); 31 | // const redis = clientTest.multi(); 32 | // const reply = await redis.get('foo'); 33 | // console.log(reply); 34 | // }; 35 | 36 | // redisDemo(); 37 | 38 | 39 | // const test = async () => { 40 | // const clientTest = createClient(`test1`); 41 | // const multi = clientTest.multi(); 42 | // // multi.set(`a`, 1); 43 | // // multi.get(`a`); 44 | // multi.get(`dogestats`); 45 | // return await multi.exec(); 46 | // } 47 | 48 | // test().then((...args) => console.log(...args)); 49 | 50 | // const test = async () => { 51 | // let data = await axios.get('http://localhost:7000/api/statistics') 52 | // // console.log(data.data) 53 | // const clientTest = createClient(`test1`); 54 | // const multi = clientTest.multi(); 55 | // multi.set(`first-test`, 123) 56 | // multi.set(`a`, JSON.stringify(data)); 57 | // multi.get(`a`); 58 | // multi.get(`first-test`); 59 | // return await multi.exec(); 60 | // } 61 | 62 | // const test = async () => { 63 | // const clientTest = createClient(`test1`); 64 | // const multi = clientTest.multi(); 65 | // multi.get(`dogestats`); 66 | // return await multi.exec(); 67 | // } 68 | 69 | // try { 70 | // test().then((...args) => console.log(...args)); 71 | // } catch (e) { 72 | // console.log("Error in redis", e) 73 | // } 74 | 75 | module.exports = { 76 | createClient, 77 | // test, 78 | }; 79 | -------------------------------------------------------------------------------- /src/database/setup.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS dogestats; 2 | 3 | USE dogestats; 4 | 5 | -- CREATE TABLE IF NOT EXISTS users ( 6 | -- uuid VARCHAR(36) NOT NULL PRIMARY KEY, 7 | -- isBot BOOLEAN NOT NULL DEFAULT FALSE, 8 | -- username TEXT(15), 9 | -- bio TEXT(160), 10 | -- numFollowers INT NOT NULL DEFAULT 0, 11 | -- numFollowing INT NOT NULL DEFAULT 0, 12 | -- displayName TEXT(50) NOT NULL, 13 | -- avatar TEXT, 14 | -- inRoom BOOLEAN NOT NULL DEFAULT FALSE 15 | -- ); 16 | 17 | CREATE TABLE IF NOT EXISTS rooms ( 18 | id VARCHAR(36) NOT NULL PRIMARY KEY, 19 | creatorId TEXT NOT NULL, 20 | roomDescription TEXT, 21 | insertedAt TIMESTAMP NOT NULL, 22 | roomName TEXT NOT NULL, 23 | numPeopleInside INT NOT NULL DEFAULT 0 24 | ); 25 | 26 | CREATE TABLE IF NOT EXISTS stats ( 27 | id INT(11) AUTO_INCREMENT NOT NULL PRIMARY KEY, 28 | totalRooms INT(11) NOT NULL DEFAULT 0, 29 | totalScheduledRooms INT(11) NOT NULL DEFAULT 0, 30 | totalOnline INT(11) NOT NULL DEFAULT 0, 31 | totalBotsOnline INT(11) NOT NULL DEFAULT 0, 32 | totalBotsSendingTelemetry INT(11) NOT NULL DEFAULT 0, 33 | totalRegistered INT(11) NOT NULL DEFAULT 0, 34 | activeInLastTwoDays INT(11) NOT NULL DEFAULT 0, 35 | topRoomID VARCHAR(36), 36 | newestRoomID VARCHAR(36), 37 | longestRoomID VARCHAR(36), 38 | statsTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 39 | ); 40 | -------------------------------------------------------------------------------- /src/public/css/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background: rgba(20,25,32); 3 | --nav: #22272E; 4 | } 5 | 6 | @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@500&display=swap"); 7 | @import url("https://fonts.googleapis.com/css2?family=Rubik&display=swap"); 8 | html, 9 | body { 10 | height: 150vh; 11 | margin: 0; 12 | padding: 0; 13 | font-family: Rubik, -apple-system, BlinkMacSystemFont, "Helvetica Neue", 14 | Roboto, system-ui, sans-serif; 15 | background: var(--background) !important; 16 | } 17 | 18 | 19 | #api-alert:not(.show), #ver-alert:not(.show) { 20 | opacity: 1; 21 | } 22 | 23 | .card-stat-count { 24 | font-size: 3em; 25 | } 26 | z #botsOnline { 27 | width: 4500px !important; 28 | max-width: 4500px !important; 29 | } 30 | 31 | .fade-in { 32 | animation: fadeIn ease 1s; 33 | -webkit-animation: fadeIn ease 1s; 34 | -moz-animation: fadeIn ease 1s; 35 | -o-animation: fadeIn ease 1s; 36 | -ms-animation: fadeIn ease 1s; 37 | } 38 | 39 | .moving-bg { 40 | background-image: url("../static/moving-bg.png"); 41 | background-color: rgba(134, 134, 134, 0.075) !important; 42 | background-size: 50%; 43 | animation: bganim 40s linear 0s infinite; 44 | -webkit-animation: bganim 40s linear 0s infinite; 45 | } 46 | 47 | .zoom-in:hover { 48 | transform: scale(1.01); 49 | transition-timing-function: ease-in-out; 50 | } 51 | 52 | .extra-btn-width { 53 | width: 140px; 54 | } 55 | 56 | .hover-detect:hover { 57 | transform: scale(1.01); 58 | transition-timing-function: ease-in-out; 59 | } 60 | 61 | .zoom-in { 62 | transition: 0.15s; 63 | } 64 | 65 | .show { 66 | display: none; 67 | } 68 | /* CLEAR - DEFAULT */ 69 | 70 | .ex-c { 71 | border: 3px solid #f4055f; 72 | background-color: rgba(20,25,32) !important; 73 | } 74 | 75 | .ex-c:hover { 76 | background-color: rgba(16, 16, 16, 0.788) !important 77 | } 78 | 79 | /* PINK */ 80 | .ex-pink { 81 | background-color: #f405615b !important; 82 | } 83 | 84 | .ex-pink:hover { 85 | background-color: #df337567 !important; 86 | } 87 | 88 | .border-p { 89 | border: 3px solid #f4055f; 90 | transition: 0.5s; 91 | } 92 | 93 | .border-p:hover { 94 | border: 3px solid #f4055f; 95 | } 96 | 97 | /* BLUE */ 98 | .ex-blue { 99 | background-color: #0559f446 !important; 100 | } 101 | 102 | .ex-blue:hover { 103 | background-color: #1361f15b !important; 104 | } 105 | 106 | .border-b { 107 | border: 3px solid #c0c0c04b; 108 | transition: 0.5s; 109 | } 110 | 111 | .border-b:hover { 112 | border: 3px solid #0559f446; 113 | } 114 | 115 | /* ORANGE */ 116 | .ex-orange { 117 | background-color: #c7880046 !important; 118 | } 119 | 120 | .ex-orange:hover { 121 | background-color: #ffae0046 !important; 122 | } 123 | 124 | .border-o { 125 | border: 3px solid #c0c0c04b; 126 | transition: 0.5s; 127 | } 128 | 129 | .border-o:hover { 130 | border: 3px solid #c7880046; 131 | } 132 | 133 | /* //////////////////////////////////// */ 134 | 135 | @media screen and (max-width: 992px) { 136 | .card-stat-count { 137 | font-size: 2em; 138 | } 139 | 140 | .p-text { 141 | display: none; 142 | } 143 | } 144 | 145 | @media screen and (max-width: 600px) { 146 | .cover-container { 147 | margin-top: 20px; 148 | } 149 | .col-md-6 { 150 | margin-bottom: 10px; 151 | } 152 | } 153 | 154 | .remove-bg { 155 | background-color: rgba(255, 255, 255, 0.041); 156 | } 157 | 158 | @keyframes bganim { 159 | 0% { 160 | background-position: 0 0; 161 | } 162 | 50% { 163 | background-position: 500px -500px; 164 | } 165 | to { 166 | background-position: 1000px -1000px; 167 | } 168 | } 169 | 170 | .blured { 171 | color: transparent; 172 | text-shadow: 0 0 5px rgba(0, 0, 0, 0.5); 173 | } 174 | 175 | .hidden { 176 | -webkit-text-security: disc; 177 | } 178 | 179 | .snow-lower { 180 | bottom: unset; 181 | position: absolute; 182 | top: 70vh; 183 | } 184 | 185 | .title-text { 186 | margin: 20px; 187 | text-align: center; 188 | vertical-align: middle; 189 | } 190 | 191 | .logo { 192 | height: 200px; 193 | } 194 | 195 | .lower-content { 196 | height: 200px; 197 | background: #f2f2f2; 198 | } 199 | 200 | .white-bg { 201 | background: #f2f2f2; 202 | } 203 | 204 | .thing1 { 205 | text-shadow: 0px 1.4px 10px rgba(0, 0, 0, 0.829); 206 | } 207 | 208 | input { 209 | writing-mode: horizontal-tb !important; 210 | -webkit-writing-mode: horizontal-tb !important; 211 | text-rendering: auto; 212 | color: -internal-light-dark(black, white); 213 | letter-spacing: normal; 214 | word-spacing: normal; 215 | text-transform: none; 216 | text-indent: 0px; 217 | text-shadow: none; 218 | display: inline-block; 219 | text-align: start; 220 | appearance: textfield; 221 | background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59)); 222 | -webkit-rtl-ordering: logical; 223 | cursor: text; 224 | margin: 0em; 225 | font: 400 13.3333px Arial; 226 | padding: 1px 2px; 227 | border-width: 2px; 228 | border-style: inset; 229 | border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133)); 230 | border-image: initial; 231 | } 232 | 233 | [type="text"]:focus, 234 | [type="email"]:focus, 235 | [type="url"]:focus, 236 | [type="password"]:focus, 237 | [type="number"]:focus, 238 | [type="date"]:focus, 239 | [type="datetime-local"]:focus, 240 | [type="month"]:focus, 241 | [type="search"]:focus, 242 | [type="tel"]:focus, 243 | [type="time"]:focus, 244 | [type="week"]:focus, 245 | [multiple]:focus, 246 | textarea:focus, 247 | select:focus { 248 | outline: transparent solid 2px; 249 | outline-offset: 2px; 250 | --tw-ring-inset: var(--tw-empty); 251 | --tw-ring-offset-width: 0px; 252 | --tw-ring-offset-color: #fff; 253 | --tw-ring-color: #2563eb; 254 | --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 255 | var(--tw-ring-offset-width) var(--tw-ring-offset-color); 256 | --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 257 | calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); 258 | box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), 259 | var(--tw-shadow, 0 0 #0000); 260 | border-color: #099aa5 !important; 261 | } 262 | 263 | .secondnav { 264 | width: 100%; 265 | --tw-bg-opacity: 1; 266 | background-color: rgba(62.73, 76.959, 90.27, var(--tw-bg-opacity)); 267 | --tw-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); 268 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), 269 | var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 270 | overflow-x: auto; 271 | } 272 | 273 | .status-card { 274 | width: 300px !important; 275 | } 276 | 277 | .nav-item-new { 278 | --tw-text-opacity: 1; 279 | color: rgba(154.148, 165.363, 177.352, var(--tw-text-opacity)) !important; 280 | align-items: center; 281 | height: 100%; 282 | margin: 0 5px 0 5px; 283 | } 284 | 285 | .min-w { 286 | min-width: 90vw !important; 287 | } 288 | /* 289 | .right-fixed { 290 | position: fixed; 291 | bottom: 0px; 292 | right: 0px; 293 | padding: 30px; 294 | } */ 295 | 296 | a.active, 297 | div.active { 298 | --tw-text-opacity: 1; 299 | color: rgba(228.608, 231.591, 235.493, var(--tw-text-opacity)) !important; 300 | box-shadow: rgb(9, 154, 165) 0px -2px inset; 301 | } 302 | 303 | .buttonload { 304 | background-color: #4caf50; 305 | /* Green background */ 306 | border: none; 307 | /* Remove borders */ 308 | color: white; 309 | /* White text */ 310 | padding: 12px 16px; 311 | /* Some padding */ 312 | font-size: 16px; /* Set a font size */ 313 | } 314 | 315 | .morenav { 316 | display: inline-block; 317 | font-size: 0.93rem !important; 318 | padding: 0.75rem 1rem; 319 | --tw-text-opacity: 1; 320 | color: rgba(154.148, 165.363, 177.352, var(--tw-text-opacity)) !important; 321 | text-decoration: none; 322 | white-space: nowrap; 323 | transition-property: all; 324 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 325 | transition-duration: 150ms; 326 | } 327 | 328 | .secondnav-items { 329 | display: flex; 330 | -webkit-box-align: center; 331 | align-items: center; 332 | font-size: 0.875rem; 333 | line-height: 1.25rem; 334 | margin-left: auto; 335 | margin-right: auto; 336 | padding-left: 0.5rem; 337 | padding-right: 0.5rem; 338 | max-width: 1200px; 339 | } 340 | 341 | .console-area:focus { 342 | --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 343 | var(--tw-ring-offset-width) var(--tw-ring-offset-color); 344 | --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 345 | calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); 346 | box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), 347 | var(--tw-shadow, 0 0 #0000); 348 | } 349 | 350 | .home-card { 351 | border-radius: 0.25rem !important; 352 | } 353 | 354 | .console-input { 355 | --tw-bg-opacity: 1; 356 | background-color: rgba(31.008, 40.8, 50.592, var(--tw-bg-opacity)); 357 | --tw-text-opacity: 1; 358 | color: rgba(228.608, 231.591, 235.493, var(--tw-text-opacity)); 359 | display: flex; 360 | -webkit-box-align: baseline; 361 | align-items: baseline; 362 | height: 40px; 363 | border-top: none; 364 | border-left: none; 365 | border-right: none; 366 | border-color: transparent; 367 | } 368 | 369 | .console-input-dollar { 370 | flex-shrink: 0; 371 | padding: 0.5rem; 372 | font-weight: 700; 373 | } 374 | 375 | .logo-text { 376 | font-size: 1.6rem !important; 377 | line-height: 2rem; 378 | font-family: "IBM Plex Sans", Roboto, system-ui, sans-serif; 379 | padding-left: 1rem !important; 380 | padding-right: 1rem !important; 381 | text-decoration: none !important; 382 | --tw-text-opacity: 1 !important; 383 | color: rgba(201.756, 209.1, 216.444, var(--tw-text-opacity)) !important; 384 | transition-property: background-color, border-color, color, fill, stroke; 385 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important; 386 | transition-duration: 150ms; 387 | } 388 | 389 | .card { 390 | border: none !important; 391 | } 392 | 393 | .border-r { 394 | border-radius: 0.25rem !important; 395 | } 396 | 397 | .card-body { 398 | --tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 399 | 0 2px 4px -1px rgba(0, 0, 0, 0.06); 400 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), 401 | var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 402 | --tw-bg-opacity: 1; 403 | background-color: var(--nav); 404 | border-radius: 0.25rem 0.25rem 0.25rem 0.25rem; 405 | padding: 0.75rem; 406 | display: flex; 407 | font-size: 0.75rem; 408 | line-height: 1rem; 409 | } 410 | .card-body.fixed-height { 411 | height: 120px; 412 | } 413 | 414 | .card-body > p { 415 | align-self: center; 416 | } 417 | 418 | .card-body > ul { 419 | list-style-type: circle; 420 | } 421 | 422 | .hide-div { 423 | display: none; 424 | } 425 | 426 | #faq-links { 427 | justify-content: space-evenly; 428 | display: flex; 429 | padding-top: 1rem; 430 | } 431 | 432 | #faq-links i { 433 | margin-right: 0.25rem; 434 | } 435 | 436 | #accordionFAQ button { 437 | text-align: left; 438 | color: #333333; 439 | } 440 | 441 | #accordionFAQ .card-header { 442 | background-color: white; 443 | } 444 | 445 | .fix-bg { 446 | background-size: cover; 447 | } 448 | 449 | h1 { 450 | color: rgba(255, 255, 255, 0.979); 451 | } 452 | 453 | .bg { 454 | background: url(https://cdn.discordapp.com/attachments/783703146484465756/801638287302983700/unknown.png); 455 | object-fit: cover; 456 | background-size: auto; 457 | background-repeat: no-repeat; 458 | } 459 | 460 | .profile-rank { 461 | display: block; 462 | } 463 | 464 | .vl { 465 | border-left: 4px solid #515151; 466 | height: 15px; 467 | width: 0px; 468 | margin-left: auto; 469 | margin-right: auto; 470 | } 471 | 472 | .card-header > button { 473 | font-size: 1.2em; 474 | font-weight: 300; 475 | padding: 0px; 476 | width: 100%; 477 | text-decoration: none !important; 478 | } 479 | 480 | [data-tooltip]:before { 481 | position: absolute; 482 | content: attr(data-tooltip); 483 | opacity: 0; 484 | } 485 | 486 | [data-tooltip]:hover:before { 487 | opacity: 1; 488 | background: rgb(15, 15, 15); 489 | border: 1px solid rgba(45, 45, 45, 0); 490 | position: relative; 491 | border-radius: 30px; 492 | padding-left: 10px; 493 | padding-right: 10px; 494 | } 495 | 496 | [data-tooltip]:not([data-tooltip-persistent]):before { 497 | pointer-events: none; 498 | } 499 | 500 | .search { 501 | height: 60px !important; 502 | } 503 | 504 | .user-list { 505 | height: 100px; 506 | margin: 5px; 507 | } 508 | 509 | .profile-img2 { 510 | height: 60px; 511 | } 512 | 513 | .title-main2 { 514 | display: inline; 515 | vertical-align: middle; 516 | margin-left: 10px; 517 | font-size: 25px; 518 | } 519 | 520 | .user-list:hover { 521 | transform: scale(1.01); 522 | transition: 0.1s; 523 | } 524 | 525 | .bg-main { 526 | height: 50vh; 527 | background: #131313; 528 | box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.2); 529 | -ms-interpolation-mode: bicubic; 530 | } 531 | 532 | .wave { 533 | content: url(/public/img/wave.svg); 534 | position: absolute; 535 | top: 0; 536 | left: 0; 537 | width: 100%; 538 | } 539 | 540 | a { 541 | color: #ffffff !important; 542 | text-decoration: none !important; 543 | } 544 | 545 | a:hover { 546 | color: #ffffff !important; 547 | text-decoration: none !important; 548 | cursor: pointer !important; 549 | } 550 | 551 | a > button:hover { 552 | color: #ffffff !important; 553 | } 554 | 555 | .border1 { 556 | border: 5px solid rgba(45, 45, 45, 0); 557 | } 558 | 559 | .stats-leader { 560 | font-size: 18px; 561 | } 562 | 563 | .badge1 { 564 | height: 23px; 565 | margin-left: 2px; 566 | } 567 | 568 | .achivement { 569 | margin: 5px; 570 | } 571 | 572 | @media (max-width: 510px) { 573 | .title-main { 574 | font-size: 21px; 575 | } 576 | .achivement { 577 | transform: scale(0.7); 578 | } 579 | } 580 | 581 | @media (max-width: 350px) { 582 | .achivement { 583 | transform: scale(0.5); 584 | } 585 | } 586 | 587 | @media (max-width: 300px) { 588 | .profile-img { 589 | height: 50px; 590 | } 591 | } 592 | 593 | @media (max-width: 250px) { 594 | .achivement { 595 | transform: scale(0.4); 596 | } 597 | } 598 | 599 | @media (max-width: 400px) { 600 | .profile-right { 601 | display: none; 602 | } 603 | } 604 | 605 | .navbar-avatar { 606 | height: 40px; 607 | border-radius: 50%; 608 | } 609 | 610 | a:hover { 611 | color: white !important; 612 | } 613 | 614 | .icon-top { 615 | margin-left: 10px; 616 | } 617 | 618 | .accounts-card { 619 | display: grid; 620 | grid-template-columns: repeat(12, minmax(0px, 1fr)); 621 | gap: 1rem; 622 | position: relative; 623 | height: 80px; 624 | } 625 | 626 | .modal-content { 627 | background: #33404d !important; 628 | } 629 | 630 | .accounts-wrapper { 631 | margin-bottom: 0px !important; 632 | line-height: 10px !important; 633 | display: inline; 634 | } 635 | 636 | .hub-text { 637 | text-align: right !important; 638 | float: right !important; 639 | -webkit-box-align: right !important; 640 | align-items: right !important; 641 | width: 100% !important; 642 | -webkit-box-align: center; 643 | align-items: center; 644 | } 645 | 646 | .status-on-bar { 647 | width: 0.5rem; 648 | position: absolute; 649 | right: 0px; 650 | z-index: 20; 651 | border-radius: 9999px; 652 | margin: 0.25rem; 653 | opacity: 0.5; 654 | transition-property: all; 655 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 656 | transition-duration: 150ms; 657 | height: calc(100% - 0.5rem); 658 | --tw-bg-opacity: 1; 659 | background-color: rgba(24.0975, 154.402, 28.441, var(--tw-bg-opacity)); 660 | } 661 | 662 | .right-col-rooms { 663 | width: 3.5rem; 664 | position: absolute; 665 | right: 10px; 666 | z-index: 20; 667 | font-size: 1.3em; 668 | border-radius: 9999px; 669 | margin: 0.25rem; 670 | transition-property: all; 671 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 672 | transition-duration: 150ms; 673 | } 674 | 675 | .status-off-bar { 676 | width: 0.5rem; 677 | position: absolute; 678 | right: 0px; 679 | z-index: 20; 680 | border-radius: 9999px; 681 | margin: 0.25rem; 682 | opacity: 0.5; 683 | transition-property: all; 684 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 685 | transition-duration: 150ms; 686 | height: calc(100% - 0.5rem); 687 | --tw-bg-opacity: 1; 688 | background-color: rgba(225.038, 45.2625, 57.2475, var(--tw-bg-opacity)); 689 | } 690 | 691 | .account-avatar { 692 | height: 48px; 693 | width: 48px; 694 | overflow: visible !important; 695 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), 696 | var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 697 | } 698 | 699 | .con-grid1 { 700 | display: flex; 701 | -webkit-box-align: center; 702 | align-items: center; 703 | grid-column: span 12 / span 12; 704 | overflow: visible !important; 705 | } 706 | 707 | .account-text { 708 | font-size: 1.325rem; 709 | line-height: 1.75rem; 710 | overflow-wrap: break-word; 711 | margin: 10px; 712 | } 713 | 714 | .card-header { 715 | --tw-bg-opacity: 1 !important; 716 | background-color: var(--nav) !important; 717 | border-top-left-radius: 0.25rem !important; 718 | border-top-right-radius: 0.25rem !important; 719 | padding: 0.75rem !important; 720 | border-bottom-width: 1px !important; 721 | --tw-border-opacity: 1 !important; 722 | border-color: rgba(18.615, 25.5, 32.385, var(--tw-border-opacity)) !important; 723 | } 724 | 725 | .card-header-text { 726 | font-size: 0.875rem; 727 | line-height: 1.25rem; 728 | text-transform: uppercase; 729 | text-align: left; 730 | } 731 | 732 | .service-text { 733 | text-align: left; 734 | font-size: 0.85rem; 735 | line-height: 1rem; 736 | margin-bottom: -1rem; 737 | } 738 | 739 | #console-window-inner { 740 | overflow-y: scroll; 741 | max-height: 100%; 742 | width: 100%; 743 | } 744 | 745 | .controls { 746 | margin-left: 3px; 747 | margin-right: 3px; 748 | line-height: 1 !important; 749 | } 750 | 751 | .card-body-text { 752 | line-height: 0; 753 | margin-bottom: 15px; 754 | } 755 | 756 | .after-cbt { 757 | margin: 5px; 758 | --tw-text-opacity: 1; 759 | color: rgba(96.492, 109.211, 122.808, var(--tw-text-opacity)); 760 | } 761 | 762 | .mid { 763 | text-align: center; 764 | align-items: center; 765 | align-self: center; 766 | } 767 | 768 | p { 769 | --tw-text-opacity: 1; 770 | line-height: 1.375; 771 | font-family: Rubik, -apple-system, BlinkMacSystemFont, "Helvetica Neue", 772 | Roboto, system-ui, sans-serif; 773 | } 774 | 775 | h3 { 776 | --tw-text-opacity: 1; 777 | color: rgba(201.756, 209.1, 216.444, var(--tw-text-opacity)); 778 | line-height: 1.375; 779 | font-family: Rubik, -apple-system, BlinkMacSystemFont, "Helvetica Neue", 780 | Roboto, system-ui, sans-serif; 781 | } 782 | 783 | .card-header > button:hover { 784 | text-decoration: none; 785 | } 786 | 787 | .card-header > button:active { 788 | text-decoration: none; 789 | } 790 | 791 | .card-header > button:focus { 792 | text-decoration: none; 793 | } 794 | 795 | .category-card-row .card { 796 | transition: 0.45s; 797 | } 798 | 799 | .category-card-row .card:hover { 800 | box-shadow: 0 5px 10px 0 rgb(0 0 0 / 30%); 801 | } 802 | 803 | .text { 804 | font-size: 2em; 805 | } 806 | 807 | .more { 808 | text-align: center !important; 809 | } 810 | 811 | .row { 812 | justify-content: center !important; 813 | } 814 | 815 | .title-main { 816 | display: inline; 817 | vertical-align: middle; 818 | margin-left: 10px; 819 | } 820 | 821 | .status-on-circle { 822 | color: rgb(5, 153, 37); 823 | } 824 | 825 | .status-off-circle { 826 | color: rgb(225, 45, 57); 827 | } 828 | 829 | .about { 830 | background: rgb(20, 20, 20); 831 | padding: 20px; 832 | } 833 | 834 | .profile-right { 835 | float: right; 836 | margin: 25px; 837 | } 838 | 839 | .cookies { 840 | overflow: hidden; 841 | position: relative; 842 | text-align: center; 843 | width: auto !important; 844 | justify-content: center !important; 845 | box-shadow: 0px 5px #ff8800bd; 846 | background: #ff9216bd !important; 847 | } 848 | 849 | .cookies:active:after { 850 | opacity: 0; 851 | } 852 | 853 | /* width */ 854 | 855 | ::-webkit-scrollbar { 856 | width: 10px; 857 | } 858 | 859 | /* Handle */ 860 | 861 | ::-webkit-scrollbar-thumb { 862 | box-shadow: rgb(123 135 147) 0px 0px 0px 1px inset, 863 | rgb(63 77 90) 0px 0px 0px 4px inset; 864 | border-radius: 0.9rem; 865 | border: 1px solid black; 866 | } 867 | 868 | .cookies:after { 869 | animation: shine 4s ease-in-out infinite; 870 | animation-fill-mode: forwards; 871 | content: ""; 872 | position: absolute; 873 | top: -110%; 874 | left: -210%; 875 | width: 200%; 876 | height: 200%; 877 | opacity: 0; 878 | transform: rotate(30deg); 879 | background: rgba(255, 255, 255, 0.13); 880 | background: linear-gradient( 881 | to right, 882 | rgba(255, 255, 255, 0.13) 0%, 883 | rgba(255, 255, 255, 0.13) 77%, 884 | rgba(255, 255, 255, 0.5) 92%, 885 | rgba(255, 255, 255, 0) 100% 886 | ); 887 | } 888 | 889 | .cookies-count { 890 | font-size: 30px; 891 | } 892 | 893 | .cookies-count2 { 894 | font-size: 16px; 895 | } 896 | 897 | .number { 898 | padding: 15px; 899 | background: #383838b6; 900 | margin: 10px; 901 | text-align: center; 902 | width: 170px; 903 | border-radius: 5px; 904 | } 905 | 906 | .text-center { 907 | text-align: center; 908 | position: absolute; 909 | top: 50%; 910 | left: 50%; 911 | transform: translate(-50%, -50%); 912 | } 913 | 914 | .category-bottom { 915 | height: 60px; 916 | border-bottom-right-radius: 20px !important; 917 | border-bottom-left-radius: 20px !important; 918 | display: flex; 919 | justify-content: center; 920 | align-items: center; 921 | font-weight: 600 !important; 922 | font-size: large; 923 | color: #646464; 924 | } 925 | 926 | .category-bottom-creator { 927 | color: white; 928 | } 929 | 930 | .category { 931 | object-fit: cover; 932 | height: 220px; 933 | } 934 | 935 | .card-1:hover { 936 | box-shadow: inset 0px 33px 25px 0 rgba(15, 164, 250, 0.712), 937 | inset 0 66px 15px 0px rgba(255, 28, 244, 0.644), 938 | inset 0 99px 5px 0px rgba(138, 0, 252, 0.733); 939 | transform: scale(1.1); 940 | z-index: 999; 941 | } 942 | 943 | .card-img-top { 944 | border-radius: 1.5rem !important; 945 | filter: blur(1.5px); 946 | -webkit-filter: blur(1.5px); 947 | } 948 | 949 | .card-img-top:hover { 950 | filter: blur(0px); 951 | -webkit-filter: blur(0px); 952 | transition: 0.5s; 953 | } 954 | 955 | .category-countdown { 956 | color: white !important; 957 | border-radius: 5px; 958 | background-color: #db4040 !important; 959 | } 960 | 961 | .card-hover:hover { 962 | transform: scale(1.1); 963 | z-index: 999; 964 | transition: 0.7s; 965 | } 966 | 967 | .flip { 968 | transform: scaleY(-1); 969 | } 970 | 971 | .spacer { 972 | margin-bottom: 20px; 973 | } 974 | 975 | .card-a { 976 | padding: 20px; 977 | border: 0px; 978 | border-radius: 0.5rem !important; 979 | z-index: 1000; 980 | } 981 | 982 | .thing2 { 983 | font-weight: 500; 984 | letter-spacing: 1.25px; 985 | } 986 | 987 | .card .red-button { 988 | font-weight: 500; 989 | border-radius: 0px; 990 | letter-spacing: 1.25px; 991 | } 992 | 993 | @keyframes fadeIn { 994 | 0% { 995 | opacity: 0; 996 | } 997 | 100% { 998 | opacity: 1; 999 | } 1000 | } 1001 | 1002 | @-moz-keyframes fadeIn { 1003 | 0% { 1004 | opacity: 0; 1005 | } 1006 | 100% { 1007 | opacity: 1; 1008 | } 1009 | } 1010 | 1011 | @-webkit-keyframes fadeIn { 1012 | 0% { 1013 | opacity: 0; 1014 | } 1015 | 100% { 1016 | opacity: 1; 1017 | } 1018 | } 1019 | 1020 | @-o-keyframes fadeIn { 1021 | 0% { 1022 | opacity: 0; 1023 | } 1024 | 100% { 1025 | opacity: 1; 1026 | } 1027 | } 1028 | 1029 | @-ms-keyframes fadeIn { 1030 | 0% { 1031 | opacity: 0; 1032 | } 1033 | 100% { 1034 | opacity: 1; 1035 | } 1036 | } 1037 | 1038 | .title { 1039 | font-size: 1.5em; 1040 | text-shadow: 0px 1.4px 15px rgba(0, 0, 0, 0.775); 1041 | } 1042 | 1043 | .snow-lower { 1044 | top: 30vh; 1045 | bottom: unset; 1046 | position: absolute; 1047 | width: 100%; 1048 | height: 277px; 1049 | object-fit: cover; 1050 | object-position: left; 1051 | } 1052 | 1053 | .snow-lower-1 { 1054 | top: 1160px; 1055 | bottom: unset; 1056 | position: absolute; 1057 | width: 100%; 1058 | height: 277px; 1059 | object-fit: cover; 1060 | object-position: left; 1061 | } 1062 | 1063 | .navbar-brand { 1064 | height: 15% !important; 1065 | width: 5%; 1066 | } 1067 | 1068 | .badtag { 1069 | border: solid 1px red !important; 1070 | background-color: #d24a4a !important; 1071 | color: white !important; 1072 | } 1073 | 1074 | .badtag a { 1075 | color: #ad2b2b !important; 1076 | } 1077 | 1078 | .notyou { 1079 | font-size: 10px; 1080 | text-decoration-style: wavy; 1081 | position: absolute; 1082 | top: 15px; 1083 | color: gray; 1084 | } 1085 | 1086 | .blacklisted_save { 1087 | border: 1px solid rgba(0, 0, 0, 0.125); 1088 | margin-left: 5px; 1089 | color: white !important; 1090 | } 1091 | 1092 | .maw img { 1093 | position: absolute; 1094 | top: 0; 1095 | left: 0; 1096 | right: 0; 1097 | margin: auto; 1098 | animation-name: stretch; 1099 | animation-duration: 2s; 1100 | animation-timing-function: ease-out; 1101 | animation-direction: alternate; 1102 | animation-iteration-count: infinite; 1103 | animation-play-state: running; 1104 | } 1105 | 1106 | .bit { 1107 | margin-top: 30px; 1108 | } 1109 | 1110 | .text { 1111 | text-align: center; 1112 | } 1113 | 1114 | @keyframes stretch { 1115 | 0% { 1116 | transform: scale(0.7); 1117 | border-radius: 100%; 1118 | } 1119 | 100% { 1120 | transform: scale(0.9); 1121 | } 1122 | } 1123 | 1124 | .status.true:before { 1125 | background-color: #94e185; 1126 | border-color: #78d965; 1127 | box-shadow: 0px 0px 4px 1px #94e185; 1128 | } 1129 | 1130 | .status.false:before { 1131 | background-color: #fc6868; 1132 | border-color: #fc6868; 1133 | box-shadow: 0px 0px 4px 1px #e18885; 1134 | } 1135 | 1136 | .checked { 1137 | font-style: italic; 1138 | color: gray; 1139 | font-size: 13px; 1140 | } 1141 | 1142 | .status:before { 1143 | content: " "; 1144 | display: inline-block; 1145 | width: 7px; 1146 | height: 7px; 1147 | border: 1px solid #000; 1148 | border-radius: 7px; 1149 | margin-bottom: 1.5px; 1150 | } 1151 | 1152 | .status-text { 1153 | font-size: 13px; 1154 | font-style: normal; 1155 | } 1156 | 1157 | .nav-item { 1158 | padding: 0 15px; 1159 | border-radius: 15px; 1160 | margin: 0 10px; 1161 | background-color: rgb(66, 64, 64); 1162 | } 1163 | 1164 | .black { 1165 | color: #00000040; 1166 | } 1167 | 1168 | .white { 1169 | color: #ffffffda; 1170 | } 1171 | 1172 | nav { 1173 | background-color: var(--nav); 1174 | z-index: 9999; 1175 | } 1176 | 1177 | #dummy-target { 1178 | position: relative; 1179 | width: 600px; 1180 | height: 300px; 1181 | border-style: solid; 1182 | } 1183 | 1184 | .position-absolute { 1185 | position: absolute; 1186 | } 1187 | 1188 | .user_avatar { 1189 | border-radius: 100px; 1190 | margin-top: -200px; 1191 | height: 160px; 1192 | background-repeat: repeat-y; 1193 | margin: -200px 20px 0 10px; 1194 | object-fit: contain; 1195 | align-self: flex-start; 1196 | } 1197 | 1198 | .user_avatar_img { 1199 | -webkit-box-shadow: 0 0 5px rgb(31, 30, 30); 1200 | box-shadow: 0 0 5px rgb(31, 30, 30); 1201 | } 1202 | 1203 | .golden { 1204 | animation: glow 1s ease-in-out infinite alternate; 1205 | } 1206 | 1207 | .blue { 1208 | animation: blue 1s ease-in-out infinite alternate; 1209 | } 1210 | 1211 | .di { 1212 | background: rgba(15, 15, 15, 0.678); 1213 | border: 1px solid rgba(45, 45, 45, 0); 1214 | border-radius: 3px; 1215 | padding: 10px; 1216 | width: 410px; 1217 | margin: 30px auto 0 190px; 1218 | z-index: 999; 1219 | transform: scale(1.15); 1220 | } 1221 | 1222 | .grow, 1223 | .mainmenu-submenu { 1224 | transition: all 0.2s ease-in-out; 1225 | } 1226 | 1227 | .di:hover { 1228 | transition: all 0.2s ease-in-out; 1229 | transform: scale(1.18); 1230 | background: rgba(15, 15, 15, 0.788); 1231 | } 1232 | 1233 | .leaderboard-item { 1234 | width: 200px; 1235 | } 1236 | 1237 | .leaderboard-item-indiv { 1238 | margin-bottom: 5px; 1239 | padding: 0 0 20px 20px; 1240 | background: rgba(15, 15, 15, 0.678); 1241 | border: 1px solid rgba(45, 45, 45, 0); 1242 | border-radius: 6px; 1243 | vertical-align: middle; 1244 | } 1245 | 1246 | .leaderboard-item-avatar { 1247 | height: 50px; 1248 | border-radius: 50px; 1249 | display: flex; 1250 | flex-direction: column; 1251 | } 1252 | 1253 | .di-button { 1254 | align-items: center; 1255 | align-self: center; 1256 | background-color: #43b581; 1257 | border: none; 1258 | border-radius: 3px; 1259 | color: #fff; 1260 | cursor: pointer; 1261 | display: flex; 1262 | font-size: 14px; 1263 | font-weight: 500; 1264 | height: 40px; 1265 | justify-content: center; 1266 | line-height: 20px; 1267 | margin-left: 10px; 1268 | min-width: 90px; 1269 | outline: 0; 1270 | padding: 2px 0; 1271 | width: auto; 1272 | transition: background-color 0.17s ease, color 0.17s ease; 1273 | -webkit-user-select: none; 1274 | -moz-user-select: none; 1275 | -ms-user-select: none; 1276 | user-select: none; 1277 | position: relative; 1278 | top: -11px; 1279 | } 1280 | 1281 | .di-details div { 1282 | overflow: hidden; 1283 | text-overflow: ellipsis; 1284 | white-space: nowrap; 1285 | } 1286 | 1287 | .di-details { 1288 | align-items: stretch; 1289 | flex: 1 1 auto; 1290 | flex-direction: column; 1291 | flex-wrap: nowrap; 1292 | justify-content: center; 1293 | max-width: 223px; 1294 | } 1295 | 1296 | .di-flex { 1297 | display: flex; 1298 | } 1299 | 1300 | .di-d-details { 1301 | color: #72767d; 1302 | font-weight: 600; 1303 | font-size: 12px; 1304 | line-height: 16px; 1305 | } 1306 | 1307 | .di-d-details span { 1308 | margin-right: 8px; 1309 | outline: 0; 1310 | } 1311 | 1312 | .di-d-details i { 1313 | border-radius: 50%; 1314 | display: inline-block; 1315 | height: 8px; 1316 | margin-right: 4px; 1317 | width: 8px; 1318 | } 1319 | 1320 | .di-d-d-online { 1321 | background-color: #43b581; 1322 | } 1323 | 1324 | .di-d-details i { 1325 | border-radius: 50%; 1326 | display: inline-block; 1327 | height: 8px; 1328 | margin-right: 4px; 1329 | width: 8px; 1330 | } 1331 | 1332 | .di-d-d-offline { 1333 | background-color: #747f8d; 1334 | } 1335 | 1336 | .di-d-name { 1337 | color: #f6f6f6; 1338 | font-weight: 600; 1339 | font-size: 16px; 1340 | line-height: 20px; 1341 | margin-bottom: 4px; 1342 | } 1343 | 1344 | .di-icon { 1345 | background-clip: padding-box; 1346 | background-position: 50%; 1347 | background-size: 100% 100%; 1348 | border-radius: 15px; 1349 | flex: 0 0 auto; 1350 | height: 50px; 1351 | margin-right: 10px; 1352 | width: 50px; 1353 | } 1354 | 1355 | .di-title { 1356 | color: #8e9297; 1357 | font-weight: 600; 1358 | font-size: 12px; 1359 | line-height: 16px; 1360 | margin-top: 0; 1361 | margin-bottom: 8px; 1362 | overflow: hidden; 1363 | text-overflow: ellipsis; 1364 | text-transform: uppercase; 1365 | white-space: nowrap; 1366 | } 1367 | 1368 | .di-button { 1369 | color: #fff; 1370 | background-color: #3ca374; 1371 | } 1372 | 1373 | [data-tooltip]:before { 1374 | position: absolute; 1375 | content: attr(data-tooltip); 1376 | opacity: 0; 1377 | } 1378 | 1379 | [data-tooltip]:hover:before { 1380 | opacity: 1; 1381 | background: rgb(15, 15, 15); 1382 | border: 1px solid rgba(45, 45, 45, 0); 1383 | border-radius: 30px; 1384 | text-align: center; 1385 | margin-top: 35px; 1386 | padding-left: 10px; 1387 | padding-right: 10px; 1388 | vertical-align: middle; 1389 | } 1390 | 1391 | [data-tooltip]:not([data-tooltip-persistent]):before { 1392 | pointer-events: none; 1393 | } 1394 | 1395 | i { 1396 | font-style: normal; 1397 | } 1398 | 1399 | @keyframes shine { 1400 | 10% { 1401 | opacity: 1; 1402 | top: -30%; 1403 | left: 30%; 1404 | transition-property: left, top, opacity; 1405 | transition-duration: 0.7s, 0.7s, 0.15s; 1406 | transition-timing-function: ease; 1407 | } 1408 | 100% { 1409 | opacity: 0; 1410 | top: -30%; 1411 | left: 30%; 1412 | transition-property: left, top, opacity; 1413 | } 1414 | } 1415 | 1416 | .text { 1417 | text-shadow: 0px 1px 7px rgb(0, 0, 0); 1418 | } 1419 | 1420 | @media only screen and (max-width: 414px) { 1421 | .right-fixed-fullscreen { 1422 | display: none; 1423 | } 1424 | .di { 1425 | display: none; 1426 | } 1427 | } 1428 | 1429 | @media only screen and (max-width: 1331px) { 1430 | .di { 1431 | display: none; 1432 | } 1433 | } 1434 | 1435 | @keyframes glow { 1436 | from { 1437 | box-shadow: 0 0 2px #fff, 0 0 15px #fff, 0 0 5px #e2e600, 0 0 10px #e2e600, 1438 | 0 0 45px #e2e600, 0 0 5px #e2e600, 0 0 5px #e2e600; 1439 | } 1440 | to { 1441 | box-shadow: 0 0 5px #fff, 0 0 10px #edff4d, 0 0 35px #c4ff55, 1442 | 0 0 45px #d8ff4d, 0 0 55px #ffe44d, 0 0 5px #d8ff4d, 0 0 5px #deff4d; 1443 | } 1444 | } 1445 | 1446 | @keyframes blue { 1447 | from { 1448 | box-shadow: 0 0 2px #fff, 0 0 15px #fff, 0 0 5px #006be6, 0 0 10px #006be6, 1449 | 0 0 45px #006be6, 0 0 5px #006be6, 0 0 5px #006be6; 1450 | } 1451 | to { 1452 | box-shadow: 0 0 5px #fff, 0 0 10px #4ddbff, 0 0 35px #55c9ff, 1453 | 0 0 45px #55c9ff, 0 0 55px #55c9ff, 0 0 5px #55c9ff, 0 0 5px #55c9ff; 1454 | } 1455 | } 1456 | 1457 | .user_text { 1458 | display: inline; 1459 | position: absolute; 1460 | top: 250px; 1461 | margin-bottom: -360px; 1462 | font-size: 40px; 1463 | } 1464 | 1465 | .bio { 1466 | text-align: center; 1467 | vertical-align: middle; 1468 | margin-bottom: 30px; 1469 | } 1470 | 1471 | .badges { 1472 | display: flex; 1473 | padding: 0.5rem; 1474 | margin-left: 181px; 1475 | margin-top: -85px; 1476 | } 1477 | 1478 | .badge-a { 1479 | height: 26px; 1480 | margin: 1px; 1481 | } 1482 | 1483 | .badge-b { 1484 | height: 20px; 1485 | margin: 1px; 1486 | } 1487 | 1488 | .user_text_2 { 1489 | display: inline; 1490 | position: absolute; 1491 | top: 250px; 1492 | margin-bottom: -360px; 1493 | font-size: 40px; 1494 | } 1495 | 1496 | .user-container { 1497 | height: 70vh; 1498 | } 1499 | 1500 | .user_banner_img { 1501 | height: 40vh; 1502 | width: 100%; 1503 | box-shadow: 0px 2px 25px 0px rgba(0, 0, 0, 0.562); 1504 | -ms-interpolation-mode: bicubic; 1505 | } 1506 | 1507 | .user_banner { 1508 | min-height: 500px; 1509 | background-attachment: fixed; 1510 | background-position: center; 1511 | background-repeat: no-repeat; 1512 | background-size: cover; 1513 | } 1514 | 1515 | .tooltip { 1516 | position: relative; 1517 | display: inline-block; 1518 | border-bottom: 1px dotted black; 1519 | } 1520 | 1521 | .tooltip .tooltiptext { 1522 | visibility: hidden; 1523 | width: 120px; 1524 | background-color: black; 1525 | color: #fff; 1526 | text-align: center; 1527 | border-radius: 6px; 1528 | padding: 5px 0; 1529 | /* Position the tooltip */ 1530 | position: absolute; 1531 | z-index: 1; 1532 | top: 100%; 1533 | left: 50%; 1534 | margin-left: -60px; 1535 | } 1536 | 1537 | .tooltip:hover .tooltiptext { 1538 | visibility: visible; 1539 | } 1540 | 1541 | .pyro > .before, 1542 | .pyro > .after { 1543 | position: absolute; 1544 | width: 5px; 1545 | height: 5px; 1546 | border-radius: 50%; 1547 | box-shadow: -120px -218.66667px blue, 248px -16.66667px #00ff84, 1548 | 190px 16.33333px #002bff, -113px -308.66667px #ff009d, 1549 | -109px -287.66667px #ffb300, -50px -313.66667px #ff006e, 1550 | 226px -31.66667px #ff4000, 180px -351.66667px #ff00d0, 1551 | -12px -338.66667px #00f6ff, 220px -388.66667px #99ff00, 1552 | -69px -27.66667px #ff0400, -111px -339.66667px #6200ff, 1553 | 155px -237.66667px #00ddff, -152px -380.66667px #00ffd0, 1554 | -50px -37.66667px #00ffdd, -95px -175.66667px #a6ff00, 1555 | -88px 10.33333px #0d00ff, 112px -309.66667px #005eff, 1556 | 69px -415.66667px #ff00a6, 168px -100.66667px #ff004c, 1557 | -244px 24.33333px #ff6600, 97px -325.66667px #ff0066, 1558 | -211px -182.66667px #00ffa2, 236px -126.66667px #b700ff, 1559 | 140px -196.66667px #9000ff, 125px -175.66667px #00bbff, 1560 | 118px -381.66667px #ff002f, 144px -111.66667px #ffae00, 1561 | 36px -78.66667px #f600ff, -63px -196.66667px #c800ff, 1562 | -218px -227.66667px #d4ff00, -134px -377.66667px #ea00ff, 1563 | -36px -412.66667px #ff00d4, 209px -106.66667px #00fff2, 1564 | 91px -278.66667px #000dff, -22px -191.66667px #9dff00, 1565 | 139px -392.66667px #a6ff00, 56px -2.66667px #0099ff, 1566 | -156px -276.66667px #ea00ff, -163px -233.66667px #00fffb, 1567 | -238px -346.66667px #00ff73, 62px -363.66667px #0088ff, 1568 | 244px -170.66667px #0062ff, 224px -142.66667px #b300ff, 1569 | 141px -208.66667px #9000ff, 211px -285.66667px #ff6600, 1570 | 181px -128.66667px #1e00ff, 90px -123.66667px #c800ff, 1571 | 189px 70.33333px #00ffc8, -18px -383.66667px #00ff33, 1572 | 100px -6.66667px #ff008c; 1573 | -moz-animation: 1s bang ease-out infinite backwards, 1574 | 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; 1575 | -webkit-animation: 1s bang ease-out infinite backwards, 1576 | 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; 1577 | -o-animation: 1s bang ease-out infinite backwards, 1578 | 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; 1579 | -ms-animation: 1s bang ease-out infinite backwards, 1580 | 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; 1581 | animation: 1s bang ease-out infinite backwards, 1582 | 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; 1583 | } 1584 | 1585 | .pyro > .after { 1586 | -moz-animation-delay: 1.25s, 1.25s, 1.25s; 1587 | -webkit-animation-delay: 1.25s, 1.25s, 1.25s; 1588 | -o-animation-delay: 1.25s, 1.25s, 1.25s; 1589 | -ms-animation-delay: 1.25s, 1.25s, 1.25s; 1590 | animation-delay: 1.25s, 1.25s, 1.25s; 1591 | -moz-animation-duration: 1.25s, 1.25s, 6.25s; 1592 | -webkit-animation-duration: 1.25s, 1.25s, 6.25s; 1593 | -o-animation-duration: 1.25s, 1.25s, 6.25s; 1594 | -ms-animation-duration: 1.25s, 1.25s, 6.25s; 1595 | animation-duration: 1.25s, 1.25s, 6.25s; 1596 | } 1597 | 1598 | @-webkit-keyframes bang { 1599 | from { 1600 | box-shadow: 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1601 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1602 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1603 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1604 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1605 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1606 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1607 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1608 | 0 0 white, 0 0 white, 0 0 white; 1609 | } 1610 | } 1611 | 1612 | @-moz-keyframes bang { 1613 | from { 1614 | box-shadow: 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1615 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1616 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1617 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1618 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1619 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1620 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1621 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1622 | 0 0 white, 0 0 white, 0 0 white; 1623 | } 1624 | } 1625 | 1626 | @-o-keyframes bang { 1627 | from { 1628 | box-shadow: 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1629 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1630 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1631 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1632 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1633 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1634 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1635 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1636 | 0 0 white, 0 0 white, 0 0 white; 1637 | } 1638 | } 1639 | 1640 | @-ms-keyframes bang { 1641 | from { 1642 | box-shadow: 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1643 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1644 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1645 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1646 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1647 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1648 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1649 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1650 | 0 0 white, 0 0 white, 0 0 white; 1651 | } 1652 | } 1653 | 1654 | @keyframes bang { 1655 | from { 1656 | box-shadow: 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1657 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1658 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1659 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1660 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1661 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1662 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1663 | 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 1664 | 0 0 white, 0 0 white, 0 0 white; 1665 | } 1666 | } 1667 | 1668 | @-webkit-keyframes gravity { 1669 | to { 1670 | transform: translateY(200px); 1671 | -moz-transform: translateY(200px); 1672 | -webkit-transform: translateY(200px); 1673 | -o-transform: translateY(200px); 1674 | -ms-transform: translateY(200px); 1675 | opacity: 0; 1676 | } 1677 | } 1678 | 1679 | @-moz-keyframes gravity { 1680 | to { 1681 | transform: translateY(200px); 1682 | -moz-transform: translateY(200px); 1683 | -webkit-transform: translateY(200px); 1684 | -o-transform: translateY(200px); 1685 | -ms-transform: translateY(200px); 1686 | opacity: 0; 1687 | } 1688 | } 1689 | 1690 | @-o-keyframes gravity { 1691 | to { 1692 | transform: translateY(200px); 1693 | -moz-transform: translateY(200px); 1694 | -webkit-transform: translateY(200px); 1695 | -o-transform: translateY(200px); 1696 | -ms-transform: translateY(200px); 1697 | opacity: 0; 1698 | } 1699 | } 1700 | 1701 | @-ms-keyframes gravity { 1702 | to { 1703 | transform: translateY(200px); 1704 | -moz-transform: translateY(200px); 1705 | -webkit-transform: translateY(200px); 1706 | -o-transform: translateY(200px); 1707 | -ms-transform: translateY(200px); 1708 | opacity: 0; 1709 | } 1710 | } 1711 | 1712 | @keyframes gravity { 1713 | to { 1714 | transform: translateY(200px); 1715 | -moz-transform: translateY(200px); 1716 | -webkit-transform: translateY(200px); 1717 | -o-transform: translateY(200px); 1718 | -ms-transform: translateY(200px); 1719 | opacity: 0; 1720 | } 1721 | } 1722 | 1723 | @-webkit-keyframes position { 1724 | 0%, 1725 | 19.9% { 1726 | margin-top: 10%; 1727 | margin-left: 40%; 1728 | } 1729 | 20%, 1730 | 39.9% { 1731 | margin-top: 40%; 1732 | margin-left: 30%; 1733 | } 1734 | 40%, 1735 | 59.9% { 1736 | margin-top: 20%; 1737 | margin-left: 70%; 1738 | } 1739 | 60%, 1740 | 79.9% { 1741 | margin-top: 30%; 1742 | margin-left: 20%; 1743 | } 1744 | 80%, 1745 | 99.9% { 1746 | margin-top: 30%; 1747 | margin-left: 80%; 1748 | } 1749 | } 1750 | 1751 | @-moz-keyframes position { 1752 | 0%, 1753 | 19.9% { 1754 | margin-top: 10%; 1755 | margin-left: 40%; 1756 | } 1757 | 20%, 1758 | 39.9% { 1759 | margin-top: 40%; 1760 | margin-left: 30%; 1761 | } 1762 | 40%, 1763 | 59.9% { 1764 | margin-top: 20%; 1765 | margin-left: 70%; 1766 | } 1767 | 60%, 1768 | 79.9% { 1769 | margin-top: 30%; 1770 | margin-left: 20%; 1771 | } 1772 | 80%, 1773 | 99.9% { 1774 | margin-top: 30%; 1775 | margin-left: 80%; 1776 | } 1777 | } 1778 | 1779 | @-o-keyframes position { 1780 | 0%, 1781 | 19.9% { 1782 | margin-top: 10%; 1783 | margin-left: 40%; 1784 | } 1785 | 20%, 1786 | 39.9% { 1787 | margin-top: 40%; 1788 | margin-left: 30%; 1789 | } 1790 | 40%, 1791 | 59.9% { 1792 | margin-top: 20%; 1793 | margin-left: 70%; 1794 | } 1795 | 60%, 1796 | 79.9% { 1797 | margin-top: 30%; 1798 | margin-left: 20%; 1799 | } 1800 | 80%, 1801 | 99.9% { 1802 | margin-top: 30%; 1803 | margin-left: 80%; 1804 | } 1805 | } 1806 | 1807 | @-ms-keyframes position { 1808 | 0%, 1809 | 19.9% { 1810 | margin-top: 10%; 1811 | margin-left: 40%; 1812 | } 1813 | 20%, 1814 | 39.9% { 1815 | margin-top: 40%; 1816 | margin-left: 30%; 1817 | } 1818 | 40%, 1819 | 59.9% { 1820 | margin-top: 20%; 1821 | margin-left: 70%; 1822 | } 1823 | 60%, 1824 | 79.9% { 1825 | margin-top: 30%; 1826 | margin-left: 20%; 1827 | } 1828 | 80%, 1829 | 99.9% { 1830 | margin-top: 30%; 1831 | margin-left: 80%; 1832 | } 1833 | } 1834 | 1835 | @keyframes position { 1836 | 0%, 1837 | 19.9% { 1838 | margin-top: 10%; 1839 | margin-left: 40%; 1840 | } 1841 | 20%, 1842 | 39.9% { 1843 | margin-top: 40%; 1844 | margin-left: 30%; 1845 | } 1846 | 40%, 1847 | 59.9% { 1848 | margin-top: 20%; 1849 | margin-left: 70%; 1850 | } 1851 | 60%, 1852 | 79.9% { 1853 | margin-top: 30%; 1854 | margin-left: 20%; 1855 | } 1856 | 80%, 1857 | 99.9% { 1858 | margin-top: 30%; 1859 | margin-left: 80%; 1860 | } 1861 | } 1862 | -------------------------------------------------------------------------------- /src/public/js/bots.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | // Chart Setup 3 | 4 | var labels = []; 5 | var config = { 6 | type: 'line', 7 | data: { 8 | 9 | labels: labels, 10 | datasets: [{ 11 | label: 'Bots', 12 | backgroundColor: 'rgba(244, 5, 95, 0.55)', 13 | borderColor: '#f4055f', 14 | data: [], 15 | fill: true, 16 | lineTension: 0, 17 | }] 18 | }, 19 | options: { 20 | responsive: true, 21 | bezierCurve: false, 22 | tooltips: { 23 | mode: 'index', 24 | intersect: false, 25 | }, 26 | elements: { 27 | point: { 28 | radius: 0 29 | } 30 | }, 31 | animation: { 32 | duration: 0 33 | }, 34 | legend: { 35 | display: false 36 | }, 37 | hover: { 38 | mode: 'nearest', 39 | intersect: true 40 | }, 41 | tooltips: { 42 | callbacks: { 43 | title: function(t, d) { 44 | return "Time: "+d.labels[t[0].index]+" seconds"; 45 | } 46 | } 47 | }, 48 | 49 | scales: { 50 | xAxes: [{ 51 | display: true, 52 | scaleLabel: { 53 | display: true, 54 | labelString: 'Seconds', 55 | fontColor: '#cacaca' 56 | }, 57 | ticks: { 58 | fontColor: '#cacaca' 59 | } 60 | }], 61 | yAxes: [{ 62 | display: true, 63 | scaleLabel: { 64 | display: true, 65 | labelString: 'Online Bots', 66 | fontColor: '#cacaca' 67 | }, 68 | ticks: { 69 | suggestedMin: 0, 70 | suggestedMax: 20, 71 | fontColor: '#cacaca' 72 | } 73 | }] 74 | } 75 | } 76 | }; 77 | 78 | var ctx = document.getElementById('botsChart').getContext('2d'); 79 | window.chart = new Chart(ctx, config); 80 | 81 | // Rooms 82 | var labelsRooms = []; 83 | var config = { 84 | type: 'line', 85 | data: { 86 | labels: labelsRooms, 87 | datasets: [{ 88 | label: 'Rooms', 89 | backgroundColor: 'rgba(244, 5, 95, 0.55)', 90 | borderColor: '#f4055f', 91 | data: [], 92 | fill: true, 93 | lineTension: 0, 94 | }] 95 | }, 96 | options: { 97 | responsive: true, 98 | bezierCurve: false, 99 | tooltips: { 100 | mode: 'index', 101 | intersect: false, 102 | }, 103 | elements: { 104 | point: { 105 | radius: 0 106 | } 107 | }, 108 | animation: { 109 | duration: 0 110 | }, 111 | legend: { 112 | display: false 113 | }, 114 | hover: { 115 | mode: 'nearest', 116 | intersect: true 117 | }, 118 | tooltips: { 119 | callbacks: { 120 | title: function(t, d) { 121 | return "Time: "+d.labels[t[0].index]+" seconds"; 122 | } 123 | } 124 | }, 125 | scales: { 126 | xAxes: [{ 127 | display: true, 128 | scaleLabel: { 129 | display: true, 130 | labelString: 'Seconds', 131 | fontColor: '#cacaca' 132 | }, 133 | ticks: { 134 | fontColor: '#cacaca' 135 | } 136 | }], 137 | yAxes: [{ 138 | display: true, 139 | scaleLabel: { 140 | display: true, 141 | labelString: 'Public Rooms', 142 | fontColor: '#cacaca' 143 | }, 144 | ticks: { 145 | suggestedMin: 0, 146 | suggestedMax: 10, 147 | fontColor: '#cacaca' 148 | } 149 | }] 150 | } 151 | } 152 | }; 153 | 154 | var ctxRooms = document.getElementById('roomBotsChart').getContext('2d'); 155 | window.chartRooms = new Chart(ctxRooms, config); 156 | 157 | 158 | 159 | function update() { 160 | $.ajax({ 161 | url: '/api/statistics', 162 | success: (payload) => { 163 | console.log(payload) 164 | if (payload.totalBots == 1) { 165 | document.getElementById('botsOnline').innerHTML = payload.totalBots + ' Bot Online'; 166 | } else if (payload.totalBots == 0) { 167 | document.getElementById('botsOnline').innerHTML = 'No bots online :('; 168 | } else { 169 | document.getElementById('botsOnline').innerHTML = payload.totalBots + ' Bots Online'; 170 | } 171 | // Set start date for client 172 | if (window.chartConfig == undefined) { 173 | window.chartConfig = { 174 | 'start' : new Date().valueOf(), 175 | 'step' : 0, 176 | 'limit' : 100 177 | } 178 | }; 179 | 180 | // User Acitivty Chart 181 | var time = window.chartConfig.step * 5; 182 | window.chart.config.data.labels.push(time); 183 | window.chartConfig.step++; 184 | window.chart.config.data.datasets.forEach(function (dataset) { 185 | dataset.data.push(payload.totalBots); 186 | }); 187 | // Check if datapoints need to be removed 188 | // if (window.chart.data.datasets[0].data.length > 20) { 189 | // // window.chart.data.datasets[0].data = window.chart.data.datasets[0].data.slice(1); 190 | // // window.chart.data.labels = window.chart.data.labels.slice(1); 191 | // } 192 | // window.chart.options.scales.xAxes[0].scaleLabel.labelString = "Minutes" 193 | // window.chartRooms.options.scales.xAxes[0].scaleLabel.labelString = "Minutes" 194 | if (window.chart.data.datasets[0].data.length >= window.chartConfig.limit) { 195 | window.chart.data.datasets[0].data = window.chart.data.datasets[0].data.slice(1); 196 | window.chart.data.labels = window.chart.data.labels.slice(1); 197 | window.chartRooms.data.datasets[0].data = window.chartRooms.data.datasets[0].data.slice(1); 198 | window.chartRooms.data.labels = window.chartRooms.data.labels.slice(1); 199 | } 200 | 201 | // Room Acitivty Chart 202 | window.chartRooms.config.data.labels.push(time); 203 | 204 | window.chartRooms.config.data.datasets.forEach(function (dataset) { 205 | dataset.data.push(payload.totalRooms); 206 | }); 207 | window.chart.update(); 208 | window.chartRooms.update(); 209 | 210 | } 211 | }); 212 | } 213 | 214 | update(); 215 | setInterval(update, 5000); 216 | }); -------------------------------------------------------------------------------- /src/public/js/home.js: -------------------------------------------------------------------------------- 1 | let lookup = ['Live', '24h', 'Week', 'Month', 'All Time']; 2 | let validOptions = ['Show Average','Show Minimum','Show Maximum']; 3 | let globalVersion; 4 | 5 | $('.dropdown-menu.keep-open').on('click', function (e) { 6 | e.stopPropagation(); 7 | }); 8 | 9 | window.onload = function () { 10 | var loadTime = window.performance.timing.domContentLoadedEventEnd-window.performance.timing.navigationStart; 11 | console.log('Page load time is '+ loadTime); 12 | document.getElementById('loadTime').innerHTML = ' ' + loadTime + 'ms'; 13 | // Set start date for client 14 | if (window.chartConfig == undefined) { 15 | window.chartConfig = { 16 | 'start': new Date().valueOf(), 17 | 'step': 0, 18 | 'limit': 100, 19 | 'version' : globalVersion, 20 | 'options' : { 21 | 'ave' : true, 22 | 'min' : false, 23 | 'max' : false, 24 | 'aveR' : true, 25 | 'minR' : false, 26 | 'maxR' : false 27 | } 28 | } 29 | }; 30 | saveSettings(window.chartConfig); 31 | } 32 | 33 | function compare(previousVersion, currentVersion){ 34 | var previousVerArr = previousVersion.split('.').map(Number); 35 | var currentVerArr = currentVersion.split('.').map(Number); 36 | 37 | if (currentVerArr[0] > previousVerArr[0]) { 38 | return "major"; 39 | } else if (currentVerArr[0] == previousVerArr[0] && currentVerArr[1] > previousVerArr[1]) { 40 | return "minor"; 41 | } else if (currentVerArr[0] == previousVerArr[0] && currentVerArr[1] == previousVerArr[1] && currentVerArr[2] > previousVerArr[2]) { 42 | return "patch"; 43 | } else { 44 | return "none"; 45 | }; 46 | }; 47 | 48 | function saveSettings(settings) { 49 | if (localStorage && typeof settings == 'object') localStorage.setItem('dhSettings', JSON.stringify(settings)); 50 | }; 51 | 52 | function toggleFullScreen() { 53 | if (!document.fullscreenElement) { 54 | document.documentElement.requestFullscreen(); 55 | $(".fsl").addClass('min-w'); 56 | 57 | } else { 58 | if (document.exitFullscreen) { 59 | document.exitFullscreen(); 60 | $(".fsl").removeClass('min-w'); 61 | 62 | } 63 | } 64 | } 65 | 66 | function readDropDownSettings() { 67 | document.getElementById('dropOptAve').checked = window.chartConfig.options.ave; 68 | document.getElementById('dropOptMin').checked = window.chartConfig.options.min; 69 | document.getElementById('dropOptMax').checked = window.chartConfig.options.max; 70 | document.getElementById('dropOptAveR').checked = window.chartConfig.options.aveR; 71 | document.getElementById('dropOptMinR').checked = window.chartConfig.options.minR; 72 | document.getElementById('dropOptMaxR').checked = window.chartConfig.options.maxR; 73 | // Room Chart 74 | } 75 | 76 | function verReload() { 77 | window.chartConfig.version = globalVersion; 78 | saveSettings(window.chartConfig); 79 | location.reload(true); 80 | } 81 | 82 | function dropdownUpdate(element) { 83 | document.getElementById(element.parentElement.getAttribute('aria-labelledby')).innerText = element.innerText; 84 | changeDataset(((element.parentElement.getAttribute('aria-labelledby') == 'userActivityChartTimeframe') ? 'statsChart' : 'roomsChart'), element.innerText) 85 | } 86 | 87 | function optionUpdate(element) { 88 | if (validOptions.indexOf(element.innerText.trim()) == -1) { 89 | console.log(`ERROR: Received: ${element.innerText}`); 90 | } else { 91 | // console.log(element.parentElement.parentElement.parentElement.children[0].innerText.trim()) 92 | // console.log(chartConfig.options["_"+element.parentElement.parentElement.parentElement.children[0].innerText.trim()]) 93 | let canvasID = 'statsChart'; 94 | let isRoomCheck = ''; 95 | if (element.parentElement.getAttribute('aria-labelledby') == 'roomActivityChartOptions') { 96 | canvasID = 'roomsChart'; 97 | isRoomCheck = 'R'; 98 | } 99 | let configIndex = validOptions.indexOf(element.innerText.trim()); 100 | let miniOpt = validOptions[configIndex].slice(5,8)+isRoomCheck; 101 | // console.log(`MiniOpt: ${miniOpt} | miniOptD: dropOpt${miniOpt}`) 102 | window.chartConfig.options[miniOpt.charAt(0).toLowerCase() + miniOpt.slice(1)] = document.getElementById("dropOpt"+miniOpt).checked; 103 | // Add or remove 104 | let dataName = ``; 105 | if (canvasID == 'statsChart') { 106 | dataName = 'totalOnline' 107 | metaIndex = 0; 108 | curChart = window.chart; 109 | } else { 110 | dataName = 'totalRooms' 111 | metaIndex = 1; 112 | curChart = window.chartRooms; 113 | } 114 | let isHidden = !(document.getElementById("dropOpt"+miniOpt).checked); 115 | curChart.data.datasets[configIndex]._meta[metaIndex].hidden = isHidden; 116 | saveSettings(window.chartConfig); 117 | curChart.update(); 118 | } 119 | } 120 | 121 | function changeDataset(canvasID, value) { 122 | // console.log(`Canvas ${canvasID} | value ${value}`) 123 | let curChart; 124 | let lookupIndex = lookup.indexOf(value); 125 | let roomCheck = ''; 126 | if (canvasID == 'statsChart') { 127 | curChart = window.chart; 128 | liveDataName = 'totalOnline'; 129 | } else { 130 | curChart = window.chartRooms; 131 | liveDataName = 'totalRooms'; 132 | roomCheck = 'R'; 133 | } 134 | // Update chart 135 | // What data do we want to update 136 | if (lookupIndex != -1) { 137 | // (new Date).toLocaleTimeString() 138 | curChart.options.scales.xAxes[0].scaleLabel.labelString = "Time"; 139 | if (lookupIndex > 0) { 140 | curChart.data.datasets[0].data = statsConfig[lookupIndex].map(({ [`ave${roomCheck}`]: val }) => val); 141 | curChart.data.datasets[1].data = statsConfig[lookupIndex].map(({ [`min${roomCheck}`]: val }) => val); 142 | curChart.data.datasets[2].data = statsConfig[lookupIndex].map(({ [`max${roomCheck}`]: val }) => val); 143 | } else { 144 | curChart.data.datasets[0].data = statsConfig[lookupIndex].map(({ [liveDataName]: val }) => val); 145 | curChart.data.datasets[1].data = []; 146 | curChart.data.datasets[2].data = []; 147 | } 148 | } 149 | 150 | if (lookupIndex == 0) { 151 | // Live 152 | curChart.data.labels = statsConfig[0].map(({ [`statsTime`]: val }) => val); 153 | curChart.options.scales.xAxes[0].scaleLabel.labelString = "Seconds"; 154 | curChart.options.tooltips.callbacks.title = function(t, d) { 155 | return "Time: "+d.labels[t[0].index]+" seconds"; 156 | } 157 | // window.chartConfig.step = 0; 158 | // Consider storing this data in the background 159 | } else if (lookupIndex == 1) { 160 | // 24h 161 | // Slice as "2021-04-18-02" is given to get day, then add hours 162 | curChart.data.labels = statsConfig[1].map(({ [`queryDay`]: val }) => parseInt(val.slice(-2))); 163 | curChart.options.tooltips.callbacks.title = function(t, d) { 164 | return "Time: "+d.labels[t[0].index]+" hours"; 165 | } 166 | } else if (lookupIndex == 2) { 167 | // Weeks 168 | let days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; 169 | curChart.data.labels = statsConfig[2].map(({ [`queryDay`]: val }) => days[new Date(val).getDay()]); 170 | curChart.options.tooltips.callbacks.title = function(t, d) { 171 | return "Time: "+d.labels[t[0].index]; 172 | } 173 | } else if (lookupIndex == 3) { 174 | // Month 175 | curChart.data.labels = statsConfig[3].map(({ [`queryDay`]: val }) => new Date(val).toLocaleDateString()); 176 | curChart.options.tooltips.callbacks.title = function(t, d) { 177 | return "Time: "+d.labels[t[0].index]; 178 | } 179 | } else if (lookupIndex == 4) { 180 | // All Time 181 | // Store start date as value and work out day, week, month, year 182 | curChart.data.labels = statsConfig[4].map(({ [`queryDay`]: val }) => new Date(val).toLocaleDateString()); 183 | curChart.options.tooltips.callbacks.title = function(t, d) { 184 | return "Time: "+d.labels[t[0].index]; 185 | } 186 | } else { 187 | console.log(`Error | lookupIndex ${lookupIndex} | Input: canvasID ${canvasID} , value ${value}`); 188 | } 189 | curChart.update(); 190 | } 191 | 192 | $(document).ready(function () { 193 | 194 | // Patreon Update 195 | // Update every now and then, patreon oauth kind of a pain to work with. May change to github sponsorships 196 | const users = ['Sean McGinty']; 197 | const activeUser = users[Math.floor(Math.random() * users.length)] 198 | $('#sponsor-main').text(activeUser); 199 | $('#sponsor-sub').text(activeUser); 200 | 201 | // Load settings from localStorage 202 | if (localStorage) { 203 | var savedExploitSettings = localStorage.getItem('dhSettings'); 204 | if (savedExploitSettings != null) { 205 | window.chartConfig = JSON.parse(localStorage.getItem('dhSettings')); 206 | window.chartConfig.start = new Date().valueOf(); 207 | window.chartConfig.step = 0; 208 | } 209 | } 210 | 211 | // Get version info 212 | $.ajax({ 213 | url: '/api/version?dogestats', 214 | success: (payload) => { 215 | globalVersion = payload.version; 216 | if (localStorage) { 217 | if (savedExploitSettings != null) { 218 | // Check version 219 | if (window.chartConfig != null) { 220 | if (window.chartConfig.version != globalVersion) { 221 | // Show message to update / reload page. 222 | // location.reload(true); 223 | updateType = compare(window.chartConfig.version, globalVersion); 224 | console.log(`Latest Version: ${globalVersion} | Current Version: ${window.chartConfig.version} | UpdateType: ${updateType}`); 225 | document.getElementById("ver-alert").classList.remove("show"); 226 | } 227 | } 228 | } 229 | } 230 | } 231 | }) 232 | 233 | // [ [ live ], [ 24h - average ], [ week - average ], [ month - average ], [ alltime - average ], [ 24h - min ], [ 24h - max ], [ week - min ], [ week - max ], [ month - min ], [ month - max ], [ alltime - min ], [ alltime - max ] ] 234 | // window.statsConfig = [[],[],[],[],[],[],[],[],[],[],[],[],[]]; 235 | window.statsConfig = [[],[],[],[],[]]; 236 | 237 | function getLongTermData() { 238 | $.ajax({ 239 | url: '/api/mysql?time=24h', 240 | success: (payload) => { 241 | statsConfig[1] = payload; 242 | 243 | // Setup 244 | chart.data.datasets[0]._meta[0].hidden = !window.chartConfig.options.ave; 245 | chart.data.datasets[1]._meta[0].hidden = !window.chartConfig.options.min; 246 | chart.data.datasets[2]._meta[0].hidden = !window.chartConfig.options.max; 247 | chartRooms.data.datasets[0]._meta[1].hidden = !window.chartConfig.options.aveR; 248 | chartRooms.data.datasets[1]._meta[1].hidden = !window.chartConfig.options.minR; 249 | chartRooms.data.datasets[2]._meta[1].hidden = !window.chartConfig.options.maxR; 250 | changeDataset('statsChart', '24h'); 251 | changeDataset('roomsChart', '24h'); 252 | } 253 | }) 254 | $.ajax({ 255 | url: '/api/mysql?time=week', 256 | success: (payload) => { 257 | statsConfig[2] = payload; 258 | } 259 | }) 260 | $.ajax({ 261 | url: '/api/mysql?time=month', 262 | success: (payload) => { 263 | statsConfig[3] = payload; 264 | } 265 | }) 266 | $.ajax({ 267 | url: '/api/mysql?time=alltime', 268 | success: (payload) => { 269 | statsConfig[4] = payload; 270 | } 271 | }) 272 | } 273 | 274 | getLongTermData(); 275 | 276 | function update() { 277 | $.ajax({ 278 | url: '/api/statistics', 279 | success: (payload) => { 280 | // console.log(payload) 281 | // Total Rooms 282 | document.getElementById('roomCount').innerHTML = payload.totalRooms; 283 | document.getElementById('roomFix').innerText = (payload.totalRooms == 1) ? '' : 's'; 284 | // Total Online People in all Rooms 285 | document.getElementById('userCount').innerHTML = payload.totalOnline; 286 | // Scheduled Rooms Count 287 | document.getElementById('scheduledCount').innerHTML = payload.totalScheduled; 288 | document.getElementById('scheduledFix').innerText = (payload.totalScheduled == 1) ? '' : 's'; 289 | // Largest Room Name 290 | document.getElementById('topRoomName').innerHTML = payload.topRoom.name; 291 | document.getElementById('topUserFix').innerText = (payload.topRoom.listeners == 1) ? '' : 's'; 292 | // Largest Room Listners 293 | document.getElementById('topUserCount').innerHTML = payload.topRoom.listeners; 294 | // Longest Room 295 | document.getElementById('longestRoom').innerHTML = payload.longestRoom.name; 296 | document.getElementById('longestUserCount').innerHTML = payload.longestRoom.listeners; 297 | document.getElementById('longestUserFix').innerText = (payload.longestRoom.listeners == 1) ? '' : 's'; 298 | // Newest Room 299 | document.getElementById('newestRoom').innerHTML = payload.newestRoom.name; 300 | document.getElementById('newestUserCount').innerHTML = payload.newestRoom.listeners; 301 | document.getElementById('newestUserFix').innerText = (payload.newestRoom.listeners == 1) ? '' : 's'; 302 | document.getElementById('botsProvidingTelem').innerHTML = payload.totalBotsSendingTelemetry + '/' + payload.totalBotsOnline + ' bots providing telemetry via dogehouse.js & dogehouse.py' 303 | 304 | if (payload.totalBotsOnline == 1) { 305 | document.getElementById('botsOnline').innerHTML = payload.totalBotsOnline + ' Bot Online'; 306 | } else if (payload.totalBotsOnline == 0) { 307 | document.getElementById('botsOnline').innerHTML = 'No bots online :('; 308 | } else { 309 | document.getElementById('botsOnline').innerHTML = payload.totalBotsOnline + ' Bots Online'; 310 | } 311 | 312 | 313 | // Get room status 314 | function getStatus(element, payloadCreation) { 315 | 316 | let currentTime = new Date().valueOf() 317 | // let serverOffset = (1000*60*60*-12) 318 | let roomCreation = new Date(payloadCreation).valueOf(); 319 | // let timeDiff = (currentTime - roomCreation - serverOffset) / (1000 * 60) ; // minutes 320 | let timeDiff = (currentTime - roomCreation) / (1000 * 60); // minutes 321 | 322 | // let timeDiffHours = timeDiff / 60 323 | // let shortenedText = ~~timeDiffHours; 324 | 325 | // shortenedText === 24 ? shortenedText = 0 : console.log(shortenedText) 326 | 327 | // let minutes = ~~((timeDiffHours - ~~timeDiffHours) * 60) 328 | // let days = 0; 329 | // if (timeDiffHours > 23) { 330 | // days = ~~(timeDiff / 60 / 24); 331 | // } 332 | // let hours = shortenedText - days * 24; 333 | 334 | let days = ~~(timeDiff / 60 / 24) 335 | let minutes = ~~(timeDiff % 60) 336 | let hours = ~~(timeDiff / 60 % 24) 337 | 338 | function changeText(text = 'Generating...') { 339 | 340 | element.innerHTML = text + " (" + (days === 0 ? "" : "Days: " + days + " | ") + "Hours: " + hours + " | Minutes: " + minutes + ")"; 341 | 342 | // shortened text is 217 343 | 344 | } 345 | 346 | switch (true) { 347 | 348 | case (timeDiff < 30): { 349 | changeText("⛽️ Fueling Rocket") 350 | break; 351 | } 352 | case (timeDiff < 60): { 353 | changeText("🚀 Taking Off") 354 | break; 355 | } 356 | case (timeDiff < 240): { 357 | changeText("🚀✨ In Space") 358 | break; 359 | } 360 | case (timeDiff < 480): { 361 | changeText("🚀🌕 Approaching Moon") 362 | break; 363 | } 364 | case (timeDiff < 1440): { 365 | changeText("🌕🐕 Lunar Doge") 366 | break; 367 | } 368 | case (timeDiff < 2880): { 369 | changeText("🚀☀️ Approaching Sun") 370 | break; 371 | } 372 | case (timeDiff < 5760): { 373 | changeText("☀️🐕 Solar Doge") 374 | break; 375 | } 376 | case (timeDiff < 11520): { 377 | changeText("🚀🌌 Approaching Galaxy") 378 | break; 379 | } 380 | case (timeDiff < 23040): { 381 | changeText("🌌🐕 Galatic Doge") 382 | break; 383 | } 384 | case (timeDiff < 23041): { 385 | changeText("🪐👾 Spotted Life") 386 | break; 387 | } 388 | } 389 | 390 | } 391 | getStatus(document.getElementById('timeOnline'), payload.topRoom.created_at) 392 | getStatus(document.getElementById('newestTimeOnline'), payload.newestRoom.created_at) 393 | getStatus(document.getElementById('longestTimeOnline'), payload.longestRoom.created_at) 394 | 395 | // Read settings 396 | readDropDownSettings(); 397 | 398 | // Stats config 399 | statsConfig[0].push({ 400 | 'totalRooms' : payload.totalRooms, 401 | 'totalOnline' : payload.totalOnline, 402 | 'statsTime' : window.chartConfig.step * 5 403 | }); 404 | 405 | window.chartConfig.step++; 406 | var time = window.chartConfig.step * 5; 407 | 408 | // Check for removal 409 | if (window.chart.data.datasets[0].data.length >= window.chartConfig.limit) { 410 | window.chart.data.datasets[0].data = window.chart.data.datasets[0].data.slice(1); 411 | } 412 | if (statsConfig.length >= window.chartConfig.limit) { 413 | statsConfig[0] = statsConfig[0].slice(1); 414 | } 415 | 416 | // User Acitivty Chart 417 | changeDataset('statsChart',document.getElementById("userActivityChartTimeframe").innerText.trim()) 418 | 419 | // Room Acitivty Chart 420 | changeDataset('roomsChart',document.getElementById("roomActivityChartTimeframe").innerText.trim()) 421 | 422 | // Update 423 | window.chart.update(); 424 | window.chartRooms.update(); 425 | 426 | var userRoomChart = document.getElementById("botuserChart"); 427 | var userRoomData = { 428 | labels: [ 429 | "Users Online", 430 | "Bots Online" 431 | ], 432 | datasets: [ 433 | 434 | { 435 | borderColor: false, 436 | data: [payload.totalOnline - payload.totalBotsOnline, payload.totalBotsOnline], 437 | backgroundColor: [ 438 | "rgba(244, 5, 95, 0.55)", 439 | "rgba(5, 89, 244, 0.55)", 440 | "rgba(244, 140, 5, 0.55)" 441 | ] 442 | }] 443 | }; 444 | 445 | let UserBotChart = new Chart(userRoomChart, { 446 | type: 'doughnut', 447 | data: userRoomData, 448 | options: { 449 | animation: false, 450 | legend: { 451 | color: '#cacaca' 452 | }, 453 | defaultFontColor: '#000' 454 | } 455 | }); 456 | 457 | // rooms / users 458 | var userRoomChart = document.getElementById("userroomChart"); 459 | var userRoomData = { 460 | labels: [ 461 | "Users Online", 462 | "Rooms", 463 | "Bots Online" 464 | ], 465 | datasets: [ 466 | 467 | { 468 | borderColor: false, 469 | data: [payload.totalOnline - payload.totalBotsOnline, payload.totalRooms, payload.totalBotsOnline], 470 | backgroundColor: [ 471 | "rgba(244, 5, 95, 0.55)", 472 | "rgba(5, 89, 244, 0.55)", 473 | "rgba(244, 140, 5, 0.55)" 474 | ] 475 | }] 476 | }; 477 | 478 | let UserRoomDoughnutChart = new Chart(userRoomChart, { 479 | type: 'doughnut', 480 | data: userRoomData, 481 | options: { 482 | segmentShowStroke: false, 483 | animation: false, 484 | legend: { 485 | color: '#cacaca' 486 | }, 487 | } 488 | }); 489 | 490 | } 491 | }); 492 | $.ajax({ 493 | url: '/api/bots', 494 | success: (payload) => { 495 | if (payload.bots != undefined) { 496 | // console.log(payload) 497 | let uniqueBots = []; 498 | for (let i = 0; i < payload.bots.length; i++) { 499 | if (payload.bots[i].bot != undefined) { 500 | uniqueBots.push(payload.bots[i]); 501 | } 502 | } 503 | // console.log(uniqueBots) 504 | if (document.getElementsByClassName("uuid").length > uniqueBots.length) { 505 | // Remove all for now 506 | document.getElementById('testBots').innerHTML = ""; 507 | } 508 | for (i = 0; i < uniqueBots.length; i++) { 509 | var botExists = false; 510 | if (document.getElementById("testBots").innerText.indexOf(uniqueBots[i].bot.uuid) == -1) { 511 | document.getElementById('testBots').innerHTML += 512 | ` 513 |
514 |
515 |
516 | 517 | 523 | 524 |
525 | ${(uniqueBots[i].room.listening == null) ? 0 : uniqueBots[i].room.listening} 526 |
527 |
528 |

529 |
530 |
531 |

` 532 | } 533 | for (let j = 0; j < uniqueBots.length; j++) { 534 | if (document.getElementsByClassName("uuid")[j] != undefined) { 535 | if (uniqueBots[i].bot.uuid == document.getElementsByClassName("uuid")[j].innerText) { 536 | botExists = true; 537 | document.getElementsByClassName("uuid")[j].parentElement.parentElement.children[2].innerHTML = ` ` + ((uniqueBots[i].room.listening == "No Room") ? 0 : uniqueBots[i].room.listening); 538 | document.getElementsByClassName("uuid")[j].parentElement.children[2].innerText = (uniqueBots[i].room.name == null) ? "" : uniqueBots[i].room.name; 539 | // document.getElementsByClassName("uuid")[j].parentElement.parentElement.children[0].src = uniqueBots[i].bot.avatar; 540 | } 541 | } 542 | } 543 | if (i == (uniqueBots.length - 1) && botExists == false) { 544 | document.getElementById('testBots').children[i].remove(); 545 | document.getElementById('testBots').children[i].remove(); 546 | } 547 | } 548 | } 549 | } 550 | }) 551 | } 552 | 553 | function checkAPIStatus() { 554 | try { 555 | $.ajax({ 556 | url: 'https://api.dogegarden.net', 557 | timeout: 1500, 558 | error: function() { 559 | $("#api-alert").removeClass('show'); 560 | }, 561 | }) 562 | } catch(err) { 563 | //no 1 cares about err so we ignore it :) 564 | } 565 | } 566 | checkAPIStatus() 567 | update(); 568 | setInterval(update, 5000); 569 | setInterval(checkAPIStatus, 10000); 570 | }); 571 | -------------------------------------------------------------------------------- /src/public/js/rooms.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | function update() { 3 | $.ajax({ 4 | url: '/api/rooms', 5 | success: (payload) => { 6 | let rooms = payload.rooms 7 | console.log(rooms) 8 | document.getElementById("roomCount").innerText = rooms.length; 9 | for (i=0; i 14 |
15 |
16 |
17 | 18 | 22 |
23 | 24 | ${rooms[i].numPeopleInside} 25 |
26 |
27 |

28 |
29 |
30 |
` 31 | } 32 | document.getElementsByClassName("right-col-rooms")[i].innerHTML = ` `+rooms[i].numPeopleInside; 33 | if (rooms.some(rooms => rooms.id == document.getElementsByClassName("account-text")[i].children[1].innerText)) { 34 | // Room Exists 35 | console.log(`Valid Room at Index ${i}`) 36 | } else { 37 | // Remove Room 38 | // Not working? 39 | console.log(`Remove ${i}`) 40 | // document.getElementsByClassName("accounts-wrapper")[0].children[i*2-1].remove(); 41 | // document.getElementsByClassName("accounts-wrapper")[0].children[i*2-1].remove(); 42 | } 43 | }; 44 | } 45 | }); 46 | } 47 | 48 | update(); 49 | setInterval(update, 10000); 50 | }); 51 | -------------------------------------------------------------------------------- /src/public/js/statistics.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | // User Activity 3 | 4 | var labels = []; 5 | var config = { 6 | type: 'line', 7 | 8 | data: { 9 | 10 | labels: labels, 11 | datasets: [{ 12 | label: 'Average Users', 13 | backgroundColor: 'rgba(244, 5, 95, 0.55)', 14 | borderColor: '#f4055f', 15 | data: [], 16 | fill: false, 17 | lineTension: 0, 18 | }, 19 | { 20 | label: 'Minimum Users', 21 | fill: false, 22 | backgroundColor: 'rgba(5, 89, 244, 0.55)', 23 | borderColor: 'rgba(5, 89, 244, 0.55)', 24 | borderDash: [5, 5], 25 | data: [], 26 | lineTension: 0, 27 | 28 | }, 29 | { 30 | label: 'Maximum Users', 31 | fill: false, 32 | backgroundColor: 'rgba(244, 140, 5, 0.55)', 33 | borderColor: 'rgba(244, 140, 5, 0.55)', 34 | borderDash: [5, 5], 35 | data: [], 36 | lineTension: 0, 37 | 38 | } 39 | ] 40 | }, 41 | options: { 42 | responsive: true, 43 | bezierCurve: false, 44 | tooltips: { 45 | mode: 'index', 46 | intersect: false, 47 | }, 48 | elements: { 49 | point: { 50 | radius: 0 51 | } 52 | }, 53 | animation: { 54 | duration: 0 55 | }, 56 | legend: { 57 | display: true, 58 | labels: { 59 | filter: (legendItem, data) => data.datasets[0].data[legendItem.index] != 0 60 | }, 61 | onClick: function(e,l) { 62 | window.chartConfig.options[validOptions[l.datasetIndex].slice(5,8).toLowerCase()] = l.hidden; 63 | window.chart.data.datasets[l.datasetIndex]._meta[0].hidden = !(l.hidden); 64 | saveSettings(window.chartConfig); 65 | readDropDownSettings(); 66 | window.chart.update(); 67 | } 68 | }, 69 | hover: { 70 | mode: 'nearest', 71 | intersect: false 72 | }, 73 | tooltips: { 74 | callbacks: { 75 | title: function(t, d) { 76 | return "Time: "+d.labels[t[0].index]+" seconds"; 77 | } 78 | } 79 | }, 80 | 81 | scales: { 82 | xAxes: [{ 83 | display: true, 84 | scaleLabel: { 85 | display: true, 86 | labelString: 'Seconds', 87 | fontColor: '#cacaca' 88 | }, 89 | ticks: { 90 | fontColor: '#cacaca' 91 | } 92 | }], 93 | yAxes: [{ 94 | display: true, 95 | scaleLabel: { 96 | display: true, 97 | labelString: 'Online Users', 98 | fontColor: '#cacaca' 99 | }, 100 | ticks: { 101 | suggestedMin: 0, 102 | suggestedMax: 30, 103 | fontColor: '#cacaca' 104 | } 105 | }] 106 | } 107 | } 108 | }; 109 | 110 | var ctx = document.getElementById('statsChart').getContext('2d'); 111 | window.chart = new Chart(ctx, config); 112 | 113 | // Rooms 114 | var labelsRooms = []; 115 | var config = { 116 | type: 'line', 117 | data: { 118 | labels: labelsRooms, 119 | datasets: [{ 120 | label: 'Average Rooms', 121 | backgroundColor: 'rgba(244, 5, 95, 0.55)', 122 | borderColor: '#f4055f', 123 | data: [], 124 | fill: false, 125 | lineTension: 0, 126 | }, 127 | { 128 | label: 'Minimum Rooms', 129 | fill: false, 130 | backgroundColor: 'rgba(5, 89, 244, 0.55)', 131 | borderColor: 'rgba(5, 89, 244, 0.55)', 132 | borderDash: [5, 5], 133 | data: [], 134 | lineTension: 0, 135 | 136 | }, 137 | { 138 | label: 'Maximum Rooms', 139 | fill: false, 140 | backgroundColor: 'rgba(244, 140, 5, 0.55)', 141 | borderColor: 'rgba(244, 140, 5, 0.55)', 142 | borderDash: [5, 5], 143 | data: [], 144 | lineTension: 0, 145 | 146 | } 147 | ] 148 | }, 149 | options: { 150 | responsive: true, 151 | bezierCurve: false, 152 | tooltips: { 153 | mode: 'index', 154 | intersect: false, 155 | }, 156 | elements: { 157 | point: { 158 | radius: 0 159 | } 160 | }, 161 | animation: { 162 | duration: 0 163 | }, 164 | legend: { 165 | display: true, 166 | labels: { 167 | filter: (legendItem, data) => data.datasets[0].data[legendItem.index] != 0 168 | }, 169 | onClick: function(e,l) { 170 | window.chartConfig.options[validOptions[l.datasetIndex].slice(5,8).toLowerCase()+"R"] = l.hidden; 171 | window.chartRooms.data.datasets[l.datasetIndex]._meta[1].hidden = !(l.hidden); 172 | saveSettings(window.chartConfig); 173 | readDropDownSettings(); 174 | window.chartRooms.update(); 175 | } 176 | }, 177 | hover: { 178 | mode: 'nearest', 179 | intersect: false 180 | }, 181 | tooltips: { 182 | callbacks: { 183 | title: function(t, d) { 184 | return "Time: "+d.labels[t[0].index]+" seconds"; 185 | } 186 | } 187 | }, 188 | scales: { 189 | xAxes: [{ 190 | display: true, 191 | scaleLabel: { 192 | display: true, 193 | labelString: 'Seconds', 194 | fontColor: '#cacaca' 195 | }, 196 | ticks: { 197 | fontColor: '#cacaca' 198 | } 199 | }], 200 | yAxes: [{ 201 | display: true, 202 | scaleLabel: { 203 | display: true, 204 | labelString: 'Public Rooms', 205 | fontColor: '#cacaca' 206 | }, 207 | ticks: { 208 | suggestedMin: 0, 209 | suggestedMax: 10, 210 | fontColor: '#cacaca' 211 | } 212 | }] 213 | } 214 | } 215 | }; 216 | 217 | var ctxRooms = document.getElementById('roomsChart').getContext('2d'); 218 | window.chartRooms = new Chart(ctxRooms, config); 219 | 220 | // // Outages 221 | // var labelsOutages = []; 222 | // var config = { 223 | // type: 'line', 224 | // data: { 225 | // labels: labelsOutages, 226 | // datasets: [{ 227 | // label: 'Outages', 228 | // backgroundColor: 'rgba(244, 5, 95, 0.55)', 229 | // borderColor: '#f4055f', 230 | // data: [], 231 | // fill: true, 232 | // lineTension: 0, 233 | // }] 234 | // }, 235 | // options: { 236 | // responsive: true, 237 | // bezierCurve: false, 238 | // tooltips: { 239 | // mode: 'index', 240 | // intersect: false, 241 | // }, 242 | // animation: { 243 | // duration: 0 244 | // }, 245 | // legend: { 246 | // display: false 247 | // }, 248 | // hover: { 249 | // mode: 'nearest', 250 | // intersect: true 251 | // }, 252 | // tooltips: { 253 | // callbacks: { 254 | // title: function(t, d) { 255 | // return "Time: "+d.labels[t[0].index]+" hours"; 256 | // } 257 | // } 258 | // }, 259 | // scales: { 260 | // xAxes: [{ 261 | // display: true, 262 | // scaleLabel: { 263 | // display: true, 264 | // labelString: 'Hours', 265 | // fontColor: '#cacaca' 266 | // }, 267 | // ticks: { 268 | // fontColor: '#cacaca' 269 | // } 270 | // }], 271 | // yAxes: [{ 272 | // display: true, 273 | // scaleLabel: { 274 | // display: true, 275 | // labelString: 'Outages', 276 | // fontColor: '#cacaca' 277 | // }, 278 | // ticks: { 279 | // suggestedMin: 0, 280 | // suggestedMax: 10, 281 | // fontColor: '#cacaca' 282 | // } 283 | // }] 284 | // } 285 | // } 286 | // }; 287 | 288 | // var ctxOutages = document.getElementById('outageChart').getContext('2d'); 289 | // window.chartOutages = new Chart(ctxOutages, config); 290 | 291 | 292 | }); 293 | -------------------------------------------------------------------------------- /src/public/js/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | window.chartColors = { 4 | red: 'rgb(255, 99, 132)', 5 | orange: 'rgb(255, 159, 64)', 6 | yellow: 'rgb(255, 205, 86)', 7 | green: 'rgb(75, 192, 192)', 8 | blue: '#f4055f', 9 | purple: 'rgb(153, 102, 255)', 10 | grey: 'rgb(201, 203, 207)' 11 | }; 12 | 13 | (function(global) { 14 | var MONTHS = [ 15 | 'January', 16 | 'February', 17 | 'March', 18 | 'April', 19 | 'May', 20 | 'June', 21 | 'July', 22 | 'August', 23 | 'September', 24 | 'October', 25 | 'November', 26 | 'December' 27 | ]; 28 | 29 | var COLORS = [ 30 | '#4dc9f6', 31 | '#f67019', 32 | '#f53794', 33 | '#537bc4', 34 | '#acc236', 35 | '#166a8f', 36 | '#00a950', 37 | '#58595b', 38 | '#8549ba' 39 | ]; 40 | 41 | var Samples = global.Samples || (global.Samples = {}); 42 | var Color = global.Color; 43 | 44 | Samples.utils = { 45 | // Adapted from http://indiegamr.com/generate-repeatable-random-numbers-in-js/ 46 | srand: function(seed) { 47 | this._seed = seed; 48 | }, 49 | 50 | rand: function(min, max) { 51 | var seed = this._seed; 52 | min = min === undefined ? 0 : min; 53 | max = max === undefined ? 1 : max; 54 | this._seed = (seed * 9301 + 49297) % 233280; 55 | return min + (this._seed / 233280) * (max - min); 56 | }, 57 | 58 | numbers: function(config) { 59 | var cfg = config || {}; 60 | var min = cfg.min || 0; 61 | var max = cfg.max || 1; 62 | var from = cfg.from || []; 63 | var count = cfg.count || 8; 64 | var decimals = cfg.decimals || 8; 65 | var continuity = cfg.continuity || 1; 66 | var dfactor = Math.pow(10, decimals) || 0; 67 | var data = []; 68 | var i, value; 69 | 70 | for (i = 0; i < count; ++i) { 71 | value = (from[i] || 0) + this.rand(min, max); 72 | if (this.rand() <= continuity) { 73 | data.push(Math.round(dfactor * value) / dfactor); 74 | } else { 75 | data.push(null); 76 | } 77 | } 78 | 79 | return data; 80 | }, 81 | 82 | labels: function(config) { 83 | var cfg = config || {}; 84 | var min = cfg.min || 0; 85 | var max = cfg.max || 100; 86 | var count = cfg.count || 8; 87 | var step = (max - min) / count; 88 | var decimals = cfg.decimals || 8; 89 | var dfactor = Math.pow(10, decimals) || 0; 90 | var prefix = cfg.prefix || ''; 91 | var values = []; 92 | var i; 93 | 94 | for (i = min; i < max; i += step) { 95 | values.push(prefix + Math.round(dfactor * i) / dfactor); 96 | } 97 | 98 | return values; 99 | }, 100 | 101 | months: function(config) { 102 | var cfg = config || {}; 103 | var count = cfg.count || 12; 104 | var section = cfg.section; 105 | var values = []; 106 | var i, value; 107 | 108 | for (i = 0; i < count; ++i) { 109 | value = MONTHS[Math.ceil(i) % 12]; 110 | values.push(value.substring(0, section)); 111 | } 112 | 113 | return values; 114 | }, 115 | 116 | color: function(index) { 117 | return COLORS[index % COLORS.length]; 118 | }, 119 | 120 | transparentize: function(color, opacity) { 121 | var alpha = opacity === undefined ? 0.5 : 1 - opacity; 122 | return Color(color).alpha(alpha).rgbString(); 123 | } 124 | }; 125 | 126 | // DEPRECATED 127 | window.randomScalingFactor = function() { 128 | return Math.round(Samples.utils.rand(-100, 100)); 129 | }; 130 | 131 | // INITIALIZATION 132 | 133 | Samples.utils.srand(Date.now()); 134 | 135 | // Google Analytics 136 | /* eslint-disable */ 137 | if (document.location.hostname.match(/^(www\.)?chartjs\.org$/)) { 138 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 139 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 140 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 141 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 142 | ga('create', 'UA-28909194-3', 'auto'); 143 | ga('send', 'pageview'); 144 | } 145 | /* eslint-enable */ 146 | 147 | }(this)); -------------------------------------------------------------------------------- /src/public/static/moving-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dogegarden/dogetracker/81d9e1a193d6896e9c40c188b3753efeb06b7d25/src/public/static/moving-bg.png -------------------------------------------------------------------------------- /src/routes/api.js: -------------------------------------------------------------------------------- 1 | const Router = require('../classes/Router'); 2 | const axios = require('axios') 3 | const Stats = require('../util/stats'); 4 | 5 | class API extends Router { 6 | constructor(client) { 7 | super(client, '/api'); 8 | } 9 | createRoute() { 10 | 11 | this.router.get('/statistics', async (req, res) => { 12 | try { 13 | let statistics = await axios.get('https://api.dogegarden.net/v1/statistics?dogestats') 14 | let initRooms = await axios.get('https://api.dogegarden.net/v1/popularRooms?dogestats') 15 | let topRoom = initRooms.data.rooms[0] 16 | 17 | let newestRoom = initRooms.data.rooms.find(rooms => rooms.inserted_at == initRooms.data.rooms.map(it => it.inserted_at).sort()[initRooms.data.rooms.length - 1]) 18 | let longestRoom = initRooms.data.rooms.find(rooms => rooms.inserted_at == initRooms.data.rooms.map(it => it.inserted_at).sort()[0]) 19 | let serverTime = new Date().valueOf(); 20 | let data = { 21 | totalRooms: statistics.data.totalRooms, 22 | totalOnline: statistics.data.totalOnline, 23 | totalScheduled: statistics.data.totalScheduledRooms, 24 | topRoom: { 25 | name: topRoom.name, 26 | description: topRoom.description, 27 | listeners: topRoom.numPeopleInside, 28 | id: topRoom.id, 29 | created_at: topRoom.inserted_at 30 | }, 31 | newestRoom: { 32 | name: newestRoom.name, 33 | description: newestRoom.description, 34 | listeners: newestRoom.numPeopleInside, 35 | id: newestRoom.id, 36 | created_at: newestRoom.inserted_at 37 | }, 38 | longestRoom: { 39 | name: longestRoom.name, 40 | description: longestRoom.description, 41 | listeners: longestRoom.numPeopleInside, 42 | id: longestRoom.id, 43 | created_at: longestRoom.inserted_at 44 | }, 45 | totalBotsOnline: statistics.data.totalBotsOnline, 46 | totalBotsSendingTelemetry: statistics.data.totalBotsSendingTelemetry, 47 | totalRegistered: statistics.data.totalRegistered, 48 | activeInLastTwoDays: statistics.data.activeInLastTwoDays, 49 | id: statistics.data.pid, 50 | serverTime: serverTime 51 | } 52 | res.json(data); 53 | } catch (e) { 54 | console.log(e) 55 | } 56 | }) 57 | 58 | this.router.get('/version', async (req, res) => { 59 | try { 60 | let packageConf = require('../../package.json') 61 | res.json(packageConf) 62 | 63 | } catch (e) { 64 | console.log(e) 65 | } 66 | }) 67 | 68 | this.router.get('/bots', async (req, res) => { 69 | try { 70 | let bots = await axios.get('https://api.dogegarden.net/v1/bots?dogestats') 71 | res.json(bots.data); 72 | } catch (e) { 73 | console.log(e) 74 | } 75 | }) 76 | 77 | this.router.get('/mysql', async (req, res) => { 78 | // console.log(req.query.time) 79 | let validQueries = ['24h', 'week', 'month', 'alltime', 'custom']; 80 | if (validQueries.indexOf(req.query.time) != -1) { 81 | 82 | try { 83 | let data = await Stats.getData(req.query.time); 84 | // console.log(data) 85 | res.json(data); 86 | } catch (e) { 87 | console.log(e); 88 | res.status(500).json({ error: e }); 89 | } 90 | } 91 | 92 | }) 93 | 94 | return this.router 95 | } 96 | } 97 | 98 | module.exports = API; 99 | -------------------------------------------------------------------------------- /src/util/Logger.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const dateFormat = require('dateformat'); 3 | const util = require('util') 4 | 5 | class Logger { 6 | static get prefix() { 7 | return chalk.gray(dateFormat(Date.now(), 'ddd HH:MM:ss:l')) 8 | } 9 | 10 | static formatInput(args) { 11 | return args.map((arg) => arg instanceof Object ? util.inspect(arg) : arg) 12 | } 13 | 14 | static info(...args) { 15 | args = this.formatInput(args) 16 | console.log(this.prefix + ' ' + chalk.green('[INFO]') + ' ' + args.join(' ')) 17 | } 18 | 19 | static error(...args) { 20 | args = this.formatInput(args) 21 | console.log(this.prefix + ' ' + chalk.red('>> [ERROR]') + ' ' + args.join(' ')) 22 | } 23 | 24 | static route(...args) { 25 | args = this.formatInput(args) 26 | console.log(this.prefix + ' ' + chalk.blue('[ROUTE]') + ' ' + args.join(' ')) 27 | } 28 | 29 | static mysql(...args) { 30 | args = this.formatInput(args) 31 | console.log(this.prefix + ' ' + chalk.cyan('[MYSQL]') + ' ' + args.join(' ')) 32 | } 33 | 34 | static API(...args) { 35 | args = this.formatInput(args) 36 | console.log(this.prefix + ' ' + chalk.cyan('[API]') + ' ' + args.join(' ')) 37 | } 38 | 39 | } 40 | 41 | module.exports = Logger; -------------------------------------------------------------------------------- /src/util/connect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | 5 | const defaultOptions = { 6 | // "port": , 7 | "host": "http://localhost", 8 | "method": `GET`, 9 | "path": `/v1`, 10 | "headers": { 11 | "Content-Type": `application/json`, 12 | "Accept": `application/json`, 13 | } 14 | }; 15 | 16 | function request ( options = {} ) { 17 | const promise = new Promise(( resolve, reject ) => { 18 | let { 19 | method = `GET`, 20 | path, 21 | body, 22 | } = options; 23 | 24 | if ( !path ) { 25 | throw new Error(`Missing path or index + action for DB connection`); 26 | } 27 | 28 | const reqOpts = { 29 | ...defaultOptions, 30 | method, 31 | path, 32 | }; 33 | 34 | if ( isDev ) { 35 | // console.table(reqOpts); 36 | console.log(path); 37 | } 38 | 39 | const req = http.request(reqOpts, ( res ) => { 40 | 41 | const bufferArray = []; 42 | 43 | // res.setEncoding(`utf8`); 44 | 45 | res.on(`data`, ( chunk ) => { 46 | bufferArray[ bufferArray.length ] = chunk; 47 | }); 48 | 49 | res.on(`end`, () => { 50 | const data = Buffer.concat(bufferArray); 51 | const obj = JSON.parse(data); 52 | resolve(obj); 53 | }); 54 | }); 55 | 56 | req.on('error', ( e ) => { 57 | reject(`Request to elasticsearch failed: ${e.message}`); 58 | }); 59 | 60 | req.end(typeof body === `object` ? JSON.stringify(body) : body || undefined); 61 | }); 62 | 63 | return promise //.catch(e => console.trace(e)); 64 | } -------------------------------------------------------------------------------- /src/util/cron.js: -------------------------------------------------------------------------------- 1 | const cron = require('node-cron'); 2 | const axios = require('axios') 3 | const mysql = require('mysql'); 4 | const Logger = require('../util/Logger') 5 | 6 | function setCron() { 7 | // Execute every 10 minutes 8 | cron.schedule('*/10 * * * *', saveMYSQL); 9 | } 10 | 11 | function autoRunMYSQL() { 12 | Logger.info(`Starting with autorun MYSQL`); 13 | saveMYSQL(); 14 | } 15 | 16 | function saveMYSQL() { 17 | var con = mysql.createConnection({ 18 | host: process.env.MYSQL_HOST, 19 | port: process.env.MYSQL_PORT, 20 | user: process.env.MYSQL_USER, 21 | password: process.env.MYSQL_PASS, 22 | database: process.env.MYSQL_DB 23 | }); 24 | 25 | con.connect(async function (err) { 26 | if (err) throw err; 27 | Logger.mysql(`Connected`); 28 | let data; 29 | try { 30 | data = await axios.get('https://api.dogegarden.net/v1/popularRooms?mysql') 31 | data = data.data; 32 | // let sql = 'INSERT IGNORE INTO users (uuid, numFollowers, displayName) VALUES '; 33 | // for (j = 0; j < data.rooms.length; j++) { 34 | // for (i = 0; i < data.rooms[j].peoplePreviewList.length; i++) { 35 | // sql += `('${data.rooms[j].peoplePreviewList[i].id}', '${data.rooms[j].peoplePreviewList[i].numFollowers}', '${data.rooms[j].peoplePreviewList[i].displayName.replace(/"/g, '\\\"').replace(/'/g, '\\\'')}'), `; 36 | // } 37 | // } 38 | // sql = sql.slice(0, sql.length - 2); 39 | 40 | // con.query(sql, function (err, result) { 41 | // if (err) throw err; 42 | // console.log("users inserted"); 43 | // }); 44 | if (data.rooms === undefined || data.rooms.rooms) { 45 | Logger.mysql("Rooms undefined, check API endpoint"); 46 | } else { 47 | sql = 'INSERT IGNORE INTO rooms (id, creatorId, roomDescription, insertedAt, roomName, numPeopleInside) VALUES '; 48 | for (i = 0; i < data.rooms.length; i++) { 49 | sql += `('${data.rooms[i].id}', '${data.rooms[i].creatorId}', '${data.rooms[i].description.replace(/"/g, '\\\"').replace(/'/g, '\\\'')}', '${data.rooms[i].inserted_at}', '${data.rooms[i].name.replace(/"/g, '\\\"').replace(/'/g, '\\\'')}', '${data.rooms[i].numPeopleInside}'), `; 50 | } 51 | sql = sql.slice(0, sql.length - 2); 52 | 53 | con.query(sql, function (err, result) { 54 | if (err) throw err; 55 | Logger.mysql("Rooms inserted"); 56 | }); 57 | } 58 | 59 | data = await axios.get('https://stats.dogegarden.net/api/statistics?mysql') 60 | data = data.data; 61 | if (data.totalRooms === undefined) { 62 | Logger.mysql("Total Rooms undefined, check API endpoint"); 63 | } else { 64 | sql = `INSERT INTO stats (totalRooms, totalScheduledRooms, totalOnline, totalBotsOnline, totalBotsSendingTelemetry, totalRegistered, activeInLastTwoDays, topRoomID, newestRoomID, longestRoomID) VALUES (${data.totalRooms}, ${data.totalScheduled}, ${data.totalOnline}, ${data.totalBotsOnline}, ${data.totalBotsSendingTelemetry}, ${data.totalRegistered}, ${data.activeInLastTwoDays}, '${data.topRoom.id}', '${data.newestRoom.id}', '${data.longestRoom.id}')`; 65 | con.query(sql, function (err, result) { 66 | if (err) throw err; 67 | Logger.mysql("Stats inserted"); 68 | }); 69 | } 70 | } catch (e) { 71 | return console.error('Error in getting data from api', e) 72 | } 73 | }); 74 | // Bots 75 | /* 76 | let uniqueBots = []; 77 | 78 | for (i=0;i} 19 | */ 20 | 21 | function getData(timeOption) { 22 | let sql; 23 | // select * from table where table.id mod 5 = 0; 24 | if (timeOption == '24h') { 25 | // sql = 'SELECT totalRooms, totalOnline, statsTime FROM stats WHERE statsTime > DATE_SUB(CURDATE(), INTERVAL 1 DAY) and id mod 2 = 0'; 26 | // sql = 'SET @Hours = HOUR(NOW()); SET @StartTime = DATE_SUB(CURDATE(), INTERVAL 1 DAY); SELECT totalRooms, totalOnline, statsTime FROM stats WHERE statsTime > DATE_ADD(@startTime, INTERVAL @Hours HOUR) AND id mod 2 = 0;'; 27 | sql = 'SELECT DATE_FORMAT(statsTime, "%Y-%m-%d-%H") as queryDay, avg(totalOnline) as ave, min(totalOnline) as min, max(totalOnline) as max, avg(totalRooms) as aveR, min(totalRooms) as minR, max(totalRooms) as maxR FROM stats WHERE statsTime > DATE_ADD(DATE_SUB(CURDATE(), INTERVAL 1 DAY), INTERVAL HOUR(NOW()) HOUR) GROUP BY DATE_FORMAT(statsTime, "%Y-%m-%d-%H"); '; 28 | } else if (timeOption == 'week') { 29 | sql = 'SELECT DATE_FORMAT(statsTime, "%Y-%m-%d") as queryDay, avg(totalOnline) as ave, min(totalOnline) as min, max(totalOnline) as max, avg(totalRooms) as aveR, min(totalRooms) as minR, max(totalRooms) as maxR FROM stats WHERE statsTime > DATE_ADD(DATE_SUB(CURDATE(), INTERVAL 7 DAY), INTERVAL HOUR(NOW()) HOUR) GROUP BY DATE_FORMAT(statsTime, "%Y-%m-%d"); '; 30 | } else if (timeOption == 'month') { 31 | sql = 'SELECT DATE_FORMAT(statsTime, "%Y-%m-%d") as queryDay, avg(totalOnline) as ave, min(totalOnline) as min, max(totalOnline) as max, avg(totalRooms) as aveR, min(totalRooms) as minR, max(totalRooms) as maxR FROM stats WHERE statsTime > DATE_ADD(DATE_SUB(CURDATE(), INTERVAL 1 MONTH), INTERVAL HOUR(NOW()) HOUR) GROUP BY DATE_FORMAT(statsTime, "%Y-%m-%d"); '; 32 | } else if (timeOption == 'alltime') { 33 | // All time 34 | let daySplit = Math.ceil((Date.now()-new Date('4 Apr 2021').valueOf())/86400000/30); // /30 for number of days for modulus 35 | // let days = Math.ceil((Date.now() - startTime)/1000/60/60/24) 36 | // sql = `SELECT totalRooms, totalOnline, statsTime FROM stats WHERE id mod ${days * 2} = 0`; 37 | 38 | // New version, for every 30 days increase modulus by one so, <30, 1/month, 2/month, 3/month etc. >= 30 && < 60, 2/month, 4/month, etc. 3/m, 6/m .. 4/m, 8/m 39 | sql = 'SELECT DATE_FORMAT(statsTime, "%Y-%m-%d") as queryDay, avg(totalOnline) as ave, min(totalOnline) as min, max(totalOnline) as max, avg(totalRooms) as aveR, min(totalRooms) as minR, max(totalRooms) as maxR FROM stats GROUP BY CONCAT(DATE_FORMAT(statsTime, "%Y-%m-"), (DATE_FORMAT(statsTime, "%d") - DATE_FORMAT(statsTime, "%d") mod '+daySplit+')) ORDER BY queryDay ASC; '; 40 | // Todo: decide between days, weeks, months, even years 41 | } else { 42 | sql = 'SELECT * FROM stats; '; 43 | } 44 | 45 | // const result = query(sql); 46 | // console.log("Ran Get Data | " + timeOption); 47 | // console.log("Ran Get Data RESULT: ", result); 48 | return query(sql); 49 | } 50 | 51 | module.exports = { 52 | getData, 53 | }; 54 | -------------------------------------------------------------------------------- /src/views/Components/Footer.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | v<%= version %> 4 |

5 |
-------------------------------------------------------------------------------- /src/views/Components/Head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | 40 | 45 | 46 | 47 | 51 | 55 | 56 | DogeTracker | <%- title %> 57 | 58 | -------------------------------------------------------------------------------- /src/views/Components/Navbar.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 31 | 32 | 40 | -------------------------------------------------------------------------------- /src/views/Components/Noscript.ejs: -------------------------------------------------------------------------------- 1 | 41 | -------------------------------------------------------------------------------- /src/views/bots.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%- include('./Components/Head', { title: 'Bots' }) %> 5 | <%- include('./Components/Navbar') %> 6 | 7 | 8 |
9 |
10 |
11 |

DogeTracker

12 |
13 |

View, manage and explore statistics and historical data from dogehouse.tv

14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | 28 |

Provided by dogehouse-js & dogehouse-api.

30 |
31 |

32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 44 |

total served users

45 |
46 |
47 | 50 |

bots sending telemetry

51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Trending Bot

61 |

62 | 63 | 64 | 65 | 66 | serving 67 | users.
Gathering Time... 68 |

69 |
70 |
71 |
72 |
73 |
74 | 75 | 76 |
77 |
78 |
79 |
80 |
Bots Online
81 |
82 | 83 |
84 |
85 |
86 |
87 |
88 |
Total Rooms Containing Bots
89 |
90 | 91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | Newest Room

103 |

104 | 105 | 106 | 107 | 108 | containing 109 | users.
Gathering Time... 110 |

111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | Longest Active Room

121 |

122 | 123 | 124 | 125 | 126 | containing 127 | users.
Gathering Time... 128 |

129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | 137 | <%- include('./Components/Footer') %> 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /src/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%- include('./Components/Head', { title: 'Home' }) %> 5 | <%- include('./Components/Navbar') %> 6 | <%- include('./Components/Noscript') %> 7 | 8 | 9 |
10 |
11 |
12 |

DogeTracker

13 |
14 |

View, manage and explore statistics and historical data from dogehouse.tv
Made possible by dogehouse-api

17 |
18 |
19 |
20 | 30 |
31 | 41 |
42 |
43 |
44 |
46 |
47 | 50 |

public rooms

51 |
52 |
53 | 56 |

online across site

57 |
58 |
59 | 62 |

scheduled rooms

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | Trending 74 |

75 |

76 | 77 | 79 | 80 | 81 | containing 82 | users.
Gathering 85 | Time... 86 |

87 |
88 |
89 |
90 |
91 |
92 | 93 | 94 |
95 |
96 |
97 |
98 |
User Activity 99 |
101 |
102 | 107 | 114 | 115 |
116 | 117 |
118 | 119 | 124 | 138 |
139 |
140 |
141 |
142 | 143 |
144 |
145 |
146 |
147 |
148 |
Room Activity 149 |
151 |
152 | 157 | 165 |
166 | 167 |
168 | 173 | 187 |
188 |
189 |
190 |
191 | 192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 | 206 | 207 | 210 |

Provided by dogehouse-api. 212 |

213 |
214 |

215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
224 |
225 |
226 | Newest Room 228 |

229 |

230 | 231 | 233 | 234 | 235 | containing 236 | users.
Gathering Time... 240 |

241 |
242 |
243 |
244 |
245 |
246 |
247 |
249 |
250 |
251 | Longest Active Room 253 |

254 |

255 | 256 | 258 | 259 | 260 | containing 261 | users.
Gathering Time... 265 |

266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
User/Bots Activity 276 |
278 |
279 | 284 | 287 |
288 |
289 |
290 |
291 | 292 |
293 |
294 |
295 |
296 |
297 |
Users/Rooms Activity 298 |
300 |
301 | 306 | 309 |
310 |
311 |
312 |
313 | 314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 | Online Bot List 326 |
327 |

329 |
330 |
331 |

332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 | 345 | 346 | 347 | Supported by . 348 |

Thank you for supporting us on patreon, . Get your name here by selecting a tier on our patreon. 350 |

351 |
352 |

353 |
354 |
355 |
356 |
357 |
358 |
359 | 360 | <%- include('./Components/Footer') %> 361 | 362 | 363 | 364 | 365 | -------------------------------------------------------------------------------- /src/views/rooms.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('./Components/Head', { title: 'Rooms'}) %> <%- 4 | include('./Components/Navbar') %> <%- include('./Components/Noscript') %> 5 | 6 | 7 |
11 |
12 |
13 |

DogeTracker

14 |
15 |

16 | View, manage and explore statistics and historical data from 17 | dogehouse.tv 18 |

19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 |

27 | Showing <%= rooms.length %> room<% if 28 | (rooms.length < 1 || rooms.length > 1) { %>s<% } %>. 29 |

30 | <% rooms.forEach(function(room){ %> 31 | 32 |
33 |
34 |
35 | 39 | 58 |
59 | 60 | <%= room.numPeopleInside %> 61 |
62 |
63 |

64 |
65 |

67 | <% }); %> 68 |
69 |
70 |
71 |
72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/views/scheduled.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%- include('./Components/Head', { title: 'Scheduled'}) %> <%- 4 | include('./Components/Navbar') %> 5 | 6 | 7 |
11 |
12 |
13 |

DogeTracker

14 |
15 |

16 | View, manage and explore statistics and historical data from 17 | dogehouse.tv 18 |

19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 |

27 | Showing <%= rooms.length %> room<% if (rooms.length < 1 || 28 | rooms.length > 1) { %>s<% } %>. 29 |

30 | <% rooms.forEach(function(room){ %> 31 | 32 |
33 |
34 |
35 | 39 | 58 |
59 | 60 | <%= room.numPeopleInside %> 61 |
62 |
63 |

64 |
65 |

67 | <% }); %> 68 |
69 |
70 |
71 |
72 | 73 | 74 | --------------------------------------------------------------------------------